MS_XAMLWriting2 - NetDevInfraWGinOSSConsortium/NetDevInfraWiki GitHub Wiki
- 戻る(XAML)
- XAMLの書き方(1)
- XAMLの書き方(2)
XAMLの書き方(1)の続き。
補足(本ページの位置付け): XAMLの書き方(1) が
XAML の言語仕様と描画の仕組み(名前空間・構文・リソース・バインディング・
レイアウト・スタイル・テンプレート・トリガ)を扱ったのに対し、
本ページはアプリケーションを組み立てる側の話題を扱う。【本ページの構成】 ① ビルディング ブロック クラス Application / Window / ナビゲーション / Win32 ダイアログ → 【アプリの骨格】をどう作るか ② 入力支援 メニュー・コマンド / ツールチップ / IME 制御 → 【業務アプリで必ず要る】UI 部品 ③ デザイナ向け機能 シェイプ / グラデーション / トランスフォーム / アニメーション → 【WPF ならでは】のベクタ描画 ④ MVVM デザイン パターン ⑤ バリデーション
WPF / Silverlight は、Windows Forms とは
全く異なる UI サブシステムであるが、
類似のビルティング ブロック クラスが使用されているため、
よく似たコードでプログラムを記述することができる。
WPF / Silverlight には Windows フォームと同様に
Application オブジェクトが存在する。
- 参考
- Microsoft Learn > .NET API ブラウザー
-
WPF では、Application オブジェクトの StartupUri 属性に
起動時に呼び出される XAML を指定する。 - Application オブジェクトはメッセージ ループを構築し、
アプリケーションが終了するまでの間、ユーザにイベントを問い合わせる。 - なお、この処理は、起動時に呼び出される XAML のパーシャル クラスに
自動生成されるので通常は確認できない。
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml">
<Application.Resources></Application.Resources>
</Application>-
なお、Silverlight では、Application オブジェクトの
StartupUri 属性がサポートされないので、
後述の Application.Startup イベントから、初期画面を起動する。 -
参考
- Microsoft Learn > .NET API ブラウザー
- Application.StartupUri プロパティ
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.application.startupuri
- Application.StartupUri プロパティ
- Microsoft Learn > .NET API ブラウザー
補足(
StartupUriを捨てる構成が主流になった): 原文の記述は今も有効だが、
実務では別の書き方が定着している。【StartupUri 方式の限界】 ・起動画面を【XAML に文字列で書く】ため、 条件分岐(ログイン画面 → メイン画面)ができない ・【DI コンテナ】から Window を解決できない ・起動前の初期化(設定読込・接続確認)を挟みにくい 【現在の定番】★ App.xaml から StartupUri を【削除】し、 OnStartup をオーバーライドする protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var w = _serviceProvider.GetRequiredService<MainWindow>(); w.Show(); } → .NET 6 以降は【Generic Host】 (Microsoft.Extensions.Hosting)と組み合わせ、 設定・ログ・DI を ASP.NET Core と同じ形で扱うのが一般的【ShutdownMode も併せて押さえる】★ 既定は OnLastWindowClose(最後のウィンドウを閉じたら終了) ・スプラッシュ画面 → メイン画面 の構成では 【OnExplicitShutdown】にしないと スプラッシュを閉じた時点でアプリが落ちる
Application オブジェクトには様々なイベント処理を実装することができる。
-
開始イベント
WPF / Silverlight での共通初期化処理を実装する場合、
Application.Startup イベントを使用する。 -
共通エラー ハンドリング イベント
WPF / Silverlight での共通エラー ハンドリング処理を実装する場合、
Application.DispatcherUnhandledException イベントで例外をハンドルする
(Open 棟梁の例外処理方式(OTR_ExceptionHandling.md)を参照)。 -
終了イベント
WPF / Silverlight での共通終了処理を実装する場合、
Application.Exit イベントを使用する。 -
イベントの実装サンプル
以下、それぞれのイベントの実装サンプルである。- XAML
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml"
Startup="Application_Startup" Exit="Application_Exit"
DispatcherUnhandledException="Application_DispatcherUnhandledException">
<Application.Resources></Application.Resources>
</Application>- コード ビハインド
private void Application_Startup(object sender, StartupEventArgs e) {
System.Diagnostics.Debug.WriteLine("開始処理");
}
private void Application_DispatcherUnhandledException(
object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) {
System.Diagnostics.Debug.WriteLine("エラー処理");
}
private void Application_Exit(object sender, ExitEventArgs e) {
System.Diagnostics.Debug.WriteLine("終了処理");
}- 参考
- Microsoft Learn > .NET API ブラウザー > Application
- Startup イベント
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.application.startup - DispatcherUnhandledException イベント
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.application.dispatcherunhandledexception - Exit イベント
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.application.exit
- Startup イベント
- Microsoft Learn > .NET API ブラウザー > Application
補足(
DispatcherUnhandledExceptionだけでは足りない): 共通エラー
ハンドリングとしてこのイベントを挙げるのは正しいが、捕まえられる範囲が限定的である。【WPF で仕掛けるべき「最後の砦」は 3 つ】★ ① Application.DispatcherUnhandledException → 【UI スレッド上】の未処理例外 → e.Handled = true で【アプリを落とさず継続できる】 ② AppDomain.CurrentDomain.UnhandledException → 【UI スレッド以外】(Thread, Timer 等)の未処理例外 → 【継続はできない】(ログを残して落ちるのみ) ③ TaskScheduler.UnobservedTaskException → 【await されなかった Task】の中の例外 → GC 時に発火するため、【遅れて】来る【async void の落とし穴】★ async void なイベント ハンドラの中の例外は ① で捕まる(SynchronizationContext 経由で再送出されるため) ただし【async void なメソッドを自分で呼んだ】場合は どこにも捕まらず【プロセスが落ちる】ことがある → イベント ハンドラ以外で async void を書かない ★
-
WPF では ASP.NET などの Web アプリケーションと同様の Session 変数ライクな
Key - Value ストアとして、Application.Properties プロパティを利用可能である。 -
以下のサンプルで、任意の個所で(画面間を跨って)
Application.Properties プロパティが取得できることを確認できる。- Application.xaml.cs
- コード ビハインド
- Application.xaml.cs
private void Application_Startup(object sender, StartupEventArgs e) {
Application.Current.Properties["Key"] = "Value1";
}- Window1.xaml / Window1.xaml.cs
- XAML
<Window x:Class="WpfApplication1.Window1"
・・・
Loaded="Window_Loaded">
<Grid>
<Button Click="Button_Click">ボタン</Button>
</Grid>- コード ビハインド
private void Window_Loaded(object sender, RoutedEventArgs e) {
MessageBox.Show((string)Application.Current.Properties["Key"]);
Application.Current.Properties["Key"] = "Value2";
}
private void Button_Click(object sender, RoutedEventArgs e) {
Window2 w2 = new Window2();
w2.Show();
}- Window2.xaml.cs
- コード ビハインド
private void Window_Loaded(object sender, RoutedEventArgs e) {
MessageBox.Show((string)Application.Current.Properties["Key"]);
}- 参考
- Microsoft Learn > .NET API ブラウザー
- Application.Properties プロパティ
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.application.properties
- Application.Properties プロパティ
- Microsoft Learn > .NET API ブラウザー
移行メモ(見出しの誤り): 移行元では、Application.xaml.cs と
Window1.xaml.cs の**コード ビハインドの見出しがいずれも「XAML」**と
なっていたため、内容に合わせて修正した
(Window1 側は XAML とコード ビハインドの 2 つに分けた)。
補足(
Application.Propertiesは「使わない」が定石): 動作としては
原文の通りだが、現在は推奨されない。【問題点】 ① 【型が object】 → 毎回キャストが要る ② 【キーが文字列】 → 打ち間違いがコンパイルで検出されない ③ 【グローバル変数そのもの】 → どこから書かれたか追えない → 単体テストで差し替えられない ④ Session 変数の比喩は誤解を招く → Web の Session は【ユーザ単位】だが、 これは【プロセス単位】の単なる静的辞書 ★【代替】★ ・アプリ設定の永続化 → Properties.Settings / appsettings.json ・画面間の値の受け渡し → コンストラクタ引数 / メッセンジャ ・アプリ全体で共有する状態 → 【DI で注入するシングルトン サービス】 (型安全・差し替え可能・テスト可能)
- 参考
- Microsoft Learn > .NET API ブラウザー
WPF では Windows Forms と同様に Window 画面を、
以下のように起動できる(「XBAP」を除く)。
- モードレス(通常画面)の起動
モードレス(通常画面)として起動。
Window2 win2 = new Window2();
win2.Show();- モーダル ダイアログの起動
モーダル ダイアログとして起動。
Window2 win2 = new Window2();
win2.Owner = this;
win2.ShowDialog();補足(
Ownerの設定は「作法」ではなく必須に近い): 上の 2 つの違いは
ShowDialog()だけではなく、Ownerの有無も重要である。【Owner を設定すると起きること】★ ・親の【上に必ず表示される】(裏に回り込まない) ・親を最小化すると【一緒に最小化される】 ・親を閉じると【一緒に閉じる】 ・WindowStartupLocation="CenterOwner" が効くようになる 【設定しないと】 ・タスクバーに別項目として並ぶ ・Alt+Tab で親の裏に隠れ、【操作不能に見える】 → 「画面が固まった」という問い合わせの典型原因 ★【ShowDialog の戻り値】 bool? (true / false / null) ・子側で this.DialogResult = true; とすると 【その時点でウィンドウが閉じる】★ ・× ボタンで閉じた場合は null var dlg = new Window2 { Owner = this }; if (dlg.ShowDialog() == true) { /* OK された */ }
Window には、以下のようなプロパティを設定できる。
| # | プロパティ | 説明 |
|---|---|---|
| 1 | Title | ウィンドウのタイトルを取得 / 設定する。 |
| 2 | WindowStartupLocation | ウィンドウ起動時の表示位置を取得 / 設定する。 |
| 3 | ResizeMode | ウィンドウのサイズ変更モードを取得 / 設定する(ResizeMode 列挙体)。 |
| 4 | WindowStyle | ウィンドウの境界線スタイルを取得 / 設定する。 |
| 5 | ShowInTaskbar | タスクバーのアイコンの表示・非表示を取得 / 設定する。 |
移行メモ(正誤・体裁): 移行元では
Window. WindowStartUpLocation、
Window. ResizeModeのようにクラス名とプロパティ名の間に空白が入り、
またWindowStartUpLocationの U が大文字(正しくはWindowStartupLocation)
となっていたため、修正した。
併せて、箇条書きを表に整理した。
- 参考
- Microsoft Learn > .NET API ブラウザー > Window
- Title プロパティ
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.window.title - WindowStartupLocation プロパティ
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.window.windowstartuplocation - ResizeMode プロパティ
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.window.resizemode - WindowStyle プロパティ
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.window.windowstyle - ShowInTaskbar プロパティ
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.window.showintaskbar
- Title プロパティ
- Microsoft Learn > .NET API ブラウザー > Window
補足(業務アプリで実際に触るのはこの辺り): 上表の 5 つに加え、
知らないと困るものを挙げておく。【組み合わせの定石】 ・【固定サイズのダイアログ】★ ResizeMode="NoResize" WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight" ← 中身に合わせて自動サイズ ・【枠なしのカスタム ウィンドウ】 WindowStyle="None" AllowsTransparency="True" → 自前でドラッグ移動・リサイズを実装する必要がある → AllowsTransparency="True" は 【描画がソフトウェア レンダリングに落ちる】ことがあり重い ★ ・【最大化サイズがおかしい】 WindowState="Maximized" だと タスクバーに被る / 画面外にはみ出すことがある → MaxHeight を SystemParameters から算出するか、 WindowChrome を使う【DPI(高解像度ディスプレイ)】★ .NET Framework 4.6.2 以降 / .NET Core 3.0 以降で 【Per-Monitor DPI 対応】が入った → app.manifest の dpiAwareness 設定が必要 → 未対応だと 4K ディスプレイで【ぼやける】
-
WPF / Silverlight では「ナビゲーション フレームワーク」により
ASP.NET などの Web アプリケーションと同様に、
WWW ブラウザの単一ウィンドウ、複数ページから構成される画面を提供できる。 -
この機能は、「ナビゲーション フレームワーク」の NavigationWindow / Page により提供され、
ウィザードや、ハイパー テキスト ソリューションに有用である。 -
なお、以下のような機能もサポートされている。
- WWW ブラウザ型の単一ウィンドウ、複数ページから構成される画面を提供
- WWW ブラウザ同様、画面遷移の履歴が残り [進む]・[戻る] 操作に対応
- ハイパーリンクによる画面遷移、HTTP コンテンツの参照をサポート
-
以下、NavigationWindow / Page の使用方法を説明する。
-
WPF の場合、初めに、NavigationWindow (を継承した)画面
(ここでは NavWindow クラス)を作成する。 -
なお、NavigationWindow は、Window クラスを継承するため、
前述のプロパティを設定可能である。 -
コード
- XAML
<NavigationWindow x:Class="WpfApplication1.NavWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="NavWindow" Height="300" Width="300">
</NavigationWindow>- コード ビハインド
public partial class NavWindow : NavigationWindow {
public NavWindow() {
InitializeComponent();
}
}- 参考
- Microsoft Learn > .NET API ブラウザー
WPF で NavigationWindow 画面を起動する方法について説明する。
- モードレス(通常画面)
NavigationWindow 画面をモードレス(通常画面)として起動できる。
なお、Application オブジェクトの StartupUri 属性から直接起動することもできる。
NavWindow normalWindow = new NavWindow();
normalWindow.Show();- モーダル ダイアログ
NavigationWindow 画面をモーダル ダイアログとして起動することもできる。
NavWindow dialogWindow = new NavWindow();
dialogWindow.Owner = this;
bool returnValue = dialogWindow.ShowDialog() ?? false;-
WPF で NavigationWindow 画面から、Page 画面をロードする方法について説明する。
-
なお、Page は FrameworkElement からの派生クラスであって、
NavigationWindow・Frame コントロールでのみホスト・ナビゲーションができる。 -
Page 画面をロードする方法
- Loaded イベントからロード
NavigationWindow 画面のコード ビハインドの Loaded イベントから Page 画面をロードする。- XAML
- Loaded イベントからロード
<NavigationWindow x:Class="WpfApplication1.NavWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="NavWindow" Height="300" Width="300" Loaded="NavigationWindow_Loaded">
</NavigationWindow>- コード ビハインド
public partial class NavWindow : NavigationWindow {
public NavWindow() {
InitializeComponent();
}
private void NavigationWindow_Loaded(object sender, RoutedEventArgs e) {
// Page1 を表示します。
this.Navigate(new Page1());
}
}- Source プロパティからロード
NavigationWindow 画面の Source プロパティに指定して Page 画面をロードすることもできる。- XAML
<NavigationWindow x:Class="WpfApplication1.NavWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="NavWindow" Height="300" Width="300" Source="Page1.xaml">
</NavigationWindow>- Frame コントロールからロード
Page 内を複数の Frame に分割し、その中に複数のページをロードすることもできる。- XAML
<StackPanel Orientation="Vertical">
<Frame Source="Page2.xaml" Navigated="Frame1_Navigated"/>
<Frame Source="Page3.xaml" Navigated="Frame2_Navigated"/>
</StackPanel>-
StartupUri 属性からロード
前述のとおり、Application オブジェクトの StartupUri 属性から
直接 Page 画面をロードすることもできる
(もしくは、前述の Application.Startup イベントから起動する)。 -
参考
- Microsoft Learn > .NET API ブラウザー
「XBAP」、「Silverlight」では、
NavigationWindow / Window が使用出来ないため、
以下の方法で、直接 Page 画面をロードする。
移行メモ(正誤): 移行元では「WindowNavigation / Window が使用出来ない」と
記載されていたが、正しくは NavigationWindow である(誤記と判断し修正)。
-
XBAP
Application オブジェクトの StartupUri 属性から直接 Page 画面をロードする。
<Application x:Class="WpfBrowserApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Page1.xaml">
<Application.Resources>
</Application.Resources>
</Application>-
Silverlight
Application オブジェクトの StartupUri 属性がサポートされないので、
Application.Startup イベントから Page 画面をロードする。- XAML
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SilverlightApplication1.App">
<Application.Resources>
</Application.Resources>
</Application>- コード ビハインド
public App(){
this.Startup += this.Application_Startup;
this.Exit += this.Application_Exit;
this.UnhandledException += this.Application_UnhandledException;
InitializeComponent();
}
private void Application_Startup(object sender, StartupEventArgs e) {
// this.RootVisual = new MainPage(); デフォルトは、ユーザコントロール
this.RootVisual = new Page1();
}なお、「Silverlight」では、
デフォルトの初期画面(Application.RootVisual)が、ユーザ コントロールとなっているため、
通常、ユーザ コントロールに Frame を追加し、Frame でページ遷移させる方法を取る。
WPF / Silverlight における Page 画面の画面遷移方法について説明する。
- Hyperlink タグによる画面遷移
-
WPF の Page 画面では、Hyperlink タグ使用して、画面遷移できる。
※ なお、Hyperlink タグは、TextBlock タグなどで囲う必要がある。
-
WPF の Page 画面では、Hyperlink タグ使用して、画面遷移できる。
<Hyperlink NavigateUri="Page2.xaml">次のページへ移動します。</Hyperlink>
<Hyperlink NavigateUri="Page1.xaml">前のページへ移動します。</Hyperlink>- Silverlight の Page 画面では、HyperlinkButton タグ使用して、画面遷移できる。
<HyperlinkButton NavigateUri="/Page2.xaml">次のページへ移動します。</HyperlinkButton>
<HyperlinkButton NavigateUri="/Page1.xaml">前のページへ移動します。</HyperlinkButton>- コードビハインドからの画面遷移
コード ビハインドからの画面遷移も可能である。- WPF の場合は、Page 画面を直接生成可能である。
this.NavigationService.Navigate(new Page2());-
Silverlight の場合は、Page 画面を Uri で指定する必要がある。
- 以下は、相対パス指定:
this.NavigationService.Navigate(new Uri("/Page2.xaml", UriKind.Relative));- 以下は、絶対パス指定:
this.NavigationService.Navigate(
new Uri("pack://application:,,,/Page2.xaml", UriKind.Absolute));-
WPF / Silverlight では、WWW ブラウザ同様、
進む・戻るボタンが常備されており、進む・戻る操作に対応している
(Web アプリケーションと同様に、戻った際は、前ページの入力の状態は保持されている)。 -
「XBAP」、「Silverlight」については、
この進む・戻るボタンは、WWW ブラウザのものと統合されている(同じボタンを利用する)。

