読者です 読者をやめる 読者になる 読者になる

徒然電脳

日々のプログラミング(とその他)忘備録 このサイトは独自研究のみに基づきます マサカリ歓迎しますというかよろしくお願いします

デザインパターン[TemplateMethod]

RPGゲーム

 RPGというと真・女神転生が好きだったりするが、今回はDQ7のような職業制のRPGについて考えてみる。
 戦闘シーンにおいて、職業が異なると攻撃方法が異なったりする。たとえば剣士なら剣で攻撃するし、弓使いは弓で攻撃する。
 このように、キャラクターに応じて攻撃方法などが異なるゲームを想定する。
 また、ここではオートモードについても考えてみる。
 オートモードのAIは簡単で、自身のHPが60以上なら攻撃を行なう。それより小さいと回復だ。これは全職業に共通するAIだとする。
 つまり、職業間で共通部分はオートモード機能。攻撃・回復は職業に依存している。
 こんかいは、そんな関係をTemplateMethodパターンっぽく書いてみようと思う。
 
 とりあえず、簡単なクラス図を。
 f:id:TempProg:20160220222428j:plain

Characterクラス

 全ての職業のキャラクターがCharacterクラスを継承する。
 

using System;

namespace TemplateMethod {
    abstract class Character {
        public int HP { get; set; }
        
        /// <summary>
        /// 何らかの方法で攻撃する。
        /// </summary>
        public abstract void Attack();

        /// <summary>
        /// 何らかの方法で回復する。
        /// </summary>
        public abstract void Heal();

        /// <summary>
        /// オートモードで戦闘する。
        /// </summary>
        public void DoAuto() {
            if (HP >= 60)
                Attack();
            else
                Heal();
        }

        public Character(int hp) {
            this.HP = hp;
        }
    }
}

 クラス図やコードを見て分かる通り、Characterクラスはabstractなクラスだ。
 AttackメソッドとHealメソッドがある。これらは職業によって異なる実装をしなければいけないので、子クラスに実装を任せている。
 そして、全職業に共通するオートモード機能はDoAutoメソッドが担っている。DoAutoメソッドでは抽象メソッドであるAttackメソッド、Healメソッドを使用している。これこそがTemplateMethodパターンの真髄だろう。DoAutoメソッドは枠組みのようなもので、明示的に実装されていながら、実際どのような動作になるかは子クラスに依存するのだ。

Swordsmanクラス

using System;

namespace TemplateMethod {
    class Swordsman : Character {
        public override void Attack() {
            Console.WriteLine("剣で攻撃!");
        }

        public override void Heal() {
            Console.WriteLine("薬草で回復!");
        }

        public Swordsman(int hp) : base(hp) { }
    }
}

 Swordsmanクラスは剣士を表すクラスだ。
 剣士は剣で攻撃するからAttackメソッドは「剣で攻撃する」という実装を行なう。
 また、回復スキルは持ち合わせていないから「薬草で回復」する。
 このように、攻撃・回復を実装するだけで、オートモード機能はCharacterクラスにあるのだから実装する必要が無い。再利用だ。
 と、このようにTemplateMethodパターンは継承をうまく使った例(悪く言えば当たり前な例)なのだろう。

Magicianクラス

 一応、魔法使いを表すクラスも記載しておく。ただし、やってることはSwordsmanクラスと同じだ。

using System;

namespace TemplateMethod {
    class Magician : Character {
        public override void Attack() {
            Console.WriteLine("黒魔法で攻撃!");
        }

        public override void Heal() {
            Console.WriteLine("白魔法で回復!");
        }

        public Magician(int hp) : base(hp) { }
    }
}

Programクラス

最後に、これらクラスの使用方法としてMainメソッドがあるProgramクラスの実装も記載する。

using System;

namespace TemplateMethod {
    class Program {
        static void Main(string[] args) {
            Character mga = new Magician(100);
            Character mgb = new Magician(40);
            Character swa = new Swordsman(100);
            Character swb = new Swordsman(40);

            mga.DoAuto();
            mgb.DoAuto();
            swa.DoAuto();
            swb.DoAuto();
        }
    }
}

