WinUI3
WinUI 3(Windows App SDK)公式 issue のうち、気になる issue のメモ(随時更新)。
未解決
- ComboBox が enum で動かない 2021/01/04
- ComboBox が enum で動かない(包括) 2023/03/30
- Ctrl+S で FilePicker を開くとキー入力を受け付けない 2024/03/03(自分)
- 管理者権限で実行すると FilePicker が開かない 2022/05/13
- 管理者権限で実行するとドラッグ&ドロップできない 2022/09/06
- 非管理者の時だけ Microsoft Store アプリで昇格できない 2024/04/24(自分)
- TabItemTemplate の挙動が変 2023/06/03(自分)
- ItemsView 例外 2023/08/31
- XAML でジェネリックを使いたい 2019/07/23
解決済
- サブメニューが更新されない 2019/11/21
- AccessKey で落ちる 2023/07/15(自分)
- RelativePanel の中で Button が広がらない 2024/05/07(自分)
- 複数回 Name に x:Bind するとビルドできない 2024/05/17(自分)
- WinForms と共存できない 2022/12/04(自分)
はじめに
C# では、WinForms(フォーム)や WPF なら、ウィンドウプロシージャーをカスタムする仕組みが用意されている(HwndSource)ので簡単なのですが、WinUI 3(Windows App SDK)には残念ながら今のところそのような仕組みはないようです。
結局ネイティブ Windows API を使うしかないようですので、その方法をまとめました。Windows API なので WinUI 3 専用ではありませんが、出番としては WinUI 3 で開発するときくらいかと思います。
はやいとこ WinUI 3 にも簡単な仕組みが用意されることを祈っています……。
サンプルプログラムのソースコードは GitHub に上げてあります。
使用する API
旧バージョンでは SetWindowLong() or SetWindowLongPtr() と CallWindowProc() を使います。欠点は先のドキュメントにいろいろ書いてありますが、個人的にはそれよりも、元のウィンドウプロシージャーを自前で管理する分、手間が増えるのかなと思います。
新バージョンでは SetWindowSubclass() と DefSubclassProc() を使用します。元のウィンドウプロシージャーは気にしなくて済みます。
ここでは新バージョンを使います。
というか、新バージョンの情報が少なくて苦労したというのもあってここにまとめたというのもあります。
やりかた
ウィンドウメッセージを扱うので、NuGet で PInvoke.User32 を入れておきます。
カスタムウィンドウプロシージャーの型(SUBCLASSPROC コールバック関数)をデリゲートとして宣言しておきます。型の詳細はこちらに書いてあります。
internal delegate IntPtr SubclassProc(IntPtr hWnd, User32.WindowMessage msg, IntPtr wPalam, IntPtr lParam, IntPtr idSubclass, IntPtr refData);
カスタムウィンドウプロシージャーを作成します。
自分が処理したいメッセージの部分だけ書いて、あとは元のウィンドウプロシージャーに丸投げします。
private IntPtr CustomSubclassProc(IntPtr hWnd, User32.WindowMessage msg, IntPtr wPalam, IntPtr lParam, IntPtr idSubclass, IntPtr refData)
{
switch (msg)
{
case User32.WindowMessage.WM_HogeHoge:
(処理)
return IntPtr.Zero;
default:
// 関心のあるメッセージ以外は次のウィンドウプロシージャーにお任せ
return DefSubclassProc(hWnd, msg, wPalam, lParam);
}
}
カスタムウィンドウプロシージャーを登録します。
_subclassProc = new SubclassProc(CustomSubclassProc);
SetWindowSubclass(hWnd, _subclassProc, IntPtr.Zero, IntPtr.Zero);
注意点の 1 つめは、SetWindowSubclass() に渡すのはカスタムウィンドウプロシージャー本体を「インスタンス化」したもの、ということです。インスタンス化していない CustomSubclassProc も SetWindowSubclass() に渡せてしまいますが罠です。
注意点の 2 つめは、そのインスタンスをずっと(カスタムウィンドウプロシージャーが不要となるまで)持ち続ける、ということです。
これらを守らないと、特に 64 ビットバイナリ(x64)において、一見うまく動くようですが、メッセージ処理をこなしているうちにアプリが強制終了します。私はこれでハマりました。
これらを守らないと、特に 64 ビットバイナリ(x64)において、一見うまく動くようですが、メッセージ処理をこなしているうちにアプリが強制終了します。私はこれでハマりました。
強制終了時は集約エラーハンドラーにすら引っかかりませんでした。デバッガーでは以下の箇所で落ちていました。
global::Microsoft.UI.Xaml.Application.Start((p) => {
var context = new global::Microsoft.UI.Dispatching.DispatcherQueueSynchronizationContext(global::Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread());
global::System.Threading.SynchronizationContext.SetSynchronizationContext(context);
new App();
});
サンプルプログラム
サンプルプログラム(GitHub に上げてあります)では、ウィンドウのタイトルバーにヘルプボタンを表示しています。
このコンテキストヘルプは WinUI 3 標準ではハンドルできないため、カスタムウィンドウプロシージャーで処理しています。
ヘルプボタンをクリックする度に、ウィンドウのサイズが変化します(アプリとしてはありえない動きですが……)。
ちなみに、SetWindowSubclass() にインスタンス化していない CustomSubclassProc を直接渡すようにコードを変更すると、うちの環境で x64 バイナリを実行すると、チェックボックス連打で 1 分もしないうちにアプリが落ちました。
【追記】
Zenn をはじめてみました。試しにこの記事を Zenn に再掲してみました。
実際にアプリを作ると WinUI 3 の良いところ悪いところを実感できたので、そのまとめ。
なお、開発は Template Studio for WinUI を用いて行っているので、素の WinUI 3 より少し高機能な環境を使用している。
良いところ(メリット)
【デザインはわりと好き】
少なくとも、素の WinForms や WPF よりは洗練されている感がある。
マテリアルデザインはデフォルト(?)の紫をはじめとしたどぎつい色が多いので、それよりも落ち着いていて好き。ただ、パステル調のマテリアルデザインはきれいなので、そちらのほうが好き。
あと、ウィンドウを半透明+ぼかしにするアクリル効果は使いどころが難しいと思う。
普通のボタンはラベルにアクセスキーを表示しておけるが、アイコンボタンはアクセスキーを表示できないので、アクセスキー表示機能があることでユーザーはアクセスキーを認識できる。
Alt キーを「離した」時に表示されるので、既に知っているアクセスキーを使うときはわざわざ表示されず、うざくないのもポイント。
【RelativePanel】
WPF にあった DockPanel はなくなり、代わりに RelativePanel が登場。ちょっと使い方が難しいが、その分、高機能になっている。
RelativePanel で一番気に入ったのは、DockPanel の LastChildFill をより良く実現できること。
DockPanel.LastChildFill の場合、残りのスペースを埋めるコントロール(右の図ではメイクファイル入力欄)を XAML 上で最後に記述することになり、結果的に、タブオーダーが見た目と異なってしまう(参照ボタンの後に入力欄が来る)。
RelativePanel なら、残りのスペースを埋めるコントロールを好きな順番で書くことができる(RelativePanel.RightOf / LeftOf を両方指定すると伸びる)ので、タブオーダーが見た目通りになる。
【静的バインディング】
XAML でのバインディングに静的バインディングの {x:Bind} が使える。
WPF は動的バインディングの {Binding} だったので、実行してみないとエラーが分からず、この点が個人的にとても嫌いだった。WinUI 3 で静的バインディングになり、コンパイル時に解決するのでミスが減る。パフォーマンスも向上するようだ。
ただ、デフォルトでバインディングの Mode が OneTime になっているので、WPF に慣れていると「値反映されない?」ってことになりがちなのは注意。
なお、{Binding} も使える。
【マルチリンガル】
恐らく Template Studio のおかげかと思うが、ローカライズの仕組みが最初から使える状態になっているのは良いと思う。
Ver 1.83 現在ではちょちょいとファイル合併 2 は多言語対応していないが、実験用に多言語対応してみてもいいかもしれない。
悪いところ(デメリット)
正直、悪いところが多すぎるし、1 つ 1 つのダメージがでかすぎる。なんとなくの印象だけど、すぐには改善されない気がする……。
※自分が気付いてないだけかもしれないので、もしご存じのかたはコメントいただけると幸いです。
【ウィンドウの機能が弱すぎる】
なんと、ウィンドウに Left / Top プロパティーがない。Template Studio では素の Window よりも強いサードパーティーの WindowEx を使っているが、それでもない。ウインドウを動かすには、コードビハインドで AppWindow.Move() を使う。MVVM どこ行った……。
ウィンドウサイズをコンテンツに合わせて調整する SizeToContent プロパティーもない。これを自力で実現するために無駄にコードを書く必要がある。自分は何をやっているのか……と虚しくなる。
なお、メインウィンドウに限っては XAML で Width / Height プロパティーを指定しても効かないが、これは Template Studio が何かしているからのようだ。コードビハインドで指定すると効くし、子ウィンドウなら XAML でも効く。
【ウィンドウをモーダルにできない】
子ウィンドウをモーダルウィンドウ(閉じるまで親ウィンドウを操作できないウィンドウ)にできない。モードレスになるので、環境設定ウィンドウで環境設定している時にメインウィンドウをクリックすると、メインウィンドウの操作ができてしまう。
確かに Windows 以外の OS に目を向ければ Haiku OS の流儀はすべてモードレスで、OS 全体でその流儀が浸透しているなら、モードレスはモードレスで使い勝手が良いし、個人的な好みで言えばモードレスかもしれない。しかし、Windows の流儀はモーダルなので、自分のアプリだけモードレスというのは違和感しかない。
ちょちょいとファイル合併 2 では擬似的にモーダルになるようにまたしても無駄にコードを書くこととなり、むなしさが増した。
【メッセージボックスが見切れる】
【ポップアップメニューがきつきつ】
本質的には ContentDialog と同じ問題なのではと思うが、ポップアップメニュー(Flyout)も無理矢理メインウィンドウの範囲内で表示しようとするので、アイテム数が多いとスクロールバー表示になってしまい、非常に使いづらい。
ボタンとの位置関係によっては、納まるアイテム数にしていても、初回だけはスクロールバー表示になってしまい、これはバグだと思う。
ドキュメントによれば、FlyoutBase.ShouldConstrainToRootBounds プロパティを false にすればウィンドウ外に表示できるようなのだが、なぜか効かない。Popup.ShouldConstrainToRootBounds プロパティのドキュメントには「WinUI 3 では false はサポートされない」と書いてあるので、そういうことなのかもしれない。
【パッケージを作るの面倒】
公開用のパッケージを作る際、msix が作成され、msixbundle が作成されない。
手動で msixbundle を作成するのが面倒くさい。
【ロードマップが更新されない】
既に 1.2 が出ているが、この先どうなるのかの見通しが分からない。
【バグっぽい】
集約エラーハンドラー(Application.UnhandledException)がうまく動かなくてずっと悩んでいたが Windows App SDK 1.2 になったら動いたので、ただのバグだったようだ。
ポップアップメニューの初回位置もしかり。
【情報が少ない】
ノウハウしかり、ロードマップしかり、WinUI 3 に関する情報が非常に少ない。
クラスの検索をすると WinUI 3 ではなく UWP が引っかかりがち。
例えば、現時点では「TextBlock WinUI3」で検索すると UWP の TextBlock(Windows.UI.Xaml.Controls 名前空間)がトップに出てきて、WinUI 3 の TextBlock(Microsoft.UI.Xaml.Controls 名前空間)にはたどり着けない。
総評
良いところに目を向けて活用していきたいと思うものの、少なくとも現状では、WinUI 3 での開発は茨の道だと思う。
機能比較表
WPF と WinUI 3 の機能比較表を作成。
更新履歴
- 2022/11/23 初版。
- 2022/11/26 バインディングについて追記。
- 2022/11/26 ポップアップメニューについて追記。
- 2023/05/21 機能比較表について記載。
アーキテクチャー的に? 構造的に? 階層的に? WinUI 3 がどのへんにいるのかがよく分からないので、調べてみた。
※情報をご存じの方はご教示いただけると幸いです!
今のところの想像はこんな感じ。
以下元ネタ。
- Windows App SDK の Windows UI ライブラリ(WinUI 3 はネイティブ UI プラットフォーム コンポーネント)
- WinUI 3 と WinUI 2 の比較(WinUI 3 は Windows App SDK の一部、WinUI 2 は Windows SDK の一部)
- Windows App SDK(Windows App SDK は Windows SDK / フォームや WPF を含む .NET / C++ Win32 を置き換えるものではなく、補完するもの)
- .NET MAUI とは
- .NET Framework 3.0 の構成の図
翔星グループ
月別アーカイブ
記事検索
最新記事
最新コメント
タグクラウド
- ACF
- Android
- AQUOSPAD
- ASPNET
- BitLocker
- Blazor
- CBuilder
- CSharp
- csv2resw
- FactoryTown
- Fantia
- GPS
- H264
- H265
- HaikuOS
- HANASU
- HDD
- IIJ
- LTE
- MicrosoftStore
- MVNO
- MVVM
- NAS
- Nexus7
- PetitKara
- PHP
- RaspberryPi
- SQLite
- SSD
- SymphonyOfWar
- TYPINGMANIA
- USB3
- UTAU
- Vegas
- VisualStudio
- Wi-Fi
- WiMAX
- Windows
- Windows10
- Windows10Mobile
- Windows11
- WinUI3
- WPF
- うたりす
- お知らせ
- お願い
- その他無線
- ちょちょいとファイル合併2
- ちょちょいと自動更新
- ちょちょいと自動更新2
- はじまるA列車
- へなぽらんど
- ゆかり
- ゆかりすたー
- ゆかりすたー4
- ゆっこビュー
- ゆっこビュー2
- アニメ
- アンケート
- イベント
- オーディオ
- カラオケ
- ゲーム
- サンプルコード
- サービス
- スピード測定
- スーパーバレットブレイク
- セキュリティ
- ソフトウェア
- タブレット
- トトリのアトリエ
- ニコカラ
- ニコカラりすたー
- ニコカラメーカー
- ニコカラメーカー2
- ニコカラメーカー3
- ネットワーク
- ハードウェア
- プラグイン
- プログレスバー素材メーカー
- ヘッドセット
- ボーカルカット
- マシンスペックまとめ
- マシンベンチマークまとめ
- ライザのアトリエ2
- ライザのアトリエ3
- ラングリッサー
- ルフランの地下迷宮と魔女ノ旅団
- ロロナのアトリエ
- 動画
- 募集
- 唄詠
- 唄詠2
- 唄詠利用
- 挨拶
- 簡易キーチェンジャー
- 考察
- 開発
- 鼻歌採譜プラグイン