mcpField
W
wpf

wpf

github.com/managedcode開発49689 回インストール今日更新
リポジトリ

wpf について

WPF は、Windows デスクトップアプリの UI、MVVM、データバインディング、モダナイズを扱う開発スキルです。ビューモデル設計、スレッド処理、移行を案内し、Windows 固有の制約も考慮します。

主な機能

  1. 01MVVM による UI の構成
  2. 02データバインディングとコマンドの設定
  3. 03スタイルとテンプレートの整理
  4. 04Dispatcher による UI スレッド処理
  5. 05モダン .NET への移行ガイド

活用シーン

  1. 01コードビハインドをテスト可能なビューモデルに整理
  2. 02バインディングとコマンドの動作調査
  3. 03WPF アプリのモダナイズ
  4. 04バックグラウンド処理から UI を安全に更新

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/managedcode/dotnet-skills --skill wpf

インストール後、Claude Code で/wpfを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

manifest.json
references/
anti-patterns.md
patterns.md

詳細

WPF

使用する場面

  • WPF UI、MVVM、バインディング、コマンド、またはデスクトップアプリのモダナイズに取り組む場合
  • WPF を .NET Framework から .NET に移行する場合
  • 新しい Windows 機能を WPF アプリに統合する場合
  • データバインディング、スタイル、テンプレート、またはコントロールのカスタマイズを実装する場合
  • デスクトップアプリケーションをバックエンドのエージェントサービスに接続し、.NET で AI エージェントを構築する場合

ドキュメント

リファレンス

ワークフロー

  1. Windows 専用の範囲を確認する — .NET スタック全体がクロスプラットフォームであっても、WPF は Windows 専用です
  2. MVVM パターンを適用する — ビューはシンプルに保ち、ロジックは ViewModel に置き、コマンドを使用します
  3. データバインディングを明示的に管理する — 適切なバインディングモードを選び、実行時に検証します
  4. スタイルとテンプレートを意図的に使用する — UI を組み合わせやすく保ち、ページ固有の場当たり的な回避策は避けます
  5. スレッドを適切に扱う — UI の更新には Dispatcher を、長時間の処理には async/await を使用します
  6. デザイナーと実行時の両方で検証する — XAML の構成エラーは実行時に初めて表面化することがよくあります

最新の上流情報

  • 2026 年 8 月の WPF の概要では、WPF が XAML、データバインディング、スタイル設定、テンプレート、リソース、ベクター/リッチメディアの合成機能を備えた Windows 専用のデスクトップ UI スタックであることが改めて示されています。タスクが明示的な移行または比較でない限り、WPF 固有のガイダンスは WinUI や MAUI のガイダンスと分けてください。
  • WPF は .NET Framework と最新の .NET の両方で利用できます。モダナイズ作業では、プロジェクトファイル、相互運用機能、デプロイ、または XAML リソースディクショナリを移行する前に、互換性の制約を洗い出し、最新のデスクトップ移行ガイダンスを参照してください。

プロジェクト構造

MyWpfApp/
├── MyWpfApp/
│   ├── App.xaml                # Application entry
│   ├── MainWindow.xaml         # Main window
│   ├── Views/                  # XAML views/windows
│   ├── ViewModels/             # MVVM ViewModels
│   ├── Models/                 # Domain models
│   ├── Services/               # Business logic
│   ├── Converters/             # Value converters
│   ├── Resources/              # Styles, templates, dictionaries
│   └── Controls/               # Custom controls
└── MyWpfApp.Tests/

MVVM パターン

MVVM Toolkit を使用した ViewModel
public partial class CustomersViewModel : ObservableObject
{
    private readonly ICustomerService _customerService;