このように、Character型の変数を使うのが良いと思われる。


今回、AttackメソッドもHealメソッドもpublicなメソッドとして定義した。しかし、設計によっては外部に公開したくない場合もある。そのような場合はprotectedなメソッドとして定義することで、子クラスでのオーバーライドも可能で、かつ外部からはアクセスできなくなる。

少々間があきそう

いつものことですが、デザインパターンは間があきそうですね

3月あたりは学生の身分ゆえずーっと暇になるでしょうから、そこで一気に更新すると思います。

それではまた

デザインパターン[Adapter]

最近良く見る共有

 ここ5年くらいで、「共有」できるサービスが増えている。例えばTwitterだとかLineだとかFacebookのこと。
 今回はFacebookとLineの共有はこのプロジェクトで実装したけど、前のプロジェクトで作っていたTwitterを再利用したいな。っていうシナリオで考えてみた。Adapterパターンはこういった「緊急的措置」の時にとても良く使えるが、逆に最初からAdapterパターンを考えているようなシステムは基本的によろしくないとのこと(以下のページを参照)。
 PHPで使うGoFパターン ひとり Advent Calendar - アダプター - Qiita

 今回は継承関係のAdapterパターンで実装した。保持関係にあるAdapterパターンもあるが、そちらは今回は省略する。


 とりあえず、簡単なクラス図。
f:id:TempProg:20151226015523j:plain

ソースコード

 以下にソースコードを示す。なお、GitHubにも同一のソースコードをアップロードした。
P-denshin/DesignPattern · GitHub


IShare.cs

using System;

namespace Adapter {
    interface IShare {
        void Share(String s);
    }
}

 IShareがTargetの役をしている。これはShare(共有)を一般化するためのインターフェイスとして作った。

Line.cs

using System;

namespace Adapter {
    class Line : IShare{
        void IShare.Share(string s) {
            Console.WriteLine("\"" + s + "\"" + "とLINEに投稿!てかLINEやってる?");
        }
    }
}

 LINEにシェアするためのクラス。今回のプロジェクトで作ったのでShareメソッドに直接LINEにシェアするコードを書く。(今回はWriteLineだけでご勘弁)

Facebook.cs

using System;

namespace Adapter {
    class Facebook : IShare{
        void IShare.Share(string s) {
            Console.WriteLine("\"" +  s + "\"" + "をFacebookに投稿!いいね!");
        }
    }
}

 Facebookにシェアするためのクラス。LINEクラスと同じ。

Twitter.cs

using System;

namespace Adapter {
    class Twitter {
        public void Tweet(String s) {
            Console.WriteLine("\"" + s  + "\"" + "ってツイートしたよ。");
        }

        public void Like(String s) {
            Console.WriteLine("\"" + s + "\"" + "をいいねしたよ。");
        }
    }
}

 Twitterに「ツイート」するクラス。Adapteeだ。ここで注意して欲しいのは「別プロジェクト」から持ってきたためIShareを実装していないのだ。
つまり、ここまでだとMainでは次のようなコードを書かなきゃいけなくなる。

IShare fb = new Facebook();
IShare ln = new Line();

fb.Share("安倍首相と繋がりがあります。企業に興味があります。");
ln.Share("イツメンとスタバでコーヒーわず");

Twitter tw = new Twitter();
tw.Tweet("キエエエエエェェェェェェェェェエエエエ");

 fb, ln変数とtw変数の型に注目してほしい。fb, lnはIShare型だ。シェアするだけなので、IShare型とすることで「シェアするんだ!」という意図をわかりやすくしている。
 対してtwはどうだろうか。型はTwitterメソッド名はTweetだし、シェアとは無関係のLikeメソッドが見えている。これでも目的の動作はするが、これが積み重なるとおそらく保守性に欠けたプログラムになるんじゃないかと思う。
 そこで、Adapterクラスを別に用意する。

