【WinUI】デフォルトで定義されているスタイルを調べた

あらすじ

  • WinUIにはコントロールに適用できるスタイルというものがあり、色や形などのテンプレートとして利用できる。
  • スタイルにはデフォルトで定義されているものもあるが、ドキュメントなどは存在せず、検索性が悪い。
  • 自分用に調べてまとめておこうと思った。
  • とりあえずボタンのスタイルのみを調べた。

結果

以下は使いそうなものだけまとめたもの。

名前 説明 画像
AccentButtonStyle アクセントボタン 色がつく
NavigationBackButtonNormalStyle 戻る動作を示す左矢印ボタン
NavigationViewOverflowButtonStyleWhenPaneOnTop 三点リーダ
NavigationViewPaneSearchButtonStyle 検索ボックスの虫眼鏡ボタン
TextBlockButtonStyle 下線のないハイパーリンク

AccentButtonStyle

多分一番使うやつ

TextBlockButtonStyle

hyperlinkbuttonのページに使い方がのってる。

使い方

<Button  Style="{StaticResource AccentButtonStyle}">AccentButton</Button>

スタイルの調べ方

この質問を参考にした。 スタイルの定義はGeneric.xamlに書かれていて、適当なブラシを選択してF12を押して定義に移動したら見れるよと言っている。

【Windows App SDK/C++】チェックボックスをTwoWayモードでバインドする

Windows App SDKC++を使用したアプリで、CheckBoxのIsCheckedプロパティをTwoWayモードでバインドします。ただそれだけです。 IsCheckedのプロパティをバインドすることで、ドメインロジックの層ではCheckBoxを意識することなく、ただバインドされたbool型の変数を参照すればよくなります。

前置き

twowayモードを使用しますが、データの流れとしてはGUIチェックボックスの状態をある変数の値に反映させる方向のみであり、例えばプログラムから画面のチェックボックスのチェック状態を操作するようなことは行いません。 この理由としては単にWindows App SDKのBinding Modeに画面から変数(ターゲットからソース)の向きのみを扱うモードが無いからです。

BindingMode 列挙型を見れば分かるように、OneTime/OneWay/TwoWayしかモードがありません。なのでCheckBoxのようにユーザが操作できるGUIの状態を変数にバインドするには必然的にTwoWayモードを使用することになります。

余談ですが.NETのAPIにはOneWayToSourceというターゲットからソースへの一方向のバインドを行うモードがあります。

アプリの動作

今回作成したアプリの挙動を説明します。

画面にはボタン、テキストブロック、チェックボックスの3つの要素があります。 ボタンをクリックすると、テキストの内容がチェックの有無に応じた値に更新されます。 チェックが入っていれば"Checked!"、チェックがなければ"Unchecked..."と表示されます。

クラス構成

一応ビューとビューモデルを分離しています。チェックボックスのIsCheckedプロパティとテキストブロックのTextプロパティはCheckBoxViewModelのメンバ変数にバインドされているので、ビュークラスの仕事はクリックイベントの処理のみです。

コード内容

プロジェクトはGithubにあげたのですべて見たい場合はそちらを確認していただくことにして、要点のみを説明します。

github.com

まずはxamlGUIの要素を作成します。 TextBlockはバインドした変数の内容を表示するだけなのでModeがOneWayになっているのに対し、CheckBoxのIsChecked要素はTwoWayとしています。

<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
        <TextBlock Text="{x:Bind ViewModel.Text, Mode=OneWay}"></TextBlock>
        <CheckBox Content="Do you agree?"
                  IsChecked="{x:Bind ViewModel.IsChecked, Mode=TwoWay}"/>
    </StackPanel>

次にCheckBoxViewModelの定義です。 idlファイルではXamlからアクセスできるようにプロパティのゲッター、セッターを宣言します。 idlファイルにいちいち書かないといけないのがC#で書くときに比べて冗長に感じるところです。。。 テキストを更新したタイミングでGUIに通知を送信するためにMicrosoft.UI.Xaml.Data.INotifyPropertyChangedを継承しています。