- コードビハインドからの進む・戻る操作
コードビハインドからは、次の処理で進む・戻る操作に対応できる。- 進む
this.NavigationService.GoForward();- 戻る
this.NavigationService.GoBack();- 履歴の管理
- 進む・戻る操作が行えるということは、画面遷移の履歴を保持しているということである。
- これを以下のコードで画面遷移の Page 履歴を消去できる。
- 進む・戻る操作が行えるということは、画面遷移の履歴を保持しているということである。
this.NavigationService.RemoveBackEntry();- Page 履歴の消去処理を自動化する場合は、\
NavigationWindow の Navigated イベントで実行すると良い。
private void NavigationWindow_Navigated(object sender, NavigationEventArgs e) {
this.NavigationService.RemoveBackEntry();
}- 「XBAP」、「Silverlight」では NavigationWindow が
存在しないため、Frame の Navigated イベントで実行する。
※ Frame を使用しない場合は、Page の Loaded イベントなどで履歴を消去する。
private void Frame1_Navigated(object sender, NavigationEventArgs e) {
if (NavigationService !=null) {
if (NavigationService.CanGoBack) {
this.NavigationService.RemoveBackEntry();
}
}
}
private void Frame2_Navigated(object sender, NavigationEventArgs e) {
if (NavigationService != null) {
if (NavigationService.CanGoBack) {
this.NavigationService.RemoveBackEntry();
}
}
}- なお、Page 履歴の管理はブラック ボックスとなっているため、
詳細は不明であるが、前 Page に戻った際に入力状態が保持されていることから、
Page 履歴により、ある程度メモリが消費されているものと考える。
メモリ消費量が拡大するような場合は Page 履歴の消去を検討する。
補足(「ブラック ボックス」の中身): 原文が「詳細は不明」とした箇所は、
仕組みが公開されているので補っておく。【ジャーナル(Journal)】★ ナビゲーション履歴は【ジャーナル】として保持される NavigationService.RemoveBackEntry() NavigationService.CanGoBack / CanGoForward 【何が保持されるか】 ・Navigate(new Page1()) …【Page インスタンスそのもの】が保持される → 入力状態が残るのはこのため → 【メモリを食う】という原文の推測は【正しい】★ ・Navigate(uri) … Uri のみが保持され、 戻る際に【再生成】される → 入力状態は消える → JournalEntry.KeepAlive で挙動を制御できる 【Page.KeepAlive プロパティ】 既定は false(Uri ナビゲーション時) true にすると【インスタンスが保持される】 → メモリ消費と状態保持のトレードオフを明示的に選べる ★【現在の位置付け】 ナビゲーション フレームワークは【今も動く】が、 新規の業務アプリでは ・【ContentControl + DataTemplate】で View を差し替える ・Prism / MVVM フレームワークの 【Region ナビゲーション】を使う 方が主流である(MVVM と噛み合わせやすいため)★
-
[新規画面遷移] → [戻る] → [進む] の一連の操作で、
表示される Page の再初期化が発生するので、
必要に応じてこれを考慮した実装とするか、[進む]・[戻る] 操作を抑止する必要がある。 -
再初期化とは?
コンストラクタや、Page の Loaded イベントの処理の実行- コンストラクタが実行されるのは、最初の Page のみである。
- Loaded イベントはすべての Page で実行される。
-
[進む]・[戻る] 操作を抑止する。
先頭 Page で Page.ShowsNavigationUI プロパティを false に設定にし、
以降の Page の [進む]・[戻る] ボタンを非表示にするか、前述の履歴消去で対応する。 -
[進む]・[戻る] ボタンを非表示にする。
- XAML
<Page x:Class="WpfApplication1.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Page1" ShowsNavigationUI="false">- BackSpace キーによる [戻る] を抑止
- [進む]・[戻る] ボタンを非表示にする場合も BackSpace キーで戻ることができるので、
必要に応じて BackSpace キーを抑止する。 - また、UI 要素にフォーカスの当たっていない状態では、
BackSpace キーを抑止することができないので、
Page.Loaded イベントなどで Page 上の入力 UI 要素にフォーカスを当てるようにする。- XAML
- [進む]・[戻る] ボタンを非表示にする場合も BackSpace キーで戻ることができるので、
<Page x:Class="WpfApplication1.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Page1" ShowsNavigationUI="false"
Loaded="Page_Loaded" KeyDown="Page_KeyDown">
<StackPanel>
<TextBlock>
<Hyperlink NavigateUri="Page2.xaml">Page2</Hyperlink>
</TextBlock>
<TextBox Name="textBox1"/>
</StackPanel>
</Page>- コード ビハインド
public partial class Page1 : Page {
public Page1() {
InitializeComponent();
}
private void Page_Loaded(object sender, RoutedEventArgs e) {
this.textBox1.Focus();
}
private void Page_KeyDown(object sender, KeyEventArgs e) {
if (e.Key == Key.Back) e.Handled = true;
}
}補足(
KeyDownでは抑止しきれないことがある): BackSpace 抑止は
Web アプリでも同じ問題があり、本質的に「戻り」を止める設計は難しい。【KeyDown で漏れる理由】★ ・KeyDown は【バブリング】イベント → 子コントロールが e.Handled = true にすると届かない → 【PreviewKeyDown】(トンネリング)で先に捕まえる方が確実 ・原文の指摘どおり、【フォーカスがない】と Page に届かない → Focusable="True" + Focus() で受け皿を作る 【より確実な手】 NavigationCommands.BrowseBack を無効化する <Page.CommandBindings> <CommandBinding Command="NavigationCommands.BrowseBack" CanExecute="Never_CanExecute" /> </Page.CommandBindings> → キーでもボタンでも【まとめて塞げる】★【設計としての助言】 「戻れると困る」画面(確定処理の後など)は、 【戻ることを禁止する】のではなく 【戻っても壊れない】ようにするのが本筋である → 二重登録は【サーバ側で冪等性キー】により防ぐ → 画面側の抑止は【補助】と位置付ける ★
NavigationWindow / Page のその他のトピック
-
UriMapper クラス
-
UriMapper クラスは「Silverlight」専用のコントロールになるが、
Hyperlink タグの NavigateUri 属性などで使用する Page.xaml へのパスを簡素化することができる。 -
UriMapper クラスは複数の UriMapping クラスから構成され、
UriMapping クラスの MappedUri プロパティに実際の Page.xaml へのパス(のパターン)を指定、
Uri プロパティに簡素化したパス(のパターン)を指定する。 -
作成した UriMapper オブジェクトを Frame.UriMapper プロパティに
追加することで、Frame 内で簡素化したパスを使用することができる。
-
-
ChildWindow コントロール
-
ChildWindow コントロールは、「Silverlight」専用のコントロールであるが、
Page 上に、子ウィンドウ(モーダル ダイアログ相当)を表示できる。 -
「Silverlight」アプリケーションのプロジェクトでは、
ChildWindow のクラステンプレートが用意されており、
これを新規作成し、以下のコードで ChildWindow を呼び出すことができる。 -
ChildWindow は、若干のアニメーションのエフェクトを伴い表示される。
また、ChildWindow から、さらに子の ChildWindow を表示させることもできる。
-
ChildWindow1 childWindow = new ChildWindow1();
childWindow.Show();-
注意事項
なお、NavigationWindow / Page の使用における注意点は、以下を参照のこと。- 「XBAP」の場合の注意点
- 「Silverlight」の場合の注意点
-
ナビゲーション フレームワーク 参考情報
- MSDN > Silverlight > アプリケーション モデルとプログラミング モデル
- アプリケーション モデル > ナビゲーションの概要
http://msdn.microsoft.com/ja-jp/library/cc838245.aspx#application_navigation
- アプリケーション モデル > ナビゲーションの概要
- @IT > Insider.NET > 連載:Silverlight 3実践プログラミング
- ナビゲーション・フレームワークとChildWindowコントロール\
- CodeZine > Silverlight 3徹底入門
- Silverlight 3で作る業務アプリケーションの要 > 「ナビゲーションフレームワーク」
http://codezine.jp/article/detail/4810
- Silverlight 3で作る業務アプリケーションの要 > 「ナビゲーションフレームワーク」
- MSDN > Silverlight > アプリケーション モデルとプログラミング モデル
-
参考
- Microsoft Learn > .NET API ブラウザー
移行メモ(リンク切れ / 廃止): 本節の UriMapper・UriMapping・ChildWindow は
いずれも Silverlight 専用であり、Silverlight 自体が
2021 年 10 月にサポート終了しているため、
Microsoft Learn 側に対応するページが存在しない(旧 MSDN のリンクも切れている)。
記録として原文の記述を残したが、新規開発での参照価値はない。
上記の Learn へのリンクも、Silverlight 版ではなく WPF 版のものである。
-
WPF / Silverlight は、従来からの種々の Win32 ダイアログを
サポートしている(一部サポートされないダイアログもある)。 -
このため、WPF / Silverlight アプリケーションであるからと言って、
「外観や、操作方法の異なる標準ダイアログに慣れる必要がある」・・・といった問題は無い。
下記の MessageBox クラスの名前空間は、Windows Forms ではなく
WPF のものだが、
この MessageBox クラスのクラス階層を確認すると
WPF のアーキテクチャに基づいていないことが確認できる。
- Microsoft Learn > .NET API ブラウザー > MessageBox クラス
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.messagebox
補足(「WPF のアーキテクチャに基づいていない」の意味): 鋭い指摘なので、
何が言いたいのかを明示しておく。【WPF の通常のコントロールのクラス階層】 DispatcherObject → DependencyObject → Visual → UIElement → FrameworkElement → Control 【MessageBox のクラス階層】★ System.Object → MessageBox ← 【これだけ】 → MessageBox は【static メソッドの集まり】であり、 内部で Win32 API の MessageBox() を呼んでいるだけ 【そこから来る実害】 ・【スタイル・テンプレートが一切効かない】★ ・DPI / テーマが WPF の見た目と揃わない ・【ボタンの文言を変えられない】(OS 言語に従う) ・単体テストで差し替えられない(static のため)【実務での扱い】★ ・MVVM では【IDialogService を挟む】 → ViewModel は MessageBox.Show を直接呼ばない → テスト時はモックに差し替える ・見た目を揃えたいなら【自前の Window】か OSS のダイアログ(MahApps の MetroDialog 等) ・Windows 標準の新しい見た目が欲しいなら 【TaskDialog】(Vista 以降、.NET 標準にはない)
-
OpenFileDialog クラス
- OpenFileDialog は、部分信頼で実行されている。
- 「XBAP」・「Silverlight」でもファイル名を取得できる。
-
SaveFileDialog クラス
- SaveFileDialog は、部分信頼で実行されている。
- 「XBAP」・「Silverlight」でもファイルを保存できる。
-
PrintDialog クラス
- このクラスの名前空間は、Windows Forms ではなく WPF のものだが、
クラス階層を確認すると WPF のアーキテクチャに基づいていないことが確認できる。 - Silverlight では、PrintDocument クラス
- このクラスの名前空間は、Windows Forms ではなく WPF のものだが、
移行メモ(正誤・体裁): 移行元では PrintDialog クラスの説明の階層が
1 段深く(---)なっていたため、他の項目に揃えた。
またPrintdocumentの表記をPrintDocumentに修正した。
- 参考
-
Microsoft Learn > .NET API ブラウザー
-
Microsoft Learn > Windows Presentation Foundation > ドキュメント
-
補足(
OpenFolderDialogが標準で入った): 長らく WPF には
「フォルダ選択ダイアログ」が標準で存在しなかったが、状況が変わっている。【歴史】 ・.NET Framework 時代 → Microsoft.Win32 には【OpenFolderDialog がない】 → System.Windows.Forms.FolderBrowserDialog を 参照追加して使う(見た目が古い)か、 Windows API Code Pack(非公式)を使うのが定番だった ・【.NET 8 で OpenFolderDialog が追加された】★ var dlg = new Microsoft.Win32.OpenFolderDialog(); if (dlg.ShowDialog() == true) { var p = dlg.FolderName; } → 現代的な IFileDialog ベースの見た目【部分信頼について】★ 原文の「部分信頼で実行されている」は 【XBAP / Silverlight のサンドボックス】を前提とした記述。 .NET Framework 4.0 で【CAS(部分信頼)は非推奨】となり、 .NET Core 以降は【部分信頼という概念自体がない】。 → 現在のデスクトップ アプリでは常に完全信頼で動く
- 前述の「ウィンドウの起動」のモーダル ダイアログの起動を参照のこと。
- 「XBAP」では使用できないので注意する。
- なお、「Silverlight」の場合は、
前述の ChildWindow コントロールで代替可能である。
- Microsoft Learn > Windows Presentation Foundation
- アプリケーション開発 > WPF ウィンドウ > ダイアログ ボックスの概要
https://learn.microsoft.com/ja-jp/dotnet/desktop/wpf/windows/dialog-boxes-overview - 移行と相互運用性 > Windows フォーム コントロールおよび同等の WPF コントロール
https://learn.microsoft.com/ja-jp/dotnet/desktop/wpf/migration/wpf-controls-equivalent-to-windows-forms
- アプリケーション開発 > WPF ウィンドウ > ダイアログ ボックスの概要
入力支援機能に関する技術要素について説明する。
- メニュー・タスクバーと「コマンド」の関連付けも、宣言的プロパティにより実装・カスタマイズ可能。
- 例えば、以下の XAML でメニュー・タスクバーと「コマンド」の概要を理解することができ、
また、メニュー・タスクバーと「コマンド」の関連付けが容易に実装可能であることが分かる。
移行メモ(用語): 移行元の見出しは「メニュー・タスクバーとコマンド」だが、
本文・サンプルで扱っているのは Windows のタスクバーではなく
**ツールバー(ToolBar / ToolBarTray)**である。
作者の見出しをそのまま残したが、読み替えて理解されたい。
- 画面