TwitterAdapter.cs

using System;

namespace Adapter {
    class TwitterAdapter : Twitter, IShare{
        void IShare.Share(string s) {
            this.Tweet(s);
        }
    }
}

 TwitterAdapterはTwitterクラスを継承し、かつIShareを実装するようなクラスだ。こうすればツイッターに「シェア」できるようになる。Likeメソッドも見えない。上に書いたような少々気持ち悪いコードも次のMainのようにかけるようになっている。

Program.cs

using System;

namespace Adapter {
    class Program {
        static void Main(string[] args) {
            IShare fb = new Facebook();
            IShare ln = new Line();

            fb.Share("安倍首相と繋がりがあります。企業に興味があります。");
            ln.Share("イツメンとスタバでコーヒーわず");

            IShare tw = new TwitterAdapter();
            tw.Share("キエエエエエェェェェェェェェェエエエエ");
        }
    }
}

使いどころ

 正直なところ本だけでは使いどころがよくわからなかったが、調べ書いてるうちになんとなくわかった気がする。
 今回の記事を書くにあったっては以下のページが大きく参考になった。
Adapterパターンを使ってみた。今あるクラスを再利用してみる。 - 感謝のプログラミング 10000時間
 基本的にデザインパターンを紹介するサイトは一般化しすぎてよくわからない物が多い。実際使うときはmethod1とか使うわけ無いはずなのに。そんな中、このように具体例を示してくれるページや結城さんの本は大いに助かっている。 
 

デザインパターン[Iterator]

デザインパターン

 先日、結城浩先生の著書「増補改訂版 Java言語で学ぶ デザインパターン入門」を購入した。


www.amazon.co.jp

 しかし、恥ずかしながらJavaをほとんど触ったことがない。著書はJavaの文法をふんだんに使っているわけではないので理解が難しいことはまずないが、理解を確認するために別言語でもう一度書いておきたい。
 そこで、比較的馴染みのあるC#で書いて頭を整理することにした。じつは結城先生自身が、C MAGAZINEでの連載ですでにC#でのデザインパターンを書いていらっしゃったりもする。もはやブログで公開する意味もないと思いつつも、とりあえず忘備録として公開することにする。

ニュースの表示

 以前、RSSの取得プログラムを書いていた。いま見てみると、かなり汚いコードだがこれはIteratorパターンを適応できる。早速このネタで書いてみることにした。とりあえず適当なクラス図書いとく。
f:id:TempProg:20151218004703j:plain
 Iteratorパターンの、いわゆるAggregateインターフェイスIteratorインターフェイスにあたるのはそれぞれIEnumerableインターフェイス、IEnumeratorインターフェイス。これらはC#で用意され、これをIEnumerableを実装しているものはforeachで回すことが出来る。
 ConcreteAggregateクラス, ConcreteIteratorクラスが対応するのはそれぞれNewsListクラス, NewsEnumeratorクラス。Newsクラスはニュース単体を表し、NewsListクラスがその集合体(=Aggregate)を表している。

ソースコード

 以下にソースコードを示す。なお、GitHubにも同一のソースコードをアップロードした。
P-denshin/DesignPattern · GitHub


News.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Iterator {
    class News {
        /// <summary>
        /// 記事のタイトル
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// 記事のURL
        /// </summary>
        public string URL { get; set; }

        /// <summary>
        /// 記事の概要
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// ニュースを表示する。
        /// </summary>
        public void Show() {
            Console.WriteLine("タイトル:" + Title);
            Console.WriteLine("URL:" + URL);
            Console.WriteLine(Description);
        }
    }
}

