C# + WPF + XAML コマンドのバインドとメニューバー
テキストエディタ + csc.exeだけで、どこまでWPFなアプリケーションが作成できるか挑戦中。
テキストエディタでWPF + XAML データバインド
データのバインドができたので、イベント(コマンド)のバインドについて
調べてみます。
サンプルのメモ帳
[C#/XAML] WPF と Windows フォームのメニューの違い (Windows フォームから WPF へ)
こちらを参考にさせて頂きました。
画面を構成する「win.xaml」はこんな感じ。
- <Window
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="WPFメモ帳"
- Width="320"
- Height="240"
- FontSize="20"
- WindowStartupLocation="CenterScreen"
- >
- <DockPanel>
- <Menu Name="menu1" DockPanel.Dock="Top">
- <MenuItem Header="File">
- <MenuItem Header="Open" Name="Command_Open" />
- <MenuItem Header="Exit" Name="Command_Exit" />
- </MenuItem>
- <MenuItem Header="Edit">
- <MenuItem Command="ApplicationCommands.Copy" />
- <MenuItem Command="ApplicationCommands.Cut" />
- <MenuItem Command="ApplicationCommands.Paste" />
- </MenuItem>
- </Menu>
- <TextBox Name="textBox1" DockPanel.Dock="Top" AcceptsReturn="True"
- VerticalScrollBarVisibility="Visible"
- HorizontalScrollBarVisibility="Visible" />
- </DockPanel>
- </Window>
プログラム本体の「Sample.cs」はこんな感じ。
- using System;
- using System.IO;
- using System.Collections.Generic;
- using System.Xml;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Markup;
- using System.Windows.Input;
- public class Sample {
- private static Window win;
- [STAThread]
- static void Main(string[] args) {
- win = new Window();
- // 作成したwin.xamlを読み込んで、Windowsオブジェクトを取得
- using (FileStream infs = new FileStream("win.xaml", FileMode.Open)) {
- XmlReader xmlReader = XmlReader.Create(infs);
- win = (Window)XamlReader.Load(xmlReader);
- }
- // メニューの[File] - [Open]
- MenuItem item = (MenuItem)win.FindName("Command_Open");
- item.Click += new RoutedEventHandler(File_Open);
- // メニューの[File] - [Close]
- item = (MenuItem)win.FindName("Command_Exit");
- item.Click += new RoutedEventHandler(Window_Close);
- // Window表示
- var app = new Application();
- app.Run(win);
- }
- // ファイルのオープン処理
- private static void File_Open(object sender, RoutedEventArgs e) {
- var dlg = new Microsoft.Win32.OpenFileDialog();
- if (dlg.ShowDialog() == false) {
- return;
- }
- TextBox textBox = (TextBox)win.FindName("textBox1");
- using (var reader = new System.IO.StreamReader(dlg.FileName)) {
- textBox.Text = reader.ReadToEnd();
- }
- }
- // プログラム終了処理
- private static void Window_Close(object sender, RoutedEventArgs e) {
- win.Close();
- }
- }
ビルドするためのバッチファイル「build.bat」はこんな感じ。
@echo off
if "%1"=="3" (
goto NET3
)
echo .NET 4
set csc="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe"
set opt=/nologo /r:WPF\PresentationCore.dll;WPF\PresentationFramework.dll;WPF\WindowsBase.dll;System.Xaml.dll
set opt=%opt% /target:winexe
set opt=%opt% /debug
%csc% %opt% Sample.cs
goto END
:NET3
echo .NET 3
set csc="C:\Windows\Microsoft.NET\Framework64\v3.5\csc.exe"
set opt=/nologo /r:PresentationCore.dll;PresentationFramework.dll;WindowsBase.dll
set opt=%opt% /target:winexe
set opt=%opt% /debug
%csc% %opt% Sample.cs
:END
一応、.Net 3.5と4で動作を確認してます。
ビルドのオプションを変更するのが面倒になってきたので、
引数なしだと.Net 4
C:\> build.bat
引数に3を指定すると.NET 3.5でビルドするようにしました。
C:\> build.bat 3
まず、.NET 3.5でビルドした場合はこんな感じになります。



続いて、.NET 4でビルドした場合。



.NETのバージョンによってファイルの保存ダイアログの形式が変わるようです。
xamlで、MenuItemに「Command="ApplicationCommands.Copy"」の
ように指定していますが、これが非常に便利。
コードを書かなくても自動的にテキストの状態を判断して、Enable制御をおこなってくれます。


ClickからCommandへ
メニューアイテムのClickに「RoutedEventHandler」を追加して、
ファイルオープンやアプリケーション終了のイベントを付与しました。
ファイルのオープンを、コピーやペーストに使用しているような
「Command」を使う方式に変更してみます。
まだ十分に理解していないのですが、まず「RoutedCommand」を作成する。
これが、Windowオブジェクトとコントロールを結びつけるオブジェクト
次に「CommandBinding」を作成して、「RoutedCommand」が呼び出された時
具体的になんの処理を実行するか結びつけます。
今回だと、「CommandBinding」にプログラムに記載している関数
「File_Open」と、実行確認を行う「File_Open_CanExecute」を登録しました。
作成した「CommandBinding」はWindowオブジェクトに追加しておきます。
メニューアイテムのCommandに指定するのは「RoutedCommand」になります。
- using System;
- using System.IO;
- using System.Collections.Generic;
- using System.Xml;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Markup;
- using System.Windows.Input;
- public class Sample {
- private static Window win;
- [STAThread]
- static void Main(string[] args) {
- win = new Window();
- // 作成したwin.xamlを読み込んで、Windowsオブジェクトを取得
- using (FileStream infs = new FileStream("win.xaml", FileMode.Open)) {
- XmlReader xmlReader = XmlReader.Create(infs);
- win = (Window)XamlReader.Load(xmlReader);
- }
- // RoutedCommandを作成
- // このオブジェクトを通じて、コマンドの制御が行われる模様
- RoutedCommand fileCommand = new RoutedCommand();
- // CommandBindingを作成
- // RoutedCommandと、そのコマンドの実行に関する関数を指定
- // fileCommandは、
- // ・実行して良いかの判断は「File_Open_CanExecute」で行う
- // ・実際に実行する処理は「File_Open」である
- CommandBinding fileCommandBinding = new CommandBinding(
- fileCommand, File_Open, File_Open_CanExecute);
- /* コンストラクタで一括指定しないパターン
- CommandBinding fileCommandBinding = new CommandBinding();
- fileCommandBinding.Command = fileCommand;
- fileCommandBinding.Executed += File_Open;
- fileCommandBinding.CanExecute += File_Open_CanExecute;
- */
- // 作成したCommandBindingをWindowオブジェクトに登録
- win.CommandBindings.Add(fileCommandBinding);
- // メニューの[File] - [Open]
- MenuItem item = (MenuItem)win.FindName("Command_Open");
- // ClickではなくCommandに登録
- //item.Click += new RoutedEventHandler(File_Open);
- item.Command = fileCommand;
- // メニューの[File] - [Close]
- item = (MenuItem)win.FindName("Command_Exit");
- item.Click += new RoutedEventHandler(Window_Close);
- // Window表示
- var app = new Application();
- app.Run(win);
- }
- // ファイルのオープン処理
- private static void File_Open(object sender, RoutedEventArgs e) {
- var dlg = new Microsoft.Win32.OpenFileDialog();
- if (dlg.ShowDialog() == false) {
- return;
- }
- TextBox textBox = (TextBox)win.FindName("textBox1");
- using (var reader = new System.IO.StreamReader(dlg.FileName)) {
- textBox.Text = reader.ReadToEnd();
- }
- }
- // ファイルオープンのコマンドを実行して良いかの確認
- private static void File_Open_CanExecute(object sender, CanExecuteRoutedEventArgs e) {
- Control target = e.Source as Control;
- Panel panel = null;
- while(true) {
- if (target.Parent is Panel) {
- panel = target.Parent as Panel;
- break;
- }
- target = target.Parent as Control;
- }
- TextBox textBox = panel.FindName("textBox1") as TextBox;
- if (textBox == null) {
- e.CanExecute = true;
- return;
- }
- e.CanExecute = (textBox.Text.Length == 0);;
- }
- // プログラム終了処理
- private static void Window_Close(object sender, RoutedEventArgs e) {
- win.Close();
- }
- }
・Windowがメニューアイテムの処理を実行しようとする
・メニューアイテムに登録されているCommandを取得する
・取得したCommandから、Window.CommandBindingsに登録されている内容を検索。
・CommandBindingを取得し、処理を実行。
こんなイメージでしょうか。
実行してみると、テキストボックスに何も入力されていない場合は
ファイルオープンが有効。

何か入力されているときは使用不可という狙い通りの動作になってくれました。

Commandのバインド
「Click」だと「+=」演算子を使用してイベントを追加しなくてはいけませんが、
「Command」だと「=」演算子でイベントの設定が可能なので、
データバインドが使えます。
「win.xaml」はこんな感じ。
- <Window
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="WPFメモ帳"
- Width="320"
- Height="240"
- FontSize="20"
- WindowStartupLocation="CenterScreen"
- >
- <DockPanel>
- <Menu Name="menu1" DockPanel.Dock="Top">
- <MenuItem Header="File">
- <MenuItem Header="Open" Name="Command_Open"
- Command="{Binding File_Open}" />
- <MenuItem Header="Exit" Name="Command_Exit" />
- </MenuItem>
- <MenuItem Header="Edit">
- <MenuItem Command="ApplicationCommands.Copy" />
- <MenuItem Command="ApplicationCommands.Cut" />
- <MenuItem Command="ApplicationCommands.Paste" />
- </MenuItem>
- </Menu>
- <TextBox Name="textBox1" DockPanel.Dock="Top" AcceptsReturn="True"
- VerticalScrollBarVisibility="Visible"
- HorizontalScrollBarVisibility="Visible" />
- </DockPanel>
- </Window>
この部分がミソ。
Command="{Binding File_Open}"
「File_Open」という名前(任意)でバインドするようにしておきました。
ソースはこんな感じ。
- using System;
- using System.IO;
- using System.Collections.Generic;
- using System.Xml;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Markup;
- using System.Windows.Input;
- public class Sample {
- private static Window win;
- [STAThread]
- static void Main(string[] args) {
- win = new Window();
- // 作成したwin.xamlを読み込んで、Windowsオブジェクトを取得
- using (FileStream infs = new FileStream("win.xaml", FileMode.Open)) {
- XmlReader xmlReader = XmlReader.Create(infs);
- win = (Window)XamlReader.Load(xmlReader);
- }
- // RoutedCommandを作成
- // このオブジェクトを通じて、コマンドの制御が行われる模様
- RoutedCommand fileCommand = new RoutedCommand();
- // CommandBindingを作成
- // RoutedCommandと、そのコマンドの実行に関する関数を指定
- // fileCommandは、
- // ・実行して良いかの判断は「File_Open_CanExecute」で行う
- // ・実際に実行する処理は「File_Open」である
- CommandBinding fileCommandBinding = new CommandBinding(
- fileCommand, File_Open, File_Open_CanExecute);
- /* コンストラクタで一括指定しないパターン
- CommandBinding fileCommandBinding = new CommandBinding();
- fileCommandBinding.Command = fileCommand;
- fileCommandBinding.Executed += File_Open;
- fileCommandBinding.CanExecute += File_Open_CanExecute;
- */
- // 作成したCommandBindingをWindowオブジェクトに登録
- win.CommandBindings.Add(fileCommandBinding);
- // メニューの[File] - [Open]
- // MenuItem item = (MenuItem)win.FindName("Command_Open");
- //// ClickではなくCommandに登録
- ////item.Click += new RoutedEventHandler(File_Open);
- //item.Command = fileCommand;
- //バインドで、Commandイベントを設定
- win.DataContext = new { File_Open = fileCommand};
- // メニューの[File] - [Close]
- MenuItem item = (MenuItem)win.FindName("Command_Exit");
- item.Click += new RoutedEventHandler(Window_Close);
- // Window表示
- var app = new Application();
- app.Run(win);
- }
- // ファイルのオープン処理
- private static void File_Open(object sender, RoutedEventArgs e) {
- var dlg = new Microsoft.Win32.OpenFileDialog();
- if (dlg.ShowDialog() == false) {
- return;
- }
- TextBox textBox = (TextBox)win.FindName("textBox1");
- using (var reader = new System.IO.StreamReader(dlg.FileName)) {
- textBox.Text = reader.ReadToEnd();
- }
- }
- // ファイルオープンのコマンドを実行して良いかの確認
- private static void File_Open_CanExecute(object sender, CanExecuteRoutedEventArgs e) {
- Control target = e.Source as Control;
- Panel panel = null;
- while(true) {
- if (target.Parent is Panel) {
- panel = target.Parent as Panel;
- break;
- }
- target = target.Parent as Control;
- }
- TextBox textBox = panel.FindName("textBox1") as TextBox;
- if (textBox == null) {
- e.CanExecute = true;
- return;
- }
- e.CanExecute = (textBox.Text.Length == 0);;
- }
- // プログラム終了処理
- private static void Window_Close(object sender, RoutedEventArgs e) {
- win.Close();
- }
- }
この部分がミソ。
//バインドで、Commandイベントを設定
win.DataContext = new { File_Open = fileCommand};
これで、わざわざMenuItemをFindNameで引っ貼り出さなくても、
イベントの追加が行えました。
- 関連記事
-
- Ubuntu + Chrome 24.0.1312.57でFlushの動画が再生できない
- C# + WPF + XAML ツリービューとデータバインド
- C# + WPF + XAML コマンドのバインドとメニューバー
- テキストエディタでWPF + XAML データバインド
- テキストエディタでWPF + XAML サブフォームの表示とパネルの入れ替え
コメント
バインド名
「Commandのバインド」の項で、「File_Open」という名前(任意)でバインドされていますが、「File_Open」は同名のメソッドがあるため分かり難くなっています。
重複しない名前の方がいいのではないでしょうか。
2017/06/21 11:10 by C♯勉強中 URL 編集