.jpg)
.gif)
WPF アプリケーションを作るクッキングガイドでは、WPF のメインコードである XAML を使いこなすために押さえておくべきポイントを、簡単なサンプルを交えながら解説していきます。
前回は、各オブジェクトに共通して設定可能な、プロパティについて解説していきましたが、今回はそのプロパティ同様、各オブジェクトに共通して設定可能なイベント処理を、ユーザー入力イベントを軸に解説していきます。
入力イベントとは?
Windows 上で動作するアプリケーションへのユーザーからのインプットは、主として下記の 3 つになります。
- マウス
- キーボード
- インク (タブレットなどのスタイラス。今回は除外)
WPF アプリケーションにおいては、前回のプロパティと同様、画面内に配置されるすべてのオブジェクトがこれらの入力を受け付けることが可能です。
アプリケーションというものが、ユーザーからの何らかのイベントを受け付けて、内部での処理 (あるいはネットワーク上に分散配置されている様々なサービス) を経た後、ユーザーに何らかのアウトプットを返すというものである以上、ユーザーからの入力もアプリケーションにおける基本の一つと言えます。この記事で は、そのユーザー入力処理を理解し、XAML で構成されたインターフェイスへユーザーからの入力を伝える方法をマスターすることを目的に解説します。
なお、ボタンやスライダー、コンボボックスなどのビルトインのコントロールコンポーネントに関しましては、ここでは言及しません。
マウスイベント
マウスイベントは、アプリケーション上で、マウスカーソルの重なっている (あるいは重ならなくなった) オブジェクトに対して発生するイベントです。使用目的は様々で、単にマウスがオブジェクト領域内に入ったかどうかを判定するためのものもあれば、マウスの 移動を判定して、その移動量をチェックするためなどに使用したります。
下記のXAML、およびコードビハインドファイルをご覧ください。
|
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/interactivedesigner/2006"
mc:Ignorable="d"
Background="#FFFFFFFF"
x:Name="DocumentRoot"
x:Class="UntitledProject1.Scene1"
Width="320" Height="240">
<Grid.Resources>
<Storyboard x:Key="OnLoaded"/>
</Grid.Resources>
<Grid.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard x:Name="OnLoaded_BeginStoryboard" Storyboard="{DynamicResource OnLoaded}"/>
</EventTrigger>
</Grid.Triggers>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<RichTextBox FontFamily="Meiryo" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,160,0,0" Width="200" Height="27" x:Name="RichTextBox">
<FlowDocument>
<Paragraph TextAlignment="Center"><Run Language="ja-jp">Touch the object with mouse.</Run></Paragraph>
</FlowDocument>
</RichTextBox>
<Rectangle Stroke="sc#1, 0.0351994745, 0.06138334, 0.22558558" Fill="sc#1, 0.07478483, 0.130415082, 0.47927928"
HorizontalAlignment="Left" VerticalAlignment="Top" Margin="60,40,0,0" Width="100" Height="100" x:Name="Rectangle"
MouseLeftButtonDown="MLDown" MouseLeftButtonUp="MLUp" MouseRightButtonDown="MRDown"
MouseRightButtonUp="MRUp" MouseEnter="MEnter" MouseLeave="MLeave"/>
</Grid> |
▲ 1-a. XAML サンプルコード (.xaml)
|
using System;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.Windows.Documents;
namespace UntitledProject1
{
public partial class Scene1
{
public Scene1()
{
this.InitializeComponent();
}
private void MLDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
FlowDocument myFlowDoc = new FlowDocument();
myFlowDoc.Blocks.Add(new Paragraph(new Run("MouseLeftDown")));
RichTextBox.Document = myFlowDoc;
}
private void MLUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
FlowDocument myFlowDoc = new FlowDocument();
myFlowDoc.Blocks.Add(new Paragraph(new Run("MouseLeftUp")));
RichTextBox.Document = myFlowDoc;
}
private void MRDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
FlowDocument myFlowDoc = new FlowDocument();
myFlowDoc.Blocks.Add(new Paragraph(new Run("MouseRightDown")));
RichTextBox.Document = myFlowDoc;
}
private void MRUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
FlowDocument myFlowDoc = new FlowDocument();
myFlowDoc.Blocks.Add(new Paragraph(new Run("MouseRightUp")));
RichTextBox.Document = myFlowDoc;
}
private void MEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
FlowDocument myFlowDoc = new FlowDocument();
myFlowDoc.Blocks.Add(new Paragraph(new Run("MouseEnter")));
RichTextBox.Document = myFlowDoc;
}
private void MLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
FlowDocument myFlowDoc = new FlowDocument();
myFlowDoc.Blocks.Add(new Paragraph(new Run("MouseLeave")));
RichTextBox.Document = myFlowDoc;
}
}
} |
▲ 1-b. XAML(1-a) のコードビハインド (.xaml.cs)
.gif)
1-c. 実行結果
上記のコードを走らせると、画面中央に青いオブジェクトが配置されます。そのオブジェクトにマウスカーソルを合わせたり外したりすると、テキスト ボックスに "Mouse Enter", "MouseLeave" のメッセージが表示されます。右クリック、左クリックも試してみてください。それぞれ、ボタンを押した時と離した時とで、異なるメッセージが表示されるの が確認できるかと思います。
これらを実現しているのが、1-a. の XAML コードの Rectangle タグ内にある、下記のプロパティの記述です。
| <Rectangle | ~ | MouseLeftButtonDown="(関数名)" MouseLeftButtonUp=" (関数名)" MouseRightButtonDown=" (関数名)" MouseRightButtonUp=" (関数名)" MouseEnter=" (関数名)" MouseLeave=" (関数名)" /> |
サンプルコード内では、関数名として MLUp や MEnter などが指定されているのがご確認いただけると思いますが、これに対応した関数の記述が 1-b. のコードビハインドファイルに記述されています。これらそれぞれの関数が、各マウスイベントによって呼び出され、メッセージを表示させているという訳で す。
このサンプルコードでは、代表的ないくつかのコードのみを実装していますが、マウスイベントは他にも存在します。詳しくは下記の表をご参照ください。
| イベント名 | 発生タイミング | ルーティング |
|---|
| MouseEnter | オブジェクトにカーソルが重なったとき | 直接型 |
| MouseLeave | オブジェクトからカーソルが外れたとき | 直接型 |
| MouseLeftButtonDown | オブジェクト上で左ボタン押下時 | トンネル型,バブル型 |
| MouseLeftButtonUp | オブジェクト上で左ボタン解放時 | トンネル型,バブル型 |
| MouseRightButtonDown | オブジェクト上で右ボタン押下時 | トンネル型,バブル型 |
| MouseRightButtonUp | オブジェクト上で右ボタン解放時 | トンネル型,バブル型 |
| MouseMove | オブジェクト上でカーソル移動時 | トンネル型,バブル型 |
| MouseWheel | オブジェクト上でホイール動作時 | トンネル型,バブル型 |
| GotMouseCapture | オブジェクトにマウスキャプチャ時 | バブル型 |
| LostMouseCapture | オブジェクトからキャプチャ消失時 | バブル型 |
| QueryCursol | マウスカーソルの表示形状確定時 | バブル型 |
なお、マウスキャプチャとは、マウスカーソルが別のオブジェクトに重なった状態になっても、キャプチャしたオブジェクトが、マウスからのイベントを 受け取り続けるための仕組みです。例えば最前面に A というオブジェクトが、その裏側に B というオブジェクトがあり、B を最前面に持ってこない状態で A の裏側をそのままドラッグして通過させたい場合などには、B にマウスをキャプチャさせることで実現することが出来ます。
■ キーボードイベント
キーボードによるテキスト入力が発生した際にも、イベントは発生します。ただし、キーボードはマウスと異なり、今現在、どのオブジェクトを対象とし ているかを特定することはできません。その代わり、キーボードには、フォーカスという概念が存在します。これは、今現在、どのオブジェクトがキーボードか らの入力を受け付けているかを表すものです。その対象を指定するにはプログラム側から制御する場合を除き、ユーザー操作により、マウスカーソルでそのオブ ジェクトがクリックされるか、あるいはタブキーでフォーカス対象を移動するなどの方法があります。
下記のサンプルをご覧ください。
|
<Grid
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/interactivedesigner/2006"
mc:Ignorable="d"
Background="#FFFFFFFF"
x:Name="DocumentRoot"
x:Class="UntitledProject1.Scene1"
Width="631" Height="442">
<Grid.Resources>
<Storyboard x:Key="OnLoaded"/>
</Grid.Resources>
<Grid.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard x:Name="OnLoaded_BeginStoryboard" Storyboard="{DynamicResource OnLoaded}"/>
</EventTrigger>
</Grid.Triggers>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<RichTextBox FontFamily="Meiryo" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,160,0,0" Width="200" Height="27" x:Name="RichTextBox">
<FlowDocument/>
</RichTextBox>
<Rectangle Stroke="sc#1, 0.0351994745, 0.06138334, 0.22558558" Fill="sc#1, 0.07478483, 0.130415082, 0.47927928"
HorizontalAlignment="Left" VerticalAlignment="Top" Margin="11,40,0,0" Width="200" Height="100" x:Name="Rectangle"/>
<RichTextBox FontFamily="Tahoma" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="20,80,0,0" Width="180"
Height="28" x:Name="RichTextBox1" KeyDown="KDown" KeyUp="KUp" GotFocus="GFocus" LostFocus="LFocus">
<FlowDocument>
<Paragraph TextAlignment="Center"><Run Language="ja-jp">Please write any text at here.</Run></Paragraph>
</FlowDocument>
</RichTextBox>
</Grid> |
▲ 2-a. XAMLサンプルコード (.xaml)
|
using System;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.Windows.Documents;
namespace UntitledProject1
{
public partial class Scene1
{
public Scene1()
{
this.InitializeComponent();
}
private void KDown(object sender, System.Windows.Input.KeyEventArgs e)
{
FlowDocument myFlowDoc = new FlowDocument();
myFlowDoc.Blocks.Add(new Paragraph(new Run("KeyDown")));
RichTextBox.Document = myFlowDoc;
}
private void KUp(object sender, System.Windows.Input.KeyEventArgs e)
{
FlowDocument myFlowDoc = new FlowDocument();
myFlowDoc.Blocks.Add(new Paragraph(new Run("KeyUp")));
RichTextBox.Document = myFlowDoc;
}
private void GFocus(object sender, System.Windows.RoutedEventArgs e)
{
FlowDocument myFlowDoc = new FlowDocument();
myFlowDoc.Blocks.Add(new Paragraph(new Run("GotFocus")));
RichTextBox.Document = myFlowDoc;
}
private void LFocus(object sender, System.Windows.RoutedEventArgs e)
{
FlowDocument myFlowDoc = new FlowDocument();
myFlowDoc.Blocks.Add(new Paragraph(new Run("LostFocus")));
RichTextBox.Document = myFlowDoc;
}
}
} |
▲ 2-b. XAML(2-a) のコードビハインド (.xaml.cs)
.gif)
2-c. 実行結果
実行すると、青い矩形内にテキストボックスが表示されます。ここをクリックし、フォーカスが移ると、GotFocus イベントが発生した旨のメッセージが表示されます。
フォーカス対象となっているオブジェクトに対して、キーボード入力がなされると、下記のイベントが発生します。
| イベント名 | 発生タイミング | ルーティング |
|---|
| GotFocus | オブジェクトがフォーカス対象となった時 | トンネル型,バブル型 |
| LostFocus | オブジェクトからフォーカスが外れた時 | トンネル型,バブル型 |
| KeyDown | オブジェクトフォーカス中にキーが押された時 | トンネル型,バブル型 |
| KeyUp | オブジェクトフォーカス中にキーが離された時 | トンネル型,バブル型 |
| TextInput | オブジェクトフォーカス中にテキスト入力がなされた時 | トンネル型,バブル型 |
なお、TextInput イベントのテキスト入力とは、キーボードからの入力に関わらず動作するものです。例えば、テキスト入力欄に、マウス操作によってコピーアンドペーストでテキストが貼付けられたときなどにも、TextInput イベントは発生します。
イベントのルーティングについて
冒頭で、「マウスイベントはマウスカーソルと重なっているオブジェクトについて発生する」と述べましたが、例えばボタンの中に画像やテキスト、矩形などが 配置されている場合などもあります。上記の原則を考えれば、そのボタン全体を正しくボタンとしてクリック判定させるためには、ボタン内に含まれる画像や矩 形などの要素全部にイベントを記述していかなければいかないのでしょうか?
もちろんそうではありません。上記のようなことのために、WPF(.NET Framework) にはイベントのルーティングという仕組みが用意されています。これは、ボタンの要素内に含まれる子要素のオブジェクトに発生したイベントが、それら要素の 親子間のツリー構造を辿り、アプリケーション全体に伝えられる仕組みです。
イベントのルーティングには、バブル型 (階層浮上型)、トンネル型 (階層降下型)、直接型の 3 種類が存在します。アプリケーション内の全てのオブジェクトの入れ子構造をツリーと捉えた時、そのツリーをルートに向かって遡るのがバブル型、逆にルート から遠い方の枝先へ浸透していくのがトンネル型、そしてツリー構造に関わらず、イベントが発生したオブジェクトにしかそのイベントが通知されないのが直接 型です。
下記の図は、その概念を表したものです。
.gif)
ここまでで解説してきた各イベントの表内の、「ルーティング」は、そのイベントのルーティングがどの方向に行われるかを表したものです。イベントによっては、双方向にルーティングを行うものも存在します。
いかがでしたでしょうか。よく使うと思われるマウス、キーボードの入力イベントを軸にお話を進めてきましたが、これら以外にも、タブレット PC などで使用されるスタイラスデバイス固有のイベントなども存在します。これらを理解することで、XAML によるインターフェイス設計から一歩踏み込み、ユーザーのインターフェイス上での「操作体験」を設計することが可能となってきます。
次回は XAML の花形 (?) 機能であるアニメーションについて解説したいと思います。
著者情報
後藤 雄介
株式会社アゼスト.gif)
アニメーションやユーザー インターフェイスのデザインなどを経て、各プロジェクトにおけるユーザー エクスペリエンス デザインやインフォメーション アーキテクトを担当する一方、Flash Media Server 2 などによる映像のストリーミング配信を利用したサービスの企画や開発を得意とする。
趣味はランニングと自転車で、健康的な開発者生活を送っている (?)
WPF アプリケーションを作る クッキングガイドのトップへ戻る
ページのトップへ