NewsList.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Iterator {
    class NewsList : IEnumerable<News> {
        List<News> newsList;

        /// <summary>
        /// 指定したインデックスにあるニュースを取得する。
        /// </summary>
        /// <param name="x">インデックス</param>
        /// <returns>指定したインデックスにある要素</returns>
        public News this[int x] {
            get {
                return newsList[x];
            }
        }

        /// <summary>
        /// ニュースの数を取得する。
        /// </summary>
        public int Length {
            get {
                return newsList.Count;
            }
        }

        /// <summary>
        /// ニュースを追加する。
        /// </summary>
        /// <param name="ns">追加するニュース</param>
        public void Add(News ns) {
            newsList.Add(ns);
        }

        /// <summary>
        /// ニュースリストを初期化する。
        /// </summary>
        public NewsList() {
            this.newsList = new List<News>();
        }

        /// <summary>
        /// ニュースリストの列挙子を取得する。
        /// </summary>
        /// <returns>ニュースリストの列挙子</returns>
        public IEnumerator<News> GetEnumerator() {
            return new NewsEnumerator(this);
        }

        /// <summary>
        /// ニュースリストの列挙子を取得する。
        /// </summary>
        /// <returns>ニュースリストの列挙子</returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }
    }
}

 NewsList.csを見てもらえれば分かる通り、NewsListクラスはIEnumerableインターフェイスを実装する。IEnumerableインターフェイスはIEnumerableインターフェイス(非ジェネリック)を実装しているため、そちらもNewsListクラスで実装。ジェネリック版は非ジェネリック版と違い、拡張メソッドによってLINQの恩恵を得ることができる。

NewsEnumerator.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Iterator {
    class NewsEnumerator : IEnumerator<News> {
        private NewsList newsList;
        private int i;

        public NewsEnumerator(NewsList nl) {
            this.newsList = nl;
            i = -1;
        }

        /// <summary>
        /// 次のニュースを取得する。
        /// </summary>
        public News Current {
            get { return newsList[i]; }
        }

        //IEnumerator<out T>がIDisposableを実装してるためについてきたもの
        //Disposeの実装の必要はない(はず)
        public void Dispose() { }

        /// <summary>
        /// 次のニュースを取得する。
        /// </summary>
        object System.Collections.IEnumerator.Current {
            //IEnumerator<out T>がIEnumeratorを実装してるためについてきたもの
            get { return this.Current; }
        }


        /// <summary>
        /// 次のニュースに進める。
        /// </summary>
        /// <returns>次のニュースが存在すればtrue 次のニュースが存在しなければfalse</returns>
        public bool MoveNext() {
            i++;
            //範囲外
            if (i >= newsList.Length) {
                return false;
            }
            return true;
        }

        /// <summary>
        /// ニュース列挙子を初期化する。
        /// </summary>
        public void Reset() {
            i = -1;
        }
    }
}

 NewsEnumeratorクラスはIEnumeratorインターフェースを実装。IEnumeratorインターフェース自体はIDisposableインターフェース, IEnumeratorインターフェース(非ジェネリック)を実装。そのため、NewsEnumeratorは3つのインターフェースを実装している。実際に重要となるのはMoveNextメソッドとCurrentプロパティ(とついでにResetメソッド)である。Currentプロパティは「現在指し示すオブジェクト」を表している。MoveNextメソッドは「次に進める」処理と「次の存在の判定」処理を同時に行わなければいけない。「次」の状態が存在しない場合はfalseを返し、使用側にそれ以上の取得を行わないよう促す。
 意外と重要なのは初期状態で、IEnumeratorにおける初期状態は「最初の状態の前」である。なぜこのような仕様になっているかというと、MoveNextメソッドで次状態の存在を確認してから取得を行なうため初期状態が「最初の状態」に設定されていると、「最初のオブジェクト」が取得できないのである。

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Iterator {
    class Program {
        static void Main(string[] args) {
            NewsList nl = new NewsList();

            nl.Add(
                new News() {
                    Title = "本日の天気",
                    URL = "http://www.~~~~.com/xxxxxx",
                    Description = "全国的に晴れやくもり、または雨でしょう"
                }
                );

            nl.Add(
                new News() {
                    Title = "ゆるゆりがすごい",
                    URL = "http://www.$$$$.com/xxxxx",
                    Description = "千鶴が可愛いと話題に"
                }
                );

            nl.Add(
                new News() {
                    Title = "ポテチが気が狂ってる",
                    URL = "http://www.%%%%%.com/xxxxx",
                    Description = "ショートケーキ味が期間限定で発売"
                }
                );

            IEnumerator<News> it = nl.GetEnumerator();
            while (it.MoveNext()) {
                it.Current.Show();
                Console.WriteLine();
            }


            foreach (News n in nl) {
                n.Show();
                Console.WriteLine();
            }
        }
    }
}

 最後に、使用方法としてMainメソッドを定義したProgram.csである。Mainメソッドの最後にwhileでの書き方とforeachでの書き方を示している。foreachで書いたものは、whileで書いたものと似たような形に展開される(実際は違う)。