- XAML
<Window.CommandBindings>
<!--ApplicationCommands.Saveのカスタム動作(実行可否、イベント処理)を実装する-->
<CommandBinding x:Name="SaveCommand"
Command="ApplicationCommands.Save"
CanExecute="SaveCommand_CanExecute"
Executed="SaveCommand_Executed"/>
<!--ApplicationCommands.Closeのカスタム動作(実行可否、イベント処理)を実装する-->
<CommandBinding x:Name="CloseCommand"
Command="ApplicationCommands.Close"
CanExecute="CloseCommand_CanExecute"
Executed="CloseCommand_Executed"/>
</Window.CommandBindings>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="ファイル(_F)">
<!--ApplicationCommands.Save → カスタム動作 → CommandBinding-->
<MenuItem Header="保存(_S)" Command="ApplicationCommands.Save">
<MenuItem.Icon><Image Source=".\icons\save.png"/></MenuItem.Icon>
</MenuItem>
<Separator/>
<!--ApplicationCommands.Close → カスタム動作 → CommandBinding-->
<!--MenuItem Header="終了(_X)" Command="ApplicationCommands.Close"-->
<!--ApplicationCommands.Close → カスタム動作 → カスタムイベント-->
<MenuItem Name="Exit" Click="Exit_Click" Header="終了(_X)">
<MenuItem.Icon><Image Source=".\icons\exit.png"/></MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="編集(_E)">
<!--ApplicationCommands.Undo → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="元に戻す(_U)" Command="ApplicationCommands.Undo">
<MenuItem.Icon><Image Source=".\icons\undo.png"/></MenuItem.Icon>
</MenuItem>
<!--ApplicationCommands.Redo → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="やり直し(_R)" Command="ApplicationCommands.Redo">
<MenuItem.Icon><Image Source=".\icons\redo.png"/></MenuItem.Icon>
</MenuItem>
<Separator/>
<!--ApplicationCommands.Cut → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="切り取り(_T)" Command="ApplicationCommands.Cut">
<MenuItem.Icon><Image Source=".\icons\cut.png"/></MenuItem.Icon>
</MenuItem>
<!--ApplicationCommands.Copy → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="コピー(_C)" Command="ApplicationCommands.Copy">
<MenuItem.Icon><Image Source=".\icons\copy.png"/></MenuItem.Icon>
</MenuItem>
<!--ApplicationCommands.Paste → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="貼り付け(_P)" Command="ApplicationCommands.Paste">
<MenuItem.Icon><Image Source=".\icons\paste.png"/></MenuItem.Icon>
</MenuItem>
<!--ApplicationCommands.Delete → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="削除(_D)" Command="ApplicationCommands.Delete">
<MenuItem.Icon><Image Source=".\icons\delete.png"/></MenuItem.Icon>
</MenuItem>
<MenuItem Header="配置(_A)">
<!--EditingCommands.AlignLeft → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="左揃え" Command="EditingCommands.AlignLeft">
<MenuItem.Icon><Image Source=".\icons\text_align_left.png"/></MenuItem.Icon>
</MenuItem>
<!--EditingCommands.AlignCenter → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="中央揃え" Command="EditingCommands.AlignCenter">
<MenuItem.Icon><Image Source=".\icons\text_align_center.png"/></MenuItem.Icon>
</MenuItem>
<!--EditingCommands.AlignRight → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="右揃え" Command="EditingCommands.AlignRight">
<MenuItem.Icon><Image Source=".\icons\text_align_right.png"/></MenuItem.Icon>
</MenuItem>
<!--EditingCommands.AlignJustify → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="両端揃え" Command="EditingCommands.AlignJustify">
<MenuItem.Icon><Image Source=".\icons\text_align_justify.png"/></MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="スタイル(_S)">
<!--EditingCommands.ToggleBold → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="太字" Command="EditingCommands.ToggleBold">
<MenuItem.Icon><Image Source=".\icons\text_bold.png"/></MenuItem.Icon>
</MenuItem>
<!--EditingCommands.ToggleItalic → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="斜体" Command="EditingCommands.ToggleItalic">
<MenuItem.Icon><Image Source=".\icons\text_italic.png"/></MenuItem.Icon>
</MenuItem>
<!--EditingCommands.ToggleUnderline → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="下線" Command="EditingCommands.ToggleUnderline">
<MenuItem.Icon><Image Source=".\icons\text_underline.png"/></MenuItem.Icon>
</MenuItem>
<!--EditingCommands.ToggleBullets → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="箇条書き" Command="EditingCommands.ToggleBullets">
<MenuItem.Icon><Image Source=".\icons\text_list_bullets.png"/></MenuItem.Icon>
</MenuItem>
<!--EditingCommands.ToggleNumbering → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="段落番号" Command="EditingCommands.ToggleNumbering">
<MenuItem.Icon><Image Source=".\icons\text_list_numbers.png"/></MenuItem.Icon>
</MenuItem>
<!--EditingCommands.IncreaseIndentation → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="インデントを増やす" Command="EditingCommands.IncreaseIndentation">
<MenuItem.Icon><Image Source=".\icons\text_indent.png"/></MenuItem.Icon>
</MenuItem>
<!--EditingCommands.DecreaseIndentation → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="インデントを減らす" Command="EditingCommands.DecreaseIndentation">
<MenuItem.Icon><Image Source=".\icons\text_indent_remove.png"/></MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="テキスト(_T)">
<!--EditingCommands.IncreaseFontSize → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="フォントの拡大" Command="EditingCommands.IncreaseFontSize"/>
<!--EditingCommands.DecreaseFontSize → フォーカスのあるコントロール(RichTextBox)の既定の動作を実行-->
<MenuItem Header="フォントの縮小" Command="EditingCommands.DecreaseFontSize"/>
</MenuItem>
</MenuItem>
<MenuItem Header="ヘルプ(_H)">
<!--RoutedCommandをコードビハインドから自作しカスタム動作(実行可否、イベント処理)を実装する-->
<MenuItem Header="バージョン情報(_A)" Name="About">
<MenuItem.Icon><Image Source=".\icons\about.png"/></MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar Header="編集:">
<Button ToolTip="元に戻す" Command="ApplicationCommands.Undo">
<Image Source=".\icons\undo.png" Stretch="Fill"/></Button>
<Button ToolTip="やり直し" Command="ApplicationCommands.Redo">
<Image Source=".\icons\redo.png" Stretch="Fill"/></Button>
<Button ToolTip="切り取り" Command="ApplicationCommands.Cut">
<Image Source=".\icons\cut.png" Stretch="Fill"/></Button>
<Button ToolTip="コピー" Command="ApplicationCommands.Copy">
<Image Source=".\icons\copy.png" Stretch="Fill"/></Button>
<Button ToolTip="貼り付け" Command="ApplicationCommands.Paste">
<Image Source=".\icons\paste.png" Stretch="Fill"/></Button>
<Button ToolTip="削除" Command="ApplicationCommands.Delete">
<Image Source=".\icons\delete.png" Stretch="Fill"/>
</Button>
</ToolBar>
<ToolBar Header="配置:">
<Button ToolTip="左揃え" Command="EditingCommands.AlignLeft">
<Image Source=".\icons\text_align_left.png" Stretch="Fill"/></Button>
<Button ToolTip="中央揃え" Command="EditingCommands.AlignCenter">
<Image Source=".\icons\text_align_center.png" Stretch="Fill"/></Button>
<Button ToolTip="右揃え" Command="EditingCommands.AlignRight">
<Image Source=".\icons\text_align_right.png" Stretch="Fill"/></Button>
<Button ToolTip="両端揃え" Command="EditingCommands.AlignJustify">
<Image Source=".\icons\text_align_justify.png" Stretch="Fill"/></Button>
</ToolBar>
<ToolBar Header="スタイル:">
<Button ToolTip="太字" Command="EditingCommands.ToggleBold">
<Image Source=".\icons\text_bold.png" Stretch="Fill"/></Button>
<Button ToolTip="斜体" Command="EditingCommands.ToggleItalic">
<Image Source=".\icons\text_italic.png" Stretch="Fill"/></Button>
<Button ToolTip="下線" Command="EditingCommands.ToggleUnderline">
<Image Source=".\icons\text_underline.png" Stretch="Fill"/></Button>
<Button ToolTip="箇条書き" Command="EditingCommands.ToggleBullets">
<Image Source=".\icons\text_list_bullets.png" Stretch="Fill"/></Button>
<Button ToolTip="段落番号" Command="EditingCommands.ToggleNumbering">
<Image Source=".\icons\text_list_numbers.png" Stretch="fill"/></Button>
<Button ToolTip="インデントを増やす" Command="EditingCommands.IncreaseIndentation">
<Image Source=".\icons\text_indent.png" Stretch="Fill"/></Button>
<Button ToolTip="インデントを減らす" Command="EditingCommands.DecreaseIndentation">
<Image Source=".\icons\text_indent_remove.png" Stretch="Fill"/></Button>
</ToolBar>
</ToolBarTray>
<RichTextBox DockPanel.Dock="Top" Width="Auto" Height="100"
IsDocumentEnabled="True" AcceptsTab="True" FontSize="24"/>
<RichTextBox DockPanel.Dock="Top" Width="Auto" Height="100"
IsDocumentEnabled="True" AcceptsTab="True" FontSize="24"/>
</DockPanel>- コード ビハインド
/// <summary>Window1.xaml の相互作用ロジック</summary>
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
// UIElementにRoutedCommandをバインドし、メニューに関連付ける。
InitRoutedCommands_about();
}
// AboutCommand(カスタムのRoutedCommand)
public static readonly RoutedCommand AboutCommand = new RoutedCommand();
private void InitRoutedCommands_about() {
// CommandBindingの生成
CommandBinding binding =
new CommandBinding(AboutCommand, AboutCommand_Executed, AboutCommand_CanExecute);
// UIElementにRoutedCommandをバインド
this.CommandBindings.Add(binding);
// メニューに関連付け
About.Command = AboutCommand;
}
// 【情報メニュー+コマンド】UIElementにRoutedCommandをバインド:カスタム動作(実行可否)
private void AboutCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = true;
}
// 【情報メニュー+コマンド】UIElementにRoutedCommandをバインド:カスタム動作(イベント処理)
private void AboutCommand_Executed(object sender, ExecutedRoutedEventArgs e) {
// 情報表示処理
}
// 【保存メニュー+コマンド】UIElementにRoutedCommandをバインド:カスタム動作(実行可否)
private void SaveCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = true;
}
// 【保存メニュー+コマンド】UIElementにRoutedCommandをバインド:カスタム動作(イベント処理)
private void SaveCommand_Executed(object sender, ExecutedRoutedEventArgs e) {
// 保存処理
}
// 【終了メニュー+コマンド】UIElementにRoutedCommandをバインド:カスタム動作(実行可否)
private void CloseCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) {
e.CanExecute = true;
}
// 【終了メニュー+コマンド】UIElementにRoutedCommandをバインド:カスタム動作(イベント処理)
private void CloseCommand_Executed(object sender, ExecutedRoutedEventArgs e) {
this.Close();
}
// 【終了メニュー】メニューのクリックイベントのみの実装で済ます場合。
private void Exit_Click(object sender, RoutedEventArgs e) {
this.Close();
}
}移行メモ(正誤): 移行元では
AboutCommand_Executedのコメントが
AboutCommand_CanExecuteと同じ「カスタム動作(実行可否)」と
なっていたが、Executed 側はイベント処理であるため
(他の Save / Close 側の記述に揃えて)修正した。
- メニュー・タスクバーと「コマンド」の関連付け
-
MenuItem
- MenuItem(MenuItem クラス)は入れ子が可能であり、
- MenuItem のアクセスキーは、MenuItem クラスの Header 属性に指定する文字列中に
「_+英数字」と設定することで(_の直後の文字をアクセスキーとして)容易に設定可能 - また、MenuItem からは Command 属性を指定して指定の「Command」を発生させることができる。
-
ToolBarTray
- ToolBarTray には ToolBar を格納でき、ToolBar には Button を格納できる。
- Button からは指定の「Command」を発生させることができる。
- ToolBarTray 内の ToolBar は、実行時に D&D にて配置を変更できる。
-
Command
- これらのコントロールから発生させた「Command」は、フォーカスのあるコントロールに受信されるが、
-
CommandTarget="{Binding ElementName=要素名}"という記述により、
対象のコントロールを明示することもできる。 - 対象のコントロールは、各種 RoutedCommand クラスの CanExecute、Execute メソッドが
発生させるイベントを処理するイベント ハンドラを実装している必要がある。
-
移行メモ(正誤): 移行元では
- MenuItem のアクセスキーが「Header 属性に指定する文字列中に**「英数字」**と設定することで
(英数字の先頭文字をアクセスキーとして)」と記載されていたが、
実際にはサンプルのとおり アンダースコア(_)の直後の文字がアクセスキーになるため修正した。- 「ToolBarTray には**、を格納でき**」と目的語が欠落していたため、
文脈から「ToolBar を格納でき」と補った。
-
標準コマンド
-
標準コマンド(ApplicationCommands, EditingCommands, etc.)に対応した処理を
実装しているコントロールには、- 「コマンド」に対応する処理の UOC は不要であり、既定の処理が実行される。
- また、「コマンド」には、既定のショートカットも割り当てられている。
-
コントロールの実装する標準コマンド
(上記サンプルでは、ApplicationCommands.Save・Close)の処理をカスタマイズしたい場合、-
「コマンド」に対応したイベント ハンドラの UOC を UIElement
(上記サンプルでは、Window)に定義・実装する必要がある。 -
標準コマンドのカスタム処理の定義・実装には、標準コマンド(ApplicationCommands)を
UIElement の CommandBindings プロパティに追加し、
ApplicationCommands.CanExecute、Execute が発生させるカスタム イベントを処理する
イベント ハンドラの UOC を UIElement 上に実装する必要がある。 -
また、上記の方式は、カスタム コマンド(RoutedCommand)に対応した
イベント ハンドラの UOC を UIElement に定義・実装する場合にも利用できる。
-
-
その他にも、以下の標準コマンドが System.Windows.Input 名前空間に用意されている。
- ComponentCommands
- MediaCommands
- NavigationCommands
-
補足(
RoutedCommandと MVVM のICommandは別物): 本節の
RoutedCommandは WPF 独自の「ルーティングされるコマンド」であり、
MVVM で使うICommandの実装(RelayCommand等)とは性格が異なる。
RoutedCommand RelayCommand( ICommandの自作実装)処理の置き場所 CommandBinding(View 側) ViewModel 側 ★ 探し方 Visual Tree を遡って CommandBinding を探す バインドされた ViewModel を直接呼ぶ 実行対象 フォーカスのある要素に依存する 依存しない 実行可否 CanExecuteイベントCanExecuteデリゲート用途 編集系の標準コマンド(Cut / Copy / Undo)★ 業務ロジックの起動 ★ 【なぜ標準コマンドが「何も書かずに動く」のか】★ TextBox / RichTextBox は 自分の CommandBindings に Cut / Copy / Paste / Undo の実装を【最初から持っている】 メニューの Command="ApplicationCommands.Cut" は → フォーカスのある要素から Visual Tree を遡り、 → 最初に見つかった CommandBinding を実行する → だから【フォーカスのある TextBox】が切り取られる ※ 逆に言うと、フォーカスが外れると メニューが【勝手にグレーアウトする】のもこの仕組みのため【CanExecute が呼ばれるタイミング】★ CommandManager.RequerySuggested により 【フォーカス移動・キー入力・マウス操作のたび】に再評価される → CanExecute に【重い処理を書いてはいけない】 → 逆に、任意のタイミングで再評価させたいときは CommandManager.InvalidateRequerySuggested()【実務での使い分け】 ・テキスト編集系(Cut/Copy/Paste/Undo)→ 標準コマンドをそのまま ★ ・保存・検索・実行などの業務操作 → ViewModel の ICommand にバインドする ★ <MenuItem Header="保存" Command="{Binding SaveCommand}" /> → CommunityToolkit.Mvvm の [RelayCommand] で自動生成できる
-
Microsoft Learn > Windows Presentation Foundation > コントロール
-
Microsoft Learn > .NET API ブラウザー
- MenuItem クラス
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.controls.menuitem - ToolBarTray クラス
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.controls.toolbartray - ToolBar クラス
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.controls.toolbar - RoutedCommand クラス
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.input.routedcommand - ApplicationCommands クラス
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.input.applicationcommands - EditingCommands クラス
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.documents.editingcommands
- MenuItem クラス
- WPF では、「ツールチップ」を任意の UI コントロールに設定可能である。
- これには FrameworkElement、FrameworkContentElement に ToolTip プロパティが
実装されているためである。
- 画面

