WPFでReactiveProperty入門 ~ Rxを使って検索結果のサムネイル画像を一括ダウンロードする
この記事は、WPFでReactiveProperty入門 ~Bing画像検索ビューアを作る - pierre3のブログ の続きです。
関連記事
- WPFでReactiveProperty入門 ~Bing画像検索ビューアを作る - pierre3のブログ
- WPFでReactiveProperty入門 ~Bing画像検索ビューアを作る (1. 検索バーの実装) - pierre3のブログ
サンプルプロジェクト
今回は、Bing画像検索を実行し、検索結果からサムネイル画像を取得して一覧表示する処理について、Model側に焦点を当てて見てみたいと思います。
目次
今回は、Model側の処理がメインになりますので、Model側の主要クラスについて簡単にまとめておきます。 Bing Search API をAzure Market Placeから購入(無料含む)すると、AzureポータルからBing Searchのプロキシクラス(BingSearchContainer.csファイル)をダウンロードして使用することが出来ます。 今回はこれをそのまま使用させて頂きました。 このプロキシクラスは、WCFデータサービスの仕組みを利用しており、 画像検索の実行には、Image() メソッドを使用します。 引数で検索条件等を色々指定できるのですが、とりあえずは第1引数に検索ワードを入れたら後は ※ パラメータの詳細は、下記リンクの"Optional Parameters" の項目で確認できます。
https://msdn.microsoft.com/en-us/library/dd250942.aspx
画像検索結果が格納される 上記のような非同期操作の実装はいくつか考えられますが、今回はReactive Extensionsを使用した方法で実装を試してみたいと思います。 IE 今回のサンプルでは、検索ワードを渡すとBing画像検索を実行して結果を ここでは、 検索実行中に発生した例外は、そのままでは 上記の例では、検索実行中に発生した例外は Rxで流れてきた検索結果オブジェクト ここでは、 Rxの中で非同期メソッドを実行するには、 こうすることで、 この一連の処理を 以下に、実装例を示します。(実際には進捗通知等の処理が入り、もう少し複雑になっていますが、ここでは説明のため簡略化しています) これで、検索を実行して、結果をサムネイルの一覧で表示するための枠組みが出来ました。 Modelの概要
クラス
説明
Bing.BingSearchContainer
Microsoftが提供するBing Search APIのプロキシクラス
Bing.ImageResult
Bing Search APIが返す画像検索結果のクラス。検索結果の画像1枚の情報が格納されている
WebImage
検索結果の画像1枚を表すクラス。Bing.ImageResultに格納されている画像URLから画像データをダウンロード後、BitmapImageとして保持する
WebImageStore
WebImageのコレクションを管理するクラス。画像検索を実行し、検索結果からWebImageを生成してコレクションに保持する
WebImageHelper
WebImage、WebImageStoreクラスを補助するメソッドを提供するクラス
Bing Search API プロキシクラス
BingSearchContainer
System.Data.Services.Client.DataServiceContext
を継承したBingSearchContainer
クラスを用いて検索を行います。null
でOKです。//Bing Image検索プロキシクラス
public class BingSearchContainer : System.Data.Services.Client.DataServiceContext
{
//画像検索用のクエリを発行
public DataServiceQuery<ImageResult> Image(String Query, String Options,
String Market, String Adult, Double? Latitude, Double? Longitude, String ImageFilters)
{
DataServiceQuery<ImageResult> query;
query = base.CreateQuery<ImageResult>("Image");
//...(中略)...
return query;
}
}
Image()
メソッドは、画像検索用クエリを表す DataServiceQuery<ImageResult>
を返却します。
DataServiceQuery<ImageResult>
のExecute()
メソッドでクエリを実行すると、検索結果がIEnumerable<ImageResult>
のかたちで返却されます。var bing = new Bing.BingSearchContainer(new Uri("https://api.datamarket.azure.com/Bing/search/"));
bing.Credentials = new NetworkCredential("accountKey", accountKey);
//画像検索用のクエリ(DataServiceQuery<Bing.ImageResult>)を発行
var query = bing.Image(searchWord, null, null, null, null, null, null);
//検索実行
IEnumerable<T> result = query.Execute();
ImageResult
ImageResult
クラスは以下のプロパティを持ちます//画像検索の結果を格納するクラス
public class ImageResult
{
public Guid ID { get; set; }
public String Title { get; set; }
public String MediaUrl { get; set; }
public String SourceUrl { get; set; }
public String DisplayUrl { get; set; }
public Int32? Width { get; set; }
public Int32? Height { get; set; }
public Int64? FileSize { get; set; }
public String ContentType { get; set; }
public Thumbnail Thumbnail { get; set; }
}
public class Thumbnail
{
public String MediaUrl { get; set; }
public String ContentType { get; set; }
public Int32? Width { get; set; }
public Int32? Height { get; set; }
public Int64? FileSize { get; set; }
}
MediaUrl
に画像データをダウンロードするためのURLが格納されています。
また、画像サイズを縮小したサムネイル用の画像データを取得することが可能で、その場合はThumbnail
プロパティのMediaUrl
を使用します。検索結果のシーケンスからサムネイル画像をまとめて非同期ダウンロードする。
IEnumerable<ImageResult>
として返される全ての検索結果(最大50件)のサムネイル画像を一度にダウンロードするのですが、(当然ながら)UIをブロックしないよう非同期で行う必要があります。Reactive Extensions で非同期ダウンロード
IEnumerable<ImageResult>
から IObservable<ImageResult>
に変換IObservable<ImageResult>
で返すヘルパーメソッドを WebImageHelper
クラスに用意しています。DataServiceQuery<ImageResult>
をExecute()して得られるIEnumerable<ImageResult>
を ToObservable()
で IObserbable<ImageResult>
に変換たものをreturn しています。static class WebImageHelper
{
//Bing Image 検索を実行して 結果を IObservable<T> で返す
public static IObservable<Bing.ImageResult> SearchImageAsObservable(
string searchWord, string accountKey, int skip, int top)
{
var bing = new Bing.BingSearchContainer(
new Uri("https://api.datamarket.azure.com/Bing/search/"));
bing.Credentials = new NetworkCredential("accountKey", accountKey);
var query = bing.Image(searchWord, null, null, null, null, null, null);
query = query.AddQueryOption("$skip", skip);
query = query.AddQueryOption("$top", top);
try
{
IEnumerable<T> result = query.Execute();
return result.ToObservable();
}
catch (Exception e)
{
return Observable.Throw<Bing.ImageResult>(e);
}
}
}
例外を
IObserbable<T>
に乗せるには Observable.Throw<T>()
IO<T>
の一連の流れの中では補足できませんが、
Observable.Throw<T>()
メソッドを使用することで、例外をIObservable<T>
として扱うことが可能になります。IObservable<ImageResult>
で包まれて、購読側のOnError()
メソッドで補足できるようになります。SelectMeny()で非同期メソッドを実行する
ImageResult
を基にサムネイル画像のダウンロードを行う処理は、以下のメソッドを用いて行います。ImageResult
オブジェクトを引数にWebImage
のインスタンスを生成後そのDownLoadThumbnailAsync()
メソッドでサムネイル画像のダウンロードを非同期に行います。戻り値はTask<WebImage>
となります。public class WebImageStore : IDisposable
{
//検索結果オブジェクト(ImageResult)でWebImageのインスタンスを生成後、
//ImageResultに格納されているサムネイル画像のURLから画像データをダウンロードする
private async Task<WebImage> CreateWebImageAsync(Bing.ImageResult bingResult)
{
var image = new WebImage(bingResult, logger);
await image.DownLoadThumbnailAsync();
return image;
}
}
SelectMeny()
を使用します。
SelectMeny()
に渡すデリゲートをasync
にして、その中で await CreateWebImageAsync()
とします。CreateWebImageAsync()
メソッドは非同期かつ並列的に実行されます。実行の結果は、完了した順に後続へ渡ります。Subscribe()
すると、OnNext()
にサムネイルのダウンロードが完了したWebImage
オブジェクトが渡ってくるので、これを ObservableCollection に逐次追加して行きます。public class WebImageStore : IDisposable
{
private IDisposable disposable;
private ObservableCollection<WebImage> imagesSource;
public ReadOnlyObservableCollection<WebImage> readonlyImages;
public IReadOnlyObservableCollection<WebImage> Images {get {return readonlyImages;}}
public void DownloadSearchResult(string searchWord)
{
disposable = WebImageHelper.SearchImageAsObservable(searchWord, bingAccountKey)
.SelectMany(async bingResult => await CreateWebImageAsync(bingResult))
.Where(webImage => webImage?.Thumbnail != null)
.Subscribe(
onNext: webImage =>
{
//ObservableCollection に追加
imagesSource.Add(webImage);
},
onError: e =>
{
//SearchImageAsObservable()内でObservable.Throw()した例外はここで補足
logger.Error("画像の検索に失敗しました。", e);
},
onCompleted: () =>
{
logger.Info("検索が完了しました。");
});
}
}
まとめ
次回は、URLからサムネイルの画像データをダウンロードする処理と、そのデータを元にBitmapImage
オブジェクトを生成する処理の詳細について書きたいと思います。