ただのニートと化している

最近はただのニート

ここ数ヶ月、自分がしたいことを見つけるとしなきゃいけないことに追われてます。結局夏から進歩なしです。
言い訳してますが、要はサボってました。
うーん、生産的にしたい。

WPFの簡単なまとめ Binding編

1回:WPFの簡単なまとめ Resorce編 - 徒然電脳
2回:WPFの簡単なまとめ Style編 - 徒然電脳
3回:WPFの簡単なまとめ Template編 - 徒然電脳

はい、4回目

代入ではなく結びつける

バインディングとは「一箇所の変更が、全体に伝わる」ということのようです。
つまりプロパティの変更をするだけで、それを参照する全てにその変更が伝わるということです。

ここでは、「なぜ値を直接代入するのではなく、バインディングを使うのか」という趣旨で書こうとした節でしたが、どうも難しい(←十分理解していない証拠)。
とりあえず、一言二言の理解でしか無いのだけれども
「プロパティ一つの変更のために、(複数の)オブジェクトへの代入を変更のたびに実行しなくても良くなるよ」
というところで終わらせていただきます。

具体的に書く

とりあえず、テキストボックスの値をラベルにバインディングするXAMLを書いてみましょう。

MainWindow.xaml

<Window x:Class="Binding.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">
    <StackPanel>
        <TextBox Name="test"/>
        <Label Content="{Binding Text}" DataContext="{x:Reference Name=test}"/>
    </StackPanel>
</Window>

TextBoxにはtestという名前が付けられています。LabelのDataContextではx:Referenceマークアップ拡張によってtestという名前のインスタンスを参照するように指定してあります。もちろん、TextBoxのことです。そして、ContentにはBindingマークアップ拡張でTextが指定されていますね。

DataContextはバインディングを行なうためのインスタンスを一つ代入します。
そしてBindingで、そのインスタンスのプロパティを指定してあげます。
つまり、Labelに書かれている意味は
「testというインスタンスのTextプロパティを私(Label)のContentプロパティにバインディングして!」
という感じです。

自作のデータで

実際のアプリケーションでは、上のようなコードを書くことは稀のような気がします。多くの場合、外部の自作クラスのプロパティを呼び込んだりするんだろうと思います。そこで、次のようなコードを書いてみました。usingディレクティブは省略します。

MainWindow.xaml

<Window x:Class="Binding.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">
    <StackPanel>
        <Label Content="{Binding Age}" Name="ageLabel"/>
        <Button Click="Button_Click" Content="Age++"/>
    </StackPanel>
</Window>

MainWindow.xaml.cs

namespace Binding {
    public partial class MainWindow : Window {
        Person person;

        public MainWindow() {
            InitializeComponent();
            person = new Person() { Age = 18 };
            ageLabel.DataContext = person;
        }

        //ボタンクリックのたびに年をとる魔法のイベント
        private void Button_Click(object sender, RoutedEventArgs e) {
            person.Age++;
        }
    }

    class Person {
        int age;
        public string Name { get; set; }
        public int Age { 
            get { return age; }
            set { age = value >= 0 ? value : age; }
        }
    }
}

