忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

DelegateCommandパターン。

 忘れては思い出しまた忘れないうちにメモしておこうシリーズ第二弾です。
今回はWPFで使われるコマンドのある種の使い方についてメモしておこうと思います。
DelegateCommandパターンです。

WPFに限った話ではありませんが、ある操作をするとき、ボタンをクリックしたり右クリックしてからメニューをクリックしたりあるいはキーボードのショートカットを使ったり複数の方法があるのが一般的です。
それぞれに別のイベントハンドラを使うのは美しくありません。
それに操作が実行不可能なときボタンを無効状態にしたりしますが、それも煩雑でめんどくさいものです。
そこで全部まとめてしまえるのがコマンドです。
具体的にはテキストのカットやペースト、ファイルを開くといった動作などいろいろなコマンドがあります。
コマンドが実行不可能なときには、コマンドをデータバインディングしたUIは自動的に使えなくなります。

コマンドはユーザー定義が可能です。
ICommandから継承するのです。
しかし継承したクラスにハードコードすると融通がきかないので、デリゲートを引数にとって柔軟性を持たせることがあります。
それがDelegateCommandパターンです。

なんのことだか分かりにくいのでコードを書いておきます。
かなり短くしました。

using System.Windows.Input;

namespace WpfApplication1
{
    public class TestViewModel
    {
        public DelegateCommand SqrtCommand { get; private set; }
        public double Number { get; set; }

        public TestViewModel()
        {
            SqrtCommand = new DelegateCommand(
                () => { System.Windows.MessageBox.Show(System.Math.Sqrt(Number).ToString()); },
                () => Number >= 0
                );
        }
    }

    public class DelegateCommand : ICommand
    {
        System.Action execute;
        System.Func<bool> canExecute;

        public bool CanExecute(object parameter)
        {
            return canExecute();
        }

        public event System.EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameter)
        {
            execute();
        }

        public DelegateCommand(System.Action execute, System.Func<bool> canExecute)
        {
            this.execute = execute;
            this.canExecute = canExecute;
        }
    }
}

これがViewModelのクラスです。
√を計算します。
たとえばNumberが9だったら3を、4だったら2を表示するわけですね。

その下にDelegateCommandクラスがあります。
今回の主役です。
これはユーザー定義のコマンドで、コマンドが「実行可能かどうか」と「何を実行するか」を司るのです。
これを次にデータバインドでボタンに突っ込むわけです。

XAMLのコードはこうなります。

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:src="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="150" Width="225">
    <Window.Resources>
        <src:TestViewModel x:Key="viewModel"/>
    </Window.Resources>
    
    <StackPanel DataContext="{StaticResource viewModel}">
        <TextBox Text="{Binding Number}"/>
        <Button Content="sqrt" Command="{Binding SqrtCommand}"/>
    </StackPanel>
</Window>


スタックパネルで2つのコントロールを表示しています。
テキストボックスと、その下にボタンです。
テキストボックスには√する数字が入ります。
ボタンをクリックするとコマンドが実行されるようになっています。

実行するとこんな感じです。

wpfDelegateCommandSample.jpg

さてテキストボックスに数字の4を入れてみましょう。
そしてsqrtボタンをクリックしてください。
するとこうなります:

wpfDelegateCommandSampleSqrt4.jpg

√4 = 2

です。
予想どうりの結果ですね。

さて、今度はテキストボックスに-4を入れてみましょう。
そしてsqrtボタンをクリックしてください。
すると・・・

wpfDelegateCommandSampleSqrt-4.jpg

なんと!!
ボタンがかってに無効状態になりました!
どんなに頑張っても押せません。

コマンドオブジェクトのCanExecuteプロパティがfalseなので、それをボタンが読み取って無効状態になってくれたのです。

ここではコマンドを実行するUIはボタンひとつだけですが、例えばメニューで実行出来るようにしても同じことです。
メニューとボタン両方が無効状態になってくれます。
-4などという値になったときには。

ちなみにこのサンプルプログラム、あまりに単純化しすぎてボタンが無効状態から戻ってくれません。
この問題はクリック出来るUIの数の少なさに由来します。
しかし普通のアプリケーションレベルのUIでは、戻ってくれます。
sqrtボタンの下にもう一つ別のボタンを作り、それをクリックすると、テキストボックスの中身が0以上なら、無効化状態から戻ってくれます。