namespace TwoWayViewModel
{
    [Microsoft.UI.Xaml.Data.Bindable]
    runtimeclass CheckBoxViewModel : Microsoft.UI.Xaml.Data.INotifyPropertyChanged
    {
        CheckBoxViewModel();
        Int32 MyProperty;
        Boolean IsChecked;
        String Text{ get; };
        void UpdateText();
    }
}

個人的に詰まったポイントがBooleanの表記です。 MIDL Predefined and Base Typesを確認し、boolean表記にしていたのですが、そうすると以下のエラーが発生します。

Classic WinRT IDL constructs cannot be used in Modern WinRT IDL types [context]: boolean

クラシックなWinRTとモダンなWinRTの違いがわからないので正確なことは言えないですが、Boolean表記とすることでビルドが通りました。

IsCheckedの実装は以下の通り、単にbool型の変数のゲッター、セッターです。

boolean IsChecked() { return is_checked_; };
void IsChecked(boolean val) { is_checked_ = val; };

boolean is_checked_ = true; // デフォルト値

これでis_checked変数にチェックボックスの値がバインドされます。 ソースからターゲットへのOneWayのバインドの場合は、値を更新したら通知イベントを発行する必要がありますが、ターゲットからソースへの場合はターゲット側の値が変化すれば自動で変数に反映されます。

あとはCheckBoxViewModelで処理を行う際にはチェックボックスを意識せずにis_checkedを参照すればよいです。簡単ですね。

WindowsデスクトップアプリのMVVM実装例をみてみる

はじめに

GUIを含んだアプリケーションのアーキテクチャにMVVMモデルがあります。 自分でWindowsデスクトップアプリケーションを作るにあたり、正しくMVVMモデルに則ったクラス設計をしたいと思いました。 しかしテンプレートのプロジェクトを立ち上げた時点で作成されるクラスがMVVMでいうところの何に当てはまるのか分からなかったので調査しました。 幸い、Microsoftの公式リポジトリに参考になるプロジェクトを発見したので、そのアプリのアーキテクチャを見ていきたいと思います。

MVVMの依存関係

分析するリポジトリ

Windows-appsample-customers-orders-databaseです。 以下のページでMVVMが採用されているサンプルとして紹介されていました。

基本および高度な MVVM

顧客リストと注文リストの内容を画面に表示するアプリですが、内容にはあまり触れず、クラスの構成を見ていきます。

パッケージ構成

パッケージ

View, UserControl, ViewModelが綺麗に整理されていることが確認できます。 Serviceパッケージもありますが、どのような責務を担っているのか気になるところです。

ちなみにContosoとはMicrosoftがサンプル用に使う架空の会社名だそうです。(初めて知った。)

Viewクラス

ViewsディレクトリとContosoApp直下の一部のクラスをモデルに起こすと以下のようになります。

Viewsのクラス図

トップにMainWindowがあり、顧客情報一覧と受注情報一覧を見るページがそれぞれあるようです。

Viewのクラス群の実装を確認すると次のようなことが分かります。

  • xamlファイル内のアプリ実行中に動的に変更される部分はすべてバインドされている。
  • 静的な値はApp.xamlに定義して各ページから参照する
  • 処理はViewModelのメソッドを呼ぶかページ更新
  • デリゲートの引数を活用している。
  • Appクラスへの参照はOK

xamlファイル内のアプリ実行中に動的に変更される部分はすべてバインドされている。

例えばCustomerDetailPage.xamlでは

<AppBarButton
    Click="{x:Bind ViewModel.StartEdit}"
    Icon="Edit"
    IsEnabled="{x:Bind vm:Converters.Not(ViewModel.IsInEdit), Mode=OneWay}"
    Visibility="{x:Bind vm:Converters.CollapsedIf(ViewModel.IsNewCustomer), Mode=OneWay}"
    Label="Edit" />

動的に変化する部分は全てbindされています。 クリックイベントに関しては私はこれまで直接メソッド名をここに書いて実装しており、依存関係的にも問題ないと思っていたので、bindする価値がどのようなところにあるのかは疑問です。

