winui-design について
WinApp CLIを使ったWinUIインターフェース設計のスキルです。サンプルやAPIの検索、コントロール選択、ウィンドウサイズ、XAML、テーマ、アクセシビリティを案内します。
主な機能
- 01実装前にWinUIサンプルを検索
- 02プロジェクト参照に基づくAPI確認
- 03要件に応じた標準コントロール選択
- 04ウィンドウサイズとXAMLの指針
- 05テーマとアクセシビリティの確認
活用シーン
- 01新しいWinUIページのコントロール選定
- 02復元済みパッケージに基づくAPI確認
- 03バインディングやレイアウトの誤り回避
- 04テーマとアクセシビリティのレビュー
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ microsoft/ win-dev-skills --skill winui-designインストール後、Claude Code で/winui-designを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/brushes-and-icons.mdlayout-review.mdtheme-accessibility.md詳細
XAML を書く前にサンプルを検索する
このワークフローには WinApp CLI 0.7+ が必要です。winapp find-ui を使用して、根拠のあるコントロールやサンプルを見つけます。先に検索し、その後でコードを書きます:
winapp find-ui "<focused feature>" # compact matches + scenario IDs
winapp find-ui --id <scenario-id> # full XAML/C# + prerequisite notes
winapp find-ui --id <id-1> --id <id-2> --json # batch, structured output
winapp find-ui --list # browse all default-source scenarios
winapp find-ui "<feature>" --refresh # force a corpus refresh
デフォルトの検索対象は、WinUI Gallery、Windows Community Toolkit、厳選されたコアパターンです。Reactor の C# のみを対象とする MVU サンプルは、--source reactor でオプトインできます。Reactor プロジェクトでのみ使用してください。Gallery/Toolkit/Reactor のコーパスは WinApp CLI によって取得およびキャッシュされますが、コアパターンはオフラインで使用できます。
復元されたプロジェクトに照らして API を確認する
API に関する前提を基にコードを書く前に、復元されたアプリのプロジェクトディレクトリから実行してください。これにより、winapp find-api はサンプルで使用されている、より新しい可能性のあるパッケージバージョンではなく、そのプロジェクト固有の参照を使います:
winapp find-api InfoBar --json --project-dir .
winapp find-api check-property InfoBar Severity IsOpen Message Title --json --project-dir .
winapp find-api members NavigationView --filter selected --json --project-dir .
複数のプロジェクトから選択するには、--project-dir <app-directory>(デフォルトは cwd)または --project <name> のどちらか一方を使用し、両方は指定しないでください。リポジトリのルートや兄弟プロジェクトからスコープを指定せずにクエリを実行すると、誤った API サーフェスが対象になる場合があります。--project sdk は、アプリの NuGet 参照ではなく、インストール済みのマシン SDK のみを検索します。結果が古く見える場合は、更新する前に復元とプロジェクトの選択を確認してください。記憶を頼りに API を推測するのではなく、検索/インデックスコマンドの確認には winapp find-api --help を使用してください。使用パターンの確認には find-ui を使用します。
アプリの形状を決める基準
ページをレイアウトする前に、リリース済みアプリの中から最も近いシルエットを選びます:
| アプリの種類 | 基準となるコントロール | 参考アプリ |
|---|---|---|
| 設定/構成ツール | 左側の NavigationView + SettingsCard / SettingsExpander | Windows Settings、Slack |
| ドキュメント/セッションエディター | TabView +全画面のコンテンツ、控えめなクローム | Windows Terminal、VS Code、Notepad |
| 階層ブラウザー | TreeView + ListView + BreadcrumbBar | File Explorer、Outlook |
| 開発者ツール/ダッシュボード | NavigationView +カードレイアウト | Dev Home、GitHub Desktop |
| 単機能ユーティリティ | モード切り替え+コンパクトなグリッド | Calculator、Snipping Tool |
| メディア/キャンバス/ヒーロー | ヒーローサーフェス、フローティングコマンドを備えた Grid。NavigationView は使用しない | Photos、Spotify、Clipchamp |
まず検討するコントロールの対応表
XAML を書く前に、要件をプラットフォームのコントロールに対応付けます。こうした対応付けは、別のフレームワークでの考え方(WPF の DataGrid、Web の <select>、HTML の <input type=date>)をそのまま持ち込まないためのものです:
- ナビゲーション: セクションが 2~7 個 →
NavigationView、ドキュメント/セッションのタブ →TabView、パンくずリスト →BreadcrumbBar、モードが 2~3 個 →SelectorBar。 - データ表示: 縦方向のリスト →
ListView、タイル/グリッド →GridViewまたはItemsRepeater+UniformGridLayout、階層 →TreeView、表形式 →GridベースのItemTemplateを備えたListViewと、その上に配置するヘッダー用Grid(WinUI にはDataGridがありません。x:Bindを使用できない列を持つCommunityToolkit.WinUI.Controls.DataGridを、安易に選ばないでください)。マスター詳細 →ListView+詳細用Grid。 - 入力: テキスト →
TextBox、数値 →NumberBox、検索 →AutoSuggestBox、日付 →CalendarDatePicker、真偽値 →ToggleSwitch、2~3 個から 1 つ選択 →RadioButtons、4 個以上から 1 つ選択 →ComboBox。 - フィードバック: 操作をブロックする判断 →
ContentDialog、コンテキストに応じたアクション →Flyout/MenuFlyout、オンボーディング/ヒント →TeachingTip、インラインの状態/非同期処理の進行状況 →InfoBar、システム通知 →AppNotification。
上記の対応付けが合わない場合は、独自に工夫する前に winapp find-ui "<intent>" を実行してください。
ウィンドウサイズの設定(WinUI 3 固有)
WinUI 3 には
SizeToContentがありません。 明示的にサイズを指定しない場合、Windows はメインウィンドウを約1024×768に設定します。これはほとんどのユーティリティには大きすぎます。MainWindowのコンストラクターでサイズを設定してください。
基準。 幅は最も幅の広い行に48のパディングを加え、20単位で切り上げます。高さは32(タイトルバー)+各行の高さの合計+各間隔の合計+48のパディングを加え、20単位で切り上げます。切り上げてください。コンテンツが切れることは、ウィンドウが少し広すぎることよりも悪い問題です。目安の範囲(基準に基づいて調整してください):
- 単機能のユーティリティ → 幅約440~560
- フォーム/単一ページのツール → 幅約600~800、高さ約640~800
- 複数ペイン(ナビゲーション+コンテンツ) → 幅約1100~1300、高さ約720~840
- ドキュメント/キャンバス/メディアエディター → 幅1280以上
AppWindow.Resize が受け取るのはDIPではなく物理ピクセルです。モニターのDPIスケールを掛けてください。コンストラクター内では XamlRoot.RasterizationScale が null で、AppWindow.Move の後は古い値になるため、最も簡潔な方法は [DllImport] GetDpiForWindow です。
using Microsoft.UI;
using Microsoft.UI.Windowing;
using System.Runtime.InteropServices;
using Windows.Graphics;
public sealed partial class MainWindow : Window
{
[DllImport("user32.dll")]
private static extern uint GetDpiForWindow(IntPtr hWnd);
public MainWindow()
{
InitializeComponent();
var hwnd = Win32Interop.GetWindowFromWindowId(AppWindow.Id);
var scale = GetDpiForWindow(hwnd) / 96.0;
// widthDip / heightDip come from the rubric above — derive, don't copy.
AppWindow.Resize(new SizeInt32((int)(widthDip * scale), (int)(heightDip * scale)));
}
}
ルートの Grid に Width/Height を設定してウィンドウサイズを変更しないでください。ウィンドウではなく、コンテンツが切り取られます。
XAMLで注意すべき点(そのままリリースすると問題になる点)
有効な x:Bind モードを確認する
x:Bind は、継承された x:DefaultBindMode によって変更されない限り、デフォルトで OneTime になります。動的なデータには、変更通知とともに OneWay/TwoWay を使用してください。明示的に指定した Mode は、継承されたデフォルトより優先されます。安定したコマンド/イベントのバインディングは、一律にモードを書き換える必要はありません。
<TextBlock Text="{x:Bind Vm.Status}" />
<TextBlock Text="{x:Bind Vm.Status, Mode=OneWay}" />
ページ/ウィンドウでは、x:Bind は DataContext ではなく、コードビハインド(たとえば、その Vm プロパティ)を基準に解決されます。型指定された DataTemplates には x:DataType を使用してください。VMを設定するために Page で使うものではありません。実行時の {Binding}/DisplayMemberPath が適切な場合もあります。AOTでは、それらのソースクラスに partial と [WinRT.GeneratedBindableCustomProperty] が必要になることがあります。実行時バインディングをすべて未サポートとみなすのではなく、winui-packaging の references/sourcegen-patterns.md を参照してください。
TextBox の双方向バインディングには UpdateSourceTrigger=PropertyChanged が必要です
<TextBox Text="{x:Bind Vm.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
デフォルトのトリガーが LostFocus になるのは、TextBox.Text に限られます(ほとんどの他のプロパティでは PropertyChanged がデフォルトです)。VMはキー入力ごとに更新されません。また、UIAのキーボードシミュレーションテスト(WinAppDriver SendKeys など)で入力直後にアサーションを行うと、フォーカスが移動するまで古いVMの状態が確認されます。
C#から添付プロパティを設定する場合は、初期化子ではなく静的セッターを使用する
using Microsoft.UI.Xaml.Automation;
// ❌ WRONG — does not compile. CS0117: 'Button' does not contain a definition for 'AutomationProperties'.
// AutomationProperties is a static class of attached-property accessors, not an instance member.
var btn = new Button { AutomationProperties = { AutomationId = "BtnSave" } };
// ✅ CORRECT
var btn = new Button { Content = "Save" };
AutomationProperties.SetAutomationId(btn, "BtnSave");
AutomationProperties.SetName(btn, "Save button");
Grid.SetRow(btn, 1);
ToolTipService.SetToolTip(btn, "Save the current document");
Converter={x:Null} は実行時に x:Bind をクラッシュさせる
{x:Bind} では、Converter に {StaticResource} による検索が必要です。Converter={x:Null} はコンパイルできますが、生成されたコードは LookupConverter("") を呼び出します。これが null を返し、その後に逆参照されるため、バインディングが初めて有効化されたときに Resource Dictionary Key can only be String-typed/NullReferenceException が発生します。コンバーターを使わない場合は、このプロパティ自体を省略してください。
IValueConverter より x:Bind の静的関数を優先する
// MainPage.xaml.cs
public static Visibility BoolToVisibility(bool v) => v ? Visibility.Visible : Visibility.Collapsed;
public static Visibility InvertBoolToVisibility(bool v) => v ? Visibility.Collapsed : Visibility.Visible;
public static bool Not(bool v) => !v;
<TextBlock Visibility="{x:Bind local:MainPage.BoolToVisibility(Vm.IsLoading), Mode=OneWay}" />
<Button IsEnabled="{x:Bind local:MainPage.Not(Vm.IsLoading), Mode=OneWay}" />
AcrylicとThemeShadowの描画ルール
BorderとControlのBackgroundSizingは、デフォルトでInnerBorderEdgeです。これにより、Acrylicが内側のストロークに正しくクリップされます。注意点は直感とは逆です。枠線のあるAcrylicサーフェスでは、OuterBorderEdgeに変更しないでください。変更すると、マテリアルがストロークの外側にはみ出します。ThemeShadowは、キャスターのTranslationのZ値から影を描画します。Microsoftが推奨する高さは、ツールチップが16、ポップアップ/フライアウトUIが32、ダイアログが128です。サーフェスの種類に合わせて選んでください。ポップアップではないキャスターの場合は、影を落とすサーフェスをThemeShadow.Receiversに追加してください。追加しないと、影を受ける面がなく、切り取られたように見えます。
テーマ設定のルール(概要)
- 使用箇所では
{ThemeResource ...}を使います(テーマの切り替え時に更新されます)。テーマごとの定義にはThemeDictionaries内で{StaticResource}を使います。ただし、SystemAccentColor/SystemColor*は例外で、{ThemeResource}のままにします。 - カスタムテーマディクショナリでは、
Light、Dark、およびHighContrastを明示的に定義します。Defaultは決して使いません。 - リソースには色相ではなく、用途に基づく名前を付けます(
CardBackgroundBrush、DangerTextBrushなど)。 - Light/Dark で動作しても、High Contrast で動作するとは限りません。Contrast テーマで別途テストしてください。
- アプリ全体ですでにシステム対応ブラシを使っている場合を除き、
HighContrastAdjustment="None"は設定しないでください。
アンチパターン
| ❌ しないこと | ✅ 代わりにすること |
|---|---|
どのアプリも反射的に左側の NavigationView として構築する | シルエットの表で最も近い形状を選びます。ヒーロー型、ドキュメント型、ユーティリティ型も同様に適切です |
| ブランドカラーや色付きの背景を標準から外れていると見なす | SystemAccentColor の上書きや、色付きの DesktopAcrylicBackdrop の使用は、Microsoft のファーストパーティアプリが差別化に使っている方法です |
| 大きすぎるウィンドウに小さなコンテンツ領域を置く | ウィンドウのサイズをコンテンツに合わせる(ウィンドウのサイズ設定 を参照)か、コンテンツを利用可能な領域いっぱいに広げます |
| カスタムのピル型/セグメント型タブ切り替えを手作業で作る | NavigationView Top または SelectorBar を使います |
| 一方のペインが構造上必要なのに、列幅を均等に 50/50 で分割する | シルエットに構造上必要なペインがある場合に限り、そのペインのサイズを固定し、コンテンツ側を可変にします |
色のリテラルをハードコードする(#RRGGBB、White) | セマンティックな名前の {ThemeResource} ブラシを使います |
ListView / GridView を ScrollViewer で囲む | コレクションコントロール自体にスクロール機能があります。高さを制約してください |
標準コントロールにカスタムの ControlTemplate を使う | 組み込みコントロールと軽量なスタイルのオーバーライドを使います |
| プレースホルダーテキストだけをフィールドラベルとして使う | 必ず視認できるラベルを用意します |
| 幅が狭いと必須コマンドが表示されなくなり、別の操作経路もない | オーバーフローメニュー、セカンダリサーフェス、またはレスポンシブな昇格ルールを用意します |
ブロックしないヒントにモーダルな ContentDialog を使う | TeachingTip、InfoBar、またはインラインステータスを使います |
| 確認なしで破壊的な操作(削除/破棄/リセット)を実行する | 動詞でラベル付けしたプライマリアクションと、セカンダリアクションの Cancel を含む ContentDialog を使います。本文に対象項目の識別情報(名前、件数)を表示します |
ListView / GridView で十分なのに、カスタムのリストコントロールを使う | プラットフォームのコレクションコントロールと仮想化を使います |
カスタム UI を構築するのは、次の条件をすべて満たす場合に限ります。プラットフォーム/Gallery/Toolkit のどのコントロールも適合しないこと。キーボード操作、フォーカス、UI Automation、テーマリソース、High Contrast、レスポンシブ動作を実装すること。既定、ホバー、押下、無効、選択、フォーカス、エラーの各状態の仕様があること。キーボードと Contrast テーマを使ってテスト済みであること。
参考資料(必要に応じて読み込み)
| ファイル | 読み込むタイミング |
|---|---|
references/brushes-and-icons.md | 用途に合ったブラシキーの確認、Icon / IconSource スロットの選択、FontIcon / SymbolIcon / PathIcon などからの選択 |
references/theme-accessibility.md | テーマディクショナリ、カスタムブラシ/スタイル/テンプレート、または High Contrast 対応を作成するとき |
references/layout-review.md | データ駆動型ページのレスポンシブ動作、ブレークポイント、空/読み込み中/エラー状態への対応をレビューするとき |