カメラ撮影時に装飾アイテムを追加するサンプル

2012年6月8日(金)
PROJECT KySS

次に、MainPage.xamlを展開して表示されるMainPage.xaml.vbをダブルクリックして、リスト3のコードを記述します。

ロジックコードを記述する

リスト3 (MainPage.xaml.vb)

Option Strict On
Imports System.Windows.Media.Imaging
Imports Microsoft.Phone

仮想ファイルシステムを作成および使用するための型が含まれている、System.IO.IsolatedStorage名前空間をインポートします。分離ストレージによって、安全なクライアント側のストレージが提供されます。

Imports System.IO.IsolatedStorage
Imports System.IO
Imports System.Xml.Linq
Imports Microsoft.Devices

このサンプルでは、アタッチされた要素が、マウスのドラッグのジェスチャに応答して、要素の上に再配置するMouseDragElementBehaviorクラスを使用するため、このクラスの含まれる、Microsoft.Expression.Interactivity.Layout名前空間のインポートが必要です。

Imports Microsoft.Expression.Interactivity.Layout<br />
Imports System.Windows.Interactivity

ImageDataInfoというクラス内にimageというプロパティを定義しておきます。

Public Class ImageDataInfo
  Property image As String
End Class

Partial Public Class MainPage
  Inherits PhoneApplicationPage
 
  ' コンストラクター
  Public Sub New()
    InitializeComponent()
  End Sub
 
  Dim kanjiIndex As Integer
  Dim imageByte As Byte()

myCameraをPhotoCameraクラスのメンバ変数として宣言します。PhotoCameraクラスは、カメラアプリケーションの基本カメラ機能を提供し、イメージ キャプチャ、フォーカス、解像度、フラッシュ モードなどの機能を有効にして構成するためのメンバが含まれています。

  Dim myCamera As PhotoCamera
  Dim bufferValue As Integer = 5119
 
  Dim imageFileName As String
  Dim recordDate As String
  Dim imageSource As WriteableBitmap
  
  Dim myImage As Image

Image型の新しいリストであるimageListメンバ変数を宣言します。

  Dim imageList As New List(Of Image)

ImageInfoクラス型の新しいリストである、myImageInfoメンバ変数を宣言します。

  Dim myImageInfo As New List(Of ImageDataInfo)
 
  Dim xmldoc As XElement
  Dim positionX As Double
  Dim positionY As Double
 
  Dim starPositionX As Double
  Dim starPositionY As Double
 
  Dim myAngle As Integer = 0
  Dim starMyAngle As Integer = 0
  Shared myIndex As Integer = 0

ページがアクティブになった時に呼び出されるメソッド

変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。DirectoryExistsメソッドでAddingIconCameraというフォルダが存在しているかどうかをチェックし、存在していない場合は、CreateDirectoryメソッドでAddingIconCameraというフォルダを作成します。

Path.Combineで、AddingIconCameraというフォルダとImageList.xmlというファイルを連結し、変数xmlFilePathに格納します。ImageList.xmlファイルがAddingIconCameraフォルダに存在する場合は、「データ一覧」のフォルダアイコンの使用を可能にし、それ以外は使用不可としておきます。

PhotoCameraクラスの新しいインスタンスを作成し、myVideoBrushという名前のVideoBrushにSetSourceメソッドでmyCameraオブジェクトを指定します。

AddHandlerステートメントで、カメラ オブジェクトが初期化された時に発生する、Initializedイベントにイベントハンドラを追加します。Succeededプロパティで、カメラ操作が失敗した時は処理を抜けます。

それ以外は、フラッシュモードを自動に設定し、変数myResolution を使って、AvailableResolutionsプロパティで、使用できるカメラの解像度を参照します。カメラでキャプチャしたイメージの解像度を設定できる、Resolutionプロパティに、先に取得したmyResolutionの一番先頭の解像度を指定します。一番先頭の解像度は640×480の解像度です。

Try~Catch~End Tryで例外処理を行います。例外が発生した場合は、メッセージを表示します。別スレッドからの表示になります。

