Symfoware

Symfowareについての考察blog

C# + WPF + XAML コマンドのバインドとメニューバー

テキストエディタ + csc.exeだけで、どこまでWPFなアプリケーションが
作成できるか挑戦中。

テキストエディタでWPF + XAML データバインド

データのバインドができたので、イベント(コマンド)のバインドについて
調べてみます。



サンプルのメモ帳



[C#/XAML] WPF と Windows フォームのメニューの違い (Windows フォームから WPF へ)
こちらを参考にさせて頂きました。


画面を構成する「win.xaml」はこんな感じ。


  1. <Window
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. Title="WPFメモ帳"
  5. Width="320"
  6. Height="240"
  7. FontSize="20"
  8. WindowStartupLocation="CenterScreen"
  9. >
  10.     <DockPanel>
  11.     
  12.         <Menu Name="menu1" DockPanel.Dock="Top">
  13.             <MenuItem Header="File">
  14.                 <MenuItem Header="Open" Name="Command_Open" />
  15.                 <MenuItem Header="Exit" Name="Command_Exit" />
  16.             </MenuItem>
  17.             <MenuItem Header="Edit">
  18.                 <MenuItem Command="ApplicationCommands.Copy" />
  19.                 <MenuItem Command="ApplicationCommands.Cut" />
  20.                 <MenuItem Command="ApplicationCommands.Paste" />
  21.          </MenuItem>
  22.         </Menu>
  23.     
  24.         <TextBox Name="textBox1" DockPanel.Dock="Top" AcceptsReturn="True"
  25.             VerticalScrollBarVisibility="Visible"
  26.             HorizontalScrollBarVisibility="Visible" />
  27.     
  28.     </DockPanel>
  29.     
  30. </Window>




プログラム本体の「Sample.cs」はこんな感じ。


  1. using System;
  2. using System.IO;
  3. using System.Collections.Generic;
  4. using System.Xml;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using System.Windows.Markup;
  8. using System.Windows.Input;
  9. public class Sample {
  10.     private static Window win;
  11.     [STAThread]
  12.     static void Main(string[] args) {
  13.     
  14.         win = new Window();
  15.     
  16.         // 作成したwin.xamlを読み込んで、Windowsオブジェクトを取得
  17.         using (FileStream infs = new FileStream("win.xaml", FileMode.Open)) {
  18.             XmlReader xmlReader = XmlReader.Create(infs);
  19.             win = (Window)XamlReader.Load(xmlReader);
  20.         }
  21.         
  22.         // メニューの[File] - [Open]
  23.         MenuItem item = (MenuItem)win.FindName("Command_Open");
  24.         item.Click += new RoutedEventHandler(File_Open);
  25.         
  26.         // メニューの[File] - [Close]
  27.         item = (MenuItem)win.FindName("Command_Exit");
  28.         item.Click += new RoutedEventHandler(Window_Close);
  29.         
  30.         // Window表示
  31.         var app = new Application();
  32.         app.Run(win);
  33.     }
  34.     
  35.     // ファイルのオープン処理
  36.     private static void File_Open(object sender, RoutedEventArgs e) {
  37.      var dlg = new Microsoft.Win32.OpenFileDialog();
  38.      if (dlg.ShowDialog() == false) {
  39.          return;
  40.      }
  41.     
  42.      TextBox textBox = (TextBox)win.FindName("textBox1");
  43.     
  44.      using (var reader = new System.IO.StreamReader(dlg.FileName)) {
  45.          textBox.Text = reader.ReadToEnd();
  46.      }
  47.     }
  48.     
  49.     // プログラム終了処理
  50.     private static void Window_Close(object sender, RoutedEventArgs e) {
  51.         win.Close();
  52.     }
  53. }




ビルドするためのバッチファイル「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でビルドした場合はこんな感じになります。

180_01.png

180_02.png

180_03.png


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

180_04.png

180_05.png

180_06.png


.NETのバージョンによってファイルの保存ダイアログの形式が変わるようです。

xamlで、MenuItemに「Command="ApplicationCommands.Copy"」の
ように指定していますが、これが非常に便利。

コードを書かなくても自動的にテキストの状態を判断して、Enable制御をおこなってくれます。

180_07.png

180_08.png






ClickからCommandへ



メニューアイテムのClickに「RoutedEventHandler」を追加して、
ファイルオープンやアプリケーション終了のイベントを付与しました。

ファイルのオープンを、コピーやペーストに使用しているような
「Command」を使う方式に変更してみます。


まだ十分に理解していないのですが、まず「RoutedCommand」を作成する。
これが、Windowオブジェクトとコントロールを結びつけるオブジェクト

次に「CommandBinding」を作成して、「RoutedCommand」が呼び出された時
具体的になんの処理を実行するか結びつけます。

今回だと、「CommandBinding」にプログラムに記載している関数
「File_Open」と、実行確認を行う「File_Open_CanExecute」を登録しました。


作成した「CommandBinding」はWindowオブジェクトに追加しておきます。
メニューアイテムのCommandに指定するのは「RoutedCommand」になります。


  1. using System;
  2. using System.IO;
  3. using System.Collections.Generic;
  4. using System.Xml;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using System.Windows.Markup;
  8. using System.Windows.Input;
  9. public class Sample {
  10.     private static Window win;
  11.     
  12.     [STAThread]
  13.     static void Main(string[] args) {
  14.     
  15.         win = new Window();
  16.     
  17.         // 作成したwin.xamlを読み込んで、Windowsオブジェクトを取得
  18.         using (FileStream infs = new FileStream("win.xaml", FileMode.Open)) {
  19.             XmlReader xmlReader = XmlReader.Create(infs);
  20.             win = (Window)XamlReader.Load(xmlReader);
  21.         }
  22.         
  23.         // RoutedCommandを作成
  24.         // このオブジェクトを通じて、コマンドの制御が行われる模様
  25.         RoutedCommand fileCommand = new RoutedCommand();
  26.         
  27.         // CommandBindingを作成
  28.         // RoutedCommandと、そのコマンドの実行に関する関数を指定
  29.         // fileCommandは、
  30.         // ・実行して良いかの判断は「File_Open_CanExecute」で行う
  31.         // ・実際に実行する処理は「File_Open」である
  32.         CommandBinding fileCommandBinding = new CommandBinding(
  33.             fileCommand, File_Open, File_Open_CanExecute);
  34.         
  35.         /* コンストラクタで一括指定しないパターン
  36.         CommandBinding fileCommandBinding = new CommandBinding();
  37.         fileCommandBinding.Command = fileCommand;
  38.         fileCommandBinding.Executed += File_Open;
  39.         fileCommandBinding.CanExecute += File_Open_CanExecute;
  40.         */
  41.         
  42.         // 作成したCommandBindingをWindowオブジェクトに登録
  43.         win.CommandBindings.Add(fileCommandBinding);
  44.         
  45.         
  46.         // メニューの[File] - [Open]
  47.         MenuItem item = (MenuItem)win.FindName("Command_Open");
  48.         // ClickではなくCommandに登録
  49.         //item.Click += new RoutedEventHandler(File_Open);
  50.         item.Command = fileCommand;
  51.         
  52.         // メニューの[File] - [Close]
  53.         item = (MenuItem)win.FindName("Command_Exit");
  54.         item.Click += new RoutedEventHandler(Window_Close);
  55.         
  56.         // Window表示
  57.         var app = new Application();
  58.         app.Run(win);
  59.     }
  60.     
  61.     // ファイルのオープン処理
  62.     private static void File_Open(object sender, RoutedEventArgs e) {
  63.      var dlg = new Microsoft.Win32.OpenFileDialog();
  64.      if (dlg.ShowDialog() == false) {
  65.          return;
  66.      }
  67.     
  68.      TextBox textBox = (TextBox)win.FindName("textBox1");
  69.     
  70.      using (var reader = new System.IO.StreamReader(dlg.FileName)) {
  71.          textBox.Text = reader.ReadToEnd();
  72.      }
  73.     }
  74.     
  75.     // ファイルオープンのコマンドを実行して良いかの確認
  76.     private static void File_Open_CanExecute(object sender, CanExecuteRoutedEventArgs e) {
  77.         Control target = e.Source as Control;
  78.         
  79.         Panel panel = null;
  80.         while(true) {
  81.             if (target.Parent is Panel) {
  82.                 panel = target.Parent as Panel;
  83.                 break;
  84.             }
  85.             target = target.Parent as Control;
  86.         }
  87.         
  88.         TextBox textBox = panel.FindName("textBox1") as TextBox;
  89.         if (textBox == null) {
  90.             e.CanExecute = true;
  91.             return;
  92.         }
  93.         
  94.         e.CanExecute = (textBox.Text.Length == 0);;
  95.     }
  96.     
  97.     // プログラム終了処理
  98.     private static void Window_Close(object sender, RoutedEventArgs e) {
  99.         win.Close();
  100.     }
  101. }




・Windowがメニューアイテムの処理を実行しようとする
・メニューアイテムに登録されているCommandを取得する
・取得したCommandから、Window.CommandBindingsに登録されている内容を検索。
・CommandBindingを取得し、処理を実行。

こんなイメージでしょうか。


実行してみると、テキストボックスに何も入力されていない場合は
ファイルオープンが有効。

180_09.png


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

180_10.png








Commandのバインド



「Click」だと「+=」演算子を使用してイベントを追加しなくてはいけませんが、
「Command」だと「=」演算子でイベントの設定が可能なので、
データバインドが使えます。

「win.xaml」はこんな感じ。


  1. <Window
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. Title="WPFメモ帳"
  5. Width="320"
  6. Height="240"
  7. FontSize="20"
  8. WindowStartupLocation="CenterScreen"
  9. >
  10.     <DockPanel>
  11.     
  12.         <Menu Name="menu1" DockPanel.Dock="Top">
  13.             <MenuItem Header="File">
  14.                 <MenuItem Header="Open" Name="Command_Open"
  15.                 Command="{Binding File_Open}" />
  16.                 <MenuItem Header="Exit" Name="Command_Exit" />
  17.             </MenuItem>
  18.             <MenuItem Header="Edit">
  19.                 <MenuItem Command="ApplicationCommands.Copy" />
  20.                 <MenuItem Command="ApplicationCommands.Cut" />
  21.                 <MenuItem Command="ApplicationCommands.Paste" />
  22.          </MenuItem>
  23.         </Menu>
  24.     
  25.         <TextBox Name="textBox1" DockPanel.Dock="Top" AcceptsReturn="True"
  26.             VerticalScrollBarVisibility="Visible"
  27.             HorizontalScrollBarVisibility="Visible" />
  28.     
  29.     </DockPanel>
  30.     
  31. </Window>




この部分がミソ。

Command="{Binding File_Open}"



「File_Open」という名前(任意)でバインドするようにしておきました。


ソースはこんな感じ。


  1. using System;
  2. using System.IO;
  3. using System.Collections.Generic;
  4. using System.Xml;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using System.Windows.Markup;
  8. using System.Windows.Input;
  9. public class Sample {
  10.     private static Window win;
  11.     
  12.     [STAThread]
  13.     static void Main(string[] args) {
  14.     
  15.         win = new Window();
  16.     
  17.         // 作成したwin.xamlを読み込んで、Windowsオブジェクトを取得
  18.         using (FileStream infs = new FileStream("win.xaml", FileMode.Open)) {
  19.             XmlReader xmlReader = XmlReader.Create(infs);
  20.             win = (Window)XamlReader.Load(xmlReader);
  21.         }
  22.         
  23.         // RoutedCommandを作成
  24.         // このオブジェクトを通じて、コマンドの制御が行われる模様
  25.         RoutedCommand fileCommand = new RoutedCommand();
  26.         
  27.         // CommandBindingを作成
  28.         // RoutedCommandと、そのコマンドの実行に関する関数を指定
  29.         // fileCommandは、
  30.         // ・実行して良いかの判断は「File_Open_CanExecute」で行う
  31.         // ・実際に実行する処理は「File_Open」である
  32.         CommandBinding fileCommandBinding = new CommandBinding(
  33.             fileCommand, File_Open, File_Open_CanExecute);
  34.         
  35.         /* コンストラクタで一括指定しないパターン
  36.         CommandBinding fileCommandBinding = new CommandBinding();
  37.         fileCommandBinding.Command = fileCommand;
  38.         fileCommandBinding.Executed += File_Open;
  39.         fileCommandBinding.CanExecute += File_Open_CanExecute;
  40.         */
  41.         
  42.         // 作成したCommandBindingをWindowオブジェクトに登録
  43.         win.CommandBindings.Add(fileCommandBinding);
  44.         
  45.         
  46.         // メニューの[File] - [Open]
  47.         // MenuItem item = (MenuItem)win.FindName("Command_Open");
  48.         //// ClickではなくCommandに登録
  49.         ////item.Click += new RoutedEventHandler(File_Open);
  50.         //item.Command = fileCommand;
  51.         
  52.         //バインドで、Commandイベントを設定
  53.         win.DataContext = new { File_Open = fileCommand};
  54.         
  55.         
  56.         // メニューの[File] - [Close]
  57.         MenuItem item = (MenuItem)win.FindName("Command_Exit");
  58.         item.Click += new RoutedEventHandler(Window_Close);
  59.         
  60.         // Window表示
  61.         var app = new Application();
  62.         app.Run(win);
  63.     }
  64.     
  65.     // ファイルのオープン処理
  66.     private static void File_Open(object sender, RoutedEventArgs e) {
  67.      var dlg = new Microsoft.Win32.OpenFileDialog();
  68.      if (dlg.ShowDialog() == false) {
  69.          return;
  70.      }
  71.     
  72.      TextBox textBox = (TextBox)win.FindName("textBox1");
  73.     
  74.      using (var reader = new System.IO.StreamReader(dlg.FileName)) {
  75.          textBox.Text = reader.ReadToEnd();
  76.      }
  77.     }
  78.     
  79.     // ファイルオープンのコマンドを実行して良いかの確認
  80.     private static void File_Open_CanExecute(object sender, CanExecuteRoutedEventArgs e) {
  81.         Control target = e.Source as Control;
  82.         
  83.         Panel panel = null;
  84.         while(true) {
  85.             if (target.Parent is Panel) {
  86.                 panel = target.Parent as Panel;
  87.                 break;
  88.             }
  89.             target = target.Parent as Control;
  90.         }
  91.         
  92.         TextBox textBox = panel.FindName("textBox1") as TextBox;
  93.         if (textBox == null) {
  94.             e.CanExecute = true;
  95.             return;
  96.         }
  97.         
  98.         e.CanExecute = (textBox.Text.Length == 0);;
  99.     }
  100.     
  101.     // プログラム終了処理
  102.     private static void Window_Close(object sender, RoutedEventArgs e) {
  103.         win.Close();
  104.     }
  105. }




この部分がミソ。

//バインドで、Commandイベントを設定
win.DataContext = new { File_Open = fileCommand};




これで、わざわざMenuItemをFindNameで引っ貼り出さなくても、
イベントの追加が行えました。


関連記事

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2013/01/20(日) 12:31:16|
  2. 備忘録
  3. | トラックバック:0
  4. | コメント:1
  5. | 編集
<<C# + WPF + XAML ツリービューとデータバインド | ホーム | テキストエディタでWPF + XAML データバインド>>

コメント

バインド名

分かりやすく、楽しく読ませてもらっています。
「Commandのバインド」の項で、「File_Open」という名前(任意)でバインドされていますが、「File_Open」は同名のメソッドがあるため分かり難くなっています。
重複しない名前の方がいいのではないでしょうか。
  1. 2017/06/21(水) 11:10:02 |
  2. URL |
  3. C♯勉強中 #VL.AffNg
  4. [ 編集 ]

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
https://symfoware.blog.fc2.com/tb.php/1090-d643802e
この記事にトラックバックする(FC2ブログユーザー)