- XAML
- UserControl
<UserControl x:Class="WpfApplication1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="250" Height="120">
<StackPanel>
<TextBlock Margin="4,4,4,4" Text="タイトル"
TextWrapping="Wrap" FontWeight="Bold" FontSize="14"/>
<Path Width="Auto" Height="1"
Fill="Black" Stretch="Fill"
Stroke="Black" Data="M20,45 L250,45"
Margin="4,4,4,4"/>
<TextBlock Margin="4,4,4,4" Text="説明文"
TextWrapping="Wrap"/>
</StackPanel>
</UserControl>- Window
<Window x:Class="WpfApplication1.Window1"
・・・
xmlns:my="clr-namespace:WpfApplication1"
・・・>
・・・
<RichTextBox DockPanel.Dock="Top" Width="Auto" Height="100"
IsDocumentEnabled="True" AcceptsTab="True" FontSize="24">
<RichTextBox.ToolTip>
<ToolTip>
<my:UserControl1/>
</ToolTip>
</RichTextBox.ToolTip>
</RichTextBox>この ToolTip プロパティには、ToolTip 属性に直接文字列で説明文を記述することも可能であるが、
「プロパティ要素構文」を使用して、説明文などの文字列だけではなく、
(image などの)任意の UI 要素も設定可能である。
例えば、上記は、ユーザ コントロールを使用して ToolTip を表示した例である。
移行メモ(体裁): 移行元では、この「説明」節が「実装」節の後にあるにも
かかわらず「以下は、ユーザ コントロールを使用して…」と
前方参照になっていたため、「上記は」に改めた。
- Microsoft Learn > .NET API ブラウザー
- FrameworkElement.ToolTip プロパティ
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.frameworkelement.tooltip - FrameworkContentElement.ToolTip プロパティ
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.frameworkcontentelement.tooltip
- FrameworkElement.ToolTip プロパティ
移行メモ(リンクの誤り): 移行元では
FrameworkElement.ToolTip プロパティの
URL がEditingCommandsクラスのものになっていた
(前節からのコピー時の誤りと思われる)ため、正しい URL に修正した。
補足(ツールチップで実際に困るのはここ): 実装は簡単だが、
既定の挙動が業務アプリに合わないことが多い。【よく変更する設定(ToolTipService の添付プロパティ)】★ ToolTipService.InitialShowDelay … 表示までの待ち時間(既定 1 秒) ToolTipService.ShowDuration … 表示し続ける時間(既定 5 秒)★ ToolTipService.BetweenShowDelay … 連続表示時の待ち無し時間 ToolTipService.Placement … 表示位置 ToolTipService.ShowOnDisabled … 【無効なコントロールでも出す】★ <Button IsEnabled="False" ToolTipService.ShowOnDisabled="True" ToolTip="権限がないため実行できません" /> → 「なぜ押せないか」を伝えられる。 既定では【無効なコントロールにツールチップが出ない】ため、 これを知らないと理由を伝える手段がなくなる【ToolTip の中身がバインドできない】★ ToolTip の中身は【Visual Tree の外】(Popup)にあるため、 親の DataContext が【継承されない】ことがある → PlacementTarget 経由で辿る <ToolTip DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">【エラー表示との組み合わせ】 バリデーション エラーの内容を ツールチップに出すのが WPF の定番 ToolTip="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource Self}}" → 後述の「バリデーション」を参照
-
WPF / Silverlight では、InputMethod クラスを利用することで IME 制御が可能である
(XAML から直接指定することも可能)。 -
ただし、
- IME2003 以外では、IME のオン・オフの切り替え(ひらがな ⇔ 直接入力)はできるが、
- 入力モードの切り替え(カタカナ ⇔ 全角英字など)はできないので注意が必要。
-
切り替え方法
- IME オン・オフ
- InputMethod.SetIsInputMethodEnabled メソッド
- InputMethod.Current.ImeState プロパティ
- 入力モード
- InputMethod.SetPreferredImeConversionMode メソッド
- InputMethod.Current.ImeConversionMode プロパティ
- IME オン・オフ
補足(Windows Forms の
ImeModeとの比較は別ページ):
UI サブシステム横断での IME 制御の考え方は
「IME制御」を参照のこと。
なお、上記の「IME2003 以外では入力モードの切り替えができない」という制約は
当時の Windows XP + Office IME 2003 環境での事象であり、
現在の Microsoft IME では状況が異なる(下記参照)。
以下は、IME 制御のサンプルである。
- 画面

