PCを新しくしたのでWin UI3の初歩を試してみた記録。
結論から言えば、まだ覚えることがあるのだが、慣れてないのもあるけど学習コストはWPFより高いなあという気がする。 はっきり言ってモダンUIを志向する場合、色々なことがあるがElectronなどのWebベースのハイブリッドアプリを志向したほうがコスパは良いなあと思ってしまった。
なんというか、色々とまだまだ整備されてない印象があるが、そろそろ.NET6もリリース以降安定してきたので、ぶっちゃけ成熟段階に入ってきたと見て良さそうである。 こうなると、WebView2との連携で組み込むのはどうだろうか?とかも思ったりもする。
そんなこんなであるが、正直WPFに飽き飽きしてる気持ちもあるし、Win 11時代に刷新されたモダンなネイティブコントロールを使える以上、自分がアプリを作るときはできるだけこの技術を試したいものだ。
ということで覚書。
開発環境について
まずWin UI3は開発環境を構築するのが難しくはないが、やや面倒だ。 そもそもVSのインストーラーで「.NETデスクトップ開発」にチェックすればデフォでOKっしょと思えるが、 実は「WindowsアプリC#テンプレート」というのがWin UI3テンプレートの実体で、私の環境ではデフォではついていなかった。MSはやる気があるのか。
重要なこととして、Microsoftの基本的な方針はMSIXとしてパッケージすることであり、開発者設定をオンにする必要がある。 はっきり言ってそういうのは面倒なのだが、UWPより良いところとしてUnPackagedな開発がサポートされた。つまるところ、exeの入ったフォルダをPublishできる。 WPFやWinFormsから移行しろよ~と言う一番のウリはここと言える。実際、出来てさえしまえばPublishはWPFと同じで難しくはない。
ところが、これがランタイムがねえとかでビルドできない。いやWindows 11 SDKで落としたんじゃねえのかと思ってしまうが、 実はここは設計を切り離したというのがコンセプトで、Windows App SDKというのを入手する必要がある。いやVS Installerに入ってないんかい。 調べ方とかはここにある。 ちなみに、公式のこの解説はなぜか上手く機能せず、結局自前で落とす必要があった。
メンドーなのでインストーラを落としてしまったが、wingetでMicrosoft.WindowsAppRuntime.1.3
とかで配布されてるようだ。
インストーラ経由で入れてしまった私は汚れ感がパないので、入れ直したほうが良いかな、と思うのだが、まだ未検証。
追記。どうやらVSInstallerがだめというわけでもなく、wingetでも駄目らしく、公式インストーラでないといけないらしい。ランタイムそのものではなく、公式経由だとDataStoreやらDDLMなんかが追加で入るのだが、それが影響しているっぽい
なお、今どきのアプリはランタイムを配布する方法としない方法があるのだが、こんな超面倒なら自己完結型で配布するのがユーザ思いだと思う。 近年のGUIアプリなら300MBくらいまでは許容範囲な気がするから、やっちゃえばOKだろう。 一方で、そうであればもういっそElectronでよくねえ、サイズ気にするなら頑張ってRustやFlutter覚えて書けば良くねえと思ってしまったのだ(笑)
追記。ランタイムの必要有無は知識不足かもしれない。ちょっと調べてみてどうやらひっそりとランタイムをひっそりダウンロードする予感がする。Spotifyとかがそうらしい。より調べる必要があるかも
とまあ、ここまででもだいぶ骨が折れてしまった。
WinUI3 Gallery
Win UI3 Galleryというアプリがあり、MS Storeから落としてこれで勉強することができる。 逆に言うと、今のところ私の理解できた範囲ではこのアプリが最上級の学習リソースであり、アプリのGitHubソースコードと一緒に確認して格闘することになる。 WPFもそうなのだが、デスクトップアプリのライブラリはこのパターンが非常に多い。ある事自体はとても素晴らしいのだが。
アプリのコードは断片的だし、GitHubのコードは成立させるために掘らなきゃいけないのがしんどい。 Web技術なら、こういうところはコピペでちょいちょいとやって、APIドキュメントを探ればほとんど事足りる。これがデスクトップアプリWeb技術でよくね、とちょっと感じている理由の1つだ。
なんにせよ、Win UI3の技術を使うなら、このアプリは今後深掘りしていくことになるだろうと思う。
タイトルバーをカスタマイズする
実は一番興味があったこと。
近年のアプリではWindows標準のタイトルバーを用いず、場合によってはやや高さが低い(Discordアプリなど)タイトルメニューがモダン感がある。
これはウインドウに書くか、そういうコントロールを作り、UIElement
オブジェクトとしてxamlからcsパーシャルクラス内で取得すると実現できる。
<!-- タイトルメニューになるGridにNameをつけるのが大事 -->
<Grid
x:Name="AppTitleBar"
Grid.Row="0"
MinHeight="30">
<StackPanel Orientation="Horizontal">
<Image
Width="16"
Height="16"
Margin="15,0,10,0"
HorizontalAlignment="Left"
Source="/Public/pen.png" />
<TextBlock
x:Name="AppTitleTextBlock"
Margin="0,0,0,0"
VerticalAlignment="Center"
Style="{ThemeResource BodyTextBlockStyle}"
Text="Windows Path Converter"
TextWrapping="NoWrap" />
</StackPanel>
</Grid>
ExtendsContentIntoTitleBar = true;
SetTitleBar(AppTitleBar); // x:Name = AppTitleBarとして生えているので取得可能
クリップボード
WPFならコピペはSystem.Windows.Clipboard
クラスを使えばいいが、Win UI3はWindows.ApplicationModel.DataTransfer.Clipboard
を使う。
名前空間が1つ増えただけでもやや上長感がある。実際にクリップボードに渡すのは、「データパッケージ」というオブジェクトなのが、分からなくもないが少し直感的ではない。
using Windows.ApplicationModel.DataTransfer;
//....
public RelayCommand CopyCommand => new(() =>
{
if (string.IsNullOrWhiteSpace(ResultText))
return;
var data = new DataPackage();
data.SetText(ResultText);
Clipboard.SetContent(data);
});
ツールチップ
WPFの組み込みコントロールはToolTipというのが直プロパティで設定できて便利であったが、Win UI3では少し噛ませてやる必要がある。 ただ、方向をしていできるオプションがついているので、ここは便利だ。
<RadioButton Content="Docker">
<ToolTipService.ToolTip>
<ToolTip Content="Dockerに使うスタイル" VerticalOffset="-90" />
</ToolTipService.ToolTip>
</RadioButton>
アクリル・マイカ
特にやりたかった表現の1つではあるのだが、私はこのあたりに驚愕した笑
WinUI3ギャラリーのリンク先はシンプルなコードなのだが、実はこのアプリはこういうヘルパを使っており、カスタム実装する必要がある。
こういうのを掘らなきゃいけないのだ。
まあ完全にコード理解していないのだけれど、一瞬見ただけでこれだけでWin32のネイティブ感が悪い意味で頭がよぎる。
ただ、アクリルな見た目を実現できたときはそこそこ満足感があった。
テーマ対応
取得はかなり容易になったと言える。はっきり言ってこれが容易じゃないと嫌になっちゃうだろう笑
具体的にはFrameworkElement.ActualThemeで簡単に列挙体値が返ってくる。これでダークモード対応が出来るわけだ。
ただし、テーマカラーの対応はスタイルリソースを作らなければならない。 こういうところもWebのUIライブラリの方が優れてるなあ、、と思ってしまう。
MVVM
正直WinUI3はMVVMにあまり関与したスキームになっていないので、MVVMスタイルの設計を使う場合、VMの移植は容易である。これはMVVMの設計が生きてくるところだ。
しかし、Vの方はコントロールがコロッと一新しているので明らかにそこが厄介だと思う。
何しろWPFと比較した場合、コントロールによってItems
がバインド出来るようなコントロールが出てくるのは良いのだが、「出来ないものもある」という中途半端さなので注意が必要だ。
バインドは難しくはないが、DataContext
という機構はない。代わりに、例えばウインドウのCSファイル側でVMプロパティを生やし、x:Bind
することで実現する。
internal MainWindowViewModel ViewModel { get; } = new MainWindowViewModel();
<TextBox Margin="0,10,0,10" Text="{x:Bind ViewModel.Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
とりあえずここまで
…いや、表現したいことはまだまだあるけど、中々覚えることが多いな。 まあ、なんか優先順位落ちそうだけどぼちぼち覚えてみることにしよう。