1. 今回やること
・今までの経験を生かして、ピクチャフォルダの画像をサムネイル表示するアプリを作成します。
・ほぼ、MSのPhoto Editerのサムネイル画像の表示部分まんまです。
・今までやってきたことの応用というか組み合わせで出来るので難しく考えずにまずはPhoto Editerを見ながらやってみてもいいかもです。
・まずはPhoto Editerでどうやって実装しているのかを確認して、それをパクリ参考にして実装します
・以下の画像のように、上はListView、下はGridViewでピクチャフォルダの中にある画像を表示します。
2. Photo Editerではどうやって実装しているか
2-1. Photo Editerとは?
・MSのWebページを見れば書いてありますが、ピクチャフォルダの中身をサムネイル表示して、画像をクリックすると大きく表示して色々なエフェクトをかけられるアプリです。
・MSのC++/WinRTの最初に出てくるサンプルです。データバインドやIAsyncAction、Operationを含むコルーチンや非同期実行されるWinRTAPI、XAMLによる各種UI等。ずっとMFCのようなレガシーな開発環境だった私にとっては本当に何がなにやら・・・という感じでした。いきなり全部入りのサンプルなんてMS鬼畜過ぎ。そんなんだから爆死するんだよ!
・私の環境ではターゲットプラットフォームのバージョンを変更しないとビルドできませんでした。同じ症状の方は変更してみてください。
・さて、Photo Editerを実行すると以下のようになります。サムネイル表示の部分を参考にさせてもらいましょう。
2-2. Photo Editerの構造について
・GithubからクローンなりでVSで見てみると、①MainPage.xml②Photo.idl③DetailPage.xamlの3つに大きく分かれています。そのうち③DetailPage.xamlはエフェクトを担当しているので今回は見ません。
2-3. Photo.idlについて
・その4でやった変更通知可能なViewModelですね。
・PhotoではPhoto.hの35~44行目のコンストラクタを見ればわかるように、サムネイル画像を直接保存していません。保存しているのは、以下の4つです。
①Windows::Storage::FileProperties::ImageProperties(ファイルのプロパティー)
②Windows::Storage::StorageFile(ストレージファイルクラス)
③ファイル名
④ファイルタイプ
・サムネイル画像はGetImageThumbnailAsync()で必要な時に表示すれば良いという考えのようです。
・GetImageThumbnailAsync()の実装はPhoto.cppの39~46行目に書かれていますね。GetThumbnailAsync()でサムネイルを作成し、SetSource()でBItmapへ変換しています。その7ではIAsyncActionしか使用しなかったのですが、co_return(値を返す)があるので今回はIAsyncOperationとなります。
2-4. MainPageについて
・MainPage.idlやMainPage.hをみると解りますが、Photo EditorではViewModel側では無く、MainPage側にIVectorを持たせて、Photoオブジェクトを保持しています(MainPage.h35~38行目、61行目)。
・そのIVectorのPhotosへのPhotoオブジェクトの追加は、MainPage()のコンストラクタでは無く、MainPage::OnNavigatedTo()からGetItemAsync()を呼び出すことで行っています(MainPage.cpp47~68行目、148~201行目)。その11で最初に呼び出されると書きましたが、それを利用していますね。
・サムネイル画像の取得はOnContainerContentChanging()で行っています。
# 3. どのように作るか ・以下の指針で作っていきましょう。そのままじゃつまらないので一部変更します。
3-1. ViewModelには①StorageFile、②サムネイル画像、③ファイル名を保存する。
3-2. MainPageにIVectorを持たせる。
3-3. OnNavigatedToでIVectorに追加する関数を読み込む。
それでは実装していきます。
# 4. thumbnail_picture_1の作成 ・いつものように、「新しいプロジェクトの作成」→「Blank App(C++/WinRT)」→「thumb_pic_1(プロジェクト名)」でプロジェクトを作成します。 ・さらにいつものようにMainPageのClick MeとMyPropertyを削除します。 ・ビルド→実行して何も無いアプリが立ち上がるのを確認して、雛型の完成です。ここから色々追加しましょう。
4-1. マニフェストの変更
・今回はピクチャーフォルダにある画像をサムネイル化するので、当たり前ですがピクチャーフォルダへアクセスする必要があります。
・アクセスするためには、それを指定する必要があります。こんなことを一々やらなきゃいけないので面倒なんです。
・やり方は「ソリューションエクスプローラー」の「Pakage.appxmanifest」をダブルクリック。そこから「機能」タブを選んで、「ピクチャライブラリ」にチェックを入れる。これでアクセス出来るようになりました。他にも使いたいものが出来たらチェックを入れて、使えるようにしてください。
・インターネット(クライアント)は最初からチェックが入っているので変えてません。
4-2. thumbViewModelの作成
・①StorageFile、②サムネイル画像、③ファイル名を保存するViewModelを作成します。名前は「thumbViewModel」とします。
・ここもいつものように、「プロジェクト」→「モジュールの追加」→「Visual C++」から「View Model」を選び名前を「thumbViewModel」へ変更して「追加」します。
4-2-1. thumbViewModel.idlの変更
・まずはいらないMyPropertyを削除。
・今回は変更通知が必要ないので、[Windows.UI.Xaml.Data.Bindable]のみ追加。
・デフォルトコンストラクタの他に、StorageFile、サムネイル画像、ファイル名を引数とするコンストラクタも追加。
・それぞれのゲッターとプライベートメンバも追加します。
・Photo Editorとは異なり、サムネイル画像を保存しているのでStorageFileはいらないのだけれど、クリックしたら大きく表示したい等の要望が出たときにすぐに対応できるように保存するようにしてみました。
namespace thumb_pic_1
{
[Windows.UI.Xaml.Data.Bindable]
runtimeclass thumbViewModel
{
thumbViewModel();
thumbViewModel(Windows.Storage.StorageFile imagefile, Windows.UI.Xaml.Media.Imaging.BitmapImage thunbimage, String imagename);
Windows.Storage.StorageFile GetImageFile{ get; };
Windows.UI.Xaml.Media.Imaging.BitmapImage GetThumbImage{ get; };
String GetImageName{ get; };
}
}
4-2-2. thumbViewModel.hの変更
・コンストラクタと各種ゲッターを実装します。
・書く量も少ないので、すべてヘッダーに実装しました。
・thumbViewModel.cppはいらないプロパティーを削除しただけなので割愛
#pragma once
#include "thumbViewModel.g.h"
namespace winrt::thumb_pic_1::implementation
{
struct thumbViewModel : thumbViewModelT<thumbViewModel>
{
thumbViewModel() = default;
thumbViewModel(
Windows::Storage::StorageFile const& imagefile,
Windows::UI::Xaml::Media::Imaging::BitmapImage const& thumbimage,
hstring const& imagename
) :
imageFile(imagefile),
thumbImage(thumbimage),
imageName(imagename)
{}
Windows::Storage::StorageFile GetImageFile() const
{
return imageFile;
}
Windows::UI::Xaml::Media::Imaging::BitmapImage GetThumbImage() const
{
return thumbImage;
}
hstring GetImageName() const
{
return imageName;
}
private:
Windows::Storage::StorageFile imageFile{ nullptr };
Windows::UI::Xaml::Media::Imaging::BitmapImage thumbImage;
hstring imageName;
};
}
namespace winrt::thumb_pic_1::factory_implementation
{
struct thumbViewModel : thumbViewModelT<thumbViewModel, implementation::thumbViewModel>
{
};
}
4-3. MainPageの変更
4-3-1. MainPage.idlの変更
・thumbViewModel.idlをインポートしてIObservableVectorを追加します。
・IObservableVectorのテンプレートパラメータはIInspectableとしました。C++/WinRT を使用した IInspectable への値のボックス化とボックス化解除を読めばわかりますが、IInspectableはWinRTのすべてのランタイムクラスのルートインターフェイスです。その5ではテンプレートパラメータをStringViewModelとしたのでそれだけしかAppend出来ませんが、今回は途中で気が変わって別のViewModelに変更しても一々変更しなくても大丈夫。
import "thumbViewModel.idl";
namespace thumb_pic_1
{
[default_interface]
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
MainPage();
Windows.Foundation.Collections.IObservableVector<IInspectable> ThumbViewModelVec{ get; };
}
}
4-3-2. MainPage.hの変更
・#include "thumbViewModel.h"を忘れずに。
・idlでプロパティーを作ったのでThumbViewModelVecを実装します。getだけなので値を返すだけです。
・C++/WinRT でのコレクションの監視可能なコレクションで「winrt::single_threaded_observable_vector 関数テンプレートを呼び出し」「オブジェクトは IObservableVector として返されます。」と書かれているので、ThumbViewModelVecはIObservableVectorを使用しています。
・あとは、OnNavigateToと各種取得関数の定義を書いてます。サムネイル画像を取得するGetThumbnailAsync関数はIAsyncOperation型となります(テンプレートパラメータの型を返すので)。
#pragma once
#include "MainPage.g.h"
#include "thumbViewModel.h"
namespace winrt::thumb_pic_1::implementation
{
struct MainPage : MainPageT<MainPage>
{
MainPage();
Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> ThumbViewModelVec() const
{
return thumbViewModelVec;
}
//最初にやること(サムネイル画像を集める)
Windows::Foundation::IAsyncAction OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs);
//ここで、StorageFile、サムネイル画像、ファイル名を集めるよ
Windows::Foundation::IAsyncAction AppendViewModelAsync();
//サムネイル画像を取得するよ
Windows::Foundation::IAsyncOperation<Windows::UI::Xaml::Media::Imaging::BitmapImage> GetThumbnailAsync(Windows::Storage::StorageFile m_imageFile);
private:
Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> thumbViewModelVec{ nullptr };
};
}
namespace winrt::thumb_pic_1::factory_implementation
{
struct MainPage : MainPageT<MainPage, implementation::MainPage>
{
};
}
4-3-3. MainPage.cppの変更
・まずはwinrt/Windows.UI.Xaml.Media.Imaging.hとwinrt/Windows.Storage.Search.hをインクルードします。
・これをインクルードしないと、「エラー C3779 'auto' を返す関数を、定義より前に使用することはできません」のエラーが出ます。
・最初この意味がわからなくて苦労しましたが、コンパイラエラー C3779にヘッダが足りないんだよ。と書かれていたのでようやく気がつきました。このエラーが発生したら、手当たり次第WinRT関連のヘッダーをインクルードしてみてください。
・さて、ヘッダーをインクルードしたら実装に入ります。
・OnNavigatedToはAppendViewModelAsync()を呼び出しているだけです。
・そのOnNavigatedToはIAsyncActionと非同期で実装しています。実はvoidでもいけます。
・AppendViewModelAsync()はピクチャーフォルダにある画像ファイルを列挙して、ThumbViewModelVecへ追加しているだけですね。ファイルやフォルダに関する詳しいことはファイル、フォルダー、およびライブラリを参照してください。
・GetThumbnailAsync()はBitmapImageを返す必要があるので、IAsyncOperation<BitmapImage>型です。簡単な使い方はコメントに書いておきました。詳しい使い方は配列を非同期的に返すを参照してください。
#include "pch.h"
#include "MainPage.h"
#include "MainPage.g.cpp"
#include <winrt/Windows.UI.Xaml.Media.Imaging.h>
#include <winrt/Windows.Storage.Search.h>
using namespace winrt;
using namespace Windows::UI::Xaml;
namespace winrt::thumb_pic_1::implementation
{
MainPage::MainPage()
{
thumbViewModelVec = winrt::single_threaded_observable_vector<IInspectable>();
InitializeComponent();
}
//AppendViewModelAsync()を呼ぶだけ
Windows::Foundation::IAsyncAction MainPage::OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs)
{
if (ThumbViewModelVec().Size() == 0)
co_await AppendViewModelAsync();
}
Windows::Foundation::IAsyncAction MainPage::AppendViewModelAsync()
{
//サーチするのは「jpg」「png」「gif」
Windows::Storage::Search::QueryOptions searchoptions{};
searchoptions.FolderDepth(Windows::Storage::Search::FolderDepth::Deep);
searchoptions.FileTypeFilter().Append(L".jpg");
searchoptions.FileTypeFilter().Append(L".png");
searchoptions.FileTypeFilter().Append(L".gif");
//ピクチャーフォルダをサーチ
Windows::Storage::StorageFolder picturesfolder = Windows::Storage::KnownFolders::PicturesLibrary();
//上記の条件に合うのを探す
auto searchresult = picturesfolder.CreateFileQueryWithOptions(searchoptions);
auto imagefiles = co_await searchresult.GetFilesAsync();
//StorageFileからサムネイル画像とファイル名を取得して、ThumbViewModelを作成し、ThumbViewModelVecへAppend(追加)
for (auto&& file : imagefiles)
{
if (file.Provider().Id() == L"computer")
{
auto thumbimage = co_await GetThumbnailAsync(file);
auto thumbview = winrt::make<thumbViewModel>(file, thumbimage, file.DisplayName());
ThumbViewModelVec().Append(thumbview);
}
}
}
//GetThumbnailAsyncは右の定義 IAsyncOperation<StorageItemThumbnail> GetThumbnailAsync(ThumbnailMode const& mode);
//IAsyncOperation型はco_awaitを使用して呼び出すと、テンプレートパラメータの型を返す。
//なのでco_await imagefile.GetThumbnailAsync()はStorageItemThumbnail型を返すよ。
//と言うわけで、GetThumbnailAsync()はco_awaitを使用して呼び出すとBitmapImageを返すよ。
Windows::Foundation::IAsyncOperation<Windows::UI::Xaml::Media::Imaging::BitmapImage> MainPage::GetThumbnailAsync(Windows::Storage::StorageFile imagefile)
{
auto thumbnail = co_await imagefile.GetThumbnailAsync(Windows::Storage::FileProperties::ThumbnailMode::PicturesView);
Windows::UI::Xaml::Media::Imaging::BitmapImage thumbnailimage{};
thumbnailimage.SetSource(thumbnail);
thumbnail.Close();
co_return thumbnailimage;
}
}
4-3-4. MainPage.xamlの変更
・上のListView側は1行のリスト項目のテンプレートを使用しています。
・下のGridView側は画像とテキストのテンプレートを使用しています。
・他にもいろんなテンプレートがあるので、試してみてください。
<Page
x:Class="thumb_pic_1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:thumb_pic_1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" >
<Grid RequestedTheme="Default">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<ListView x:Name="list1" ItemsSource="{x:Bind ThumbViewModelVec}" Grid.Row="0">
<ListView.ItemTemplate>
<DataTemplate x:Name="singleLineTemplate" x:DataType="local:thumbViewModel">
<StackPanel Orientation="Horizontal" Height="160">
<Image Source="{x:Bind GetThumbImage}" Height="150" Width="150" VerticalAlignment="Center"/>
<TextBlock Text="{x:Bind Path=GetImageName}" VerticalAlignment="Center" Style="{ThemeResource BaseTextBlockStyle}" Foreground="{ThemeResource SystemControlPageTextBaseHighBrush}" Margin="12,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<GridView ItemsSource="{x:Bind ThumbViewModelVec}" Grid.Row="1" >
<GridView.ItemTemplate>
<DataTemplate x:Name="ImageTextDataTemplate" x:DataType="local:thumbViewModel">
<StackPanel Height="200" Width="180" Margin="12">
<Image Source="{x:Bind GetThumbImage}" Height="150" Width="150" Stretch="UniformToFill"/>
<StackPanel Margin="0,12">
<TextBlock Text="{x:Bind Path=GetImageName}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid MaximumRowsOrColumns="10" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
</Grid>
</Page>
5. まとめ
・今回はPhoto Editorのサムネイル画像の表示部分のみを作ってみました。
・ListViewやGridViewに「Click」のイベントハンドラーを追加すれば、別ウインドウに大きく表示させたり、前回やったナビゲートを使用してPhoto Editorと同じようにMainPageに大きく表示させたりと言うことが可能となります。頑張って追加してみてください。
githubはここに
次は何を作ろうかなぁ・・・