-
XAML
イベント ハンドラで IME 制御する方法と、XAML で IME 制御する方法がある。- イベント ハンドラで IME 制御
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Height="23" Width="80" Text="制御なし:"/>
<TextBox Name="textBox0" Height="23" Width="120"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Height="23" Text="以下、イベントで制御"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Height="23" Width="80" Text="OFF:"/>
<TextBox Name="textBox1" Height="23" Width="120"
GotFocus="textBox1_GotFocus" LostFocus="textBox1_LostFocus"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Height="23" Width="80" Text="ON:"/>
<TextBox Name="textBox2" Height="23" Width="120"
GotFocus="textBox2_GotFocus" LostFocus="textBox2_LostFocus"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Height="23" Width="80" Text="全角片仮名:"/>
<TextBox Name="textBox3" Height="23" Width="120"
GotFocus="textBox3_GotFocus" LostFocus="textBox3_LostFocus"/>
</StackPanel>- XAML で IME 制御
<StackPanel Orientation="Horizontal">
<TextBlock Height="23" Text="以下、XAMLで制御"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Height="23" Width="80" Text="OFF:"/>
<TextBox Name="textBox4" Height="23" Width="120"
InputMethod.PreferredImeState="Off"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Height="23" Width="80" Text="ON:"/>
<TextBox Name="textBox5" Height="23" Width="120"
InputMethod.PreferredImeState="On"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Height="23" Width="80" Text="半角片仮名:"/>
<TextBox Name="textBox6" Height="23" Width="120"
InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="Native,Fixed,Katakana"/>
</StackPanel>
</StackPanel>- コード ビハインド
下記はイベント ハンドラによる IME 制御である。
/// <summary>Window1.xaml の相互作用ロジック</summary>
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
}
InputMethodState ims = InputMethodState.DoNotCare;
ImeConversionModeValues imc = ImeConversionModeValues.DoNotCare;
private void textBox1_GotFocus(object sender, RoutedEventArgs e) {
ims = InputMethod.Current.ImeState;
InputMethod.Current.ImeState = InputMethodState.Off;
}
private void textBox1_LostFocus(object sender, RoutedEventArgs e) {
InputMethod.Current.ImeState = ims;
}
private void textBox2_GotFocus(object sender, RoutedEventArgs e) {
ims = InputMethod.Current.ImeState;
InputMethod.Current.ImeState = InputMethodState.On;
}
private void textBox2_LostFocus(object sender, RoutedEventArgs e) {
InputMethod.Current.ImeState = ims;
}
private void textBox3_GotFocus(object sender, RoutedEventArgs e) {
ims = InputMethod.Current.ImeState;
imc = InputMethod.Current.ImeConversionMode;
InputMethod.Current.ImeState = InputMethodState.On;
InputMethod.Current.ImeConversionMode = ImeConversionModeValues.Native
| ImeConversionModeValues.FullShape | ImeConversionModeValues.Katakana;
}
private void textBox3_LostFocus(object sender, RoutedEventArgs e) {
InputMethod.Current.ImeState = ims;
InputMethod.Current.ImeConversionMode = imc;
}
}移行メモ(正誤): XAML 側の
textBox6は「半角片仮名」というラベルだが、
指定はNative,Fixed,Katakanaであり、FullShape/Fixedの別で言えば
全角・半角を決めるのはFullShape(全角)の有無である
(Fixedは「固定入力」を意味し、全角・半角の別ではない)。
移行元の記述をそのまま残したが、半角片仮名を意図するなら
FullShapeを含めない指定が必要である。
イベント ハンドラでの IME 制御の動作は、
以下の点が、XAML による IME 制御の場合と異なる。
-
GotFocus、LostFocus イベントを扱っており、
GotFocus イベントで現状の IME 設定を保存し、
LostFocus イベントで IME 設定を復元している。 -
このため、入力時の IME 設定変更は LostFocus イベントで無効になる。
-
Microsoft Learn > .NET API ブラウザー
-
Microsoft Connect|WinXP&IME2003環境で、WPFのTextBoxに対するIME制御(カタカナ指定)ができない
http://connect.microsoft.com/VisualStudioJapan/feedback/details/375651/winxp-ime2003-wpf-textbox-ime
移行メモ(リンク切れ): 上記の Microsoft Connect は
2018 年にサービス終了しており、リンクは到達しない。
記録として残す。
補足(今の推奨と、当時からの変化): 本節の記述は
Windows XP + Office IME 2003 時代のものである。現在の状況を補っておく。【① イベント ハンドラ方式は使わない】★ InputMethod.Current は【アプリ全体で共有される】ため、 GotFocus / LostFocus で保存・復元する実装は ・フォーカス遷移が入れ子になると【値が壊れる】 ・非同期処理が挟まると復元されない → 【XAML の添付プロパティ方式】を使う ★ InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="Native,FullShape" → フォーカスの出入りは WPF が面倒を見る ※ 完全に IME を禁じたいなら InputMethod.IsInputMethodEnabled="False" (数値・コード入力欄で有効)★【② 入力モードの切り替えは「今は効く」】 原文の「IME2003 以外では入力モードの切り替えができない」は 【当時の制約】であり、 現在の Microsoft IME(Windows 10 / 11)では PreferredImeConversionMode は【概ね機能する】★ ただし、 ・【Google 日本語入力】等のサードパーティ IME では 無視されることがある ・Windows 10 1809 以降の【新しい Microsoft IME】は 挙動が変わった時期があり、 バージョン差で結果が揺れる → 【対象環境で必ず実機確認する】ことが前提 ★【③ ImeConversionModeValues の組み合わせ】 Native … かな入力(日本語入力オン) Alphanumeric… 英数 Katakana … 片仮名 FullShape … 【全角】★(付けなければ半角) Roman … ローマ字入力 Fixed … 固定(ユーザによる変更を抑止) 全角カタカナ → "Native,FullShape,Katakana" 半角カタカナ → "Native,Katakana" 全角英数 → "Alphanumeric,FullShape"
デザイナ向けの技術要素について説明する。
-
WPFのアーキテクチャ の「クラス階層」の Visual クラスで説明したように、
WPF / Silverlight では、Shape クラスから派生する
様々なベクタ グラフィックスを記述できる。 -
Shape クラスから派生するベクタ グラフィックス描画クラスには、次のものがある。
| # | クラス | 説明 |
|---|---|---|
| 1 | Rectangle クラス | 四角形を描画 |
| 2 | Ellipse クラス | 楕円を描画 |
| 3 | Line クラス | 直線を描画 |
| 4 | Polyline クラス | 一連の直線を描画 |
| 5 | Path クラス | 一連の直線と曲線を描画 |
移行メモ(体裁): 箇条書きを表に整理した。
補足(Shape の一覧はもう少しある): 原文の 5 つに加え、
**Polygon(閉じた多角形)**がある。
Polylineが「開いた折れ線」なのに対し、Polygonは始点と終点が自動的に閉じる。【Shape の描画コストに注意】★ Shape は【FrameworkElement の派生】=レイアウトに参加する → 数が増えると【重い】 【大量に描くなら】 ・DrawingVisual + VisualCollection(レイアウトを通らない) ・Geometry を 1 つの Path に統合(GeometryGroup)★ ・WriteableBitmap にピクセルで描く → 数千個の点をプロットするグラフなどでは必須の判断
-
ただし、Visual Studio では Path 要素の編集が困難である。
このため Expression Blend のデザイナでは、[ペン] や [鉛筆] など、
Path 要素の編集用ツールを使用すると良い。
以下は、Expression Blend デザイナの [ペン] や [鉛筆] を使用して記述した Path 要素の描画である。- 画面