AddHandlerステートメントで、イメージが利用可能な場合に発生する、CaptureImageAvailableイベントに、myCamera_CaptureImageAvailableイベントハンドラを追加します。

  Protected Overrides Sub OnNavigatedTo(e As System.Windows.Navigation.NavigationEventArgs)
    Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
      If storage.DirectoryExists("AddingIconCamera") = False Then
        storage.CreateDirectory("AddingIconCamera")
      End If
 
      Dim xmlFilePath As String = Path.Combine("AddingIconCamera", "ImageList.xml")
      If storage.FileExists(xmlFilePath) = True Then
        TryCast(ApplicationBar.Buttons(1), ApplicationBarIconButton).IsEnabled = True
      Else
        TryCast(ApplicationBar.Buttons(1), ApplicationBarIconButton).IsEnabled = False
      End If
      myCamera = New PhotoCamera
      myVideoBrush.SetSource(myCamera)
 
      AddHandler myCamera.Initialized, Sub(cameraSender As Object, cameraArgs As CameraOperationCompletedEventArgs)
          Try
              If cameraArgs.Succeeded = False Then
                Exit Sub
              Else
                myCamera.FlashMode = FlashMode.Auto
                Dim myResolution As IEnumerable(Of Size) = myCamera.AvailableResolutions
                myCamera.Resolution = myResolution.First
              End If
          Catch
              Deployment.Current.Dispatcher.BeginInvoke(Sub()
                MessageBox.Show("カメラがスタートするまでお待ちください。")
                  End Sub)
              End Try
          End Sub
      AddHandler myCamera.CaptureImageAvailable, AddressOf myCamera_CaptureImageAvailable
  End Sub

イメージが利用可能な場合に発生する処理

変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表す、IsolateStorageFileクラスとして宣言します。Path.Combineで、AddingIconCameraというフォルダとimageFileNameに格納されている画像名とを連結し、変数filePathに格納します。同様にAddingIconCameraというフォルダとImageList.xmlというファイル名を連結して、変数xmlFilePathに格納します。

別スレッドで以下の処理を行います。

IsolatedStorageFileクラスのCreateFileメソッドで、変数filePathに格納されているファイルを作成します。バイト型として5119の領域を確保します。e.ImageStream.Readメソッドで、イメージストリームからバイト数を読み取り、変数myByteReadに格納します。この変数が0より大きい間、IsolatedStorageFileStream.Writeメソッドで、読み込んだイメージストリームのバイト数をバッファに書き込んでいきます。

写真の撮影とエンコードが正しくできているかは、ContentReadyEventArgs.ImageStreamプロパティから取得できますので、これを分離ストレージへ保存します。e.ImageStreamを閉じます。

e.ImageStream.Readの書式は下記の通りです。

e.ImageStream.Read(バイト配列, データの格納を開始するバッファ内のバイト オフセット(通常0を指定), 現在のストリームから読み取るバイトの最大数。)

IsolatedStorageFileStream.Writeメソッドの書式は下記の通りです。

IsolatedStorageFileStream.Write(書き込むバッファ, 開始位置を示すバッファ内のバイト オフセット(通常0を指定) ,書き込む最大バイト数,)

別スレッドの下記の処理を行います。

imageStorageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。分離ストレージ内のファイルを表す、IsolatedStorageFileStreamクラス用オブジェクト変数streamを用意し、IsolatedStorageFile.OpenFileメソッドでfilePathに格納しているファイルを、指定したモード、指定したファイルアクセスモードで開きます。

WriteableBitmap型の変数imageSourceを宣言し、PictureDecoder.DecodeJpeg(stream)で、撮った写真をJPEGファイルとしてWriteableBitmapオブジェクトにデコードします。PictureDecoder.DecodeJpegメソッドはMicrosof.Phone名前空間に属しています。WriteableBitmapクラスは書き込み更新することのできるBitmapSourceを提供するクラスです。dummyImageのSourceプロパティにimageSourceオブジェクトを指定します。一度分離ストレージに保存した画像を読み込んで、dummyImageに表示しています。

