dotnet-winui について
Windows App SDK で WinUI 3 アプリを開発するためのスキルです。.NET 開発者向けに、XAML、MVVM、配布方式、Windows 連携、UWP からの移行を解説します。
主な機能
- 01WinUI 3 プロジェクトの設定
- 02コンパイル時バインディングと XAML の遅延読み込み
- 03CommunityToolkit.Mvvm のパターン
- 04MSIX と非パッケージ配布
- 05通知、ウィジェット、ライフサイクル連携
- 06UWP 移行ガイド
活用シーン
- 01WinUI 3 アプリの開始
- 02ビューとビューモデルの構成
- 03アプリの配布方式の選択
- 04UWP プロジェクトの WinUI 3 への移行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-winuiインストール後、Claude Code で/dotnet-winuiを入力して読み込まれたか確認します。
詳細
dotnet-winui
WinUI 3/Windows App SDK の開発:UseWinUI と Windows 10 TFM によるプロジェクトのセットアップ、コンパイル済みバインディング(x:Bind)と遅延読み込み(x:Load)を使った XAML パターン、CommunityToolkit.Mvvm による MVVM、MSIX とアンパッケージ配置モード、Windows との統合(ライフサイクル、通知、ウィジェット)、UWP からの移行ガイダンス、エージェントでよくある落とし穴。
バージョンの前提: .NET 8.0 以降をベースラインとします。Windows App SDK 1.6 以降(現在の安定版)。TFM は net8.0-windows10.0.19041.0 です。.NET 9 の機能には明示的にマークを付けています。
対象範囲: このスキルでは、WinUI 3 のプロジェクトセットアップ、XAML パターン、MVVM 統合、パッケージ化モード、Windows プラットフォームとの統合、UWP からの移行ガイダンスを扱います。デスクトップのテストは [skill:dotnet-ui-testing-core] が担当します。移行の判断マトリックスは [skill:dotnet-wpf-migration] が担当します。
対象外: デスクトップ UI テスト(Appium、WinAppDriver)については、[skill:dotnet-ui-testing-core] を参照してください。一般的な Native AOT パターンについては、[skill:dotnet-native-aot] を参照してください。UI フレームワーク選択の判断ツリーについては、[skill:dotnet-ui-chooser] を参照してください。WPF パターンについては、[skill:dotnet-wpf-modern] を参照してください。
相互参照:デスクトップのテストについては [skill:dotnet-ui-testing-core]、WPF パターンについては [skill:dotnet-wpf-modern]、移行ガイダンスについては [skill:dotnet-wpf-migration]、一般的な AOT については [skill:dotnet-native-aot]、フレームワークの選択については [skill:dotnet-ui-chooser]、一般的な P/Invoke パターン(CsWin32 は P/Invoke 宣言を生成します)については [skill:dotnet-native-interop]、アクセシビリティのパターン(AutomationProperties、AutomationPeer、UI Automation)については [skill:dotnet-accessibility] を参照してください。
プロジェクトのセットアップ
WinUI 3 は Windows App SDK(旧称 Project Reunion)をランタイムおよび API レイヤーとして使用します。プロジェクトでは、Windows 10 の特定バージョンを対象とする TFM を指定します。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<UseWinUI>true</UseWinUI>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.*" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.*" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.*" />
</ItemGroup>
</Project>
プロジェクトの構成
MyWinUIApp/
App.xaml / App.xaml.cs # Application entry, resource dictionaries
MainWindow.xaml / .xaml.cs # Main window
ViewModels/ # MVVM ViewModels
Views/ # XAML pages (for Frame navigation)
Models/ # Data models
Services/ # Service interfaces and implementations
Assets/ # Images, icons
Package.appxmanifest # MSIX manifest (packaged mode)
Properties/
launchSettings.json
ホストビルダーパターン
最新の WinUI アプリでは、依存関係の注入とサービスの構成に汎用ホストを使用します。
// App.xaml.cs
public partial class App : Application
{
private readonly IHost _host;
public App()
{
this.InitializeComponent();
_host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
// Services
services.AddSingleton<INavigationService, NavigationService>();
services.AddSingleton<IProductService, ProductService>();
// ViewModels
services.AddTransient<MainViewModel>();
services.AddTransient<ProductDetailViewModel>();
// Views
services.AddTransient<MainPage>();
services.AddTransient<ProductDetailPage>();
// Windows
services.AddSingleton<MainWindow>();
})
.Build();
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
await _host.StartAsync();
var mainWindow = _host.Services.GetRequiredService<MainWindow>();
mainWindow.Closed += async (_, _) =>
{
await _host.StopAsync();
_host.Dispose();
};
mainWindow.Activate();
}
public static T GetService<T>() where T : class
{
var app = (App)Application.Current;
return app._host.Services.GetRequiredService<T>();
}
}
TFM の要件
net8.0-windows10.0.19041.0 TFM は、次の内容を指定します。
- .NET 8.0 — ランタイムのバージョン
- Windows 10 ビルド 19041(バージョン 2004)— Windows SDK の最小バージョン
Windows App SDK の機能によっては、より高い SDK バージョンが必要になる場合があります。
- ウィジェット(Windows 11):
net8.0-windows10.0.22000.0(Windows 11 ビルド 22000) - Mica バックドロップ:
net8.0-windows10.0.22000.0 - スナップレイアウトとの統合:
net8.0-windows10.0.22000.0
XAML パターン
WinUI 3 の XAML は UWP の XAML とは異なります。ルート名前空間は Windows.UI.Xaml ではなく Microsoft.UI.Xaml です。
コンパイル済みバインディング(x:Bind)
x:Bind は {Binding} よりもコンパイル時の型チェックが可能で、パフォーマンスにも優れています。プロパティは DataContext ではなく、コードビハインドクラスを基準に解決されます。
<Page x:Class="MyApp.Views.ProductListPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyApp.ViewModels">
<Page.Resources>
</Page.Resources>
<StackPanel Padding="16" Spacing="12">
<TextBox Text="{x:Bind ViewModel.SearchTerm, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Search" Command="{x:Bind ViewModel.SearchCommand}" />
<ListView ItemsSource="{x:Bind ViewModel.Products, Mode=OneWay}"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:ProductViewModel">
<StackPanel Orientation="Horizontal" Spacing="12" Padding="8">
<Image Source="{x:Bind ImageUrl}" Height="60" Width="60" />
<StackPanel>
<TextBlock Text="{x:Bind Name}" Style="{StaticResource BodyStrongTextBlockStyle}" />
<TextBlock Text="{x:Bind Price}" Style="{StaticResource CaptionTextBlockStyle}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Page>
// Code-behind: expose ViewModel property for x:Bind
public sealed partial class ProductListPage : Page
{
public ProductListViewModel ViewModel { get; }
public ProductListPage()
{
ViewModel = App.GetService<ProductListViewModel>();
this.InitializeComponent();
}
}
{Binding} との主な違い:
x:Bindはコンパイル時に解決されます(型安全で高速)- 既定のモードは
OneTimeです({Binding}のようにOneWayではありません) DataContextではなく、コードビハインドクラスを基準に解決されますDataTemplateの項目にはx:DataTypeが必要です
遅延読み込み(x:Load)
x:Load を使うと、必要になるまで要素の作成を遅らせ、ページの初回読み込み時間を短縮できます。
<StackPanel>
<TextBlock Text="Always visible" />
<StackPanel x:Load="{x:Bind ViewModel.ShowDetails, Mode=OneWay}" x:Name="DetailsPanel">
<TextBlock Text="Detail content loaded on demand" />
<ListView ItemsSource="{x:Bind ViewModel.DetailItems, Mode=OneWay}" />
</StackPanel>
</StackPanel>
x:Load を使用する場面: すぐには表示されない、複雑なリスト、設定パネル、詳細ビューなどの負荷の高い UI セクションに使用します。バインドされたプロパティが true になると要素が作成され、false になると破棄されます。
NavigationView パターン
WinUI アプリでは通常、ページ間のナビゲーションに Frame と NavigationView を使用します。
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<NavigationView x:Name="NavView"
IsBackButtonVisible="Collapsed"
SelectionChanged="NavView_SelectionChanged">
<NavigationView.MenuItems>
<NavigationViewItem Content="Home" Tag="home" Icon="Home" />
<NavigationViewItem Content="Products" Tag="products" Icon="Shop" />
<NavigationViewItem Content="Settings" Tag="settings" Icon="Setting" />
</NavigationView.MenuItems>
<Frame x:Name="ContentFrame" />
</NavigationView>
</Window>
MVVM
WinUI 3 は CommunityToolkit.Mvvm(MAUI で使用されているものと同じ MVVM Toolkit)と統合されています。ソースジェネレーターにより、プロパティやコマンドのボイラープレートコードが不要になります。
// ViewModels/ProductListViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
public partial class ProductListViewModel : ObservableObject
{
private readonly IProductService _productService;
public ProductListViewModel(IProductService productService)
{
_productService = productService;
}
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SearchCommand))]
private string _searchTerm = "";
[ObservableProperty]
private ObservableCollection<ProductViewModel> _products = [];
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private bool _showDetails;
[RelayCommand]
private async Task LoadProductsAsync(CancellationToken ct)
{
IsLoading = true;
try
{
var items = await _productService.GetProductsAsync(ct);
Products = new ObservableCollection<ProductViewModel>(
items.Select(p => new ProductViewModel(p)));
}
finally
{
IsLoading = false;
}
}
[RelayCommand(CanExecute = nameof(CanSearch))]
private async Task SearchAsync(CancellationToken ct)
{
var results = await _productService.SearchAsync(SearchTerm, ct);
Products = new ObservableCollection<ProductViewModel>(
results.Select(p => new ProductViewModel(p)));
}
private bool CanSearch() => !string.IsNullOrWhiteSpace(SearchTerm);
}
主なソースジェネレーター属性:
[ObservableProperty]-- バッキングフィールドからINotifyPropertyChangedを実装するプロパティを生成します[RelayCommand]-- メソッドからICommandを生成します(非同期、キャンセル、CanExecuteに対応)[NotifyPropertyChangedFor]-- 依存プロパティのPropertyChangedを発生させます[NotifyCanExecuteChangedFor]-- プロパティが変更されたときに、コマンドのCanExecuteを再評価します
パッケージ化
WinUI 3 では、MSIX パッケージ化とパッケージ化なしの 2 つの展開モデルに対応しています。選択したモデルによって、アプリの ID、機能、配布方法が変わります。
MSIX パッケージ化された展開
MSIX は既定のパッケージ化モデルです。アプリの ID、クリーンなインストール/アンインストール、自動更新、Windows 統合 API へのフルアクセスを提供します。
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10">
<Identity Name="MyApp" Publisher="CN=Contoso" Version="1.0.0.0" />
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements DisplayName="My App"
Description="WinUI 3 application"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png" />
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
</Capabilities>
</Package>
# Build MSIX package
dotnet publish -c Release -r win-x64
パッケージ化なしの展開
パッケージ化なしのモードでは、MSIX の要件がなくなります。アプリはアプリ ID を持たない標準的な Win32 実行ファイルとして動作します。
<PropertyGroup>
<WindowsPackageType>None</WindowsPackageType>
</PropertyGroup>
トレードオフ:
| 機能 | MSIX パッケージ化 | パッケージ化なし |
|---|---|---|
| アプリ ID | あり | なし |
| クリーンなインストール/アンインストール | あり(プログラムの追加と削除) | 手動 |
| 自動更新 | あり(Store、App Installer) | 手動 |
| バックグラウンドタスク | フルサポート | 制限あり |
| トースト通知 | フルサポート | COM 登録が必要 |
| ウィジェット(Windows 11) | あり | なし |
| ファイルの種類の関連付け | マニフェスト経由 | レジストリ経由 |
| 配布 | Store、サイドロード、App Installer | xcopy、インストーラー(MSI/EXE) |
| 起動時間 | やや遅い(パッケージの検証) | 速い |
パッケージ化なしを選ぶ場合:
- 既存の展開インフラストラクチャを使用する社内エンタープライズツール
- xcopy による展開、または既存の MSI/EXE インストーラーとの統合が必要なアプリ
- パッケージ化のオーバーヘッドが不要な簡易プロトタイプ
- Windows の ID 機能を必要としないアプリ
Windows との統合
アプリのライフサイクル
WinUI 3 アプリでは、UWP の CoreApplication とは異なる、Windows App SDK のアクティブ化およびライフサイクルモデルを使用します。
// Handle activation kinds (protocol, file, toast, etc.)
public partial class App : Application
{
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
// Check for specific activation
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
switch (activationArgs.Kind)
{
case ExtendedActivationKind.Protocol:
var protocolArgs = (ProtocolActivatedEventArgs)activationArgs.Data;
HandleProtocolActivation(protocolArgs.Uri);
break;
case ExtendedActivationKind.File:
var fileArgs = (FileActivatedEventArgs)activationArgs.Data;
HandleFileActivation(fileArgs.Files);
break;
default:
// Normal launch
break;
}
}
}
通知
トースト通知には、Windows App SDK の通知 API が必要です。
using Microsoft.Windows.AppNotifications;
using Microsoft.Windows.AppNotifications.Builder;
// Register for notification activation
var notificationManager = AppNotificationManager.Default;
notificationManager.NotificationInvoked += OnNotificationInvoked;
notificationManager.Register();
// Send a toast notification
var builder = new AppNotificationBuilder()
.AddText("Order Shipped")
.AddText("Your order #12345 has shipped.")
.AddButton(new AppNotificationButton("Track")
.AddArgument("action", "track")
.AddArgument("orderId", "12345"));
AppNotificationManager.Default.Show(builder.BuildNotification());
ウィジェット(Windows 11)
ウィジェットには Windows 11(ビルド 22000 以降)と、MSIX パッケージ化された展開が必要です。実装では、IWidgetProvider を実装するウィジェットプロバイダーを作成し、MSIX マニフェストに登録します。
主な手順:
IWidgetProviderインターフェイスを実装します(メソッド:CreateWidget、DeleteWidget、OnActionInvoked、OnWidgetContextChanged、OnCustomizationRequested、Activate、Deactivate)- プロバイダーを MSIX マニフェストの COM クラスとして登録します
- Adaptive Cards の JSON 形式を使用してウィジェットテンプレートを定義します
- プロバイダーメソッドから更新後のウィジェットコンテンツを返します
インターフェイスの仕様とマニフェスト登録の詳細については、Windows App SDK のウィジェットに関するドキュメントを参照してください。
タスクバーとの統合
WinUI 3 でタスクバーの進行状況を表示するには、ITaskbarList3 インターフェイスを介した Win32 COM 相互運用が必要です。マネージドの TaskbarManager があった UWP とは異なり、WinUI 3 にはマネージドラッパーが用意されていません。
// Taskbar progress requires COM interop in WinUI 3
// Use CsWin32 source generator or manual P/Invoke for ITaskbarList3
// 1. Add CsWin32: <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.*" />
// 2. Add to NativeMethods.txt: ITaskbarList3
// See: https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-itaskbarlist3
UWP からの移行
UWP から WinUI 3 への移行には、名前空間の変更、API の置き換え、プロジェクトの再構成が伴います。
名前空間の変更
| UWP の名前空間 | WinUI 3 の名前空間 |
|---|---|
Windows.UI.Xaml | Microsoft.UI.Xaml |
Windows.UI.Xaml.Controls | Microsoft.UI.Xaml.Controls |
Windows.UI.Xaml.Media | Microsoft.UI.Xaml.Media |
Windows.UI.Xaml.Input | Microsoft.UI.Xaml.Input |
Windows.UI.Composition | Microsoft.UI.Composition |
Windows.UI.Text | Microsoft.UI.Text |
Windows.UI.Colors | Microsoft.UI.Colors |
そのまま使用: Windows.Storage、Windows.Networking、Windows.Security、Windows.ApplicationModel、Windows.Devices — これらの WinRT API は引き続き Windows.* 名前空間に属します。
API の置き換え
| UWP API | WinUI 3 での置き換え |
|---|---|
CoreApplication.MainView | App.MainWindow(独自にウィンドウへの参照を保持します) |
CoreDispatcher.RunAsync | DispatcherQueue.TryEnqueue |
Window.Current | App クラスでウィンドウへの参照を手動で保持します |
ApplicationView.Title | window.Title = "..." |
CoreWindow.GetForCurrentThread | 利用できません。キーボード API には InputKeyboardSource を使用します |
SystemNavigationManager.BackRequested | NavigationView.BackRequested |
移行手順
- Windows App SDK テンプレートを使用して新しい WinUI 3 プロジェクトを作成します
- ソースファイルをコピーし、名前空間を更新します(
Windows.UI.XamlからMicrosoft.UI.Xamlへ) - すべての
.xamlファイルでXAML 名前空間を更新します - 非推奨 API を置き換えます(上の表を参照)
.appxmanifestの UWP 形式から Windows App SDK 形式へパッケージングを移行します- Windows App SDK と互換性のあるバージョンにNuGet パッケージを更新します
- Windows 統合機能(通知、バックグラウンドタスク、ファイルの関連付け)をテストします
フレームワークをまたぐ包括的な移行手順については、[skill:dotnet-wpf-migration] を参照してください。
UWP の .NET 9 プレビューパス: Microsoft は、プレビュー版として .NET 9 での UWP サポートを発表しました。これにより、WinUI 3 に移行せずに UWP アプリで最新の .NET を使用できます。WinUI への完全な移行にコストがかかりすぎる一方で、最新の .NET ランタイム機能が必要な場合は、この方法を検討してください。
エージェントが陥りやすい注意点
- UWP の XAML と WinUI 3 の XAML を混同しないでください。 ルート名前空間は
Windows.UI.XamlからMicrosoft.UI.Xamlに変更されています。Windows.UI.Xaml.*型を使用するコードは、WinUI 3 プロジェクトではコンパイルできません。 Window.Currentを使用しないでください。 WinUI 3 には静的なWindow.Currentプロパティがありません。Appクラスでウィンドウへの参照を手動で保持し、DI または静的プロパティを介して渡してください。CoreDispatcherを使用しないでください。CoreDispatcher.RunAsync()をDispatcherQueue.TryEnqueue()に置き換えてください。CoreDispatcherは WinUI 3 では利用できない UWP API です。- MSIX が必須だと思い込まないでください。 WinUI 3 は、
<WindowsPackageType>None</WindowsPackageType>を使用して、パッケージ化しないデプロイをサポートしています。アプリの ID、ストアでの配布、または必要とする Windows 統合機能がある場合にのみ MSIX を使用してください。 x:Bindの既定値がOneTimeであることを忘れないでください。 既定値がOneWayの{Binding}とは異なり、x:Bindの既定値はOneTimeです。初回のバインド後に変更されるプロパティには、必ずMode=OneWayまたはMode=TwoWayを指定してください。- 19041 より古いビルドの Windows 10 をターゲットにしないでください。 Windows App SDK 1.6 以降では、最低ビルド 19041(バージョン 2004)が必要です。それより古いビルドをターゲットにすると、実行時エラーが発生します。
- パッケージ化しないアプリで Widgets や Mica を使用しないでください。 これらの機能には、アプリの ID を伴う MSIX パッケージ化デプロイが必要です。パッケージ化しないモードで使用しようとすると、何も起きないまま失敗するか、例外がスローされます。
- CommunityToolkit.Mvvm と手動の INotifyPropertyChanged 実装を混在させないでください。 一貫して
[ObservableProperty]を使用してください。ソース生成による実装と手書きの実装を混在させると、気付きにくいバインドの不具合が発生します。 - Host ビルダーのライフサイクルを忘れないでください。
OnLaunchedで_host.StartAsync()を呼び出し、ウィンドウが閉じるときに_host.StopAsync()を呼び出してください。ライフサイクル管理を忘れると、DI に登録されたIHostedServiceインスタンスが起動も停止もしなくなります。
前提条件
- Windows デスクトップ ワークロードを含む .NET 8.0 以降
- Windows App SDK 1.6 以降(
UseWinUI経由で自動的に参照されます) - Windows 10 バージョン 2004(ビルド 19041)以降
- Windows App SDK ワークロードを含む Visual Studio 2022 以降、または C# Dev Kit を含む VS Code
- ウィジェットを使用する場合:Windows 11(ビルド 22000 以降)