- XAML
<Grid>
<Path Fill="White" Stretch="Fill" Stroke="Black" StrokeThickness="5"
Data="M208,68 C182.37169,68 155.97712,69 131,69 ・・・"/>
<Path Fill="White" Stretch="Fill" Stroke="Black" StrokeThickness="5" Margin="50"
Data="M88,132 C88,132 87.5,250.5 120.5,234.5 ・・・"/>
</Grid>-
さらに、VS や Expression Blend を用い、Ellipse・TextBlock などの描画コンテンツを作成し、
これを Expression Blend で、これらを Path に変換するといった機能も用意されている。- 画面

- XAML
<Grid>
<Viewbox Height="400" Width="400">
<Grid>
<Ellipse Stroke="Black" StrokeThickness="5"
Height="400" Width="400"/>
<TextBlock Text="WPF" FontSize="150"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Viewbox>
</Grid>-
この変換を行うには、Expression Blend の [オブジェクトとタイムライン] で
Ellipse・TextBlock を選択した状態で、
メニューの [オブジェクト] → [パス] → [複合パスの作成] を選択する。
以下は、上記の Ellipse・TextBlock を Path に変換した XAML である。- XAML
<Grid>
<Path Data="M397.5,200 C397.5,309.07624 309.07624,397.5 200,397.5 C90.923762,397.5 2.5,309.07624 2.5,200 C2.5,90.923762 90.923762,2.5 200,2.5 C309.07624,2.5 397.5,90.923762 397.5,200 z M59.0835,149.3165 L71.974126,149.3165 L87.794437,223.73042 L107.13037,149.3165 L120.021,149.3165 L139.35694,223.73042 L155.17725,149.3165 L168.06787,149.3165 L145.80225,250.6835 L134.0835,250.6835 L113.57569,169.23834 L93.067874,250.6835 L81.349124,250.6835 z M192.08984,160.44929 L192.08984,199.1211 L222.55859,199.1211 C228.02734,199.1211 232.32422,197.5586 235.44922,194.4336 C238.96484,190.91799 240.72266,186.23049 240.72266,180.37113 C240.72266,173.73052 239.16016,168.84771 236.03516,165.72272 C232.51953,162.2071 228.02734,160.44929 222.55859,160.44929 z M178.61329,149.3165 L225.48828,149.3165 C234.08203,149.31651 241.11328,152.05088 246.58203,157.51961 C252.05078,162.98835 254.78515,170.60552 254.78515,180.37113 C254.78515,190.13674 252.24609,197.5586 247.16797,202.63671 C242.08984,207.71483 234.86328,210.25389 225.48828,210.25389 L192.08984,210.25389 L192.08984,249.51163 L178.61329,249.51163 z M273.5337,149.3165 L340.9165,149.3165 L340.9165,160.44929 L287.01027,160.44929 L287.01027,193.26173 L329.19775,193.26173 L329.19775,204.39452 L287.01027,204.39452 L287.01027,249.51163 L273.5337,249.51163 z" Stretch="Fill" Stroke="Black" StrokeThickness="5"/>
</Grid>補足(Expression Blend の現在): 本節は Expression Blend を前提としているが、
製品としての Expression シリーズはすでに存在しない。【経緯】★ Expression Studio(Blend / Design / Web / Encoder) → 2012 年に【製品ラインが終息】 → Blend だけが【Blend for Visual Studio】として Visual Studio に【同梱】された 現在(Visual Studio 2022) ・ワークロード「.NET デスクトップ開発」に含まれる ・XAML ファイルを右クリック → 【Blend for Visual Studio で デザイン】で起動 ・Path の [ペン]/[鉛筆]、複合パスの作成、 アニメーションのタイムラインは【今も使える】★【今のワークフロー】 ・アイコン・図形は【SVG から変換】するのが主流 → SVG の path の d 属性は WPF の Path.Data と【ほぼ同じ文法】★ → 変換ツール(SvgToXaml 等)で流用できる ・デザイナとの受け渡しは Figma / Illustrator → SVG → XAML ・大量のアイコンは【フォント アイコン】 (Segoe MDL2 Assets / Segoe Fluent Icons)で済ませる【Viewbox は覚えておく価値がある】★ 上のサンプルで使われている Viewbox は、 中身を【指定サイズに拡大縮小】するコンテナ。 ベクタなので【拡大してもぼやけない】 → アイコンを 1 つ描いて、 Viewbox で任意サイズに使い回すのが定番
-
Microsoft Learn > .NET API ブラウザー
- Shape クラス
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.shapes.shape - Rectangle クラス
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.shapes.rectangle - Ellipse クラス
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.shapes.ellipse - Line クラス
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.shapes.line - Polyline クラス
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.shapes.polyline - Path クラス
https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.shapes.path
- Shape クラス
-
Microsoft Learn > Windows Presentation Foundation > グラフィックスとマルチメディア
WPF / Silverlight では、様々なグラデーションをデザイン可能。
-
簡単なグラデーション
以下の簡素なグラデーションであれば、XAML で直接記述することも可能。- 画面