さぁ実行しましょう。最初はきちんと18って表示されますね!
予想通り、ボタンをクリックするたびにラベルの年齢はあがり・・・ません。

ボタンをクリックするたびAgeは1つずつ増加していくはずです。それなのにラベルの表示値は18のまま。これは、「変更の通知」がされてないためです。

変更通知

変更を通知する機能を持つためには、INotifiyPropertyChanged(System.Compornent名前空間)インターフェイスを実装しなきゃいけません。INotifiyPropertyChangedインターフェイスはPropertyChangedというイベントハンドラしか持っていない質素なインターフェイスです。プロパティが変更された時に、こいつを発生させるようにしてやればいいですね!
それじゃぁ、Personクラスを次のように書き換えましょう。

MainWindow.xaml.cs Personクラス

class Person : INotifyPropertyChanged{
    int age;
    string name;

    public event PropertyChangedEventHandler PropertyChanged;

    public string Name {
        get { return name; }
        set { name = value; NotifiyPropertyChanged("Name"); }
    }
    public int Age { 
        get { return age; }
        set {
            if (value >= 0) {
                age = value;
                NotifiyPropertyChanged("Age");
            }
        }
    }

    //イベントが実装されてれば実行する。
    private void NotifiyPropertyChanged(String propertyName) {
        if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

PropertyChangedEventHandlerの第一引数はsenderです。基本的にはthisを入れてやります。第二引数はPropertyChangedEventArgsのインスタンスを入れてやります。そのインスタンスを生成するときには、変更されたプロパティ(今回ならAgeやName)の名前を渡してやります。実はこの名前がBinding時に指定するプロパティ名です。もしこの時AgeではなくToshiって書いてしまったら、XAMLでも{Binding Toshi}って書き直さなきゃいけません。混乱を避けるために、できるだけ実際のプロパティ名と一致させたいですね。
なおPropertyChangedイベントハンドラには、バインディング先(今回ならLabelのこと)でPersonクラスでの変更が起きた際の処理が追加されているはずです。

方向指定

これまでのバインディングはAの変更をBに適応させるという一方通行的なバインディングでした。ここではその方向を操ってあげましょう。といっても、かなり簡単です。xamlを次のように書き換えてください。

MainWindow.xaml

<Window x:Class="Binding.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">
    <StackPanel>
        <!-- Binding Mode -->
        <TextBox Text="{Binding Age, Mode=TwoWay}" Name="ageLabel"/>
        <Button Click="Button_Click" Content="Age++"/>
    </StackPanel>
</Window>

ただ単にBindingにModeが追加されただけです。はい。
実行してみると、先ほどと変わりませんね。18からボタンをおすたびに加算されていきます。
違うのはここからです。LabelではなくTextBoxに変わっているので、その値を変えてみましょう。
そしてボタンを押します。すると、その変えた値から1加わりました。

・・・
だからどうした。という声が聞こえてきそうです。いえいえ、これはとても大事です。1加算された、ということはテキストボックスの値だけでなく、Ageの値も変更されていたということなのですから。もしAgeの値が変更されていなければ、テキストボックスの値を変更する前の値に対して1加算されたものが表示されるだけだったでしょう。
なぜこんなことが起きたか。それはMode=TwoWayのおかげだということはわざわざ言わなくても大丈夫ですね。

モードの種類です。
OneWay・・・バインディング元からバインディング先への変更のみ適応
TwoWay・・・バインディング元からの変更もバインディング先からの変更も適応
OneWayToSource・・・バインディング先からバインディング元への変更のみ適応
OneTime・・・一度だけバインディング元からバインディング先への変更を適応
Default・・・初期設定を使う。要素によって異なる

Modeを指定しないとDefaultとなりますが、これは要素によって異なります。たとえば、LabelならOneWay、TextBoxだとTwoWayです。混乱を避けるために、できるだけModeは明示したいところです。

コンバート

なんか歳なのにただの数値として出力するのはそっけないですね。数値の横に「歳」の漢字を挿入してやりたいところです。これはコンバータを使って実装できます。とりあえずコンバータクラスを作りましょう。

MainWindow.xaml.cs AgeConverterクラス

    class AgeConverter : IValueConverter {
        object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
            return ((int)value).ToString() + "歳";
        }

        object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
            int a = 0;
            int.TryParse(((string)value).Substring(0, ((string)value).Length - 1), out a);
            return a;
        }
    }