静的な値はApp.xamlに定義して各ページから参照する

<!-- CustomerDetailPage.xaml -->
<VisualState.StateTriggers>
    <AdaptiveTrigger MinWindowWidth="{StaticResource LargeWindowSnapPoint}" />
</VisualState.StateTriggers>

<!-- App.xaml -->
<x:Double x:Key="LargeWindowSnapPoint">1008</x:Double>

グローバル変数みたいな使い方ですね。

処理はViewModelのメソッドを呼ぶかページ更新

MVVMのお手本はこんな感じなのかと関心した部分です。 Viewクラスは自身のメンバにViewModelインスタンスを持っておき、イベントが発生するたびにViewModelの必要なメソッドを実行します。

画面への表示に関しては何も記述されていません。 後述しますが画面に表示する内容はViewModelクラスの変数にバインドされているので、Viewのクラスでは全く現れません。

Appクラスへの参照はOK

意外だったところで、PageクラスからAppクラスを参照している箇所がありました。

var customer = App.ViewModel.Customers.Where(cust => cust.Model.Id == guid).FirstOrDefault();

Appクラスはエントリーポイント的なイメージで、ここを参照しているのは見たことはありませんでした。やっていいんだ。 というかなんで参照できてるのかわかってない部分があります。

ViewModelクラス

次にViewModelを見ていきます。 MVVMの考え方でいけば、Modelが持つ情報を加工したりする人です。

ViewとViewModelの関係の部分を取り上げてクラス図を描きました。 まず、ViewModelにstringを返すメソッドが多いです。これはViewにバインドされているメソッドで、ViewModel内の変数が更新されれば勝手にViewのほうも更新されるものです。

ViewModelのクラス群の実装を確認すると次のようなことが分かります。 - 「選択中のモデル」のような特別なModelの情報を持つ - 「編集中」のような一時的な状態を持つ

「選択中のモデル」のような特別なModelの情報を持つ

このアプリで起こるシチュエーションとして、画面上のリストの中から1つの項目を選択した状態で詳細ページを開くというものがあります。 この、選択中の項目の情報を管理する役割をViewModelが担っています。

「編集中」のような一時的な状態を持つ

GUI上の操作のシーケンスに応じて状態を管理したいケースがあります。そしてその状態を使用してGUIの状態を操作したいケース、例えばModelの編集中はあるボタンを押せないようにしたいといったときに使うような状態はViewModelクラスで管理されています。

Modelクラス

長くなったので分けます

【WinUI3】unpackagedアプリを作成する

WinUI3のプロジェクトでは、packaged・unpackagedを選択できます。 unpackagedなアプリとすることで、exeファイルから直接実行できるようになります。

違いについては公式のドキュメントを参照してもらうとして、今回はunpackagedのアプリを作成する際の手順をまとめます。

準備:プロジェクト作成

「新しいプロジェクトの作成」から「空のアプリ、パッケージ化(デスクトップのWinUI3)」を選択します。

手順1:プロジェクトのアンロード

ソリューションエクスプローラからプロジェクトを右クリックし、リストの中から「プロジェクトのアンロード」を選択します。

手順2:.vcxprojファイルを編集する

表示される.vcxprojファイルの要素の末尾に以下の要素を追加します。

<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<WindowsPackageType>None</WindowsPackageType>

また、すでに存在する<AppxPackage>の内容をfalseに変更します。

↓変更済の状態

<PropertyGroup Label="Globals">
    <!-- 中略 -->
    <TargetName>$(RootNamespace)</TargetName>
    <DefaultLanguage>ja-JP</DefaultLanguage>
    <MinimumVisualStudioVersion>16.0</MinimumVisualStudioVersion>
    <AppContainerApplication>false</AppContainerApplication>
    <AppxPackage>false</AppxPackage>  <!-- falseに変更 -->
    <ApplicationType>Windows Store</ApplicationType>
    <ApplicationTypeRevision>10.0</ApplicationTypeRevision>
    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
    <WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion>
    <UseWinUI>true</UseWinUI>
    <EnableMsixTooling>true</EnableMsixTooling>
    <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
    <WindowsPackageType>None</WindowsPackageType>