- XAML
<Window.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="0"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</Window.Background>-
複雑なグラデーション
しかし、以下の複雑なグラデーションであれば、
Expression Blend のデザイナを使用した方が、容易にグラデーションをデザインできる。- 画面

- XAML
<Window.Background>
<RadialGradientBrush GradientOrigin="0.3,0.3">
<GradientStop Color="Aqua" Offset="0.1"/>
<GradientStop Color="Black" Offset="0.2"/>
<GradientStop Color="Aqua" Offset="0.3"/>
<GradientStop Color="Black" Offset="0.4"/>
<GradientStop Color="Aqua" Offset="0.5"/>
<GradientStop Color="Black" Offset="0.6"/>
<GradientStop Color="Aqua" Offset="0.7"/>
<GradientStop Color="Black" Offset="0.8"/>
<GradientStop Color="Aqua" Offset="0.9"/>
</RadialGradientBrush>
</Window.Background>補足(Brush と
Freeze()): グラデーションは手軽だが、
性能面で知っておくべきことがある。【Brush は Freezable】★ Brush / Geometry / Transform は Freezable の派生。 Freeze() すると ・【変更不可】になる代わりに ・変更通知の購読が不要になり【軽くなる】 ・【スレッドを跨いで共有できる】 リソースに置いた Brush は 【自動的に Freeze される】ことが多いが、 コードで大量に生成する場合は明示的に Freeze する ★ var b = new SolidColorBrush(Colors.Red); b.Freeze();【グラデーションの描画コスト】 ・LinearGradientBrush … 【GPU で処理される】ため軽い ・RadialGradientBrush … やや重い ・大きな面(Window 全体)に 複雑なグラデーション+リサイズ を重ねると目に見えて重くなる → 一覧の【各行の背景】に凝ったグラデーションを敷くと スクロールが引っかかる。行の背景は単色が無難 ★【他の Brush】 ImageBrush … 画像を敷く VisualBrush … 【他の UI 要素をそのまま描く】★ (鏡像・反射・サムネイル表現に使う) DrawingBrush … Drawing を敷く(タイル模様)
-
WPF で 2D 平面での変換を行う「トランスフォーム処理」を使用して以下の効果を追加できる。
- 「回転、拡大縮小、傾斜、平行移動」
- 「アニメーションなど一時効果」
-
「トランスフォーム処理」に使用できるクラスには、
System.Windows.Media 名前空間の Transform クラスから派生する、下記クラスがある。
| # | クラス | 変換の内容 |
|---|---|---|
| 1 | MatrixTransform | 任意の行列による変換 |
| 2 | RotateTransform | 回転 |
| 3 | ScaleTransform | 拡大縮小 |
| 4 | SkewTransform | 傾斜(せん断) |
| 5 | TranslateTransform | 平行移動 |
| 6 | TransformGroup | 上記の組み合わせ |
移行メモ(体裁): 箇条書きを表に整理し、各クラスの変換内容を補った。
-
なお、TransformGroup を使用すれば、
複数の Transform クラスを組み合わせて適用することができる。 -
以下、下記の図形に対する、上記2つの「トランスフォーム処理」の例。
- 画面

- XAML
<Grid>
<Border Height="100" Width="100" BorderBrush="Black" BorderThickness="1" >
<Rectangle Height="100" Width="100" Fill="Blue"/>
</Border>
</Grid>-
FrameworkElement.LayoutTransform プロパティに設定された Transform クラスを使用し、
レイアウト時に「トランスフォーム処理」を実行する。- このため、「トランスフォーム処理」後も UI 要素がレイアウト時の描画領域内に収まる。
- このような特徴から、この「トランスフォーム処理」は、
表示を垂直から水平に切り替えたり、ズームしたりするなどの用途で利用可能である。
-
以下は、LayoutTransform プロパティに RotateTransform クラスを指定して
UI 要素を回転させた例である。- 画面

- XAML
<Grid>
<Border Height="100" Width="100" BorderBrush="Black" BorderThickness="1" >
<Rectangle Height="100" Width="100" Fill="Blue">
<Rectangle.LayoutTransform>
<RotateTransform CenterX="50" CenterY="50" Angle="45"/>
</Rectangle.LayoutTransform>
</Rectangle>
</Border>
</Grid>-
UIElement.RenderTransform プロパティに設定された Transform クラスを使用し、
レイアウト後に「トランスフォーム処理」しレンダリングする。- このため、場合によっては UI 要素がレイアウトの描画領域の外にでてしまう場合がある。
- このような特徴から、この「トランスフォーム処理」は、
「アニメーション」(後述)と組み合わせるなどして
UI 要素に注目を集めるなどの用途で利用可能である。
-
以下は、RenderTransform プロパティに RotateTransform クラスを指定して
UI 要素を回転させた例である。- 画面

