WPF(Windows Presentation Foundation)は、Windowsアプリケーション開発における強力なフレームワークです。本記事では、C#とWPFを使用したアプリケーション開発の基礎から応用までをわかりやすく解説します。これからWPFを学びたい初心者の方から、スキルを深めたい経験者の方まで、幅広く役立つ内容となっています。
WPFとは何か
WPF(Windows Presentation Foundation)は、Microsoftが提供するアプリケーションフレームワークで、リッチなユーザーインターフェイスを作成するためのツールです。WPFは、XAML(Extensible Application Markup Language)を使用してUIレイアウトを宣言的に記述し、C#などのコードビハインドで動作を実装することができます。.NET自体はクロスプラットフォームですが、WPFアプリケーションの実行先はWindowsです。macOSやLinuxでそのまま実行できるUIフレームワークではありません。詳しくはMicrosoftのWPF概要を参照してください。
WPFの基本概念
WPFの基本概念には、以下のようなものがあります:
XAML
XAMLは、UI要素を定義するためのマークアップ言語です。HTMLに似ており、WPFアプリケーションのレイアウトや外観を定義するために使用されます。
コントロール
WPFには、ボタンやテキストボックスなど、多くのビルトインコントロールが用意されています。これらのコントロールを組み合わせることで、ユーザーインターフェイスを構築します。
データバインディング
WPFは、データとUI要素を効率的に連携させるデータバインディング機能を提供します。これにより、コードを少なくしつつ、動的なUIを実現することができます。
WPFの特徴
WPFには、多くの特徴があります:
柔軟なレイアウトシステム
グリッドやスタックパネルなどのレイアウトコンテナを使用して、複雑なレイアウトを簡単に作成できます。
スタイルとテンプレート
WPFは、スタイルとテンプレートを使用してUIの外観を一貫性を持ってカスタマイズすることができます。
アニメーションとグラフィックス
WPFは、アニメーションやベクターグラフィックスを簡単に実装できるため、視覚的にリッチなアプリケーションを作成するのに適しています。
WPFのこれらの基本概念と特徴を理解することで、リッチでインタラクティブなWindowsアプリケーションの開発が可能になります。
WPF開発環境のセットアップ
WPFアプリケーションを開発するためには、適切な開発環境をセットアップする必要があります。ここでは、Windows上のVisual Studioで、.NET版のWPFプロジェクトを作成します。2026年10月20日時点の新規学習用の基準は、Visual Studio 2026と.NET 10(LTS)です。.NET 10のサポート終了予定日は2028年11月14日です。導入時は公式の作成手順と.NETのサポートポリシーで対応状況を確認し、サポート中の更新版を使用してください。
Visual Studioのインストール
Visual Studioは、Microsoftが提供する統合開発環境(IDE)で、WPFアプリケーションの開発に最適です。以下の手順でインストールを行います:
Visual Studioのダウンロード
- Visual Studioの公式サイトにアクセスします。
- 利用条件に合うエディションを選び、インストーラーをダウンロードします。Communityの利用にはライセンス条件があるため、組織で使う場合は条件を確認してください。
インストールの実行
- ダウンロードしたインストーラーを実行します。
- 「ワークロード」の選択画面で、「.NETデスクトップ開発」を選択します。
- 対象の.NET SDK(この記事では.NET 10)を含むコンポーネントを確認し、「インストール」をクリックします。
WPFプロジェクトの作成
Visual Studioがインストールされたら、WPFプロジェクトを作成します。以下の手順で進めます:
新しいプロジェクトの作成
- Visual Studioを起動し、「新しいプロジェクトの作成」をクリックします。
- 「テンプレートの検索」ボックスに「WPF」を入力し、言語をC#に絞り、「WPFアプリケーション」を選択します。この記事では「WPFアプリケーション(.NET Framework)」は選びません。
- プロジェクト名を「WpfApp」とし、保存場所を指定して「次へ」を選びます。追加情報で.NET 10.0(LTS)を選択して作成します。別名にする場合は、コードの名前空間とXAMLのx:Classも合わせて変更してください。
プロジェクトの構成
新しいWPFプロジェクトが作成されると、以下のようなファイルとフォルダーが含まれます:
- App.xaml:アプリケーション全体の設定を行うファイル
- MainWindow.xaml:アプリケーションのメインウィンドウのデザインを記述するファイル
- MainWindow.xaml.cs:メインウィンドウのコードビハインドファイル
プロジェクトファイルでは、対象がnet10.0-windows、UseWPFがtrueになっていることを確認します。以降の各節は学習用の独立した例です。同名クラスやウィンドウ全体のコードを追記せず、節ごとにファイルを置き換えるか、新しいプロジェクトで試してください。XAMLだけの例では、使わなくなったイベントハンドラーや旧コントロール名を参照するコードも除きます。
基本的なWPFアプリケーションの作成
WPF開発環境が整ったら、実際に基本的なWPFアプリケーションを作成してみましょう。ここでは、シンプルなアプリケーションを作成する手順を紹介します。
プロジェクトの準備
新しいWPFプロジェクトが作成された状態から始めます。Visual Studioで「MainWindow.xaml」を開き、デザインビューとXAMLビューを確認します。
UIのデザイン
まずは、簡単なユーザーインターフェイスをデザインします。今回は、テキストボックスとボタンを配置し、ボタンをクリックするとテキストボックスの内容を表示するアプリケーションを作成します。
XAMLコードの記述
以下のコードを「MainWindow.xaml」に記述します:
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="入力内容の表示" Height="220" Width="400">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox x:Name="inputTextBox" MinHeight="30"/>
<Button Grid.Row="1" Content="Click Me" Width="100"
Margin="0,8,0,8" HorizontalAlignment="Left" Click="Button_Click"/>
<TextBlock x:Name="outputTextBlock" Grid.Row="2" TextWrapping="Wrap"/>
</Grid>
</Window>コードビハインドの記述
次に、ボタンのクリックイベントを処理するコードを記述します。「MainWindow.xaml.cs」を開き、以下のコードに置き換えます:
using System.Windows;
namespace WpfApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
outputTextBlock.Text = inputTextBox.Text;
}
}
}アプリケーションの実行
以上の設定が完了したら、アプリケーションを実行してみましょう。Visual Studioの「デバッグ」メニューから「デバッグなしで開始」を選択すると、アプリケーションが起動します。テキストボックスに文字を入力し、「Click Me」ボタンをクリックすると、入力した文字が表示されることを確認できます。
このようにして、基本的なWPFアプリケーションを作成する手順を学びました。次は、XAMLとデザインの基本について詳しく見ていきます。
XAMLとデザイン
WPFアプリケーションのUIを設計する際に重要な要素であるXAML(Extensible Application Markup Language)について詳しく見ていきましょう。XAMLは、UIレイアウトやデザインを宣言的に記述するための言語です。
XAMLの基本
XAMLは、XML形式で記述され、WPFアプリケーションのUI要素を定義します。各要素は、タグとして記述され、属性でプロパティを設定します。
基本的なXAML構文
以下は、基本的なXAML構文の例です:
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Click Me" Width="100" Height="50" VerticalAlignment="Top" HorizontalAlignment="Left"/>
</Grid>
</Window>この例では、<Window>要素がウィンドウを定義し、その中に<Grid>レイアウトコンテナを配置しています。さらに、<Button>要素がグリッド内に配置されています。
UIレイアウトの設計
WPFでは、複数のレイアウトコンテナを使用して柔軟なUIレイアウトを作成できます。
グリッドレイアウト
<Grid>は、行と列を使ってUI要素を配置するためのコンテナです。以下は、2行2列のグリッドの例です:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Content="Button 1" Grid.Row="0" Grid.Column="0"/>
<Button Content="Button 2" Grid.Row="0" Grid.Column="1"/>
<Button Content="Button 3" Grid.Row="1" Grid.Column="0"/>
<Button Content="Button 4" Grid.Row="1" Grid.Column="1"/>
</Grid>この例では、<RowDefinition>と<ColumnDefinition>を使用してグリッドの行と列を定義し、各ボタンを指定されたセルに配置しています。
デザインツールの使用
Visual Studioには、XAMLデザインを視覚的に編集するためのデザインビューが搭載されています。このツールを使うと、ドラッグアンドドロップでUI要素を配置し、プロパティウィンドウで各要素のプロパティを設定できます。
Blend for Visual Studio
Blend for Visual Studioは、さらに高度なデザインとアニメーションを作成するためのツールです。以下のような機能があります:
- 高度なレイアウト編集
- アニメーションとトランジションの設計
- スタイルとテンプレートの編集
これらのツールを活用することで、より洗練されたUIデザインを効率よく作成できます。
XAMLとデザインの基本を理解することで、WPFアプリケーションの見た目をより直感的で魅力的なものにすることができます。次は、データバインディングの基礎について学びます。
データバインディングの基礎
WPFの強力な機能の一つであるデータバインディングについて学びましょう。データバインディングを使用すると、UI要素とデータソースを効率的に連携させることができます。
データバインディングの基本概念
データバインディングとは、UI要素(バインディングターゲット)とデータソース(バインディングソース)を結びつける仕組みです。ソースの変更を継続してUIへ反映するには、バインディングのモードに加え、変更通知が必要です。通常のC#オブジェクトではINotifyPropertyChangedなどを使います。単なる自動実装プロパティを書くだけでは、コードから変更した値が自動的に再表示されるとは限りません。
シンプルなデータバインディングの例
以下は、テキストボックスとテキストブロックをデータバインディングする例です:
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="要素間バインディング" Height="200" Width="400">
<StackPanel Margin="16">
<TextBox x:Name="inputTextBox" MinHeight="30"/>
<TextBlock Text="{Binding Text, ElementName=inputTextBox, Mode=OneWay}"
Margin="0,12,0,0" TextWrapping="Wrap"/>
</StackPanel>
</Window>この例では、テキストボックスのTextプロパティとテキストブロックのTextプロパティをバインディングしています。テキストボックスに入力した内容がテキストブロックにリアルタイムで反映されます。TextBox自身のTextを同じTextBoxへバインドする必要はありません。この例はElementNameでソースを指定するため、DataContextも不要です。
バインディングのモード
データバインディングにはいくつかのモードがあります:
OneWay
データソースの変更がバインディングターゲットに反映されますが、その逆はありません。
TwoWay
データソースとバインディングターゲットの間で双方向にデータが反映されます。ユーザーの入力をデータソースに反映したい場合に使用します。
OneTime
初期化時にソースの値をターゲットへ反映し、通常のプロパティ変更は追跡しません。DataContextが変更された場合には再評価されます。仕様はBindingModeのリファレンスを参照してください。
OneWayToSource
バインディングターゲットの変更がデータソースに反映されますが、その逆はありません。
データコンテキストの設定
{Binding Name}のようにソースを明示しないバインディングは、その要素のDataContextを参照します。DataContextは通常、親要素から継承されます。一方、Source、ElementName、RelativeSourceを指定した場合は、その指定先がソースです。
コードビハインドでのデータコンテキスト設定
以下はMainWindow.xaml.cs全体の例です。表示用のXAMLには<TextBlock Text="{Binding Name}"/>を配置します。このPersonは初期表示だけのモデルです。表示後の変更通知は後のMVVM節で実装します。
using System.Windows;
namespace WpfApp;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new Person { Name = "John Doe" };
}
}
public class Person
{
public string Name { get; set; } = string.Empty;
}XAMLでのデータコンテキスト設定
コードビハインドでの設定に代えて、XAMLでも設定できます。次の例ではPersonを同じWpfApp名前空間のpublicクラスとして残し、コンストラクターのDataContext = ...行を削除してください。両方で設定すると、後から代入した値が使われます。
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp"
Title="MainWindow" Height="200" Width="400">
<Window.DataContext>
<local:Person Name="Jane Doe"/>
</Window.DataContext>
<Grid>
<TextBlock Text="{Binding Name}" Width="200" Height="30" Margin="10"/>
</Grid>
</Window>データバインディングの基礎を理解することで、WPFアプリケーションのデータ管理がより効率的に行えるようになります。次は、イベントハンドリングについて学びましょう。
イベントハンドリング
WPFアプリケーションでは、ユーザーインターフェイスの操作に対する応答としてイベントを処理することが重要です。ここでは、イベントハンドリングの基本について説明します。
イベントとは
イベントは、ユーザー操作(クリック、キー入力など)やシステム操作(ロード、リサイズなど)に対する応答として発生します。WPFでは、イベントをハンドラーに関連付けて処理を行います。
イベントハンドラーの作成
イベントハンドラーは、特定のイベントが発生したときに実行されるメソッドです。以下は、ボタンのクリックイベントを処理する例です:
XAMLでイベントハンドラーを定義する
XAML内でイベントハンドラーを定義し、対応するメソッドをコードビハインドに記述します。
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="200" Width="400">
<Grid>
<Button Content="Click Me" Width="100" Height="50" VerticalAlignment="Top" HorizontalAlignment="Left" Click="Button_Click"/>
</Grid>
</Window>コードビハインドでイベントハンドラーを実装する
次に、対応するコードビハインドファイルにイベントハンドラーを実装します:
using System.Windows;
namespace WpfApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Button clicked!");
}
}
}この例では、Button_Clickというメソッドがボタンのクリックイベントに関連付けられており、ボタンがクリックされるとメッセージボックスが表示されます。
他の一般的なイベント
WPFでは、多くのコントロールに対してさまざまなイベントをハンドリングすることができます。いくつかの例を挙げます:
TextBoxのTextChangedイベント
TextChangedはテキストが変化したときに発生します。ユーザー入力だけでなく、プログラムによる代入や初期化時にも発生し得るため、UIの初期化状態に注意します。ハンドラーはMainWindowクラス内に置き、TextChangedEventArgsにはSystem.Windows.Controls名前空間を使用します。
<TextBox Width="200" Height="30" Margin="10" TextChanged="TextBox_TextChanged"/>private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
// 文字が変更されたときの処理
}WindowのLoadedイベント
Loadedは要素ツリーが表示先へ接続され、レイアウト計算などが済んだ段階で発生します。描画完了や、非同期データ取得の完了を保証するイベントではありません。要素のライフタイムイベントも参照してください。
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="200" Width="400" Loaded="Window_Loaded">
<Grid>
<!-- UI要素 -->
</Grid>
</Window>private void Window_Loaded(object sender, RoutedEventArgs e)
{
// ウィンドウがロードされたときの処理
}イベントハンドリングのベストプラクティス
- シンプルなハンドラー:イベントハンドラーはシンプルに保ち、必要ならば他のメソッドを呼び出して処理を分割します。
- エラーハンドリング:ハンドラー内で発生する可能性のあるエラーを適切に処理します。
- デバッグ:デバッグツールを使用してイベントの流れを確認し、期待どおりに動作しているかをチェックします。
イベントハンドリングの基本を理解することで、ユーザーインターフェイスの操作に対して適切な応答を実装することができます。次は、スタイルとテンプレートについて学びます。
スタイルとテンプレート
WPFでは、スタイルとテンプレートを使用してUIの外観や動作を一貫性を持ってカスタマイズすることができます。これにより、コードの再利用性が向上し、デザインの一貫性が保たれます。
スタイルの基本
スタイルは、複数のUI要素に共通の外観や動作を定義するために使用します。スタイルを適用することで、同じ設定を繰り返し記述する手間を省くことができます。
スタイルの定義
以下は、ボタンのスタイルを定義する例です:
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="200" Width="400">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Background" Value="LightBlue"/>
<Setter Property="Foreground" Value="Navy"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Margin" Value="10"/>
</Style>
</Window.Resources>
<StackPanel Margin="8">
<Button Content="Button 1"/>
<Button Content="Button 2"/>
<Button Content="Button 3"/>
</StackPanel>
</Window>この例では、<Window.Resources>内にスタイルを定義し、すべてのボタンに適用しています。これにより、ボタンの背景色、前景色、フォントの太さ、マージンが統一されます。
コントロールテンプレートの基本
コントロールテンプレートは、コントロールの外観全体を再定義するために使用されます。これにより、独自のデザインをコントロールに適用することができます。
コントロールテンプレートの定義
以下は、ボタンのコントロールテンプレートを定義する例です:
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="200" Width="400">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}" BorderBrush="DarkBlue" BorderThickness="2" CornerRadius="10">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel Margin="8">
<Button Content="Custom Button 1" Background="LightBlue" Margin="4" Height="40"/>
<Button Content="Custom Button 2" Background="LightGreen" Margin="4" Height="40"/>
</StackPanel>
</Window>この例では、ボタンのテンプレートをカスタマイズし、角丸の境界線と中央に配置されたコンテンツを持つ独自のデザインを作成しています。これは構造を学ぶための最小例で、標準テンプレートの押下・マウスオーバー・無効状態の見た目を再現していません。実用画面では既存テンプレートのコピーを出発点にし、キーボードフォーカスを含めて各状態の識別性を確認してください。公式のテンプレート作成手順が参考になります。
スタイルとテンプレートの適用範囲
- グローバルスタイル:アプリケーション全体で使用されるスタイルやテンプレートは、
App.xamlに定義します。 - ローカルスタイル:特定のウィンドウやページでのみ使用されるスタイルやテンプレートは、該当するXAMLファイルに定義します。
リソースの再利用
リソースディクショナリを使用して、スタイルやテンプレートを再利用可能なリソースとして管理できます。次はApp.xamlのApplication要素内に置く断片です。プロジェクト直下にリソースディクショナリStyles.xamlを追加し、その中のResourceDictionary要素にStyleを定義してから参照します。ファイルが存在しなければ読み込みエラーになります。
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Styles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>スタイルとテンプレートを適切に活用することで、WPFアプリケーションのデザインとユーザー体験を大幅に向上させることができます。次は、MVVMパターンの導入について学びます。
MVVMパターンの導入
WPFアプリケーションの開発において、MVVM(Model-View-ViewModel)パターンは非常に重要です。このデザインパターンを導入することで、コードの再利用性とテストのしやすさが向上します。
MVVMパターンとは
MVVMパターンは、以下の三つのコンポーネントから構成されます:
- Model:データとビジネスロジックを定義する部分
- View:ユーザーインターフェイスを定義する部分(XAMLファイル)
- ViewModel:ViewとModelの仲介役として、データバインディングやコマンドを提供する部分
MVVMの基本構造
ここでは、名前の追加・削除ができる小さなアプリを作ります。新しいWpfAppプロジェクトで、次の3つのC#ファイルをプロジェクト直下へ追加し、MainWindowの2ファイルを置き換えてください。すべての名前空間をWpfAppに統一します。App.xamlとApp.xaml.csはテンプレートのまま使います。実務ではModelsやViewModelsへ分けられますが、その場合は名前空間とXAMLの対応も変更します。
Modelの実装
Person.csです。個々の名前・年齢の変更も表示へ通知できるよう、INotifyPropertyChangedを実装します。
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApp;
public class Person : INotifyPropertyChanged
{
private string _name = string.Empty;
private int _age;
public string Name
{
get => _name;
set
{
if (_name == value) return;
_name = value;
OnPropertyChanged();
}
}
public int Age
{
get => _age;
set
{
if (_age == value) return;
_age = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? name = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}ViewModelの実装
MainViewModel.csです。ObservableCollection<Person>は要素の追加・削除を通知し、Personは要素内部の変更を通知します。People自体は差し替えないためgetのみです。NewNameの変更時には、表示用の通知と「追加できるか」の再判定通知を別々に送ります。RelayCommandは後述のファイルを同じプロジェクトへ追加します。
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace WpfApp;
public class MainViewModel : INotifyPropertyChanged
{
private string _newName = string.Empty;
public ObservableCollection<Person> People { get; } = new()
{
new Person { Name = "John Doe", Age = 30 },
new Person { Name = "Jane Doe", Age = 25 }
};
public string NewName
{
get => _newName;
set
{
if (_newName == value) return;
_newName = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NewName)));
AddPersonCommand.RaiseCanExecuteChanged();
}
}
public RelayCommand AddPersonCommand { get; }
public RelayCommand DeletePersonCommand { get; }
public MainViewModel()
{
AddPersonCommand = new RelayCommand(
_ =>
{
People.Add(new Person { Name = NewName.Trim(), Age = 20 });
NewName = string.Empty;
},
_ => !string.IsNullOrWhiteSpace(NewName));
DeletePersonCommand = new RelayCommand(
parameter =>
{
if (parameter is Person person) People.Remove(person);
},
parameter => parameter is Person);
}
public event PropertyChangedEventHandler? PropertyChanged;
}Viewの実装
MainWindow.xamlです。入力を1文字ずつNewNameへ反映するため、UpdateSourceTrigger=PropertyChangedを明示します。TextBox.Textの既定のソース更新タイミングはLostFocusです。Modeは更新の方向、UpdateSourceTriggerは更新のタイミングを指定するため、両者を区別します。詳しくはデータバインディングの公式解説を参照してください。
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp"
Title="MVVMの人名リスト" Height="360" Width="480">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<DockPanel Margin="16">
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,12">
<Label Content="追加する名前" Target="{Binding ElementName=nameInput}"/>
<TextBox x:Name="nameInput"
Text="{Binding NewName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="追加" Command="{Binding AddPersonCommand}"
HorizontalAlignment="Left" Margin="0,8,0,0" Padding="16,4"/>
</StackPanel>
<ListBox ItemsSource="{Binding People}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="{Binding Name}" Width="180"/>
<TextBlock Text="{Binding Age}" Width="40"/>
<Button Content="削除" Padding="8,2"
Command="{Binding DataContext.DeletePersonCommand,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</Window>MainWindow.xaml.csにはUIを初期化する処理だけを置きます。XAML側でDataContextを生成しているので、ここで再設定しません。
using System.Windows;
namespace WpfApp;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}ListBoxの各DataTemplateではDataContextが1件のPersonになります。「削除」のCommandはRelativeSourceでWindowのViewModelへ戻り、CommandParameter="{Binding}"でその行のPersonを渡します。親のコマンドと行データを同じバインディングパスで探さないことがポイントです。
コマンドの実装
RelayCommand.csです。ICommandのExecuteは処理、CanExecuteは実行可能条件、CanExecuteChangedは条件を再評価してもらう通知です。ここではパラメーターを受け取れる最小実装にしています。WPFのコマンド概要も参照してください。
using System;
using System.Windows.Input;
namespace WpfApp;
public class RelayCommand : ICommand
{
private readonly Action<object?> _execute;
private readonly Predicate<object?>? _canExecute;
public RelayCommand(Action<object?> execute, Predicate<object?>? canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object? parameter) => _canExecute?.Invoke(parameter) ?? true;
public void Execute(object? parameter)
{
if (CanExecute(parameter)) _execute(parameter);
}
public event EventHandler? CanExecuteChanged;
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}確認する動作は、空白だけの名前では追加ボタンが無効になること、入力すると有効になること、追加後に入力が空に戻ること、各行の削除ボタンがそのPersonだけを削除することです。Person.NameやAgeをコードから変更した場合も通知されます。この例はメモリ上のリストであり、終了すると追加内容は失われます。保存は次のデータベースの節で扱います。
このようにして、MVVMパターンを導入することで、WPFアプリケーションの構造を整理し、保守性と拡張性を向上させることができます。次は、デバッグとトラブルシューティングについて学びます。
デバッグとトラブルシューティング
WPFアプリケーションの開発中に発生する問題を効率的に解決するためには、デバッグとトラブルシューティングの技術が重要です。ここでは、基本的なデバッグ方法と一般的な問題の解決策を紹介します。
Visual Studioを使用したデバッグ
Visual Studioには、強力なデバッグツールが搭載されています。以下の機能を使用して、コードの問題を見つけて修正する方法を学びます。
ブレークポイントの設定
ブレークポイントは、コードの実行を一時停止し、現在の状態を調査するためのポイントです。以下の手順で設定します:
- デバッグ対象のコード行を右クリックします。
- 「ブレークポイントの設定」を選択します。
変数のウォッチ
変数の値を監視するために、ウォッチウィンドウを使用します。ブレークポイントで停止した後、変数を右クリックし、「ウォッチに追加」を選択します。
ステップ実行
コードを一行ずつ実行して問題を特定するために、F10キー(ステップオーバー)やF11キー(ステップイン)を使用します。
一般的なトラブルシューティング
WPFアプリケーションでよく発生する問題とその解決策をいくつか紹介します。
バインディングエラー
データバインディングが正しく機能しない場合、以下の方法で原因を特定します:
- Visual Studioの出力ウィンドウを確認し、バインディングエラーのメッセージを探します。
- バインディングパスやデータコンテキストが正しいか確認します。
UIが更新されない
UIがデータの変更に反応しない場合は、参照しているオブジェクト、Mode、変更通知を確認します。以下は、PropertyChangedイベントとOnPropertyChangedメソッドを実装済みのクラス内に置くプロパティの断片です。完全な実装は前節のPersonを参照してください。ObservableCollectionが通知するのはコレクションの変更であり、中のPersonの変更通知を代わりに発行するわけではありません。
private string _name = string.Empty;
public string Name
{
get { return _name; }
set
{
if (_name == value) return;
_name = value;
OnPropertyChanged(nameof(Name));
}
}パフォーマンスの問題
WPFアプリケーションのパフォーマンスが低下する場合、以下の対策を試します:
- 非同期処理:I/Oは対応する非同期APIをawaitし、CPU負荷の高い処理や同期APIしかない処理は必要に応じてTask.Runへ分離します。asyncを付けるだけでは別スレッドになりません。UIスレッドでTask.ResultやWait()を呼ばず、UI要素やバインド中のコレクションの更新はUIスレッドへ戻して行います。詳しくはWPFのスレッドモデルを参照してください。
- バーチャライゼーション:大量のデータを表示する場合、バーチャライゼーションを使用して必要な部分だけをレンダリングします。
プロファイリングと診断ツール
Visual Studioには、プロファイリングと診断ツールが含まれており、パフォーマンス問題を特定するのに役立ちます。
パフォーマンスプロファイラー
パフォーマンスプロファイラーを使用して、アプリケーションのパフォーマンスを分析します:
- メニューから「デバッグ」 > 「パフォーマンスプロファイラー」を選択します。
- 「CPU使用率」や「メモリ使用量」などの項目を選択してプロファイルを開始します。
診断ツールの使用
診断ツールは、アプリケーションの動作中にリアルタイムでパフォーマンスデータを収集します:
- デバッグ中に「診断ツール」ウィンドウを開きます。
- イベントタイムラインやメモリ使用量を監視します。
これらのデバッグとトラブルシューティングの技術を活用することで、WPFアプリケーションの問題を効率的に解決し、品質の高いソフトウェアを開発することができます。次は、応用例としてデータベース接続について学びます。
応用例: データベース接続
WPFアプリケーションでは、データベースと連携してデータの取得や保存を行うことが多くあります。ここではSQLiteから人名リストを読み込む独立した例を作ります。前節のメモリ上のMVVM例とは別のWpfAppプロジェクトで試してください。
データベースの準備
SQLiteはファイルで扱える軽量なデータベースです。この例の保存先は、実行ユーザーのローカルアプリデータフォルダー配下のWpfApp/people.sqliteです。作業ディレクトリに依存する相対パスや、書き込み権限がない可能性のあるアプリのインストール先は使いません。
SQLiteデータベースの作成
テーブルと初期データの内容は次のとおりです。下のC#コードが作成処理も行うため、このSQLを別のツールで実行する必要はありません。
CREATE TABLE People (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL,
Age INTEGER NOT NULL
);
INSERT INTO People (Name, Age) VALUES ('John Doe', 30);
INSERT INTO People (Name, Age) VALUES ('Jane Doe', 25);サンプル行はデータベースファイルの新規作成時だけ追加します。既存ファイルでは再挿入しません。これは単一アプリで学習するための簡単な初期化で、実用アプリのスキーマ移行や複数プロセスによる同時初期化を扱うものではありません。
プロジェクトにSQLiteライブラリを追加
この.NET 10向けの例では、Microsoftが提供するMicrosoft.Data.Sqliteを使用します。System.Data.SQLiteとは別のプロバイダーです。パッケージ名・using・クラス名・接続文字列のオプションを混在させないでください。
NuGetパッケージのインストール
- ソリューションエクスプローラーでプロジェクトを右クリックし、「NuGetパッケージの管理」を選択します。
- 「参照」タブで「Microsoft.Data.Sqlite」を検索し、対象の.NETに対応する安定版を選びます。この記事の.NET 10プロジェクトでは10.0系の更新版を使用します(確認時点の安定版は10.0.13)。
データベース接続の実装
Modelの定義
Person.csです。SQLiteの整数主キーに合わせてIdはlongにします。この節では読み込み後の表示だけを行うため、通常のプロパティにしています。既存行を編集してその場で表示更新する場合は、前節と同様に変更通知が必要です。
namespace WpfApp;
public class Person
{
public long Id { get; set; }
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
}ViewModelの実装
MainViewModel.csです。接続・コマンド・リーダーはusingで解放し、列名を明示して読み込みます。Microsoft.Data.Sqliteの非同期APIは同期的に実行されるため、この小さな例ではデータベース処理をTask.Runで分離します。接続オブジェクトはその処理内で生成し、スレッド間で共有しません。
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Data.Sqlite;
namespace WpfApp;
public class MainViewModel
{
public ObservableCollection<Person> People { get; } = new();
// UIスレッドから呼び出す。コンストラクターではDBへ接続しない。
public async Task LoadAsync()
{
List<Person> rows = await Task.Run(ReadPeople);
// WPFの同期コンテキストへ戻ってからバインド中のコレクションを更新。
People.Clear();
foreach (Person person in rows) People.Add(person);
}
private static List<Person> ReadPeople()
{
string directory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"WpfApp");
Directory.CreateDirectory(directory);
string path = Path.Combine(directory, "people.sqlite");
bool isNewFile = !File.Exists(path);
string connectionString = new SqliteConnectionStringBuilder
{
DataSource = path
}.ToString();
using var connection = new SqliteConnection(connectionString);
connection.Open();
using (var initialize = connection.CreateCommand())
{
initialize.CommandText = """
CREATE TABLE IF NOT EXISTS People (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL,
Age INTEGER NOT NULL
);
""";
initialize.ExecuteNonQuery();
if (isNewFile)
{
initialize.CommandText = """
INSERT INTO People (Name, Age) VALUES ('John Doe', 30);
INSERT INTO People (Name, Age) VALUES ('Jane Doe', 25);
""";
initialize.ExecuteNonQuery();
}
}
using var command = connection.CreateCommand();
command.CommandText = "SELECT Id, Name, Age FROM People ORDER BY Id;";
using var reader = command.ExecuteReader();
var result = new List<Person>();
while (reader.Read())
{
result.Add(new Person
{
Id = reader.GetInt64(0),
Name = reader.GetString(1),
Age = reader.GetInt32(2)
});
}
return result;
}
}ReadPeopleは通常のListを返すだけで、UIやPeopleには触れません。UIスレッドからLoadAsyncを呼び、await後にPeopleを更新します。接続文字列はSqliteConnectionStringBuilderで組み立てています。実用規模では読み込み処理を別サービスへ分け、キャンセル、再試行、ページ分割などを検討します。
Viewの実装
MainWindow.xamlです。今回はDataContextの設定をコードビハインド側にまとめ、Loadedイベントから初回の読み込みを開始します。
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SQLiteの人名リスト" Height="350" Width="480"
Loaded="Window_Loaded">
<Grid Margin="16">
<ListBox ItemsSource="{Binding People}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,4">
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
<TextBlock Text="{Binding Age}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>MainWindow.xaml.csです。async voidはイベントハンドラーに限って使い、通常の非同期メソッドはTaskを返してawaitします。ファイルやSQLiteのエラーはハンドラーで捕捉します。
using System;
using System.IO;
using System.Windows;
using Microsoft.Data.Sqlite;
namespace WpfApp;
public partial class MainWindow : Window
{
private readonly MainViewModel _viewModel = new();
public MainWindow()
{
InitializeComponent();
DataContext = _viewModel;
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= Window_Loaded; // 初回だけ読み込む。
try
{
await _viewModel.LoadAsync();
}
catch (Exception ex) when (ex is SqliteException or IOException or UnauthorizedAccessException)
{
MessageBox.Show(this, $"データを読み込めませんでした。\n{ex.Message}",
"読み込みエラー", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}初回実行で2件の名前・年齢が表示され、再起動しても初期データが増えないことを確認してください。ファイル作成に失敗した場合は、メッセージと保存先の権限を確認します。SQLite接続・コマンド・リーダーの同時共有は避けてください。詳しくはMicrosoft.Data.Sqliteのエラーとスレッド安全性を参照してください。
まとめ
ここで実装したのは、初期化と読み込みです。SQL Serverなどでも「データを取得してViewModelへ渡す」という構成は利用できますが、プロバイダー・接続文字列・SQL構文・非同期APIの特性は同一ではありません。次の演習では、この読み込み例を追加・削除・更新へ拡張します。
演習問題
これまで学んだ内容を実践するために、いくつかの演習問題を提供します。直前のSQLite例を拡張してください。すべてのSQLでユーザー入力を文字列連結せず、パラメーターへ渡します。例えばWHERE Id = $idとcommand.Parameters.AddWithValue("$id", person.Id)を組み合わせます。詳細はSQLパラメーターの公式解説を参照してください。
演習1: 新しいデータの追加機能の実装
SQLiteデータベースに新しいレコードを追加する機能を実装してください。以下の手順を参考にしてください:
- 新しいテキストボックスとボタンをUIに追加し、ユーザーが名前と年齢を入力できるようにします。
- ボタンをクリックすると、入力されたデータをデータベースに追加し、
Peopleコレクションを更新するコードをViewModelに実装します。
ヒント
- 空白名を拒否し、年齢をint.TryParseなどで検証します。
INSERT INTO People (Name, Age) VALUES ($name, $age)を使い、値はパラメーターへ設定します。 - 保存成功後にLoadAsyncで再読み込みすれば、データベースで採番されたIdも取得できます。失敗時に先にUIへ追加しないでください。
演習2: データの削除機能の実装
既存のレコードを削除する機能を実装してください。以下の手順を参考にしてください:
- 各リストアイテムの横に削除ボタンを追加します。
- 削除ボタンをクリックすると、選択されたレコードをデータベースから削除し、
Peopleコレクションを更新するコードをViewModelに実装します。
ヒント
DELETE FROM People WHERE Id = $idのように主キーで対象を限定します。CommandParameterで行のPersonを渡す方法は前のMVVM例を参考にしますが、メモリ上の削除だけではDBは変更されません。- データベースから削除後、
Peopleコレクションから該当するアイテムを削除してUIに反映させます。
演習3: データの更新機能の実装
既存のレコードを更新する機能を実装してください。以下の手順を参考にしてください:
- リストアイテムをクリックすると、そのレコードの詳細を編集できるUIを表示します。
- 編集内容を保存するボタンを追加し、クリックするとデータベースと
Peopleコレクションを更新するコードをViewModelに実装します。
ヒント
UPDATE People SET Name = $name, Age = $age WHERE Id = $idのように主キーで対象を限定し、更新件数を確認します。- 成功後にLoadAsyncで再読み込みするか、INotifyPropertyChangedを実装したPersonを更新します。ObservableCollectionだけではNameやAgeの変更は通知されません。
演習4: 検索機能の実装
リスト内のデータを検索する機能を実装してください。以下の手順を参考にしてください:
- 検索用のテキストボックスを追加し、ユーザーが名前で検索できるようにします。
- テキストボックスに入力されたキーワードに基づいて
Peopleコレクションをフィルタリングし、UIに表示するコードをViewModelに実装します。
ヒント
WHERE Name LIKE $patternとパラメーターを使います。部分一致なら値を"%" + keyword + "%"とし、SQL本文へ連結しません。LIKEの%や_はワイルドカードなので、文字どおりに探す場合は別途エスケープを設計します。- 結果はUIスレッドでPeopleへ反映します。入力のたびに検索する場合は短い待機時間を設け、古い検索結果が新しい結果を上書きしないようにします。少量の読み込み済みデータならICollectionViewによる画面上のフィルターも選択肢です。
まとめ
これらの演習を通じて、WPFアプリケーションのさまざまな機能を実装する方法を学びます。各演習は、実際のアプリケーション開発に必要なスキルを磨くための実践的な課題です。取り組む中で発見した問題や疑問点を解決しながら、WPFとデータベース連携の技術を深めてください。
まとめ
WPFアプリケーション開発の基礎から応用までを学びました。以下に、この記事で学んだ重要なポイントを振り返ります。
WPFの基本概念
WPF(Windows Presentation Foundation)は、リッチなユーザーインターフェイスを作成するための強力なフレームワークです。XAMLを使用してUIを宣言的に定義し、C#などのコードビハインドで動作を実装します。
開発環境のセットアップ
Visual Studioを使用してWPFプロジェクトを作成し、基本的なUI要素を配置する方法を学びました。開発環境の適切な設定が、効率的なアプリケーション開発の基盤となります。
データバインディングとイベントハンドリング
データバインディングを使用してデータとUI要素を連携させ、イベントハンドリングを通じてユーザーインタラクションを処理する方法を学びました。これにより、動的で反応性の高いアプリケーションを構築できます。
スタイルとテンプレート
スタイルとテンプレートを利用してUIの外観を一貫してカスタマイズする方法を学びました。これにより、再利用可能でメンテナンスしやすいコードを書くことができます。
MVVMパターンの導入
MVVM(Model-View-ViewModel)パターンを導入することで、アプリケーションの構造を整理し、テストやメンテナンスを容易にする方法を学びました。これにより、コードの再利用性と拡張性が向上します。
デバッグとトラブルシューティング
Visual Studioのデバッグツールを使用して、アプリケーションの問題を効率的に解決する方法を学びました。ブレークポイントの設定やステップ実行、変数のウォッチなどの技術を活用して、コードの問題を特定し修正することができます。
データベース接続
Microsoft.Data.Sqliteでファイルとテーブルを初期化し、UIスレッドをブロックせず一覧へ読み込む例を示しました。追加・削除・更新は演習で拡張し、パラメーター、主キー、変更通知、例外処理を確認します。
演習問題
学んだ内容を実践するための演習問題に取り組むことで、理解を深めることができます。これらの演習を通じて、実際のアプリケーション開発に必要なスキルを磨いてください。
以上の内容を通じて、WPFアプリケーションの開発に必要な基礎知識と応用技術を習得しました。これからも継続して学び、より高度なアプリケーションを開発できるよう努力してください。



コメント