    [ObservableProperty]
    private ObservableCollection<Customer> _customers = [];

    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(SaveCommand))]
    private Customer? _selectedCustomer;

    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(RefreshCommand))]
    private bool _isLoading;

    public CustomersViewModel(ICustomerService customerService)
    {
        _customerService = customerService;
    }

    [RelayCommand(CanExecute = nameof(CanRefresh))]
    private async Task RefreshAsync()
    {
        IsLoading = true;
        try
        {
            var items = await _customerService.GetAllAsync();
            Customers = new ObservableCollection<Customer>(items);
        }
        finally
        {
            IsLoading = false;
        }
    }

    private bool CanRefresh() => !IsLoading;

    [RelayCommand(CanExecute = nameof(CanSave))]
    private async Task SaveAsync()
    {
        if (SelectedCustomer is null) return;
        await _customerService.SaveAsync(SelectedCustomer);
    }

    private bool CanSave() => SelectedCustomer is not null;
}
ビューのバインディング
<Window x:Class="MyWpfApp.Views.CustomersView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:MyWpfApp.ViewModels"
        d:DataContext="{d:DesignInstance Type=vm:CustomersViewModel}">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <ToolBar Grid.Row="0">
            <Button Content="Refresh"
                    Command="{Binding RefreshCommand}"/>
            <Button Content="Save"
                    Command="{Binding SaveCommand}"/>
        </ToolBar>

        <DataGrid Grid.Row="1"
                  ItemsSource="{Binding Customers}"
                  SelectedItem="{Binding SelectedCustomer}"
                  AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name"
                                    Binding="{Binding Name}"/>
                <DataGridTextColumn Header="Email"
                                    Binding="{Binding Email}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

依存関係の注入

public partial class App : Application
{
    private readonly IHost _host;

    public App()
    {
        _host = Host.CreateDefaultBuilder()
            .ConfigureServices((context, services) =>
            {
                // Services
                services.AddSingleton<ICustomerService, CustomerService>();
                services.AddSingleton<INavigationService, NavigationService>();

                // ViewModels
                services.AddTransient<CustomersViewModel>();
                services.AddTransient<CustomerDetailViewModel>();

                // Views
                services.AddTransient<MainWindow>();
                services.AddTransient<CustomersView>();
            })
            .Build();
    }

    protected override async void OnStartup(StartupEventArgs e)
    {
        await _host.StartAsync();
        var mainWindow = _host.Services.GetRequiredService<MainWindow>();
        mainWindow.Show();
        base.OnStartup(e);
    }

    protected override async void OnExit(ExitEventArgs e)
    {
        await _host.StopAsync();
        _host.Dispose();
        base.OnExit(e);
    }
}

データバインディングモード


<TextBlock Text="{Binding CreatedDate, Mode=OneTime}"/>


<TextBlock Text="{Binding Name, Mode=OneWay}"/>


<TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>


<TextBox Text="{Binding SearchFilter, Mode=OneWayToSource}"/>

値コンバーター

public class BooleanToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool boolValue)
        {
            return boolValue ? Visibility.Visible : Visibility.Collapsed;
        }
        return Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value is Visibility.Visible;
    }
}

// Multi-value converter
public class MultiplyConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values.Length == 2 && values[0] is double a && values[1] is double b)
        {
            return a * b;
        }
        return 0.0;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

スタイルとテンプレート

リソースディクショナリ
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    
    <Style TargetType="Button">
        <Setter Property="Padding" Value="10,5"/>
        <Setter Property="Margin" Value="5"/>
        <Setter Property="Background" Value="#0078D4"/>
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Border Background="{TemplateBinding Background}"
                            CornerRadius="4"
                            Padding="{TemplateBinding Padding}">
                        <ContentPresenter HorizontalAlignment="Center"
                                          VerticalAlignment="Center"/>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    
    <Style x:Key="DangerButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
        <Setter Property="Background" Value="#D32F2F"/>
    </Style>
</ResourceDictionary>

スレッド処理と Dispatcher

// Update UI from background thread
await Task.Run(async () =>
{
    var data = await LoadDataAsync();

    // Must use Dispatcher to update UI
    Application.Current.Dispatcher.Invoke(() =>
    {
        Items.Clear();
        foreach (var item in data)
        {
            Items.Add(item);
        }
    });
});