拍手[2回]

PR

DataTriggerの使い方(WPF)

個人的に WPFは覚えたと思ってしばらく経つとすぐ忘れるということを繰り返しています。
恥ずかしながらDataTriggerも以前使ったはずなのに、すっかり使い方を忘れてしまいました。

二度と忘れないよう、DataTriggerの使い方をメモしておこうと思います。
と言ってもしばらく立つと忘れるんでしょうねーと思いつつ。


目的

DataTriggerはどういうときに使うのか?
それはあるデータがある値を取るとき、あることをする、というものです。

なんのこっちゃなので、具体例を出しましょう。
ウィンドウの中にチェックボックスが一つあったとします。

wpfDataTriggerNotChecked.jpg


これにチェックを入れるとCheckBoxのContentプロパティが変わって欲しいとしましょう。

wpfDataTriggerChecked.jpg


Contentプロパティが

""→"Checked!!"

と変わったのです。
チェックを外すとテキストは元に戻ります。
"Checked!!"→""

これを実現するにはC#のコードを書く必要はなく(DataTriggerを使う必要もなく)、下のようなXAMLで十分です。

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="150" Width="225">
    <CheckBox>
        <CheckBox.Style>
            <Style TargetType="CheckBox">
                <Style.Triggers>
                    <Trigger Property="IsChecked" Value="true">
                        <Setter Property="Content" Value="Checked!!"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </CheckBox.Style>
    </CheckBox>
</Window>

つまりCheckBoxのIsCheckedがtrueになったとき、CheckBoxのContentを"Checked!!"にする、という意味です。

DataTriggerを使う

さて、上の例ではこのXAMLで十分ですが、実際にはもうちょっと凝ったことをしたくなることがあります。
例えばMVVMパターン。
ViewModelのプロパティXをIsCheckedにバインドして、そしてプロパティXの値を元にトリガー(たとえばTextBlockのTextなんかを変更するのでしょう)を起動すると考えてください。

つまり

CheckBox.IsChecked → ViewModel.X → TextBlock.Text

というようなデータの流れです。

このケースでは、コントロールではなく、ViewModelのプロパティの値によってトリガーを起動する必要があります。
そういったときにDataTriggerが使えます。
 
今度も上と同じ仕様にしてみましょう。
チェックを入れると文字が表示されるのです。
ただし今度はテキストブロックを明示的に使ってみます。

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:src="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="150" Width="225">
    <Window.Resources>
        <src:TestViewModel x:Key="viewModel"/>

        <Style x:Key="style" TargetType="TextBlock">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsViewModelEnabled}" Value="true">
                    <Setter Property="Text" Value="Checked!!"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    
    <CheckBox DataContext="{StaticResource viewModel}" IsChecked="{Binding IsViewModelEnabled}" >
        <TextBlock Style="{StaticResource style}"/>
    </CheckBox>
</Window>



今度はC#側も必要です。 
ViewModelを使うからです。

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

    public class TestViewModel
    {
        public bool IsViewModelEnabled { get; set; }
    }
}


 
このプログラムは上のプログラムと同じように動作します。
違うのは、ViewModelを介して、動いているというところです。
(と言っても、ViewModelにはプロパティしか有りませんが。)
(不思議なことにINotifyPropertyChangedインターフェースは必要ありません)
 
アプリケーションが動くときにはUIだけで完結するよりもViewModelなC#ロジックで動くことが多いのではないでしょうか。(多分)
そういうときにDataTriggerは役立つかもしれません。

もっともこれ、boolとstringを変換するConverterで同じことが出来ますが。

拍手[3回]


WPFで列挙型の表示(DataTemplateSelector)

 WPFの話です。

さて以下のような列挙型があり、
それをWPFで表示したくなったとします。

public enum FigureType
{
    Ellipse,
    Rectangle,
    Triangle
}
3つの値はそれぞれ図形のタイプを表しています。
丸、四角、三角です。

例えば「この中から一つ図形をユーザーに選ばせたい」という状況を考えてください。

