その1:http://blogs.wankuma.com/kazuki/archive/2008/02/21/124157.aspx
その2:http://blogs.wankuma.com/kazuki/archive/2008/02/21/124343.aspx
WPFのルーティングイベントは、3種類ある。
バブル:イベント発生元から親へ親へ上がっていく。
トンネル:要素ツリーのルートから子へ下がっていく。
直接:普通のCLRのイベントと同じく発生もとの人のみ。
よく使われてるのは、トンネルとバブルの二つだと思われる。
WPFのコントロールを眺めてると、PreviewHogehoge、Hogehogeという感じでセットでイベントがあるものが多く見られる。
これは、PreviewHogehogeがトンネルで、Hogehogeがバブルのルーティングイベントになってる。
そして、このイベントの引数のRoutedEventArgsのHandledプロパティをTrueに設定すると、それ以降のイベントが処理されなくなる。
MSDNを読む限りだと、「イベントを完全に処理したぜ!って自信があるときはHandledプロパティをTrueにして、要素ツリー内で同じイベントをリッスンしてる人たちとの間で誤動作を起こさないようにするといいわ。」という感じの内容が書いてあった。
というわけで、いつものPersonクラスをネタに簡単にルーティングイベントをこさえて試してみようと思う。
DependencyObjectの勉強をしてたときは、DependencyObjectを継承してたけど、ルーティングイベントはDependencyObjectではなくFrameworkElementで実装されてるようなのでFrameworkElementを継承して作る。
using System.Windows;
namespace WpfMyEvent
{
public class Person : FrameworkElement
{
}
}
ここにイベントを作りこんでいく。
どんなネタにしようか悩んだ結果、誕生日をイベントとして扱ってみようと思う。
まずは、バブルのルーティングイベントBirthDayをこいつに登録する。
using System.Windows;
namespace WpfMyEvent
{
public class Person : FrameworkElement
{
#region 誕生日イベント関係
// 誕生日イベント
public static RoutedEvent BirthDayEvent = EventManager.RegisterRoutedEvent(
"BirthDay", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Person));
public event RoutedEventHandler BirthDay
{
add { AddHandler(BirthDayEvent, value); }
remove { RemoveHandler(BirthDayEvent, value); }
}
#endregion
}
}
動作確認をしてみる。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
namespace WpfMyEvent
{
class Program
{
[STAThread]
static void Main(string[] args)
{
var person = new Person();
person.BirthDay += (s, e) =>
{
Console.WriteLine("人 > 誕生日イベント!!");
};
person.RaiseEvent(new RoutedEventArgs(Person.BirthDayEvent));
}
}
}
結果は書くまでもないけど、下のような感じ。
人 > 誕生日イベント!!
んじゃPreviewイベントもこさえてみようと思う。これは、イベント登録するときの第二引数をRoutingStrategy.TunnelにすればOKみたい。
ということで、さっくりと実装。
using System.Windows;
namespace WpfMyEvent
{
public class Person : FrameworkElement
{
#region 誕生日イベント関係
// 誕生日直前イベント
public static RoutedEvent PreviewBirthDayEvent = EventManager.RegisterRoutedEvent(
"PreviewBirthDay", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(Person));
public event RoutedEventHandler PreviewBirthDay
{
add { AddHandler(PreviewBirthDayEvent, value); }
remove { RemoveHandler(PreviewBirthDayEvent, value); }
}
// 誕生日イベント
public static RoutedEvent BirthDayEvent = EventManager.RegisterRoutedEvent(
"BirthDay", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Person));
public event RoutedEventHandler BirthDay
{
add { AddHandler(BirthDayEvent, value); }
remove { RemoveHandler(BirthDayEvent, value); }
}
#endregion
}
}
ということで、誕生日直前イベントの動作も確認~。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
namespace WpfMyEvent
{
class Program
{
[STAThread]
static void Main(string[] args)
{
var person = new Person();
person.BirthDay += (s, e) =>
{
Console.WriteLine("人 > 誕生日イベント!!");
};
person.RaiseEvent(new RoutedEventArgs(Person.BirthDayEvent));
person.PreviewBirthDay += (s, e) =>
{
Console.WriteLine("人 > 誕生日直前イベント!!");
};
person.RaiseEvent(new RoutedEventArgs(Person.PreviewBirthDayEvent));
}
}
}
実行すると、こんな感じ。まだ連動して動くことは無い。
人 > 誕生日イベント!!
人 > 誕生日直前イベント!!
どうやって連動させて動かすんだろうと考えたんだけど、自分でがんばって連動するしかないんだろうね~。ということで、HappyBirthdayメソッドをこさえて、そこでPreviewとセットで動くようにしてみた。
ついでに、親子関係をきづくためのAddChildメソッドもこさえる。
これは、論理ツリーに追加するようにした。こうすると、バブルやトンネルのルーティングイベントがちゃんと親子の要素で連動するチック。
using System.Windows;
namespace WpfMyEvent
{
public class Person : FrameworkElement
{
#region 誕生日イベント関係
// 誕生日直前イベント
public static RoutedEvent PreviewBirthDayEvent = EventManager.RegisterRoutedEvent(
"PreviewBirthDay", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(Person));
public event RoutedEventHandler PreviewBirthDay
{
add { AddHandler(PreviewBirthDayEvent, value); }
remove { RemoveHandler(PreviewBirthDayEvent, value); }
}
// 誕生日イベント
public static RoutedEvent BirthDayEvent = EventManager.RegisterRoutedEvent(
"BirthDay", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Person));
public event RoutedEventHandler BirthDay
{
add { AddHandler(BirthDayEvent, value); }
remove { RemoveHandler(BirthDayEvent, value); }
}
// 誕生日イベントが起きるきっかけのメソッド
public void HappyBirthDay()
{
// まず、事前のイベントを起こして
var args = new RoutedEventArgs();
args.RoutedEvent = PreviewBirthDayEvent;
RaiseEvent(args);
// 誕生日イベントを起こす
args.RoutedEvent = BirthDayEvent;
RaiseEvent(args);
}
#endregion
// 子を登録
public void AddChild(Person child)
{
this.AddLogicalChild(child);
}
}
}
RoutedEventArgsのRoutedEventプロパティは読み書きOKなので、それをすげかえてRaiseEventを投げるようにしてみた。
テストプログラム。親と子を作ってイベントをしかけてる。
二度目のHappyBIrthdayでは、親が事前にイベントをかすめとって誕生日イベントをなかったことにしてる。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
namespace WpfMyEvent
{
class Program
{
[STAThread]
static void Main(string[] args)
{
// 親子の関係を構築!!
var parent = new Person();
var child = new Person();
parent.AddChild(child);
// 誕生日のイベントを登録
child.BirthDay += (s, e) =>
{
Console.WriteLine("子供 > 祝ってくれてありがとう!");
e.Handled = true;
};
// 誕生日だよ!祝って!!
child.HappyBirthDay();
// 親が横取り計画
parent.PreviewBirthDay += (s, e) =>
{
e.Handled = true;
Console.WriteLine("親 > 今年は誕生日しません!");
};
Console.WriteLine("--一年後--");
// 誕生日だよ!祝って!!
child.HappyBirthDay();
}
}
}
実行結果。二度目の誕生日は、悲しい結果に終わってる。
子供 > 祝ってくれてありがとう!
--一年後--
親 > 今年は誕生日しません!