</PropertyGroup>

手順3:「プロジェクトの再読み込み」を実行

再びソリューションエクスプローラのプロジェクトを右クリックし、今度は「プロジェクトの再読み込み」を選択します。

確認

この状態で所望のビルド構成でビルドすると、exeファイルからアプリが起動するようになります。

【C++/WinRT】ファイルの追加・削除を検知する

WinUI3を使用したデスクトップアプリで、指定したフォルダ内のファイルの追加、削除を検知します。

今回は以下の2通りの方法を紹介します。

  1. フォルダ内のファイルを列挙し、ファイル名のリストを保持しておく。
  2. ファイルの追加・削除・変更をトリガーにしたイベントを作成する。

プロジェクトの作成

VisualStudioの新しいプロジェクトの作成から空のアプリ、パッケージ化(デスクトップのWinUI3)のプロジェクトを作成します。

WinUI3のプロジェクトテンプレートを使用する

そのままビルドすると、ボタンのみの画面が表示されます。

初期状態

ここに機能を追加していきます。

1. フォルダ内のファイルを列挙し、ファイル名のリストを保持しておく。

継続的にフォルダ内の状態を取得し、前回の状態を変わっていれば必要な処理を行うということを考えます。まずはフォルダ内のファイルの一覧を取得する機能を追加します。

まずXamlファイルにファイル名を表示するためのTextBlockを追加します。サイズは適当です。

<StackPanel Orientation="Vertical" 
            HorizontalAlignment="Center" 
            VerticalAlignment="Center">
    <Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
    <!-- TextBlockを追加 -->
    <TextBlock x:Name="myTextBlock" Width="700" Height="400" />
</StackPanel>

ヘッダーとソースも変更します。 非同期処理を含むので、myButton_Clickの返り値をvoidからIAsyncActionに変更しています。

// MainWindow.xaml.h
Windows::Foundation::IAsyncAction myButton_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& args);
// MainWindow.xaml.cpp
// 追加
#include "winrt/Windows.Storage.h"
#include "winrt/Windows.Storage.Search.h"

// 省略

Windows::Foundation::IAsyncAction MainWindow::myButton_Click(IInspectable const&, RoutedEventArgs const&)
{
    //myButton().Content(box_value(L"Clicked"));

    // テキストファイルのみを対象にする
    auto file_type{ single_threaded_vector<hstring>({L".txt"}) };
    Windows::Storage::Search::QueryOptions options = Windows::Storage::Search::QueryOptions{ Windows::Storage::Search::CommonFileQuery::DefaultQuery, file_type };
    Windows::Storage::StorageFolder target_folder{ co_await Windows::Storage::StorageFolder::GetFolderFromPathAsync(L"C:\\Users\\username\\Documents\\target_dir") };
    Windows::Storage::Search::StorageFileQueryResult query = target_folder.CreateFileQueryWithOptions(options);
    auto files{ co_await query.GetFilesAsync() };

    hstring result{ L"" };
    for (auto file : files) {
        result = result + L"; " + file.Name();
    }

    myTextBlock().Text(result);
}

ボタンがクリックされるたびに、以下の処理を行っています。 - 検索対象の拡張子のリストを作成 - クエリオプションを作成 - 検索対象のフォルダオブジェクトを取得 - フォルダに対してクエリを実行し、結果オブジェクトを取得 - 結果オブジェクトからファイルオブジェクトのリストを取得

今回は単に取得したファイルのファイル名を画面に表示していますが、このリストを保持しておき、前回の結果と比較することでファイルの作成・削除を検知することが可能になります。

2. ファイルの追加・削除・変更をトリガーにしたイベントを作成する。

別の方法としてイベントを使用する方法があります。

上記のコードの中に出てきたStorageFileQueryResultクラスにはContentsChangedイベントが用意されています。

公式ドキュメント > StorageFileQueryResult クラス