InkPresenterで初期化された、新しいWriteableBitmapのインスタンスを作成します。dummyImageに画像を表示させた時点で、分離ストレージのAddingIconCameraフォルダ内に保存した画像を、DeleteFileメソッドで削除しておきます。

分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数Streamを用意し、IsolatedStorageFile.CreateFileメソッドで分離ストレージのAddingIconCameraフォルダ内に、変数imageFileNameに格納されている画像ファイルを作成します。

Extensions.SaveJpegメソッドで、WriteableBitmapオブジェクトを、JPEGストリームにエンコードします。これは、JPEGファイルのターゲットとなる幅と高さを設定するためのパラメータを持っています。書式は下記の通りです。

Extensions.SaveJpeg(WriteableBitmapオブジェクト,イメージデータストリーム,WriteableBitmapオブジェクトのPixelWidth, WriteableBitmapオブジェクトのPixelHeight,0(固定),0~100の間の写真の品質(70以上を指定))

分離ストレージのAddingIconCamera内に指定した画像ファイルが作成されます。

変数xmlStorageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。AddingIconCameraというフォルダ内にImageList.xmlが存在していない場合は、Visual Basic の埋め込み式を用いて、XML宣言とルート要素、その子要素として、属性に”RecordDate”を指定し、その子要素として要素を作成します。埋め込み式の構文である を用いて“RecordDate”属性の値にメンバ変数recordDateの値を、要素の値にメンバ変数imageFileNameの値を指定します。これは、ASP.NETで使用される構文と同じです。

分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数xmlStreamを用意し、IsolatedStorageFile.CreateFileメソッドで、分離ストレージ内に、xmlFilePath変数の持っているフォルダ名付きXMLファイルを作成します。

ImageList.xmlファイルが存在する場合は、新しいStreamWriter 生成し、IsolatedStorageFile.OpenFileメソッドで、指定したファイルアクセスを使用して指定したモードでファイルを開き、初期化します。Writeメソッドで埋め込み式のXMLをストリームに書き込みます。別スレッドで、保存した旨のメッセージを表示し、dummyImageを非表示にします。「データ一覧」アイコンの使用可、不可を決めるIchiranShowプロシージャを実行します。

次は、既にAddingIconCameraフォルダ内にImageList.xmlが存在する場合の処理です。

IsolatedStorageFileクラスのOpenFileメソッドでAddingIconCameraフォルダ内のImageList.xmlファイルを、指定したファイルアクセスを使用して、指定したモードで開きます。開いたファイルをStreamReaderで読み込みます。ReadToEndメソッドでファイルの最後まで読み取り、変数readXmldocに格納しておきます。読み込んだXMLテキストをParseメソッドでXElementとして読み込みます。

追加する要素を、埋め込み式を用いて生成し、”RecordDate”属性の値や、の内容テキストを指定します。新しく生成したXML要素を、読み込んだXMLにAddメソッドで追加します。IsolatedStorageFileStreamを閉じます。