// Better: Use async/await properly
private async Task LoadDataAsync()
{
    IsLoading = true;
    try
    {
        // This runs on background thread
        var data = await _service.GetDataAsync();

        // This automatically marshals to UI thread
        Items = new ObservableCollection<Item>(data);
    }
    finally
    {
        IsLoading = false;
    }
}

避けるべきアンチパターン

アンチパターン問題点より良い方法
コードビハインドにロジックを置くテストが難しく、密結合になるViewModel を使った MVVM を使用する
同期的なブロッキング呼び出しUI がフリーズするasync/await を使用する
INotifyPropertyChanged を手動で実装する定型コードが増え、エラーが起きやすいMVVM Toolkit の属性を使用する
色やサイズをハードコードする一貫性がなく、テーマを適用しにくいリソースディクショナリを使用する
あらゆる箇所で直接 Dispatcher.Invoke を使う複雑で、エラーが起きやすいasync/await によるマーシャリングを優先する
巨大な ViewModel保守が困難になる役割を絞った ViewModel に分割する
バインディングの検証を省略する実行時エラーが見過ごされるValidatesOnDataErrors を使用する
すべてにイベントハンドラーを使うメモリリークや結合の原因になるコマンドとバインディングを使用する

ベストプラクティス

  1. .NET 5 以降ではコンパイル済みバインディングを使用します:

    • パフォーマンス向上のため、x:CompileBindings="True" を有効にします
  2. 検証には INotifyDataErrorInfo を実装します:

    [ObservableProperty]
    [NotifyDataErrorInfo]
    [Required(ErrorMessage = "Name is required")]
    [MinLength(2, ErrorMessage = "Name must be at least 2 characters")]
    private string _name = string.Empty;
    
  3. 長期間有効なサブスクリプションには弱いイベントパターンを使用します:

    WeakEventManager<Source, EventArgs>.AddHandler(source, "EventName", Handler);
    
  4. 大規模なコレクションは仮想化します:

    <ListBox VirtualizingPanel.IsVirtualizing="True"
             VirtualizingPanel.VirtualizationMode="Recycling"
             ItemsSource="{Binding LargeCollection}"/>
    
  5. 可能な場合は Freezable をフリーズします:

    var brush = new SolidColorBrush(Colors.Blue);
    brush.Freeze(); // Thread-safe, better performance
    
  6. デザイン時データを使用します:

    <Window d:DataContext="{d:DesignInstance Type=vm:MainViewModel, IsDesignTimeCreatable=True}">
    

テスト

[Fact]
public async Task RefreshCommand_LoadsCustomers()
{
    var mockService = new Mock<ICustomerService>();
    mockService.Setup(s => s.GetAllAsync())
        .ReturnsAsync(new[] { new Customer { Name = "Test" } });

    var viewModel = new CustomersViewModel(mockService.Object);

    await viewModel.RefreshCommand.ExecuteAsync(null);

    Assert.Single(viewModel.Customers);
    Assert.Equal("Test", viewModel.Customers[0].Name);
}

[Fact]
public void SaveCommand_CannotExecute_WhenNoSelection()
{
    var mockService = new Mock<ICustomerService>();
    var viewModel = new CustomersViewModel(mockService.Object);

    viewModel.SelectedCustomer = null;

    Assert.False(viewModel.SaveCommand.CanExecute(null));
}

提供内容

  • より整理された WPF ビューとビューモデルの境界
  • より安全なバインディングとスレッド処理
  • 実際の Windows の制約に基づく移行ガイダンス
  • テスト可能な ViewModel を備えた MVVM パターン

検証項目

  • バインディングとコマンドのフローが明示されている
  • コードビハインドに隠れたビジネスロジックが含まれていない
  • Windows 固有の前提条件が明示されている
  • スレッド処理とディスパッチャーの使用が適切である
  • スタイルとリソースが適切に整理されている

コメント