mcpField
D
dotnet-winui

dotnet-winui

github.com/wshaddix開発8699 回インストール35 週間前に更新
リポジトリ

dotnet-winui について

Windows App SDK で WinUI 3 アプリを開発するためのスキルです。.NET 開発者向けに、XAML、MVVM、配布方式、Windows 連携、UWP からの移行を解説します。

主な機能

  1. 01WinUI 3 プロジェクトの設定
  2. 02コンパイル時バインディングと XAML の遅延読み込み
  3. 03CommunityToolkit.Mvvm のパターン
  4. 04MSIX と非パッケージ配布
  5. 05通知、ウィジェット、ライフサイクル連携
  6. 06UWP 移行ガイド

活用シーン

  1. 01WinUI 3 アプリの開始
  2. 02ビューとビューモデルの構成
  3. 03アプリの配布方式の選択
  4. 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 Installerxcopy、インストーラー(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 マニフェストに登録します。

主な手順:

  1. IWidgetProvider インターフェイスを実装します(メソッド:CreateWidget、DeleteWidget、OnActionInvoked、OnWidgetContextChanged、OnCustomizationRequested、Activate、Deactivate)
  2. プロバイダーを MSIX マニフェストの COM クラスとして登録します
  3. Adaptive Cards の JSON 形式を使用してウィジェットテンプレートを定義します
  4. プロバイダーメソッドから更新後のウィジェットコンテンツを返します

インターフェイスの仕様とマニフェスト登録の詳細については、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.XamlMicrosoft.UI.Xaml
Windows.UI.Xaml.ControlsMicrosoft.UI.Xaml.Controls
Windows.UI.Xaml.MediaMicrosoft.UI.Xaml.Media
Windows.UI.Xaml.InputMicrosoft.UI.Xaml.Input
Windows.UI.CompositionMicrosoft.UI.Composition
Windows.UI.TextMicrosoft.UI.Text
Windows.UI.ColorsMicrosoft.UI.Colors

そのまま使用: Windows.Storage、Windows.Networking、Windows.Security、Windows.ApplicationModel、Windows.Devices — これらの WinRT API は引き続き Windows.* 名前空間に属します。

API の置き換え
UWP APIWinUI 3 での置き換え
CoreApplication.MainViewApp.MainWindow(独自にウィンドウへの参照を保持します)
CoreDispatcher.RunAsyncDispatcherQueue.TryEnqueue
Window.CurrentApp クラスでウィンドウへの参照を手動で保持します
ApplicationView.Titlewindow.Title = "..."
CoreWindow.GetForCurrentThread利用できません。キーボード API には InputKeyboardSource を使用します
SystemNavigationManager.BackRequestedNavigationView.BackRequested
移行手順
  1. Windows App SDK テンプレートを使用して新しい WinUI 3 プロジェクトを作成します
  2. ソースファイルをコピーし、名前空間を更新します(Windows.UI.Xaml から Microsoft.UI.Xaml へ)
  3. すべての .xaml ファイルでXAML 名前空間を更新します
  4. 非推奨 API を置き換えます(上の表を参照)
  5. .appxmanifest の UWP 形式から Windows App SDK 形式へパッケージングを移行します
  6. Windows App SDK と互換性のあるバージョンにNuGet パッケージを更新します
  7. Windows 統合機能(通知、バックグラウンドタスク、ファイルの関連付け)をテストします

フレームワークをまたぐ包括的な移行手順については、[skill:dotnet-wpf-migration] を参照してください。

UWP の .NET 9 プレビューパス: Microsoft は、プレビュー版として .NET 9 での UWP サポートを発表しました。これにより、WinUI 3 に移行せずに UWP アプリで最新の .NET を使用できます。WinUI への完全な移行にコストがかかりすぎる一方で、最新の .NET ランタイム機能が必要な場合は、この方法を検討してください。


エージェントが陥りやすい注意点

  1. UWP の XAML と WinUI 3 の XAML を混同しないでください。 ルート名前空間は Windows.UI.Xaml から Microsoft.UI.Xaml に変更されています。Windows.UI.Xaml.* 型を使用するコードは、WinUI 3 プロジェクトではコンパイルできません。
  2. Window.Current を使用しないでください。 WinUI 3 には静的な Window.Current プロパティがありません。App クラスでウィンドウへの参照を手動で保持し、DI または静的プロパティを介して渡してください。
  3. CoreDispatcher を使用しないでください。 CoreDispatcher.RunAsync() を DispatcherQueue.TryEnqueue() に置き換えてください。CoreDispatcher は WinUI 3 では利用できない UWP API です。
  4. MSIX が必須だと思い込まないでください。 WinUI 3 は、<WindowsPackageType>None</WindowsPackageType> を使用して、パッケージ化しないデプロイをサポートしています。アプリの ID、ストアでの配布、または必要とする Windows 統合機能がある場合にのみ MSIX を使用してください。
  5. x:Bind の既定値が OneTime であることを忘れないでください。 既定値が OneWay の {Binding} とは異なり、x:Bind の既定値は OneTime です。初回のバインド後に変更されるプロパティには、必ず Mode=OneWay または Mode=TwoWay を指定してください。
  6. 19041 より古いビルドの Windows 10 をターゲットにしないでください。 Windows App SDK 1.6 以降では、最低ビルド 19041(バージョン 2004)が必要です。それより古いビルドをターゲットにすると、実行時エラーが発生します。
  7. パッケージ化しないアプリで Widgets や Mica を使用しないでください。 これらの機能には、アプリの ID を伴う MSIX パッケージ化デプロイが必要です。パッケージ化しないモードで使用しようとすると、何も起きないまま失敗するか、例外がスローされます。
  8. CommunityToolkit.Mvvm と手動の INotifyPropertyChanged 実装を混在させないでください。 一貫して [ObservableProperty] を使用してください。ソース生成による実装と手書きの実装を混在させると、気付きにくいバインドの不具合が発生します。
  9. 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 以降)

参考資料

コメント