ImageList.xmlファイルが存在する場合は、新しいStreamWriter 生成し、IsolatedStorageFile.OpenFileメソッドで、指定したファイルアクセスを使用して、指定したモードでファイルを開きます。Writeメソッドで、新しいXML要素の追加されたXMLをストリームに書き込みます。別スレッドで、保存した旨のメッセージを表示し、dummyImageを非表示にします。「データ一覧」のアイコンの使用可、不可を決めるIchiranShowプロシージャを実行します。

    Private Sub myCamera_CaptureImageAvailable(sender As Object, e As ContentReadyEventArgs)
        Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
        
        Dim filePath As String = Path.Combine("AddingIconCamera", imageFileName)
        Dim xmlFilePath As String = Path.Combine("AddingIconCamera", "ImageList.xml")
 
        Using myStream As IsolatedStorageFileStream = storage.CreateFile(filePath)
          Dim myBuffer(bufferValue) As Byte
          Dim myByteRead As Integer = -1
          Do
              myByteRead = e.ImageStream.Read(myBuffer, 0, myBuffer.Length)
              myStream.Write(myBuffer, 0, myByteRead)
          Loop While (myByteRead > 0)
          e.ImageStream.Close()
        End Using
 
        Deployment.Current.Dispatcher.BeginInvoke(Sub()
              Dim imageStorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
              Using stream As IsolatedStorageFileStream = imageStorage.OpenFile(filePath, FileMode.Open, FileAccess.Read)
                Dim imageSource As WriteableBitmap = PictureDecoder.DecodeJpeg(stream)
                dummyImage.Source = imageSource
              End Using
              Dim myWriteableBitmap As WriteableBitmap
              myWriteableBitmap = New WriteableBitmap(InkPresenter1, Nothing)
              If imageStorage.FileExists(Path.Combine("AddingIconCamera", imageFileName)) = True Then
              imageStorage.DeleteFile(Path.Combine("AddingIconCamera", imageFileName))
                End If
              Using Stream As IsolatedStorageFileStream = storage.CreateFile(Path.Combine("AddingIconCamera", imageFileName))
System.Windows.Media.Imaging.Extensions.SaveJpeg(myWriteableBitmap, Stream, 480, 640, 0, 85)
                                                   End Using
                                                 End Sub)

              'XMLファイルの保存
              Dim xmlStorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
 
              If xmlStorage.FileExists(xmlFilePath) = False Then
                Dim xmldoc As XDocument = <?xml version="1.0" encoding="utf-8"?>
                                          <PhotoBooth>
                                            <Picture RecordDate=<%= recordDate %>>
                                              <ImageName><%= imageFileName %></ImageName>
                                            </Picture>
                                          </PhotoBooth>
                Using xmlStream As IsolatedStorageFileStream = xmlStorage.CreateFile(xmlFilePath)
                End Using
 
                If xmlStorage.FileExists(xmlFilePath) = True Then
                    Using xmlwriter As StreamWriter = New StreamWriter(xmlStorage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Write), System.Text.Encoding.UTF8)
                      xmlwriter.Flush()
                      xmlwriter.Write(xmldoc.ToString)
                    End Using
                    Deployment.Current.Dispatcher.BeginInvoke(Sub()
                        MessageBox.Show("画像とXMLファイルを保存しました。")
                        dummyImage.Source = Nothing
                        dummyImage.Visibility = Windows.Visibility.Collapsed
                        IchiranShow()
                          End Sub)
                 End If
              Else
                 Dim xmlStream As IsolatedStorageFileStream = xmlStorage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Read)
                 Using xmlreader As StreamReader = New StreamReader(xmlStream)
 
                     Dim readXmldoc As String = xmlreader.ReadToEnd
                     Dim doc As XElement = XElement.Parse(readXmldoc)
                     Dim addXml As XElement = <Picture RecordDate=<%= recordDate %>>
                                                <ImageName><%= imageFileName %></ImageName>
                                              </Picture>
                     doc.Add(addXml)
                     xmlStream.Close()
 
                     If xmlStorage.FileExists(xmlFilePath) = True Then
                       Using xmlwriter As StreamWriter = New StreamWriter(storage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Write), System.Text.Encoding.UTF8)
                         xmlwriter.Flush()
                         xmlwriter.Write(doc.ToString)
                         Deployment.Current.Dispatcher.BeginInvoke(Sub()
                           dummyImage.Source = Nothing
                           dummyImage.Visibility = Windows.Visibility.Collapsed
                           MessageBox.Show("画像とXMLファイルを保存しました。")
                           IchiranShow()
                                                       End Sub)
                       End Using
                     End If
                   End Using
              End If
        End Sub

「データ一覧」のアイコンの使用可否を決める処理

分離ストレージ内のAddingIconCameraフォルダ内にImageList.xmlファイルが存在する場合は、「データ一覧」のアイコンの使用を可能にし、それ以外は使用不可とします。

    Private Sub IchiranShow()
      Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
      Dim filePath As String = Path.Combine("AddingIconCamera", "ImageList.xml")
      If storage.FileExists(filePath) = True Then
        TryCast(ApplicationBar.Buttons(1), ApplicationBarIconButton).IsEnabled = True
      Else
        TryCast(ApplicationBar.Buttons(1), ApplicationBarIconButton).IsEnabled = False
      End If
    End Sub

