WPF > イベント


※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

イベントのアタッチ

通常のUIではイベントソースとイベントリスナは同じオブジェクトとなります。
(イベントソースとはイベントの発生源)
(イベントリスナとはイベントをアタッチしたオブジェクト)

<Panel>
	<Button Click="onClickMyButton" Text="Button" />
</Panel>
/**
 * ButtonがClickイベントを受け取ったときの実行関数
 */
private void onClickMyButton(object sender,RoutedEventArgs e) {
	// ユーザーによってボタンがクリックしたときの処理
}

イベントはルーティングによって伝播されるので、次のサンプルのようにButton要素の親要素であるPanel要素でもButton要素が受け取るイベントを受け取ることができます。

<Panel Button.Click="onClickCommonButton">
	<Button Text="Button1" />
	<Button Text="Button2" />
</Panel>
private void onClickCommonButton(object sender,RoutedEventArgs e) {
	// ユーザーによってボタンがクリックしたときの処理
}

この場合、ユーザーがButton1をクリックしてもButton2をクリックしても、onClickCommonButtonハンドラが呼び出されます。

コードによるイベントのアタッチ

XAMLに「button1」という名前で定義してあるButton要素に、分離コードでClickイベントをアタッチします。

namespace My {
	public partial class MainWindow : Window {
		public MainWindow() {
			InitializeComponent();
 
			// XAMLで定義したbutton1へClickイベントをアタッチ
			this.button1.Click += new RoutedEventHandler( onClickButton1 ); // イベントへエンティティを追加
			// ↑または、次のようなサポートメソッドを使う
			// this.AddHandler( Button.ClickEvent, new RoutingEventHandler(onClickButton1) );
		}
 
 
		private void onClickButton1(object sender, RoutedEventArgs e) {
		}
	}
}

イベントのアタッチ方法が2種類あるが、どちらも中身は同じ。
「button1.Click」はButton.Clickイベントで、この定義は次のようになっている。

public event RoutedEventHandler Click {
	add {
		this.AddHandler( Button.ClickEvent, value);
	}
	remove {
		this.AddHandler( Button.ClickEvent, value);
	}
}

イベントルーティング

バブル型とはイベントを最初に発行したVisualObjectから親へ向かって伝播するイベントで、プログラマが実装するユーザーインターフェースに関するイベントの大半がバブル型イベントです。

トンネル型はバブル型の逆で、イベントを最初に発行したVisualObjectから子へ向かって伝わっていくイベントです。アプリケーションの内部ロジックによって発行される事が多く、VisualObjectにアプリケーションの状態を伝える目的で使用する。

直接型は、イベントはターゲットにのみ発行されるイベントで、バブル型やトンネル型のようなイベントのルーティングは行われません。


<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
	<StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
		<Button Name="YesButton" Width="Auto" >Yes</Button>
		<Button Name="NoButton" Width="Auto" >No</Button>
		<Button Name="CancelButton" Width="Auto" >Cancel</Button>
	</StackPanel>
</Border>

YesButtonのボタンがクリックされた場合、フレームワークはButtonに対してClickイベントを発行します。
しかし、サンプルのようにClickイベントに対するハンドラがアタッチされていない場合、処理されなかったイベントは親要素であるStackPanelに送られます。

トンネル型イベント

UIElementが次のようなツリー構造の場合に、イベントを発行した際にイベントが伝播していく様子を示したものです。

階層構造を構成
r0
└─r1
    └─r2 (← ここでイベントを発行する)
        └─r3

トンネル型のイベントは次の順番に伝播します。

r0 → r1 → r2

特筆すべきは、r2をイベントソースとしたにもかかわらず、トンネル型のイベントルーティングは上位階層のイベントから呼び出されているということです。

この動作を検証するためのソースコードです。

/// <summary>
/// ButtonBaseから実装をコピー
/// </summary>
class RoutedContent : ContentControl {
	/// <summary>
	/// AddHandler()で追加できるイベントの種類
	/// </summary>
	public static readonly RoutedEvent MyRoutingEvent;
 
	static RoutedContent() {
		MyRoutingEvent = EventManager.RegisterRoutedEvent("MyRouting",
			 RoutingStrategy.Tunnel, // トンネル型のイベントであることを設定
			 typeof(RoutedEventArgs),
			 typeof(RoutedContent));
	}
 
	/// <summary>
	/// CLIイベント
	/// </summary>
	public event RoutedEventHandler MyRouting {
		add {
			base.AddHandler(MyRoutingEvent, value);
		}
		remove {
			base.RemoveHandler(MyRoutingEvent, value);
		}
	}
 
	/// <summary>
	/// イベントの実行
	/// </summary>
	/// <param name="e"></param>
	protected void fire_MyRoutingEvent(RoutedEventArgs e) {
		base.RaiseEvent(e);
	}
 
	/// <summary>
	/// 外部からインスタンスを使ってイベントを発行するための関数
	/// </summary>
	public void fire() {
		RoutedEventArgs e = new RoutedEventArgs(MyRoutingEvent, this);
		fire_MyRoutingEvent(e);
	}
}
class Program {
	[STAThread] 
	static void Main(string[] args) {
		RoutedContent r0 = new RoutedContent();
		r0.Name = "r0";
		RoutedContent r1 = new RoutedContent();
		r1.Name = "r1";
		RoutedContent r2 = new RoutedContent();
		r2.Name = "r2";
		RoutedContent r3 = new RoutedContent();
		r3.Name = "r3";
 
		// 階層構造を構成
		// r0
		// └─r1
		//     └─r2
		//         └─r3
		r0.Content = r1;
		r1.Content = r2;
		r2.Content = r3;
 
		// イベントにアタッチ
		r0.MyRouting += new RoutedEventHandler(onMyRouting);
		r1.MyRouting += new RoutedEventHandler(onMyRouting);
		r2.MyRouting += new RoutedEventHandler(onMyRouting);
		r3.MyRouting += new RoutedEventHandler(onMyRouting);
 
		// ルート要素から2番目に位置する「r2」でイベントを発行する
		r2.fire();
 
		Console.ReadLine();
	}
 