このイベントはクエリ対象フォルダのファイル追加・削除・変更を検知することができます。使ってみましょう。 新しくプロジェクトを作成し、追記していきます。

xamlは先ほどと同様に、表示用のTextBlockを追加します。 また、イベントを設定するためのボタンも追加します。

<!-- 再掲です -->
<StackPanel Orientation="Vertical" 
            HorizontalAlignment="Center" 
            VerticalAlignment="Center">
    <Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
    <Button x:Name="myButton2" Click="myButton2_Click">Set event</Button>
    <!-- TextBlockを追加 -->
    <TextBlock x:Name="myTextBlock" Width="700" Height="400" />
</StackPanel>

ヘッダーとソースを編集します。 先ほどの例と異なるのは、イベントを登録するための関数と、イベントを検知したときに行う処理(デリゲート)を作成する点、そしてイベントを登録するStorageFileQueryResultインスタンスはアプリの起動中は生存している必要があるため、MainWindowクラスのメンバ変数に持たせておきます。

// MainWindow.xaml.h
struct MainWindow : MainWindowT<MainWindow>
{
    void myButton_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& args);
    void myButton2_Click(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::RoutedEventArgs const& args);
    
    Windows::Foundation::IAsyncAction SetFileEvent();
    void QueryContentsChanged(Windows::Storage::Search::IStorageQueryResultBase const& sender, IInspectable const& args);

    Windows::Storage::Search::StorageFileQueryResult query_ = { nullptr };
    std::string state_ = { "default" };
};
// 追加
#include "winrt/Windows.Storage.h"
#include "winrt/Windows.Storage.Search.h"

// 中略
Windows::Foundation::IAsyncAction MainWindow::SetFileEvent() {
    auto file_type{ single_threaded_vector<hstring>({L".txt"}) };
    auto options = Windows::Storage::Search::QueryOptions{ Windows::Storage::Search::CommonFileQuery::DefaultQuery, file_type };
    auto target_folder{ co_await Windows::Storage::StorageFolder::GetFolderFromPathAsync(L"C:\\Users\\username\\Documents\\target_dir") };

    query_ = target_folder.CreateFileQueryWithOptions(options);
    // イベントの発生前にGetFilesAsyncを少なくとも1回は呼び出す必要がある
    auto files{ co_await query_.GetFilesAsync() };
    // delegateを渡す
    query_.ContentsChanged({ this,&MainWindow::QueryContentsChanged });
}

void MainWindow::myButton2_Click(IInspectable const&, RoutedEventArgs const&){
    SetFileEvent();
}

void MainWindow::QueryContentsChanged(Windows::Storage::Search::IStorageQueryResultBase const& sender, IInspectable const& args) {
    state_ = "changed";
}

SetFileEvent関数内で、ContentsChangedイベントにQueryContentsChangedデリゲートを渡しています。 開発者自身でデリゲートを作成する際は、ドキュメントを確認し、登録先のイベントに合った引数を持つデリゲートを作成する必要があります。

ContentsChangedが受け取るデリゲートの型はTypedEventHandler<IStorageQueryResultBase,IInspectable>なので、それに一致させるようにMainWindow::QueryContentsChangedを定義しています。

このアプリの実行時の挙動を説明します。 アプリの起動後、Set eventと書かれたボタンを押すことでMainWindow::QueryContentsChangedデリゲートがContentsChangedイベントにセットされます。 この状態でClick Meのボタンを押すと、state_変数の初期値であるdefaultの文字列が表示されます。

対象のフォルダに新たに.txtの拡張子を持つファイルを作成すると、内部でContentsChangedイベントが発火し、state_変数の値が更新されます。 よって、再びClick Meのボタンを押すと、今度はchangedと表示されます。

【将棋振り返り】連続で手番が来てチャンスを逃したケース

将棋を指していて新たに自分の欠点と対策を発見したので記録します。

以下の条件のときに深く考えずに指してしまい、チャンスを逃しました。

  • すでに今後の方針が決まっている状態であり、手番がまわってくれば指したい手がある
  • 相手から対処が必要な手を指され、一度受けにまわった(これにより盤面の条件が変わった)