ページが読み込まれた時の処理

XElement.Loadメソッドでkanji.xmlを読み込みます。Descendantsメソッドで、子孫要素であるすべての 要素のコレクションに対して、各要素を変数result に格納しながら、ImageDataInfoクラスの「image」プロパティに、Imageフォルダを連結した 要素の内容テキストを指定して、AddメソッドでmyImageInfoオブジェクトに追加していきます。
ListBoxのItemsSourceプロパティにmyImageInfoオブジェクトを指定すると、漢字画像の一覧が表示されます。

Private Sub MainPage_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
  xmldoc = XElement.Load("kanji.xml")
  For Each result In From c In xmldoc.Descendants("image") Select c
  myImageInfo.Add(New ImageDataInfo With {.image = "Image/" & result.Value})
  Next
  ListBox1.ItemsSource = myImageInfo
  End Sub

「カメラ」アイコンをタップした時の処理

PhotoCameraにファインダーに表示されるイメージがある場合は、dummyImageコントロールを表示します。現在の年月日時間分秒.jpgファイル名を作成し、メンバ変数imageFileNameに格納しておきます。また、現在の年/月/日/時:分:秒という文字列を、メンバ変数recordDateに格納しておきます。

CaptureImageメソッドで、ファインダーに表示された現在のイメージのフル解像度キャプチャを開始します。例外が発生した場合は別スレッドで、メッセージを表示します。

  Private Sub CameraGo(sender As Object, e As EventArgs)
    If myCamera Is Nothing = False Then
      Try
        dummyImage.Visibility = Windows.Visibility.Visible
        imageFileName = DateTime.Now.ToString("yyyyMMddHHmmss") & ".jpg"
        recordDate = DateTime.Now.ToString("yyyy/MM/dd/HH:mm:ss")
        myCamera.CaptureImage()
      Catch
        Me.Dispatcher.BeginInvoke(Sub() MessageBox.Show("エラーが発生しましたが、データは保存されています。"))
      End Try
    End If
  End Sub

「フォルダ」アイコンがタップされた時の処理

これから作成するDataIchiranPage.xamlに遷移します。

  Private Sub ListGo(sender As Object, e As EventArgs)
    NavigationService.Navigate(New Uri("/DataIchiranPage.xaml", UriKind.Relative))
  End Sub

「×」のクリアアイコンがタップされた時の処理

InkPresenterに配置された漢字画像を一気に削除します。

  Private Sub ClearGo(sender As Object, e As EventArgs)
    For i As Integer = 0 To imageList.Count - 1
      Inkpresenter1.Children.Remove(imageList(i))
    Next
  End Sub

ListBoxから漢字画像が選択された時の処理

選択されたインデックスをメンバ変数kanjiIndexに格納しておきます。

  Private Sub ListBox1_SelectionChanged(sender As Object, e As System.Windows.Controls.SelectionChangedEventArgs) Handles ListBox1.SelectionChanged
    kanjiIndex = ListBox1.SelectedIndex
  End Sub

漢字画像が選択され、InkPresenterがダブルタップされた時の処理

新しい、MouseDragElementBehaviorクラスのインスタンスbehaviorオブジェクトを作成します。MouseDragElementBehaviorクラスは、アタッチされた要素を、マウスのドラッグのジェスチャに応答して要素の上に再配置するクラスです。

Attachメソッドで指定されたオブジェクトにアタッチします。この場合、ListBoxから選択され配置された画像にアタッチすることになるため、配置された画像は、どの場所にでもドラッグが可能になります。

メンバ変数kanjiIndexに対応する要素の内容を取得し、変数kanjiNameに格納しておきます。新しいImageのインスタンスmyImageオブジェクトを作成します。