すると、WPFでは全体として次のようになるでしょう。
(全体として、と言いましたがMainWindow.csとMainWindow.xamlだけです。
App.xaml、App.csはデフォルトのままなので割愛します。)


C#
using System.Windows;

namespace DataTemplateSelectorDemo
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            figuresView.ItemsSource = System.Enum.GetValues(typeof(FigureType));
        }
    }

    public enum FigureType
    {
        Ellipse,
        Rectangle,
        Triangle
    }
}

XAML
<Window x:Class="DataTemplateSelectorDemo.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">

    <ListBox x:Name="figuresView"/>
    
</Window>


この結果がどうなるかというと、以下のようなぐあいです。
dataTemplateSelectorWithoutSelector.png

リストボックス中に3つの選択肢が出てきます!
リストボックスなのでマウスでクリックするとそれ相応のイベントが発生し
選択した図形をもとにプログラムを書くことができます。

しかしここで表示されるのはあくまでも文字なので、
ちょっと使いにくいと考える人もいるでしょう。

Ellipse, Rectangle, Triangleよりも
○、□、△のほうが直感的です。

DataTemplateSelectorを使う

では列挙型をもとに図形を表示するにはどうすればいいでしょうか?
WPFではSystem.Windows.Controls.DataTemplateSelectorを利用するとできます。

(別の方法もあるのですが)
これを使うと以下のようになります。

C#
using System.Windows;
using System.Windows.Controls;

namespace DataTemplateSelectorDemo
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            figuresView.ItemsSource = System.Enum.GetValues(typeof(FigureType));
        }
    }

    public enum FigureType
    {
        Ellipse,
        Rectangle,
        Triangle
    }


    public class FigureTypeDataTemplateSelector : DataTemplateSelector     {
public DataTemplate EllipseDataTemplate { get; set; }
public DataTemplate RectangleDataTemplate { get; set; }
public DataTemplate TriangleDataTemplate { get; set; }

public override DataTemplate SelectTemplate(
object item,
DependencyObject container
)
{
if (!(item is FigureType)) return null;

switch ((FigureType)item)
{
case FigureType.Ellipse:
return EllipseDataTemplate;
case FigureType.Rectangle:
return RectangleDataTemplate;
case FigureType.Triangle:
return TriangleDataTemplate;
default: return null;
}
}
}
}

XAML
<Window x:Class="DataTemplateSelectorDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:model="clr-namespace:DataTemplateSelectorDemo"
        Title="MainWindow" Height="350" Width="525">

    <ListBox x:Name="figuresView" ItemTemplateSelector="{DynamicResource templateSelector}"/>

    <Window.Resources>
<DataTemplate x:Key="MyEllipseDataTemplate">
<Ellipse Stroke="Black" Width="100" Height="100"/>
</DataTemplate>
<DataTemplate x:Key="MyRectangleDataTemplate">
<Rectangle Stroke="Black" Width="150" Height="100"/>
</DataTemplate>
<DataTemplate x:Key="MyTriangleDataTemplate">
<Polygon Stroke="Black" Points="100,0 0,100 200,100"/>
</DataTemplate>

<model:FigureTypeDataTemplateSelector
x:Key="templateSelector"
EllipseDataTemplate="{StaticResource MyEllipseDataTemplate}"
RectangleDataTemplate="{StaticResource MyRectangleDataTemplate}"
TriangleDataTemplate="{StaticResource MyTriangleDataTemplate}"
/> </Window.Resources>
</Window>


DataTemplateSelectorは、「どの値の時にどのように表示するか」を決定することができます。
丸の時には○を表示するように、四角の時には□を表示するように、三角の時には△を表示するようにしたいものです。
そういったことを実際に決定するのがDataTemplateSelector.SelectTemplate()メソッドで、
上のコードではswitchで場合分けしてデータの表示方法(DataTemplate)をreturnしています。

結果はこうなります。

dataTemplateSelectorDemoWithFigure.jpg


リストボックス中に図形が表示されました!

ここでは列挙型を表示しましたが、
もちろん、DataTemplateSelectorは列挙体だけでなくintなど他の値であっても使えるクラスです。























拍手[0回]