実践例

自分が後手番だったので盤は反転しています。

現在は飛車で4七の地点への成りこみを見せて、相手がそれを受けた局面。

相手は受けに適した持ち駒が無く、飛車をまわれば5九に銀を打てる。また金で受けるのも玉から守り駒が離れてこちらの感触がいい。よって桂を打ったが、働きが悪そうでこちらの感触がいい、という状態です。

第一図

ここで自分の手が広いので方針を考えます。相手から早い攻めが無さそうなので、やはり4筋の突破を目指したいと考え、4五銀と打ちました。(ただしAIが言うには4九角が最善らしい。たしかに。)

次に4六銀、同歩、同飛とすれば、飛車成を見せながら歩切れも解消できます。

それに対して相手は3二角と金取りに打ち、馬を作りにきました。

第二図

金を引いて受かっているので2二金として、相手は6五角成と馬をつくりました。

第三図

ここが問題の局面です。自分の頭の中では、飛車先を攻めていくという方針を立てた直後で、その考えに迷いはありません。しかも変に局面が変わる前に銀をさばかないと遊んでしまいそうな心配もあり、深く考えずに4六銀と桂馬を取ってしまいました。

しかし実はここで馬が狭く、6四歩とすれば5六馬、同銀と交換ができ、AI曰く後手勝勢です。大チャンスを逃してしまいました。

なぜ気づけなかったか

1.方針を考えた直後だったから

1局の中で手が広い場面はそんなに多く現れません。中盤で手が広い場面に直面し、方針を考えると、そこからしばらくはその方針・攻めを実現することを目指します。その状態では攻めの速度や相手から攻められる隙がないかを考えることはありますが、特に問題がなければ違う方針に切り替えることを検討することはありません。持ち時間の短いルールなら特に。

今回の場合は、方針を決めた直後に相手に大きな隙ができましたが、自分のやりたいことが既に決まっていたので、相手に隙が無いかチェックするプロセスが働きませんでした。

2. 相手から動いてきたことによる状況の変化だったから

自分が銀を打ってから桂を取る間の変化は、相手主導かつ短い手順で意図もはっきりしています。

この直後に「もしかしたら隙ができたかも」と考えるスイッチが自分はONにならなかったようです。

3. 隙を探すモチベーションがなかった

この局面では自分のほうがやや良いと思って指してしました。なのでそこまで難しい順を発見しなくても、着実に攻めれば差は広がるだろうという考えがありました。

これによって隙を探すモチベーションが下がっている状態にあったと考えられます。

対処

理想的には常に複数の選択肢を持ちながら最善を選ぶべきだと思います。なので言ってしまえば、「常にほかに良い手がないか考えましょう」になるのですが、そもそも考えるスイッチが入らなかったことが問題なのでもう少し別の教訓を考えます。

まず相手の手を咎める意識をより強く持つことが必要です。相手の指し手を批判的に見ることは全ての手番でやるべきだと思いますが、あんまりできてない印象です。

後は局所的に局面が変わったことを意識することです。今回は直前に盤面を広く見ていたので、馬ができた後にもう一度広く見て隙を探すことをしませんでしたが、手番がきたことを認識し、さっきと違う状態になったのが馬ができた6五の地点周辺だけだという考えがあれば、6四歩を発見することは容易だったと思います。

【C++/WinRT】ファイルからの画像読み込み

XamlC++/WinRTを使用してGUIを作る勉強中です。

画像のクラスが複数あるので、それぞれの読み込みと変換方法をメモしておきます。

パターンは以下の3つです。

  • 画像をBitmapImageとして読み込む
  • 画像をSoftwareBitmapとして読み込む
  • 画像を読み込み、ピクセル情報にアクセスする

画像をBitmapImageとして読み込む

メンバ変数のbitmapImageに書き込みます