コンバータクラスはIValueConverterインターフェイスを実装してやります。
Convertメソッドバインディング元のプロパティが仮引数valueに、その返り値がバインディング先のプロパティへとデータが流れます。ConvertBackメソッドはその逆です。一方のXAMLは以下のように変更します。

MainWindow.xaml

<!-- xmlns:thisに注意 -->
<Window x:Class="Binding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:this="clr-namespace:Binding"
        Title="MainWindow" Height="350" Width="525">
    
    <StackPanel>
        <StackPanel.Resources>
            <this:AgeConverter x:Key="AgeConv"/>
        </StackPanel.Resources>

        <TextBox Text="{Binding Age, Mode=TwoWay, Converter={StaticResource AgeConv}}" Name="ageLabel">
        </TextBox>
        <Button Click="Button_Click" Content="Age++"/>
    </StackPanel>
</Window>

リソースにAgeConverterのインスタンスを用意します。そして、Bindingの方でConverterが取得。こうすることでコンバータの実装ができます。




バインディングは記事を探すともっともっといろんなことがあります。そいつらも追々勉強してまとめていきたいとは思っています。(・。・;
疲れた・・・

WPFの簡単なまとめ Template編

1回:WPFの簡単なまとめ Resorce編 - 徒然電脳
2回:WPFの簡単なまとめ Style編 - 徒然電脳

はい、3記事目です。

外観を根幹から変えること

今まではリソースやスタイルを使って、プロパティの取りうる範囲で外観を変更してきました。
今回は、その壁を超えて「コントロール」ごと外観を変えてしまいましょう。

Template?

とりあえず、コードを見ましょうか。

MainWindow.xaml

<Window x:Class="Template.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">
    <StackPanel>
        <!-- ControlTemplateの中のコントロールの外観を持つようになる -->
        <Button Content="Button" Click="Button_Click">
            <Button.Template>
                <ControlTemplate TargetType="Button">
                    <Label>実はボタンです</Label>
                </ControlTemplate>
            </Button.Template>
        </Button>
        
        <!-- ControlTemplate内にコンテナを置くといろんなことができる -->
        <Button Content="Button" Click="Button_Click">
            <Button.Template>
                <ControlTemplate>
                    <Grid>
                        <Rectangle Fill="NavajoWhite" Height="20"/>
                        <Label HorizontalAlignment="Center">これも実はボタン</Label>
                    </Grid>
                </ControlTemplate>
            </Button.Template>
        </Button>

    </StackPanel>
</Window>

実行してみましょう。
f:id:TempProg:20150704211348j:plain
もちろん、どちらもクリックできて・・・
f:id:TempProg:20150704211404j:plain
(Button_Clickの実装は省略します)

要素のTemplateプロパティでテンプレートを指定可能です。TemplateプロパティにはControlTemplateのインスタンスを入れてやります。ControlTemplate内には1つの要素をとることができ、最初のボタンの例ではlabelを入れてあります。
実行結果を見てやれば一目瞭然ですが、外観としてはボタンの影も形もありません。ただラベルがあるだけです。しかし、「ボタンとしての機能を有します」。クリックすれば分かる通り、ボタンのクリックイベントが発生します。2つ目のボタンのように、ControlTemplate内にコンテナ(GridとかStackPanelなど)を置くことでいろんな外観を設定できるようになります。

StyleでTemplateを指定

StyleとしてTenplateを指定することも可能です。

MainWindow.xaml

<Window x:Class="Template.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">
    <StackPanel>
        <!-- TemplateはsetterのpropertyをTemplateにしてしまえばスタイルとしても使える不思議 -->
        <StackPanel.Resources>
            <Style TargetType="Button">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="Button">
                            <Grid>
                                <Rectangle Fill="NavajoWhite" Height="20"/>
                                <Label HorizontalAlignment="Center">ボタン</Label>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </StackPanel.Resources>

        <Button Click="Button_Click"/>
        <Button Click="Button_Click"/>

    </StackPanel>
</Window>

このようにすれば、すべてのボタンがSetter.Value内のコントロールの外観を持つようになります。もちろん、Styleですからx:Keyの指定等によって特定の要素のみに適応させたりすることも可能です。

消えたContentプロパティ

上までの例ではButtonのContentプロパティが消えてしまっています。これでは困ります。そこで、Template内でContetnPresenterさんを呼び出しましょう。

MainWindow.xaml

<Window x:Class="Template.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">
    <StackPanel>
        <!-- TemplateはsetterのpropertyをTemplateにしてしまえばスタイルとしても使える不思議 -->
        <StackPanel.Resources>
            <Style TargetType="Button">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="Button">
                            <Grid>
                                <Rectangle Fill="SkyBlue" Height="40"/>
                                <!-- ContentPresenter -->
                                <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </StackPanel.Resources>
        
        <Button Click="Button_Click" Content="ボタンA"/>
        <Button Click="Button_Click" Content="ボタンB"/>

    </StackPanel>
</Window>

このように、ContentPresenter要素をおいてやることでButton要素のContentプロパティの値をそこに表示させることが可能です。

なお、TemplateBindingを用いることでもできます。

MainWindow.xaml

<Window x:Class="Template.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">
    <StackPanel>
        <StackPanel.Resources>
            <Style TargetType="Button">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="Button">
                            <Grid>
                                <Rectangle Fill="SkyBlue" Height="40"/>
                                <!-- TemplateBinding -->
                                <Label Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </StackPanel.Resources>

        <Button Click="Button_Click" Content="ボタンA"/>
        <Button Click="Button_Click" Content="ボタンB"/>

    </StackPanel>
</Window>

TemplateBindingマークアップ拡張でテンプレートの適応先のプロパティを持ってくる(バインディングする)ことができます。正直なところ、こちらのほうがわかりやすいようなきがします。今のところContentPresenterの存在する意味は謎です。どなたかご存知でしたらご連絡お願いします。

トリガー

スタイル同様、ControlTemplateにもTriggersプロパティがあります。ただし、SetterはControlTemplate内の要素に対して設定することになります。ソースを見てみましょう。

MainWindow.xaml

<Window x:Class="Template.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">
    <StackPanel>
        <StackPanel.Resources>
            <Style TargetType="Button">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="Button">
                            <Grid>
                                <Rectangle Fill="SkyBlue" Height="40"/>
                                <Label Name="contentLabel" Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                            </Grid>
                            
                            <!-- Triggers -->
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsMouseOver" Value="True">
                                    <Setter TargetName="contentLabel" Property="FontSize" Value="20" />
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </StackPanel.Resources>
        
        <Button Click="Button_Click" Content="ボタンA"/>
        <Button Click="Button_Click" Content="ボタンB"/>

    </StackPanel>
</Window>

こうすれば、要素にマウスオーバーした時にフォントサイズが大きくなります。

Setterには何の要素に対してプロパティを設定すればいいか指定してやる必要があります。そこでlabelにNameをつけてやり、その値をSetterのTargetNameプロパティで指定してやります。こうすれば、どの要素のフォントサイズを大きくすればいいかわかりますね。もちろん、StyleレベルでTriggersを指定することも可能ですが、この時はTemplateを1から書かなきゃいけません。


とりあえずテンプレートも書きたいことは書いた・・・
間違い等ありましたら、指摘してくださるとありがたいです。