- XAML
<Grid>
<Border Height="100" Width="100" BorderBrush="Black" BorderThickness="1" >
<Rectangle Height="100" Width="100" Fill="Blue">
<Rectangle.RenderTransform>
<RotateTransform CenterX="50" CenterY="50" Angle="45"/>
</Rectangle.RenderTransform>
</Rectangle>
</Border>
</Grid>※ なお、こちらの「トランスフォーム処理」は「Silverlight」でもサポートされる。
補足(
LayoutTransformとRenderTransformの使い分け): 原文の説明は正確だが、
図で押さえると迷わなくなる。【WPF のレイアウトの 2 パス】★ ① Measure(測る) → ② Arrange(配置する) → ③ Render(描く) LayoutTransform … 【① と ② の間】で効く → 変換後の大きさで【場所を取り直す】 → 周りの要素が【押しのけられる】 → 【レイアウトが再計算される=重い】★ RenderTransform … 【③ の直前】に効く → 場所は変換前のまま → 周りの要素は【動かない】 → はみ出す・重なる → 【GPU で処理され、極めて軽い】★【使い分けの結論】 ・アニメーション → 必ず【RenderTransform】★ (LayoutTransform でアニメすると 毎フレーム レイアウトが走り、確実にカクつく) ・縦書きラベル、90 度回転した列見出し → 【LayoutTransform】(幅と高さを入れ替えたい)★ ・ズーム機能 → スクロールと連動させたいなら LayoutTransform 見た目だけ拡大なら RenderTransform【RenderTransformOrigin が便利】★ CenterX / CenterY はピクセル指定だが、 RenderTransformOrigin は【0〜1 の相対値】で指定できる RenderTransformOrigin="0.5,0.5" ← 【中心を軸に回す】 → サイズが変わっても軸がずれない → 上のサンプルの CenterX="50" CenterY="50" は Width/Height が 100 だから中心になっているだけで、 サイズを変えると【軸がずれる】
-
Microsoft Learn > .NET API ブラウザー
-
Microsoft Learn > Windows Presentation Foundation > グラフィックスとマルチメディア
移行メモ(未執筆): 移行元では本節(「実装」「説明」「参考」の 3 つの小見出し)が
見出しのみで本文が存在しない。
XAMLの書き方(1) の「イベント トリガ」から
本節への参照があるため、見出しは残したうえで、
以下に最小限の補足を追記した。
補足(WPF のアニメーションの要点): 本節が未執筆のため、
参照元から辿った読者のために骨子を示す。【中心となる概念】★ Storyboard … アニメーションの入れ物(時間軸) XxxAnimation … 型ごとのアニメーション DoubleAnimation / ColorAnimation / PointAnimation / ThicknessAnimation Storyboard.TargetName / TargetProperty … 【何の、どのプロパティを】動かすか EventTrigger … 【いつ】始めるか<Rectangle Width="100" Height="100" Fill="Blue"> <Rectangle.RenderTransform> <RotateTransform x:Name="rt" /> <!-- 名前が要る --> </Rectangle.RenderTransform> <Rectangle.Triggers> <EventTrigger RoutedEvent="Loaded"> <!-- いつ --> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="rt" Storyboard.TargetProperty="Angle" From="0" To="360" Duration="0:0:2" RepeatBehavior="Forever" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle>【押さえどころ】 ① 【アニメーションは最優先】★ 依存関係プロパティの優先順位で【最上位】にある → アニメ中はローカル値の代入が効かない → 終わった後も FillBehavior="HoldEnd"(既定)だと 【値を握り続ける】 → 以後 SetValue が効かず「なぜか色が変わらない」 → FillBehavior="Stop" にするか、 BeginAnimation(prop, null) で解除する ★ ② 【RenderTransform を動かす】 Width / Height / Margin を動かすと 毎フレーム レイアウトが走って重い → Scale / Translate / Rotate と Opacity を動かす ★ ③ 【イージング】 EasingFunction(QuadraticEase, BackEase 等)で 等速でない自然な動きになる ④ 【VisualStateManager】 ボタンの押下・ホバーなどコントロールの状態遷移は トリガーより VisualStateManager で書く方が現代的 ★【業務アプリでの節度】 ・処理待ちの表現(ProgressRing の回転) ・エラー箇所への注意喚起(短い点滅・シェイク) 程度に留める。 画面遷移のたびに派手に動くと【操作が遅く感じる】★ OS の「アニメーションを表示しない」設定 (SystemParameters.ClientAreaAnimation)も尊重する
MVVM(Model - View - View Model)モデルとは、
前述の「データ バインディング」の仕組みを活用したデザイン パターンであり、
従来の3層デザイン パターンの「モデル オブジェクト」、「ビュー オブジェクト」に加えて、
「バインディング ソース」である「ビュー・モデル オブジェクト」を新設する
デザイン パターンである。
このデザイン パターンのメリットは、
「モデル オブジェクト」、「ビュー オブジェクト」間の結合部を、
「データ バインディング」による「ビュー・モデル オブジェクト」に限定し、
疎結合を実現できる点である。
なお、MVVM デザイン パターンのためのガイドラインには、以下のものがある。
- XAML で実装できるものは、コードビハインドに実装しない。
- 「ビュー・モデル オブジェクト」は DataContext として「ビュー オブジェクト」に渡す。
- 「ビュー・モデル オブジェクト」と「モデル オブジェクト」は、
「データ バインディング」により結合されるため、「ビュー オブジェクト」には、一切アクセスしない。 - 「ビュー・モデル オブジェクト」は、INotifyPropertyChanged インターフェイスを実装するべき。
- 「ビュー・モデル オブジェクト」は、「モデル オブジェクト」をカプセル化することで、
「ビュー オブジェクト」から、データ ストレージの複雑さを隠蔽する。
-
MSDNマガジン > 発行物 > Model - View - ViewModelデザイン パターンによるWPFアプリケーション
http://msdn.microsoft.com/ja-jp/magazine/dd419663.aspx -
Moving from Windows Applications to WPF - CodeProject
http://www.codeproject.com/KB/WPF/WPF_with_MVVM.aspx?msg=3220512

移行メモ(リンク切れ): 上記の MSDN マガジンの記事
(Josh Smith, "WPF Apps With The Model-View-ViewModel Design Pattern",
MSDN Magazine 2009年2月号)は MVVM の古典として知られるが、
MSDN マガジンのアーカイブ移行に伴い当該 URL は到達しない。
現在は Microsoft Learn のアーカイブ
(https://learn.microsoft.com/en-us/archive/msdn-magazine/2009/february/patterns-wpf-apps-with-the-model-view-viewmodel-design-pattern )で読める。
補足(ガイドライン 5 項目の現在の評価): この 5 項目は
今読んでも妥当である。現在の実装手段を対応付けておく。
# 原文のガイドライン 現在の実装手段 1 XAML で書けるものはコードビハインドに書かない バインディング / トリガー / Behavior で置き換える 2 ViewModel は DataContext で渡す DI + DataTemplate(暗黙) で自動解決 ★3 ViewModel は View に一切アクセスしない メッセンジャ / IDialogServiceで間接化 ★4 INotifyPropertyChangedを実装する[ObservableProperty](ソース ジェネレータ) ★5 ViewModel が Model をカプセル化する 変わらず 【原文が触れていない、今なら必須の 2 点】★ ① 【コマンド(ICommand)】 ボタン クリックをコードビハインドに書かないための要。 ガイドライン 1 を実現する【中心的な手段】だが 原文には登場しない。 [RelayCommand] 属性で自動生成できる ② 【非同期】 ViewModel から async でサービスを呼ぶのが標準。 ただし ICommand.Execute は void を返すため、 【AsyncRelayCommand】等の非同期対応コマンドを使う (二重実行の抑止・キャンセル・例外の受け取りが要る)【MVVM の実装ライブラリ】 ・【CommunityToolkit.Mvvm】★(旧 MvvmLight の後継、Microsoft 製) → 軽量。ソース ジェネレータで定型コードが消える → 【まずこれを選ぶ】 ・Prism … Region ナビゲーション、モジュール分割 大規模向け ・ReactiveUI … Rx ベース。学習コストは高いが強力【MVVM の「やり過ぎ」に注意】★ ガイドライン 3(View に一切アクセスしない)を 厳格に守ろうとすると、 「フォーカス移動」「スクロール位置」といった 【純粋に View の関心事】まで ViewModel から操作しようとして破綻する。 → そうした処理は【添付ビヘイビア】で View 側に置く → 後述の「フォーカス制御」で作者が 「問題が多い」と書いているのは、まさにこの領域である
-
WPF のバリデーション・フレームワークの使い方。
-
以下をプロトタイプ開発したので参考にすることができる。
- SampleProgram/UISubsystem/WPF/Validation at master · OpenTouryoProject/SampleProgram
https://github.com/OpenTouryoProject/SampleProgram/tree/master/UISubsystem/WPF/Validation
- SampleProgram/UISubsystem/WPF/Validation at master · OpenTouryoProject/SampleProgram
https://github.com/OpenTouryoProject/SampleProgram/tree/master/UISubsystem/WPF/Validation/InputField
https://github.com/OpenTouryoProject/SampleProgram/tree/master/UISubsystem/WPF/Validation/DataGrid
フォーカス制御については、いろいろ試しているが
問題が多いので使わない方向で考えた方が良さそう。
補足(バリデーションの選択肢と、フォーカス制御の難しさ): 本章は
サンプル コードへのリンクのみで本文がないため、要点を補う。【WPF のバリデーションの 4 つのやり方】★ ① 【ValidationRule】(WPF 独自) <Binding.ValidationRules> に置く。 → View 側に検証ロジックが来るので MVVM と相性が悪い ② 【IDataErrorInfo】 ViewModel が this[string columnName] を実装。 → ValidatesOnDataErrors=True で有効化 → 単純だが【1 項目 1 エラー】しか返せない ③ 【INotifyDataErrorInfo】★(.NET 4.5 以降・推奨) → 【1 項目に複数エラー】を返せる → 【非同期検証】ができる(サーバ問い合わせ等) → ValidatesOnNotifyDataErrors=True(既定で True) → CommunityToolkit.Mvvm の ObservableValidator が実装済みで、 【DataAnnotations 属性】と組み合わせられる ★ [Required(ErrorMessage = "必須です")] [MaxLength(10)] [ObservableProperty] private string _name; ④ 【例外を投げる】(ValidatesOnExceptions) → セッターで throw する。非推奨 ★【エラーの見せ方】 既定では【赤い枠】が出るだけで理由が見えない。 → Validation.ErrorTemplate を差し替えるか、 ツールチップにエラー内容を出す(前述) ToolTip="{Binding (Validation.Errors)/ErrorContent, RelativeSource={RelativeSource Self}}"【「一覧のバリデーション」が難しい理由】★ DataGrid は ・行単位(IEditableObject / RowValidationRules) ・セル単位(列の Binding の検証) の【2 層】で検証が走り、 「行を抜けるまでコミットされない」ため エラー表示のタイミングが直感に反する。 → CommittingEdit / RowEditEnding を理解しないと 【エラーのまま次の行に進める】等の穴ができる【フォーカス制御が「問題が多い」理由】★ 作者の結論(使わない方向)は【妥当】である。 ・WPF のフォーカスは【論理フォーカス】と 【キーボード フォーカス】の【2 種類】がある → FocusManager.FocusedElement と Keyboard.Focus() が 別物で、片方だけ設定しても効かないことがある ・Loaded の時点では【まだフォーカスできない】ことがある → Dispatcher.BeginInvoke(DispatcherPriority.Input, ...) で遅延させる回避策が必要 ・テンプレート内の実体(PART_EditableTextBox 等)に 当てないと効かないコントロールがある ・仮想化された一覧では【要素が存在しない】ことがある 【現実的な落としどころ】 ・初期フォーカスだけ設定する(FocusManager.FocusedElement) ・Tab 順は【TabIndex と KeyboardNavigation.TabNavigation】 で宣言的に整える ★ ・「エラー箇所へ自動でフォーカス」は 【添付ビヘイビア】に隔離し、 効かない環境があることを前提に設計する
Tags: 移行, .NET開発, UIサブシステム, WPF/Silverlight, XAML