InkPresenterのダブルタップされたXの座標値をpositionXに、Y座標をpositionYに格納しておきます。myImageオブジェクトのWidthとHeightを指定し、SetValueメソッドで、LeftPropertyとTopPropertyの値を指定します。ダブルタップした中心に画像が表示されるよう、WidthとHeightそれぞれの値の半分を差し引いています。

Sourceプロパティには、Imageフォルダ内のkanjiNameに格納されている画像名を指定します。Tagプロパティには、配置される画像の個数を指定しておきます。

各プロパティの設定されたmyImageオブジェクトをInkPresenterに追加し、同時にImageのリストであるimageListにも追加しておきます。このimageListの値は×アイコンで画像を全部消去する場合に使用しています。

AddHandlerステートメントで、myImageオブジェクトがタップされた時のイベントハンドラを追加します。

新しいCompositeTransformクラスのインスタンスmyCompsiteオブジェクトを作成します。タップするごとに画像を45度ずつ回転させ、その回転角をmyCompsiteオブジェクトのRotationプロパティに指定します。

タップされた画像のRenderTransformにCompsiteTransformのオブジェクトmyCompsiteを指定します。これで、InkPresenterに配置された画像をタップすると45度ずつ回転するようになりました。Attachメソッドで指定されたオブジェクト(myImage)にアタッチします。

  Private Sub Inkpresenter1_DoubleTap(sender As Object, e As System.Windows.Input.GestureEventArgs) Handles Inkpresenter1.DoubleTap
    If kanjiIndex < 0 Then
      MessageBox.Show("漢字画像を選択してください。")
      Exit Sub
    End If
    Dim behavior As New MouseDragElementBehavior
    Dim kanjiName As String = xmldoc.Descendants("image")(kanjiIndex).Value
    myImage = New Image
    positionX = e.GetPosition(Inkpresenter1).X
    positionY = e.GetPosition(InkPresenter1).Y
 
    With myImage
      .Width = 100
      .Height = 100
      .SetValue(Canvas.LeftProperty, positionX - 50)
      .SetValue(Canvas.TopProperty, positionY - 50)
      .Source = New BitmapImage(New Uri("Image/" & kanjiName, UriKind.Relative))
      .Tag = myIndex
    End With
      myIndex = myIndex + 1
      InkPresenter1.Children.Add(myImage)
      imageList.Add(myImage)
 
      AddHandler myImage.Tap, Sub(imageSender As Object, imageArgs As System.Windows.Input.GestureEventArgs)
        Dim tapPositionX = imageArgs.GetPosition(InkPresenter1).X
        Dim tapPositionY = imageArgs.GetPosition(InkPresenter1).Y
        Dim no As Integer = CInt(DirectCast(imageSender, Image).Tag)
        imageList(no).SetValue((Canvas.LeftProperty), tapPositionX - 50)
        imageList(no).SetValue((Canvas.TopProperty), tapPositionY - 50)
        Dim myComposite As New CompositeTransform
        myComposite.CenterX = 50
        myComposite.CenterY = 50
        If myAngle > 360 Then myAngle = 0
           myAngle = myAngle + 45
           myComposite.Rotation = myAngle
           imageList(no).RenderTransform = myComposite
        End Sub
           behavior.Attach(myImage)
  End Sub

ページがアクティブでなくなった時の処理

myIndexを0で初期化し、myCameraオブジェクトの関連付けを破棄します。

  Protected Overrides Sub OnNavigatingFrom(e As System.Windows.Navigation.NavigatingCancelEventArgs)
    myIndex = 0
    myCamera = Nothing
    MyBase.OnNavigatingFrom(e)
  End Sub
End Class
  • カメラ撮影時に装飾アイテムを追加するサンプル

四国のSOHO。薬師寺国安(VBプログラマ)と、薬師寺聖(デザイナ、エンジニア)によるコラボレーション・ユニット。1997年6月、Dynamic HTMLとDirectAnimationの普及を目的として結成。共同開発やユニット名義での執筆活動を行う。XMLおよび.NETに関する著書や連載多数。最新刊は「Silverlight実践プログラミング」両名とも、Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。http://www.PROJECTKySS.NET/

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています