はじめに
前回に引き続きLive FrameItのSDKを利用した仮想フォトフレームアプリケーションの作成です。今回はユーザーのコレクションを取得してLive FrameItから配信される画像を取得・

作成するサンプルアプリケーションのソースコードは、
登録トークンを使用したデイバスIDの取得
前回、
デバイス側でLive IDアカウントによるユーザー認証を行わない代わりに登録トークンという文字列を使用してデバイスをLive FrameItへ登録します。詳しい手順は前回を参照してください。
登録トークンの取得
まずデバイスは、
' (注: まだ不完全な例です)
Dim client = New DeviceSvcSoapClient
Dim manufacturerId = "Virtual Photo Frame"
Dim serialNumber = Now.Ticks.ToString
Dim result = client.GetClaimToken(manufacturerId, serialNumber)
Dim calimToken = result.ClaimToken
GetClaimTokenメソッドの戻り値は、

デイバスIDの取得
ユーザーによる登録トークンの入力が完了すると、
Private Sub RegisterDeviceWithToken()
Dim result As DeviceBindResults
Using client = New DeviceSvcSoapClient
Dim manufacturerId = "Virtual Photo Frame" '製造元の名前
Dim serialNumber = Now.Ticks.ToString ' シリアル番号
' 登録トークンの取得
Dim tokenResult = client.GetClaimToken(manufacturerId, serialNumber)
If tokenResult.ResponseCode <> 0 Then
' (ResponseCode = 1 のとき取得失敗)
Exit Sub
End If
' ユーザーにトークンの提示
MessageBox.Show(tokenResult.ClaimUrl & " へアクセスして登録トークン " & _
tokenResult.ClaimToken & " を設定してください。" & vbCrLf & _
"設定完了後 OK ボタンをクリックしてください。")
' デバイスIDの取得
result = client.DeviceBind(tokenResult.ClaimToken, manufacturerId, serialNumber)
End Using
If result.ResponseCode = 0 Then
' 取得成功 (ResponseCode = 0)
' デバイスIDの保存例 (プロジェクトのプロパティの設定で String型の DeviceId という値を追加しておきます)
My.Settings.DeviceId = result.DeviceId
My.Settings.Save()
End If
End Sub
UIの作成
デバイスIDの取得後は、
プロジェクトの作成
Visual StudioのプロジェクトはWPFアプリケーションを選択します

デザイン
見た目の部分はXAMLファイルに記述します。プロジェクト作成後にはWindow1.

今回作成するアプリケーションは、

以下にXAMLファイルの内容を示します。ウィンドウや画像の大きさや位置は、
<Window x:Class="FrameWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:VirtualPhotoFrame"
Title="仮想フォトフレーム" Width="800" Height="750"
AllowsTransparency="True" WindowStyle="None" Background="Transparent" ResizeMode="NoResize"
MouseLeftButtonDown="Window_MouseLeftButtonDown" >
<Grid>
<Grid.ContextMenu>
<ContextMenu>
<ContextMenu.CommandBindings>
<CommandBinding Command="{x:Static me:FrameWindow.SelectCollectionCommand}"
Executed="SelectCollectionCommand_Executed" />
</ContextMenu.CommandBindings>
<MenuItem Header="デバイスの登録" Click="RegisterMenuItem_Click" />
<MenuItem Header="コレクションの選択" x:Name="CollectionMenuItem">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="Command" Value="{x:Static me:FrameWindow.SelectCollectionCommand}" />
<Setter Property="CommandParameter" Value="{Binding FeedUrl}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
<MenuItem Header="コレクションの再取得" Click="GetCollectionMenuItem_Click" />
<Separator />
<MenuItem Header="終了" Click="ExitMenuItem_Click" />
</ContextMenu>
</Grid.ContextMenu>
<Grid Background="Black" Margin="157,170,141,182" Width="480" Height="360" />
<!-- ↓ Live FrameIt から配信された画像を表示 -->
<Image x:Name="PhotoImage" Margin="157,170,141,182" Width="480" Height="360" />
<!-- ↓ Live FrameIt の画像の上に表示する額の画像 -->
<Image Source="frame.png" />
</Grid>
</Window>
ユーザーのコレクションの数によってメニューの項目数も変わります。この部分はWPFの特徴であるバインディングやコマンドという機能を使用して実装します。それ以外のユーザー操作による処理部分はWindows.
設定の保存
デバイスIDや表示するコレクションのRSSフィードのURLを保存するために、