	private static void onMyRouting(object sender, RoutedEventArgs e) {
		ContentControl obj = (ContentControl)sender;
 
		Console.WriteLine("イベント名={0}", e.RoutedEvent.Name);
		Console.WriteLine("リスナーのオブジェクト={0}", obj.Name);
		Console.WriteLine();
	}
}

バブル型イベント

UIElementが次のようなツリー構造の場合に、イベントを発行した際にイベントが伝播していく様子を示したものです。

階層構造を構成
r0
└─r1
    └─r2 (← ここでイベントを発行する)
        └─r3

バブル型のイベントは次の順番に伝播します。

r2 → r1 →r0
/// <summary>
/// ButtonBaseから実装をコピー
/// </summary>
class RoutedContent : ContentControl {
	/// <summary>
	/// AddHandler()で追加できるイベントの種類
	/// </summary>
	public static readonly RoutedEvent MyRoutingEvent;
 
	static RoutedContent() {
		MyRoutingEvent = EventManager.RegisterRoutedEvent("MyRouting",
			 RoutingStrategy.Bubble, // バブル型のイベントであることを設定
			 typeof(RoutedEventArgs),
			 typeof(RoutedContent));
	}
 
	/// <summary>
	/// CLIイベント
	/// </summary>
	public event RoutedEventHandler MyRouting {
		add {
			base.AddHandler(MyRoutingEvent, value);
		}
		remove {
			base.RemoveHandler(MyRoutingEvent, value);
		}
	}
 
	/// <summary>
	/// イベントの実行
	/// </summary>
	/// <param name="e"></param>
	protected void fire_MyRoutingEvent(RoutedEventArgs e) {
		base.RaiseEvent(e);
	}
 
	/// <summary>
	/// 外部からインスタンスを使ってイベントを発行するための関数
	/// </summary>
	public void fire() {
		RoutedEventArgs e = new RoutedEventArgs(MyRoutingEvent, this);
		fire_MyRoutingEvent(e);
	}
}
class Program {
	[STAThread] 
	static void Main(string[] args) {
		RoutedContent r0 = new RoutedContent();
		r0.Name = "r0";
		RoutedContent r1 = new RoutedContent();
		r1.Name = "r1";
		RoutedContent r2 = new RoutedContent();
		r2.Name = "r2";
		RoutedContent r3 = new RoutedContent();
		r3.Name = "r3";
 
		// 階層構造を構成
		// r0
		// └─r1
		//     └─r2
		//         └─r3
		r0.Content = r1;
		r1.Content = r2;
		r2.Content = r3;
 
		// イベントにアタッチ
		r0.MyRouting += new RoutedEventHandler(onMyRouting);
		r1.MyRouting += new RoutedEventHandler(onMyRouting);
		r2.MyRouting += new RoutedEventHandler(onMyRouting);
		r3.MyRouting += new RoutedEventHandler(onMyRouting);
 
		// ルート要素から2番目に位置する「r2」でイベントを発行する
		r2.fire();
 
		Console.ReadLine();
	}
 
	private static void onMyRouting(object sender, RoutedEventArgs e) {
		ContentControl obj = (ContentControl)sender;
 
		Console.WriteLine("イベント名={0}", e.RoutedEvent.Name);
		Console.WriteLine("リスナーのオブジェクト={0}", obj.Name);
		Console.WriteLine();
	}
}

ダイレクト型イベント

UIElementが次のようなツリー構造の場合に、イベントを発行した際にイベントが伝播していく様子を示したものです。

階層構造を構成
r0
└─r1
    └─r2 (← ここでイベントを発行する)
        └─r3

ダイレクト型イベントは伝播しないので、イベントを発行したイベントソースのみが対象になります。

r2

処理済みフラグ(Handled)

ハンドラの引数であるRoutedEventArgsに定義されているHandledプロパティを使用すると、イベントルーティングを制御できます。
ハンドラ内でHandledプロパティをtrueにセットした場合、それ以降のイベントルーティングは呼び出されません。

通常のインターフェース設計ではHostedフラグは明示的にtrueに設定しておく方がよいです。(
たとえば、ButtonコントロールのClickイベントをアタッチした場合、アタッチしたハンドラで目的としたロジックを実装するので、イベントハンドリングによって親がこのイベントを処理する必要はないはずです。

入力イベントのルーティング

入力系のイベントでは、トンネル型とバブル型の2つのイベントタイプが対になって動作しており、この仕組みはユーザーの入力を処理する上で非常に重要な仕組みです。

キーボードからの入力があった場合、イベントソースはトンネル型イベントであるPreviewKeyDownイベントを発行します。

トンネル型イベントのハンドラが呼び出される順はルート要素側から順番なので、イベントソースまでの経路でPreviewKeyDownイベントがHandledされた場合、それ以降の要素にイベントはルーティングされません。
つまり、キーボードからの入力を検証し処理すべきでない場合はHandledフラグをセットすれば、それ以降のイベントとバブル型イベント(KeyDownイベント)が実行されないことを意味します。

イベントルーティングによって各イベントタイプでロジックを棲み分けることができます。

  • トンネル型イベントでは、入力値の検証のためのロジックを実装
  • バブル型イベントでは、入力に対する応答のためのロジックを実装

読んでいる本


Effective C#

QLOOKアクセス解析

ここを編集