Windows::Foundation::IAsyncAction MainPage::OpenFileAsBitmapImage_Click(IInspectable const&, RoutedEventArgs const&)
{
    auto lifetime = get_strong();
        
    FileOpenPicker openPicker;
    openPicker.ViewMode(PickerViewMode::Thumbnail);
    openPicker.SuggestedStartLocation(PickerLocationId::PicturesLibrary);
    openPicker.FileTypeFilter().ReplaceAll({ L".jpg", L".jpeg", L".png" });

    StorageFile loadedImageFile{ co_await openPicker.PickSingleFileAsync() };

    IRandomAccessStream fileStream{ co_await loadedImageFile.OpenAsync(FileAccessMode::Read) };
        
    m_bitmapImage.SetSource(fileStream);
}

画像をSoftwareBitmapとして読み込む

メンバ変数のm_softwareBitmapに書き込みます

Windows::Foundation::IAsyncAction MainPage::OpenFileAsSoftwareBitmap_Click(IInspectable const&, RoutedEventArgs const&)
{
    auto lifetime = get_strong();

    FileOpenPicker openPicker;
    openPicker.ViewMode(PickerViewMode::Thumbnail);
    openPicker.SuggestedStartLocation(PickerLocationId::PicturesLibrary);
    openPicker.FileTypeFilter().ReplaceAll({ L".jpg", L".jpeg", L".png" });

    StorageFile loadedImageFile{ co_await openPicker.PickSingleFileAsync() };

    IRandomAccessStream fileStream = co_await loadedImageFile.OpenAsync(FileAccessMode::Read);

    BitmapDecoder decoder { co_await BitmapDecoder::CreateAsync(fileStream)};

    m_softwareBitmap = co_await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat::Bgra8, BitmapAlphaMode::Premultiplied);
}

画像を読み込み、ピクセル情報にアクセスする

StorageFileを開いたのちにピクセル情報にアクセスします。 最終的にメンバ変数のm_writeableBitmapに書き込みます。

Windows::Foundation::IAsyncAction MainPage::OpenFileAsPixel_Click(IInspectable const&, RoutedEventArgs const&)
{
    auto lifetime = get_strong();

    uint32_t scaledSize = 300;
    m_writeableBitmap = Imaging::WriteableBitmap(scaledSize, scaledSize);

    FileOpenPicker openPicker;
    openPicker.ViewMode(PickerViewMode::Thumbnail);
    openPicker.SuggestedStartLocation(PickerLocationId::PicturesLibrary);
    openPicker.FileTypeFilter().ReplaceAll({ L".jpg", L".jpeg", L".png" });

    StorageFile loadedImageFile = co_await openPicker.PickSingleFileAsync();

    IRandomAccessStream fileStream{ co_await loadedImageFile.OpenAsync(Windows::Storage::FileAccessMode::Read) };

    BitmapDecoder decoder{ co_await BitmapDecoder::CreateAsync(fileStream) };

    // Scale the image to the appropriate size.
    Windows::Graphics::Imaging::BitmapTransform transform;
    transform.ScaledWidth(scaledSize);
    transform.ScaledHeight(scaledSize);

    Windows::Graphics::Imaging::PixelDataProvider pixelData{ co_await decoder.GetPixelDataAsync(
        Windows::Graphics::Imaging::BitmapPixelFormat::Bgra8, // WriteableBitmap uses BGRA format 
        Windows::Graphics::Imaging::BitmapAlphaMode::Straight,
        transform,
        Windows::Graphics::Imaging::ExifOrientationMode::IgnoreExifOrientation, // This sample ignores Exif orientation 
        Windows::Graphics::Imaging::ColorManagementMode::DoNotColorManage
    ) };

    winrt::com_array<uint8_t> sourcePixels{ pixelData.DetachPixelData() };

    auto bufferByteAccess{ m_writeableBitmap.PixelBuffer().as<::IBufferByteAccess>() };
    uint8_t* pTargetBytes{ nullptr };
    bufferByteAccess->Buffer(&pTargetBytes);

    for (auto& element : sourcePixels)
    {
        *(pTargetBytes++) = element;
    }

    ImageElement().Source(m_writeableBitmap);
}