DeviceIdとCollectionFeedUrlという項目を追加しました。
コードの記述
本題ではないメニューのクリックイベント処理部分などをまとめて以下に示します。.xaml.
Imports VirtualPhotoFrame.ServiceReference
Imports Microsoft.WindowsLive.Id.Client
Imports System.Net
Imports System.ServiceModel
Imports System.ServiceModel.Channels
Imports System.ServiceModel.Syndication ' System.ServiceModel.Web を参照追加します
Imports System.Threading
Imports System.Windows.Threading
Class FrameWindow
Public Shared ReadOnly SelectCollectionCommand As RoutedCommand = _
New RoutedCommand("SelectCollection", GetType(FrameWindow))
Private Sub Window_MouseLeftButtonDown(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
' ウィンドウをドラッグで移動できるようにする
DragMove()
End Sub
Private Sub RegisterMenuItem_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
' RegisterDevice: 前回紹介したデバイスの登録手続きを記述したメソッド
' 取得したデバイス ID は アプリケーション設定保存する
' My.Settings.DeviceId = result.DeviceId
' My.Settings.Save()
RegisterDevice()
End Sub
Private Sub ExitMenuItem_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
' アプリケーションの終了
Me.Close()
End Sub
…
End Class
この後も、
コレクションの取得
それではコレクション情報を取得してみましょう。デバイスIDを取得するとユーザーのコレクションを取得できるようになります。取得には、
Using client = New DeviceSvcSoapClient
Dim results = client.GetCollectionInfo("取得したデバイス ID")
If results.ResponseCode = 0 Then
' 取得成功 (ResponseCode = 0)
' コレクション名と RSS フィード URL の表示
For Each l In results.CollectionInfoList
Console.WriteLine("Name: {0}, Url: {1}", _
l.Name, _
l.FeedUrl)
Next
End If
End Using
戻り値のCollectionInfoListプロパティを参照すると、
作成中のアプリケーションのコードには次のようにメソッド化したものを追記しておきましょう。またコレクションの再取得メニューをクリック時にメソッドを呼び出すようにします。
Private Sub GetCollectionInfo()
Using client = New DeviceSvcSoapClient
Dim results = client.GetCollectionInfo(My.Settings.DeviceId)
If results.ResponseCode <> 0 Then
MessageBox.Show("コレクションの取得に失敗しました。")
Exit Sub
End If
' コレクションをメニューに設定
CollectionMenuItem.ItemsSource = results.CollectionInfoList
End Using
End Sub
Private Sub GetCollectionMenuItem_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
' コレクションの取得
GetCollectionInfo()
End Sub
コレクションが取得できたら、
Private Sub SelectCollectionCommand_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs)
' コレクションの選択
' コレクション RSS フィード URL の保存
Dim url = CStr(e.Parameter)
My.Settings.CollectionFeedUrl = url
My.Settings.Save()
' RSS フィード 取得
Dim thread = New System.Threading.Thread(AddressOf FetchFeed)
thread.Start(url)
End Sub
フィードURLの保存後、
RSSフィードの解析
コレクションの取得でコレクションのURLが取得できました。この実体はRSSフィードのURLです。デバイスはRSSフィードを解析してユーザー所望の画像を表示します。それでは、
RSSフィード取得時の処理はFetchFeedというメソッドを作りその中に記述していくことにします。このメソッドと、
' RSS フィード更新用タイマー
Private WithEvents FeedTimer As New DispatcherTimer
' スライドショー(画像切り替え)表示用タイマー
Private WithEvents SlideShowTimer As New DispatcherTimer
' 画像 URL から保存したファイル名を参照するための Dictionary
Private CachedImages As New Dictionary(Of String, String)
Private CachedImagesLock As New ReaderWriterLockSlim
' スライドショー用の List, 表示順に 画像 URL を格納
Private SlideShowImageUrls As New List(Of String)
Private SlideShowIndex As Integer
Private SlideShowImageUrlsLock As New ReaderWriterLockSlim
Private Sub FetchFeed(ByVal state As Object)
' (RSS フィード取得処理を記述)
End Sub
RSSフィードの取得とそのタイミング
Live FrameItから配信される画像には天気予報やニュースなど時間経過とともに表示する価値が失われるもの、
GetCollectionInfoメソッドでは次のようなRSSフィードのURLが得られます。
http://rss.frameit.com/GenRss/genrss.ashx?id=...&pin=...
※pinパラメータはユーザーが暗証番号
設定していない場合ありません。
このURL先の内容を確認してみましょう。フィードの先頭部分は次のような構成になっています。
<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0" xmlns:frameit="http://www.frameit.live.com/firss/" xmlns:media="http://search.yahoo.com/mrss/">
<channel>
<ttl>53</ttl>
<title>旅行写真</title>
<link>http://frameit.live.com</link>
<generator>http://frameit.live.com</generator>
<lastBuildDate>Tue, 13 Oct 2009 08:05:15 -0700</lastBuildDate>
<pubDate>Tue, 13 Oct 2009 08:05:15 -0700</pubDate>
<description />
<item>
...
<ttl>と<pubDate>という項目がフィードに含まれていることがわかります。ttlはTime-to-Liveの略語で、
では、
' (FetchFeed メソッド内に追記)
FeedTimer.Stop()
SlideShowTimer.Stop()
' RSSフィード取得
Dim url = CStr(state) ' RSS フィード URL
Dim feed As SyndicationFeed
Try
feed = SyndicationFeed.Load(Xml.XmlReader.Create(url))
Catch ex As Exception
' 取得できなかった場合、時間を置いて再度取得する
FeedTimer.Interval = New TimeSpan(0, 1, 0)
FeedTimer.Start()
SlideShowTimer.Start()
Exit Sub
End Try
' 有効時間(分)取得
Dim ttl = (From ext In feed.ElementExtensions _
Where ext.OuterName = "ttl" _
Select ext.GetObject(Of Integer)()).SingleOrDefault
' フィードの発行日時取得
Dim pubDate = (From ext In feed.ElementExtensions _
Where ext.OuterName = "pubDate" _
Select ext.GetObject(Of String)()).SingleOrDefault
' 次回フィード取得時間を求めタイマーを設定
Dim span = CDate(pubDate).AddMinutes(ttl).Subtract(Now) ' 次回フィードするまでの時間
FeedTimer.Interval = span
FeedTimer.Start()
フィード取得はタイマーを用いて定期的に取得するため、
画像のダウンロード
続いてフィードに含まれている複数の<item>要素をみていきましょう。ひとつの<item>要素は次のような構成になっています。
<item>
<frameit:sourceIcon>http://image.frameit.com/GenImage/source.ashx?sq=1&st=6&mkt=ja-jp</frameit:sourceIcon>
<frameit:sourceCategory>ニュースと情報</frameit:sourceCategory>
<frameit:sourceName>天気予報</frameit:sourceName>
<title>東京都 東京</title>
<link>http://1.image.frameit.com/GenImage/Item.ashx?...</link>
<category>東京都 東京</category>
<guid isPermaLink="true">http://1.image.frameit.com/GenImage/Item.ashx?...</guid>
<description>
<![CDATA[ <img src="http://1.image.frameit.com/GenImage/Item.ashx?..." /><br/>東京都 東京
]]>
</description>
<pubDate>Tue, 13 Oct 2009 08:05:15 -0700</pubDate>
<enclosure type="image/jpeg" url="http://1.image.frameit.com/GenImage/Item.ashx?..." />
<media:content type="image/jpeg" width="480" height="400" url="http://1.image.frameit.com/GenImage/Item.ashx?..." />
<media:thumbnail width="77" height="64" url="http://1.image.frameit.com/GenImage/Item.ashx?...&thumb=1" />
</item>
例はLiveFrame Itで生成された天気予報の画像です。デバイスが参照すべき画像のURLが<link>や<guid>などの多数の要素に含まれています。ただし、
デバイスは、
- 画像はダウンロードしてデバイスのストレージにキャッシュしておく
- フィードに記載された順に画像を表示する
キャッシュ動作は、
フィードに記載された順に表示する理由は、
以上の動作に加えて、
フィードから画像URLを参照するコードは次のようになります。今回は<media:content>要素からURLを取得しています。
' スライドショーの URL リストをクリア
SlideShowImageUrlsLock.EnterWriteLock()
SlideShowImageUrls.Clear()
SlideShowImageUrlsLock.ExitWriteLock()
' フィードから画像 URL 参照
Dim newImages = New List(Of String)
For Each item In feed.Items
' の url 属性値取得
Dim imageUrl = (From ext In item.ElementExtensions _
Where ext.OuterName = "content" _
AndAlso ext.OuterNamespace = "http://search.yahoo.com/mrss/" _
Select ext.GetReader.GetAttribute("url")).SingleOrDefault
' 取得順にリストへ画像 URL を追加
SlideShowImageUrlsLock.EnterWriteLock()
SlideShowImageUrls.Add(imageUrl)
SlideShowImageUrlsLock.ExitWriteLock()
newImages.Add(imageUrl)
Next
取得したURLの順は記憶しておく必要があるので、
続いて画像のダウンロード部分を以下に示します。2回目以降のフィード取得時の動作も考慮して画像の削除を行ってから、
' フィードにない画像ファイル削除
Dim deletedKeys = New List(Of String)
For Each k In CachedImages.Keys
If newImages.Contains(k) Then
Continue For
End If
Try
CachedImagesLock.EnterReadLock()
My.Computer.FileSystem.DeleteFile(CachedImages(k))
deletedKeys.Add(k)
Catch ex As Exception
' Ignore
Finally
CachedImagesLock.ExitReadLock()
End Try
Next
' リストから古い画像URLを削除
CachedImagesLock.EnterWriteLock()
For Each k In deletedKeys
CachedImages.Remove(k)
Next
CachedImagesLock.ExitWriteLock()
' 新しい画像をダウンロード
For Each u In newImages
CachedImagesLock.EnterReadLock()
Dim contains = CachedImages.ContainsKey(u)
CachedImagesLock.ExitReadLock()
If contains Then
Continue For
End If
' 新しくダウンロード
Try
Dim client = New WebClient
Dim file = System.IO.Path.Combine(CachePath, Guid.NewGuid.ToString)
client.DownloadFile(u, file)
CachedImagesLock.EnterWriteLock()
CachedImages.Add(u, file)
CachedImagesLock.ExitWriteLock()
Catch ex As Exception
' Ignore
End Try
Next
以上が、
Private Sub FeedTimer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles FeedTimer.Tick
FeedTimer.Stop()
' RSS フィード 再取得
Dim thread = New System.Threading.Thread(AddressOf FetchFeed)
thread.Start(My.Settings.CollectionFeedUrl)
End Sub
Private CachePath As String = System.IO.Path.Combine(My.Application.Info.DirectoryPath, "cache")
Private Sub FrameWindow_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
' 画像をダウンロードするフォルダ作成
My.Computer.FileSystem.CreateDirectory(CachePath)
If My.Settings.DeviceId = Then
Exit Sub
End If
' コレクションの取得、メニュー設定
GetCollectionInfo()
' RSS フィード 読み出し
If My.Settings.CollectionFeedUrl <> Then
Dim thread = New System.Threading.Thread(AddressOf FetchFeed)
thread.Start(My.Settings.CollectionFeedUrl)
End If
End Sub
画像の表示
最後にダウンロードした画像をウィンドウに表示する処理を書いて完了です。以下のようにスライドショー用のタイマーのTickイベントに処理を記述します。
Private Sub SlideShowTimer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles SlideShowTimer.Tick
SlideShowTimer.Stop()
' 表示する画像の URL を List から取得
Dim url As String
Dim count As Integer
SlideShowImageUrlsLock.EnterReadLock()
url = SlideShowImageUrls(SlideShowIndex)
count = SlideShowImageUrls.Count
SlideShowImageUrlsLock.ExitReadLock()
' ダウンロードした画像があるかどうか
Dim exists As Boolean
Dim file As String =
CachedImagesLock.EnterReadLock()
If CachedImages.ContainsKey(url) Then
file = CachedImages(url)
exists = System.IO.File.Exists(file)
Else
exists = False
End If
CachedImagesLock.ExitReadLock()
' 画像がある場合、Image コントロールに設定
If exists Then
Try
PhotoImage.Source = New BitmapImage(New Uri(file))
Catch ex As Exception
End Try
End If
' List の Index 値をインクリメントまたは 0 に戻す
Dim prevIndex = Interlocked.Increment(SlideShowIndex)
If prevIndex >= count - 1 Then
Interlocked.Exchange(SlideShowIndex, 0)
End If
' 次回画像表示用にタイマーを設定
SlideShowTimer.Interval = New TimeSpan(0, 0, 10)
SlideShowTimer.Start()
End Sub
以上です。実際に実行して動作を確認してみましょう。Live FrameItサービス以外で提供された画像は既にインターネット上から削除されていたり、
おわりに
今回はここまでです。いかがでしたでしょうか。アプリケーションの動作のためコード量が少し多かったですが、
作成した仮想フォトフレームアプリケーションのソースコードは、
- VirtualPhotoFrame.
zip (147KB)
次回もLive FrameItのSDKについてです。今回紹介しきれなかった内容を扱う予定です。