Quantcast
Channel: かずきのBlog@hatena
Viewing all 1388 articles
Browse latest View live

WPF4.5入門 その29 「ContextMenuコントロール」

$
0
0

いや長いですね。その28書いてから1年と1月たってました。でもWPFのバージョンは変わらない!!

ContextMenuコントロール

ContextMenuコントロールは、特定のコントロールに対して固有のメニュー(主に右クリックしたときに表示されるメニュー)を提供するためのコントロールです。ContextMenuコントロールの見た目を以下に示します。

f:id:okazuki:20140811215337p:plain

ContextMenuの基本的な使い方

ContextMenuコントロールは、コントロールの基本クラスであるFrameworkElementクラスで定義されているContextMenuプロパティに設定して利用します。ContextMenuコントロールのItemsプロパティにMenuItemを設定して、メニューの項目を定義します。MenuItemコントロールは、メニューの1項目を表すコントロールで、Headerプロパティに設定した項目を表示します。上記の使用例のようなメニューを定義するXAMLは以下のようになります。

<Border …省略…><Border.ContextMenu><ContextMenu><MenuItem Header="メニュー1"/><MenuItem Header="メニュー2"/><MenuItem Header="メニュー3"/><MenuItem Header="メニュー4"/></ContextMenu></Border.ContextMenu></Border>

MenuItemコントロールは、Buttonコントロールと同じようにClickイベントがあります。Clickイベントにイベントハンドラを設定することで、MenuItemが押されたときの処理を記述できます。 メニュー1をクリックしたときに、Hello worldというメッセージを表示するには以下のように記述します。

<Border …省略…><Border.ContextMenu><ContextMenu><MenuItem Header="メニュー1"Click="MenuItem_Click"/><MenuItem Header="メニュー2"/><MenuItem Header="メニュー3"/><MenuItem Header="メニュー4"/></ContextMenu></Border.ContextMenu></Border>

コードビハインドには、以下のようなメソッドを定義します。

privatevoid MenuItem_Click(object sender, RoutedEventArgs e)
{
    MessageBox.Show("Hello world");
}

メニューの表示非表示の制御

ContextMenuコントロールは、基本的にマウスの右クリックで表示されますが、IsOpenプロパティを使用することで、プログラムから表示非表示を制御したり、現在表示中かどうか判断することが出来ます。

プロパティ説明
public bool IsOpen { get; set; }ContextMenuコントロールが表示されているかどうかを取得または設定します。trueのときに表示、falseのときは非表示を表します。

メニューの階層表示

階層構造を持ったメニューを定義するには、以下のようにMenuItemを入れ子に定義します。このように定義することでMenuItemのItemsプロパティにMenuItemが設定されて、階層構造を持ったメニューを定義できます。

<ContextMenu><MenuItem Header="メニュー1"Click="MenuItem_Click"/><MenuItem Header="メニュー2"><MenuItem Header="メニュー 2-1" /><MenuItem Header="メニュー 2-2" /><MenuItem Header="メニュー 2-3" /></MenuItem><MenuItem Header="メニュー3"/><MenuItem Header="メニュー4"/></ContextMenu>

実行すると、以下のようにメニュー2に子のメニューが出来ます。

f:id:okazuki:20140811215933p:plain

過去記事


WPF4.5入門 その30「Menuコントロール」

$
0
0

メニュー系コントロール地味だけど、ちゃんとしたアプリだと使うところですよね。ということで今日は普通のメニューコントロールです。

Menuコントロール

Menuコントロールは、Window上部などに表示されるメニュー項目です。一般的にファイル、編集などの項目などがあります。WPFのMenuコントロールを使うと以下のようなメニューを作成することが出来ます。

f:id:okazuki:20140812122318p:plain

基本的な使い方

Menuコントロールも通常のコントロールと同様に、画面の好きな位置にレイアウトできます。そのため一般的なメニューの位置である画面上部に表示するためには、Gridなどのレイアウトコントロールを使い画面の上部に表示されるように調整する必要があります。 画面の置き場所をレイアウトする以外は、基本的にContextMenuコントロールと同様の方法で使うことが出来ます。上記の例のような画面を表示するためのXAMLを以下に示します。

<Grid><Grid.RowDefinitions><RowDefinition Height="Auto" /><RowDefinition /></Grid.RowDefinitions><Menu><MenuItem Header="ファイル(_F)" /><MenuItem Header="編集(_E)" /><MenuItem Header="オプション(_O)"><MenuItem IsCheckable="True"Header="オプション1" /><MenuItem IsCheckable="True"Header="オプション2" /><MenuItem IsCheckable="True"Header="オプション3" /></MenuItem></Menu></Grid>

Gridコントロールを使用して、画面上部にMenuコントロールを配置しています。MenuコントロールではなくMenuItemコントロールの使い方になりますが、Headerプロパティに”_F”などのように記述すると、Altを押した後のキーボードショートカットを指定することが出来ます。 また、オプションメニューで示しているようにIsCheckableプロパティをtrueにすることで、チェック可能なメニュー項目を作成可能です。チェックの有無の確認はIsCheckedプロパティで指定可能です。

プロパティ説明
public bool IsCheckable { get; set; }MenuItemがチェック可能かどうかを取得または設定します。Trueの場合に、チェック可能となります。
public bool IsChecked { get; set; }MenuItemがチェックされているかどうかを取得または設定します。チェックされている場合Trueになります。

過去記事

WPF4.5入門 その31 「ToolBarコントロール」

$
0
0

ToolBarコントロールは、名前の通り、アプリケーションのツールバーを実装するためのコントロールです。ToolBarコントロールは、ToolBarTrayコントロール内に配置する形で実装します。ToolBarTrayには複数のToolBarコントロールを設置でき、ユーザーはマウスを使ってToolBarコントロールの位置を移動させることが出来ます。 以下にToolBarTrayコントロールにToolBarコントロールを2つ置いて、その中にButtonやCheckBoxやToggleButtonを置いた例を以下に示します。ToolBarコントロール内の縦線は、Separatorコントロールを使用しています。

f:id:okazuki:20140812214936p:plain

CheckBoxコントロールとToggleButtonコントロールは、クリックをしてチェックをつけた状態にしています。画面上で色が変わっていることが確認できます。上記の画面のXAMLを以下に示します。

<ToolBarTray><ToolBar><Button Content="item1" /><Button Content="item2" /><Button Content="item3" /><Separator /><Button Content="item4" /><CheckBox Content="item5" /></ToolBar><ToolBar><Button Content="item6" /><Button Content="item7" /><Button Content="item8" /><Separator /><ToggleButton Content="item9" /></ToolBar></ToolBarTray>

ToolBarコントロールの位置の制御

ToolBarコントロールは、ToolBarTrayコントロールに複数ある場合ユーザーによって並び替えることができます。これを制御するプロパティが、ToolBarコントロールのBandプロパティとBandIndexプロパティになります。

プロパティ説明
public int Band { get; set; }ToolBarTrayコントロールのOrientationプロパティがHorizontalの場合に、ToolBarコントロールを何行目に表示するかを表す。ToolBarTrayコントロールのOrientationプロパティがVerticalの場合にToolBarコントロールの何列目に表示するかを表す。
public int BandIndex { get; set; }ToolBarTrayコントロールのOrientationプロパティがHorizontalの場合に、ToolBarコントロールを何列目に表示するかを表す。ToolBarTrayコントロールのOrientationプロパティがVerticalの場合にToolBarコントロールの何行目に表示するかを表す。

以下にToolBarコントロールを2つおいて、それぞれのBandプロパティとBandIndexプロパティを表示するプログラムの実行例を示します。ToolBarコントロールを並び替えると、それに応じてBandプロパティとBandIndexプロパティの値が変わっていることが確認できます。

f:id:okazuki:20140812215131p:plain

f:id:okazuki:20140812215155p:plain

ユーザーが並び替えたToolBarコントロールの状態を保持するには各ToolBarコントロールのBandプロパティとBandIndexプロパティを保存・復元すればいいことがわかります。

過去記事

WPF4.5入門 その32 「CheckBoxコントロール」

$
0
0

CheckBoxコントロールは、オン・オフ・その他の状態を表すコントロールです。一般的に以下のような見た目をしています。

f:id:okazuki:20140812225842p:plain

上記の画面のXAMLを以下に示します。

<StackPanel><CheckBox Content="オン"IsChecked="True"/><CheckBox Content="オフ"IsChecked="False"/><CheckBox Content="その他"IsChecked="{x:Null}"IsThreeState="True"/></StackPanel>

CheckBoxコントロールの代表的なプロパティを以下に示します。

プロパティ説明
public Nullable IsChecked { get; set; }オンの時にtrue、オフの時にfalse、その他の時にnullが設定されています。
public bool IsThreeState { get; set; }CheckBoxがオン・オフの2つの状態ではなく、オン・オフ・その他の3つの状態を持つかどうかを表します。trueのときに、その他の状態を持つようになります。デフォルト値はfalseです。

また、CheckBoxコントロールは、選択状態が変わったことを検知するための以下のイベントも備えています。

イベント説明
CheckedIsCheckedプロパティがtrueになったときに呼び出されます。
UncheckedIsCheckedプロパティがfalseになったときに呼び出されます。
IndeterminateIsCheckedプロパティがnullになったときに呼び出されます。

これらのイベントを使うことで、選択状態に応じた処理を行うことが出来ます。 CheckBoxのチェック状態に応じてTextBlockの表示を切り替えるプログラムは以下のようになります。

<StackPanel><CheckBox IsThreeState="True"Content="CheckBox"Checked="CheckBox_Checked"Unchecked="CheckBox_Unchecked"Indeterminate="CheckBox_Indeterminate"/><TextBlock x:Name="textBlock"Text="オフ"/></StackPanel>
privatevoid CheckBox_Checked(object sender, RoutedEventArgs e)
{
    this.textBlock.Text = "オン";
}

privatevoid CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
    this.textBlock.Text = "オフ";
}

privatevoid CheckBox_Indeterminate(object sender, RoutedEventArgs e)
{
    this.textBlock.Text = "その他";
}

実行すると以下のような結果になります。

f:id:okazuki:20140812230229p:plain

過去記事

WPF4.5入門 その33 「ComboBoxコントロール」

$
0
0

ComboBoxコントロールは、複数の選択肢の中から1つをユーザーに選択してもらうためのユーザーインターフェースを提供するコントロールです。オプションとして、複数の選択肢の中から1つを選ぶか、自由にテキストを入力する方法も提供することができます。

ComboBoxの基本的な使い方は、ItemsSourceプロパティにオブジェクトのコレクションを設定して、ItemTemplateで見た目を定義する方法になります。例として、以下のようなNameとAgeプロパティを持つPersonクラスを表示する場合のコードを示します。

// ComboBoxに表示するオブジェクトpublicclass Person
{
    publicstring Name { get; set; }
    publicint Age { get; set; }
}

ComboBoxのXAMLでの定義は以下のようになります。ItemTemplateに、Personクラスの表示を定義するテンプレートを設定しています。

<ComboBox x:Name="comboBox"><ComboBox.ItemTemplate><DataTemplate><StackPanel Orientation="Horizontal"><TextBlock Text="{Binding Name}"Margin="2.5"/><TextBlock Text="{Binding Age}"Margin="2.5"/></StackPanel></DataTemplate></ComboBox.ItemTemplate></ComboBox>

コードビハインドのコンストラクタで、このComboBoxコントロールのItemsSourceプロパティにデータを設定します。

var items = Enumerable.Range(1, 10)
    .Select(i => new Person { Name = "おおた" + i, Age = 20 + i })
    .ToList();

this.comboBox.ItemsSource = items;

この状態で実行すると、以下のようにItemTemplateに設定した見た目の通り「名前 年齢」の順番でデータが並んだ状態で表示されます。

f:id:okazuki:20140814200814p:plain

また、IsEditableプロパティをtrueに設定することで、選択肢にない項目をTextBoxと同じ要領で入力できるようにもなります。選択した項目の表示場所がTextBoxになることで、ItemTemplateの表示ができなくなるため、TextSearch.TextPathプロパティで選択した項目の、どのプロパティの値を表示するのか指定する必要があります。(省略した場合は、ToStringの結果がそのまま表示されます)

<ComboBox x:Name="comboBoxEditable"Grid.Column="2"Grid.Row="2"MinWidth="150"IsEditable="True"TextSearch.TextPath="Name"><ComboBox.ItemTemplate><DataTemplate><StackPanel Orientation="Horizontal"><TextBlock Text="{Binding Name}"Margin="2.5"/><TextBlock Text="{Binding Age}"Margin="2.5"/></StackPanel></DataTemplate></ComboBox.ItemTemplate></ComboBox>

f:id:okazuki:20140814200852p:plain

選択項目の操作

ComboBoxコントロールで選択された要素を取得するには、インデックスを取得する方法と、選択された値そのものを取得するプロパティがあります。また、Textプロパティを使うことで現在選択中の項目のテキストも取得できます。

プロパティ説明
public int SelectedIndex { get; set; }ComboBoxコントロールで現在選択中の項目のインデックスを取得または設定します。未選択時は-1です。
public object SelectedItem { get; set; }ComboBoxコントロールで現在選択中の値を取得または設定します。
public string Text {get; set; }ComboBoxコントロールで現在のテキストを取得または設定します。

以下のように設定した、ComboBoxコントロールのSelectedIndexプロパティ、SelectedItemプロパティ、Textプロパティを表示するプログラムの実行結果を示します。

<ComboBox x:Name="comboBox"Grid.ColumnSpan="2"TextSearch.TextPath="Name"><ComboBox.ItemTemplate><DataTemplate><StackPanel Orientation="Horizontal"><TextBlock Text="{Binding Name}"Margin="2.5"/><TextBlock Text="{Binding Age}"Margin="2.5"/></StackPanel></DataTemplate></ComboBox.ItemTemplate></ComboBox>

実行直後は、SelectedIndexプロパティが-1になっていることが確認できます。また、SelectedItemプロパティはnullでTextプロパティは空文字になっています。

f:id:okazuki:20140814201102p:plain

3番目の項目を選択すると以下のような表示になります。

f:id:okazuki:20140814201127p:plain

SelectedIndexプロパティが3番目を表す2になっている点と、SelectedItemプロパティが、選択項目のPersonオブジェクトになっていることが確認できます。TextプロパティはTextSearch.TextPath添付プロパティでNameプロパティを表示するようにしているため、Nameプロパティの値が表示されていることがわかります。

過去記事

WPF4.5入門 その34 「ListBoxコントロール」

$
0
0

ListBoxコントロールは、1つまたは複数の項目をユーザーに選択させることが出来るコントロールです。基本的な使用方法は、ComboBoxコントロールと同様になります。ItemTemplateによる見た目の設定、ItemsSourceプロパティによる、選択項目の設定、SelectedItemプロパティやSelectedIndexプロパティによる選択項目の管理ができます。 これは、ListBoxコントロールとComboBoxコントロールが、同じSelectorコントロールを継承しているためです。ここでは、ListBoxコントロール固有のSelectionModeプロパティの説明のみを行います。

プロパティ説明
public SelectionMode SelectionMode { get; set; }ユーザーがどのようにListBoxコントロールの項目を選択するか取得または設定します。SelectionMode列挙型は以下の値があります。Extended:Shiftキーを押しながら連続した複数項目を選択できる。Multiple:Shiftキーを押さなくても複数項目を選択できる。Single:単一項目を選択できる。

以下のようにSelectionModeプロパティに、Multipleを設定したときの動作例を以下に示します。

<ListBox x:Name="listBox"SelectionMode="Multiple"><ListBox.ItemTemplate><DataTemplate><!-- Name プロパティを持つPersonクラス用のテンプレート --><TextBlock Text="{Binding Name}" /></DataTemplate></ListBox.ItemTemplate></ListBox>

f:id:okazuki:20140814205302p:plain

このように複数項目が、選択されている場合SelectedItemsプロパティで取得します。

プロパティ説明
public IList SelectedItems { get; }複数選択されている項目のリストを取得します。

過去記事

WPF4.5入門 その35 「RadioButtonコントロール」

$
0
0

RadioButtonコントロールは、複数の選択肢の中から1つをユーザーに選択してもらうときに使うコントロールです。RadioButtonコントロールは、デフォルトでは同じパネル(StaciPanelやGridなど)にあるRadioButtonコントロールから、1つだけチェックをつけることが出来ます。また、一度つけてしまったチェックは、ユーザーからは確認できないという特徴があります。チェック状態はCheckBoxコントロールと同様にIsCheckedプロパティで確認でき、Checkedイベントでチェック状態に変更があったことをハンドリングできます。

以下にRadioButtonコントロールの基本的な使い方を示します。1つのStackPanel上に置いたRadioButtonコントロールにCheckedイベントのハンドラを設定しています。

<GroupBox Header="Group1"><StackPanel><RadioButton Content="Button1"Checked="RadioButton_Checked"/><RadioButton Content="Button2"Checked="RadioButton_Checked" /><RadioButton Content="Button3"Checked="RadioButton_Checked"/><TextBlock x:Name="textBlockSelected" /></StackPanel></GroupBox>

RadioButton_Checkedイベントハンドラでは、senderを使って選択されたRadioButtonコントロールを取得してtextBlockSelectedにContentの内容を表示しています。

privatevoid RadioButton_Checked(object sender, RoutedEventArgs e)
{
    var radioButton = (RadioButton)sender;
    this.textBlockSelected.Text = radioButton.Content.ToString();
}

実行すると初期状態では、どのRadioButtonコントロールにもチェックは入っていません。

f:id:okazuki:20140815124411p:plain

マウスで選択すると、選択した項目のContentが、下部に表示されます。

f:id:okazuki:20140815124456p:plain

同じパネル上で複数のグループを作成する方法

デフォルトの挙動では、同じパネルに置いたRadioButtonコントロールは、1つしか選択できません。この制限を回避するために、GroupNameというプロパティがあります。GroupNameに文字列を指定すると、同じGroupNameのRadioButtonコントロールの中から1つ選択できるという挙動になります。

以下に、1つのStackPanel上に2つの異なるGroupNameを指定したXAMLを示します。

<GroupBox Header="Group2"><StackPanel><RadioButton GroupName="group1"Content="Button1-1" /><RadioButton GroupName="group1"Content="Button1-2" /><RadioButton GroupName="group1"Content="Button1-3" /><RadioButton GroupName="group2"Content="Button2-1" /><RadioButton GroupName="group2"Content="Button2-2" /><RadioButton GroupName="group2"Content="Button2-3" /></StackPanel></GroupBox>

実行して、group1とgroup2のRadioButtonコントロールをそれぞれ選択した結果を以下に示します。

f:id:okazuki:20140815124644p:plain

同一パネル上でも、複数のRadioButtonコントロールのグループが出来ていることが確認できます。

過去記事

WPF4.5入門 その36 「Sliderコントロール」

$
0
0

Sliderコントロールは、任意の範囲の数値から1つをユーザーに選択させるコントロールです。デフォルトでは0~10の間の実数を選択する動作をします。以下のようにStackPanel上に置いただけのSliderコントロールの動作を以下に示します。

<StackPanel Margin="5"><Slider x:Name="slider" /><TextBlock Text="{Binding Value, ElementName=slider}" /></StackPanel>

起動直後は、以下のように0をさしています。

f:id:okazuki:20140815220207p:plain

スライダーコントロールの余白の線をクリックすると1ずつ値が増えていきます。以下の図は、3回クリックしたときの様子です。値が3になっていることが確認できます。

f:id:okazuki:20140815220236p:plain

つまみを移動することで、範囲内の任意の値を選択することもできます。

f:id:okazuki:20140815220301p:plain

Sliderコントロールの値を取得するにはValueプロパティを使用します。

プロパティ説明
public double Value { get; set; }Sliderコントロールの現在の値を取得または設定します。

選択範囲のカスタマイズ

Sliderコントロールは、MinimumプロパティとMaximumプロパティを使って最大値と最小値を変更することが出来ます。

プロパティ説明
public double Minimum { get; set; }Sliderコントロールの最小値を取得または設定します。
public double Maximum { get; set; }Sliderコントロールの最大値を取得または設定します。

以下のようにXAMLでMinimumプロパティ、Maximumプロパティ、Valueプロパティを指定した実行結果を示します。

<StackPanel Margin="5"><Slider x:Name="slider"Minimum="-20"Maximum="20"Value="-10" /><TextBlock Text="{Binding Value, ElementName=slider}" /></StackPanel>

f:id:okazuki:20140815220517p:plain

初期状態で-10、最小値が-20、最大値が20になっていることが確認できます。

値の増減幅の設定

Sliderコントロールは、余白をクリックしたときの値の移動量(デフォルトは1)と、矢印キーを押したときの値の移動量(デフォルトは0.1)を設定できます。それぞれ、LargeChangeプロパティと、SmallChangeプロパティで指定します。

プロパティ説明
public double LargeChange { get; set; }マウスでつまみの横の余白の線をクリックしたときの値の変化量を取得または設定します。
public double SmallChange { get; set; }矢印キーを押したときの値の変化量を取得または設定します。

縦方向のスライダーと目盛り

Sliderコントロールは、横方向だけではなく縦方向にしたり、Sliderコントロールに目盛りをつけることが出来ます。

プロパティ説明
public Orientation Orientation {get; set; }スライダーが横方向なのか縦方向なのかを取得または設定します。デフォルト値はHorizontal(水平)です。Verticalを設定することで縦方向にできます。
public TickPlacement TickPlacement { get; set; }Sliderコントロールの目盛りの状態を取得または設定します。 None:目盛りなし。(デフォルト値)。 Both:横方向のときは上下に、縦方向のときは左右に目盛りがつきます。BottomRight:横方向のときは下に、縦方向のときは右に目盛りがつきます。TopLeft:横方向のときは上に、縦方向のときは左に目盛りがつきます。

縦方向にして、目盛りをつける場合のXAMLを以下に示します。

<StackPanel Margin="5"Orientation="Horizontal"><Slider x:Name="slider"Minimum="10"Maximum="100"SmallChange="1"LargeChange="10"TickPlacement="BottomRight"Orientation="Vertical"/><TextBlock Text="{Binding Value, ElementName=slider}" /></StackPanel>

実行すると以下のように縦方向で目盛りつきのSliderコントロールが表示されます。

f:id:okazuki:20140815220901p:plain

過去記事


WPF4.5入門 その37 「TabControl」

$
0
0

TabControlは、以下のようなタブで切り替えて複数のコンテンツを表示するUIを提供するためのコントロールです。

f:id:okazuki:20140815232131p:plain

TabControlは、ItemsプロパティにTabItemコントロールを指定してタブを作成します。TabItemコントロールは、Headerプロパティにタブのヘッダーに表示するコンテンツを設定して、Contentプロパティにタブの中に表示するコンテンツを設定します。上記の画面のXAMLを以下に示します。

<TabControl><TabItem Header="TabItem1"><TextBlock Text="TabItem1 Content" /></TabItem><TabItem Header="TabItem2"><TextBlock Text="TabItem2 Content" /></TabItem><TabItem Header="TabItem3"><TextBlock Text="TabItem3 Content" /></TabItem></TabControl>

TabControlでコレクションを表示する

TabControlは、ComboBoxコントロールやListBoxコントロールと同じSelectorコントロールを継承しています。そのため、ItemsSourceプロパティにコレクションを設定すると、ItemTemplateプロパティに従ってTabのヘッダーを表示することが出来ます。Tabのコンテンツ部は、ContentTemplateプロパティにDataTemplateを指定して表示方法を定義します。

以下のようなPersonクラスを表示するTabControlを説明します。

publicclass Person
{
    publicstring Name { get; set; }
    publicint Age { get; set; }
}

TabControlのItemTemplateでNameを表示して、ContentTemplateでNameとAgeを表示するXAMLを以下に示します。

<TabControl x:Name="tabControl"><TabControl.ItemTemplate><DataTemplate><TextBlock Text="{Binding Name}" /></DataTemplate></TabControl.ItemTemplate><TabControl.ContentTemplate><DataTemplate><Grid><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition Width="Auto" /><ColumnDefinition /></Grid.ColumnDefinitions><Label Grid.Row="0"Grid.Column="0"Content="名前" /><TextBlock Grid.Row="0"Grid.Column="1"Text="{Binding Name}"VerticalAlignment="Center" /><Label Grid.Row="1"Grid.Column="0"Content="年齢" /><TextBlock Grid.Row="1"Grid.Column="1"Text="{Binding Age}"VerticalAlignment="Center" /></Grid></DataTemplate></TabControl.ContentTemplate></TabControl>

コードビハインドのコンストラクタで、以下のようにTabControlのItemsSourceプロパティに値を設定します。

public MainWindow()
{
    InitializeComponent();

    var source = Enumerable.Range(1, 10)
        .Select(i => new Person { Name = "おおた" + i, Age = 20 + i });
    this.tabControl.ItemsSource = source;
}

実行すると、以下のようにコレクションがTabで表示されます。

f:id:okazuki:20140815232441p:plain

過去記事

WPF4.5入門 その38 「ファイルダイアログ」

$
0
0

WPFは、ファイルを開いたり保存するためのダイアログを提供しています。以下の2つのダイアログがあります。

  • SaveFileDialog:ファイルを保存するときに使用するダイアログです。
  • OpenFileDialog:ファイルを開くときに使用するダイアログです。

これらのダイアログは、主に以下のような流れで使用します。

  • インスタンスを生成する。
  • Titleプロパティと、Filterプロパティを設定する。
  • ShowDialogを呼び出してダイアログを表示する。
  • ダイアログの戻り値を確認してOKが押されている場合は、FileNameプロパティかFileNamesプロパティを使用して選択されたファイルを取得する。

プロパティの説明を以下に示します。

プロパティ説明
public string Title { get; set; }ファイルダイアログのタイトルに表示されるテキストを取得または設定します。
public string Filter { get; set; }ファイルダイアログで選択できるファイルの種類を示すフィルターを取得または設定します。
public string FileName { get; set; }ファイルダイアログで選択されたファイルのフルパスを取得または設定します。
public string[] FileNames { get; }ファイルダイアログで選択されたすべてのファイルのフルパスを取得します。

Filterプロパティの書式について詳しく説明します。Filterプロパティは以下のような書式で指定します。

全てのファイル|*.*|ワードファイル|*.doc;*.docx

表示用のラベルと対象の拡張子を|で区切って記述します。対象の拡張子が複数ある場合は;で区切って指定します。上記の記述では、全てのの拡張子を対象とした全てのファイルと、docとdocxという拡張子を対象としたワードファイルという選択肢がファイルダイアログに表示されます。

次に、ShowDialogメソッドについて説明します。

メソッド説明
public Nullable ShowDialog()ファイルダイアログを表示します。OKが選択された場合は、trueを返して、それ以外の場合はfalseを返します。

以下に、OpenFileDialogとSaveFileDialogの使用例を示します。

画面は、ButtonコントロールとTextBlockコントロールを並べたシンプルなものです。

<StackPanel><StackPanel Orientation="Horizontal"><Button Content="FileOpen"Click="FileOpenButton_Click" /><Button Content="FileSave"Click="FileSaveButton_Click" /></StackPanel><TextBlock x:Name="textBlockFileName" /></StackPanel>

コードビハインドの、Buttonのクリックイベントでダイアログを表示してファイル名を画面に表示しています。

privatevoid FileOpenButton_Click(object sender, RoutedEventArgs e)
{
    var dialog = new OpenFileDialog();
    dialog.Title = "ファイルを開く";
    dialog.Filter = "全てのファイル(*.*)|*.*";
    if (dialog.ShowDialog() == true)
    {
        this.textBlockFileName.Text = dialog.FileName;
    }
    else
    {
        this.textBlockFileName.Text = "キャンセルされました";
    }
}

privatevoid FileSaveButton_Click(object sender, RoutedEventArgs e)
{
    var dialog = new SaveFileDialog();
    dialog.Title = "ファイルを保存";
    dialog.Filter = "テキストファイル|*.txt";
    if (dialog.ShowDialog() == true)
    {
        this.textBlockFileName.Text = dialog.FileName;
    }
    else
    {
        this.textBlockFileName.Text = "キャンセルされました";
    }
}

実行してボタンを押すと、以下のようにダイアログが表示されます。

f:id:okazuki:20140816112627p:plain

ファイルを選択するとTextBlockに選択したファイルのフルパスが表示されます。

f:id:okazuki:20140816112707p:plain

過去記事

WPF4.5入門 その39 「情報を表示するコントロール」

$
0
0

Labelコントロール

Labelコントロールは、コントロールに対するラベルを表示するコントロールです。LabelコントロールはButtonコントロールなどと同じContentControlを継承しているため、Contentプロパティに任意の値を設定してContentTemplateプロパティを使って、表示をカスタマイズできますが、文字列を指定するのが一般的です。

Labelコントロールは、「_アルファベット」でアクセスキーを提供して、Targetプロパティに設定したコントロールにフォーカスをうつす機能があります。

<StackPanel Margin="5"><Label Content="ファイル(_F)"Target="{Binding ElementName=textBox1}" /><TextBox x:Name="textBox1" /><Label Content="編集(_E)"Target="{Binding ElementName=textBox2}" /><TextBox x:Name="textBox2" /></StackPanel>

上記の例ではAlt+FでtextBox1へフォーカスが移動し、Alt+EでtextBox2へフォーカスが移動します。

ProgressBarコントロール

ProgressBarコントロールは、ユーザーに処理の進捗状況を表示するためのコントロールです。ProgressBarコントロールは、Sliderコントロールと同じ親クラスを持っていて、Maximumプロパティ、Valueプロパティなどを使って値の範囲を指定することが出来ます。また、IsIndeterminateプロパティをtrueに設定することで、固定の値ではなく、何かの作業をしていることを示すアニメーションを表示することが出来ます。 以下に使用例を示します。

<StackPanel Margin="5"><Label Content="Maximum=100, Value=50" /><ProgressBar Maximum="100"Value="50"Height="20" /><Label Content="IsIndeterminate = True" /><ProgressBar IsIndeterminate="True"Height="20" /></StackPanel>

実行すると、以下のようになります。

f:id:okazuki:20140816145654p:plain

StatusBarコントロール

StatusBarコントロールは、水平方向に項目を並べてユーザーに情報を表示するためのコントロールです。StatusBarコントロールは、StatusBarItemコントロールを内部に複数持ち、SeparatorコントロールでStatusBarItemコントロールの間を区切って表示することもできます。主に画面の下部に表示することが多いコントロールです。

以下にStatusBarコントロールの使用例を示します。

<StatusBar><StatusBarItem><TextBlock Text="ステータスバーのサンプル" /></StatusBarItem><Separator /><StatusBarItem><ProgressBar IsIndeterminate="True"Width="150"Height="10"/></StatusBarItem><StatusBarItem><TextBlock Text="情報表示に使用します" /></StatusBarItem></StatusBar>

実行すると以下のようになります。

f:id:okazuki:20140816145811p:plain

TextBlockコントロール

TextBlockコントロールは、テキストを画面に表示するためのコントロールです。基本的な使い方はTextプロパティに文字列を設定して使います。

<TextBlock Text="文字列を指定します" />

TextBlockコントロールは、Textプロパティ以外にInlinesというプロパティもあり、ここにRunやHyperlinkをつかって書式付のテキストやハイパーリンクを挿入することもできます。

<TextBlock><Run Text="いろいろ指定できる" /><Hyperlink NavigateUri="http://www.bing.com">リンクできる</Hyperlink><Run Foreground="Red"Text="色も付けれる" /><Run FontFamily="メイリオ"Text="フォントも変えれます" /><LineBreak /><Run Text="改行も入れることができます" /></TextBlock>

このXAMLは、以下のように表示されます。

f:id:okazuki:20140816150002p:plain

過去記事

WPF4.5入門 その40 「Popup、ToolTip、TextBox、Image、MediaElementコントロール」

$
0
0

全然関連性のないコントロールをまとめた記念すべき40回です。

Popupコントロール

opupコントロールは、画面上に別ウィンドウとして項目を表示するためのコントロールです。Popupコントロールは、IsOpenプロパティを持っていて、このプロパティの値がtrueになったときに表示されます。Popupコントロールは、デフォルトでは、親要素の下に表示されます。PlacementプロパティにTop、Bottom、Right、Leftなどの値を設定することで表示位置を下以外にすることが出来ます。

Buttonコントロールの周りにPopupコントロールが表示されるプログラムの例を以下に示します。

<Grid><StackPanel HorizontalAlignment="Center"VerticalAlignment="Center"><Button Content="popup button"Click="Button_Click"/><Popup x:Name="popup1"><TextBlock Background="LightGray"Text="Bottom(Default)" /></Popup><Popup x:Name="popup2"Placement="Left"><TextBlock Background="LightGray"Text="Left" /></Popup><Popup x:Name="popup3"Placement="Top"><TextBlock Background="LightGray"Text="Top" /></Popup><Popup x:Name="popup4"Placement="Right"><TextBlock Background="LightGray"Text="Right" /></Popup></StackPanel></Grid>

Buttonを押すたびに表示、非表示を切り替えています。

privatevoid Button_Click(object sender, RoutedEventArgs e)
{
    var popups = new[] 
    {
        this.popup1,
        this.popup2,
        this.popup3,
        this.popup4
    };

    foreach (var popup in popups)
    {
        popup.IsOpen = !popup.IsOpen;
    }
}

実行してButtonをクリックすると以下のように上下左右にPopupコントロールが表示されます。

f:id:okazuki:20140816220423p:plain

Popupコントロールは非常に低レベルなレイヤのコントロールで、柔軟に細かく設定ができる反面制御がとても難しいコントロールになります。普通は、ComboBoxコントロールやContextMenuコントロールなどで内部的にPopupコントロールが使われているので、そちらを使いますが、どうしてもPopupさせるWindowがほしい場合は、このコントロールの利用を検討してください。(例としてはインテリセンスの自前実装など)

Popupコントロールの細かい制御方法についてはMSDNの以下のページを参照してください。

ToolTipコントロール

ToolTipコントロールは、マウスをコントロール上に置いたときに補助的な情報を表示するためのコントロールです。ToolTipをコントロールに表示するには、ほぼ全てのコントロールの基底クラスであるFrameworkElementクラスとFrameworkContentElementクラスで定義されているToolTipプロパティを使用します。

一番単純な例は、ToolTipプロパティに文字列を指定する方法です。

<Button Content="Button"ToolTip="ツールチップ" />

このボタンの上にマウスカーソルを持っていくと、以下のようにツールチップが表示されます。

f:id:okazuki:20140816220700p:plain

ToolTipコントロールは、Buttonコントロールなどと同じContentControlを親に持ちます。そのため、ToolTipコントロールのContentプロパティにコントロールを直接入れて表示したり、ContentTemplateプロパティを使ってデータを任意の形で表示することが出来ます。

以下は、ToolTip内に画像を表示している例です。

<Button Content="Button">
    <Button.ToolTip>
        <ToolTip>
            <StackPanel Orientation="Horizontal">
                <Image Source="anthem.jpg" Height="100" />
                <TextBlock Text="アンセム" />
            </StackPanel>
        </ToolTip>
    </Button.ToolTip>
</Button>

実行して、ボタンの上にマウスカーソルを持っていくと以下のように表示されます。

f:id:okazuki:20140816220750p:plain

TextBoxコントロール

TextBoxコントロールは文字列を入力するインターフェースを提供するコントロールです。入力された文字列は、Textプロパティで取得できます。Textが変更されたタイミングはTextChangedイベントを購読することで判別できます。

基本的なTextBoxの使用方法を以下に示します。

<StackPanel><TextBox TextChanged="TextBox_TextChanged" /><TextBlock x:Name="textBlock"TextWrapping="Wrap"/></StackPanel>
privatevoid TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    var textBox = (TextBox)sender;
    this.textBlock.Text = textBox.Text + "が入力されました";
}

TextBoxコントロールの入力内容をTextBlockコントロールへ設定しています。実行すると以下のようになります。

f:id:okazuki:20140816220851p:plain

改行やタブを受け入れるTextBoxコントロール

TextBoxコントロールは、デフォルトでは、改行やタブを受け付けません。メモ帳のような動作をさせるためには、以下の4つのプロパティを使用します。

プロパティ説明
public bool AcceptsReturn { get; set; }Enterキーを押すことで改行をすることができるかどうかを取得または設定します。デフォルト値はfalse(改行できない)です。
public bool AcceptsTab { get; set; }Tabキーを押すことでタブを挿入するかどうかを取得または設定します。デフォルト値はfalse(タブではなくフォーカス移動をする)です。
public Visibility HorizontalScrollBarVisibility { get; set; }水平方向のスクロールバーの表示方法を指定します。以下の値が設定可能です。Auto:必要な場合は表示をして不要な場合は非表示になります。Disable:無効化します。Visible:常に表示します。Hidden:非表示にします。
public Visibility VerticalScrollBarVisibility { get; set; }垂直方向のスクロールバーの表示方法を指定します。設定値はHorizontalScrollBarVisibilityと同じです。

改行や、タブの挿入が可能で、横スクロールバーと縦スクロールバーが常に表示されるTextBoxコントロールは以下のように定義します。

<TextBox AcceptsReturn="True"AcceptsTab="True"HorizontalScrollBarVisibility="Visible"VerticalScrollBarVisibility="Visible"/>

実行して文字を打ち込むと以下のように改行や、スクロールバーの表示を確認できます。

f:id:okazuki:20140816221110p:plain

Imageコントロール

Imageコントロールは、画面に画像を表示するコントロールです。Sourceプロパティに指定した画像と、Stretchプロパティに指定した画像の拡大方法をもとに、画像を表示します。StretchプロパティはViewBoxの拡大方法と同じ方法で画像を拡大・縮小します。

SourceプロパティはImageSource型ですが、XAMLからは文字列で画像のURIを指定することが出来ます。プロジェクトにビルドアクションResourceに指定された画像はプロジェクトのルートからの相対URIで指定可能です。プロジェクトにanthem.jpgという画像がある場合に、それを表示するImageコントロールのXAMLは以下のようになります。

<Image Source="anthem.jpg"Stretch="UniformToFill" />

実行すると以下のように表示されます。

f:id:okazuki:20140816221157p:plain

コードからSourceプロパティを指定するコードを以下に示します。画面のXAMLは以下の通りです。

<Grid><Grid.RowDefinitions><RowDefinition Height="Auto" /><RowDefinition /></Grid.RowDefinitions><Menu><MenuItem Header="開く(_O)"Click="MenuItem_Click" /></Menu><Image x:Name="image"Grid.Row="1"Stretch="Uniform" /></Grid>

メニューで画像を開いて、Imageに表示するというUIです。MenuItemのクリック時の処理は以下のようになります。処理内容はコメントの通りです。

privatevoid MenuItem_Click(object sender, RoutedEventArgs e)
{
    // 画像を開く
    var dialog = new OpenFileDialog();
    dialog.Filter = "画像|*.jpg;*.jpeg;*.png;*.bmp";
    if (dialog.ShowDialog() != true)
    {
        return;
    }

    // ファイルをメモリにコピー
    var ms = new MemoryStream();
    using (var s = new FileStream(dialog.FileName, FileMode.Open))
    {
        s.CopyTo(ms);
    }
    // ストリームの位置をリセット
    ms.Seek(0, SeekOrigin.Begin);
    // ストリームをもとにBitmapImageを作成
    var bmp = new BitmapImage();
    bmp.BeginInit();
    bmp.StreamSource = ms;
    bmp.EndInit();
    // BitmapImageをSourceに指定して画面に表示するthis.image.Source = bmp;
}

実行してファイルを開くと以下のように画面に画像が表示されます。

f:id:okazuki:20140816221307p:plain

MediaElementコントロール

MediaElementコントロールは、音楽や動画を再生するためのコントロールになります。Sourceプロパティに再生したいファイルへのURIを指定して使用します。LoadedBehaviorプロパティをManualにすることで、Playメソッドで再生を行い、Pauseメソッドで一時停止を行い、Stopメソッドで停止を行うなどの細かい制御が可能になります。またVolumeプロパティやSpeedRatioプロパティによって一般的な動画プレイヤーが備えるべき基本機能を提供します。

ファイルを開いて再生するコードを以下に示します。

<Grid><Grid.RowDefinitions><RowDefinition Height="Auto" /><RowDefinition /></Grid.RowDefinitions><Menu><MenuItem Header="開く(_O)"Click="MenuItem_Click" /></Menu><MediaElement x:Name="mediaElement"Grid.Row="1" /></Grid>
privatevoid MenuItem_Click(object sender, RoutedEventArgs e)
{
    // 動画を開く
    var dialog = new OpenFileDialog();
    dialog.Filter = "動画|*.mp4";
    if (dialog.ShowDialog() != true)
    {
        return;
    }

    // SourceにURIを指定して再生する。// LoadedBehaviorがPlay(デフォルト値)なので自動再生される。
    var uri = new Uri(dialog.FileName);
    this.mediaElement.Source = uri;
}

実行して動画を開いた画面を以下に示します。

f:id:okazuki:20140816221442p:plain

過去記事

WPF4.5入門その40までまとめたPDF作りました

$
0
0

ばらばらの記事だとみるのもめんどくさいのでPDFにまとめました。 SlideShareからダウンロードできます。WPF学習の一助になれば幸いです。

WPF4.5入門 その41 「DispatcherObject」

$
0
0

コントロールの使い方ばかりを書くのも飽きてきたので趣向を変えていきます。

DispatcherObject

WPFでは、他のUIフレームワークと同様にUIを操作するには専用のスレッドから操作をする必要があります。WPFでは、この操作を簡単にするためにDispatcherという仕組みを提供しています。WPFの継承階層を上へ上へたどっていくとDispatcherObjectというクラスに必ず当たります。このクラスにはDispatcherというプロパティが定義されていて、このオブジェクトが生成されたスレッドから操作されているかをチェックする仕組みや、オブジェクトが生成されたスレッドで処理のキューイングを行う仕組みを提供しています。

DispatcherObjectで提供される主な機能を以下に示します。 |プロパティ名|説明| |----------|----| |public Dispatcher Dispatcher { get; }|DispatcherObjectに紐づけられたスレッドに対応するDispatcherオブジェクトを取得します。|

メソッド名説明
public void VerifyAccess()現在のスレッドがDispatcherObjectに紐づけられたスレッドかどうかチェックします。チェックの結果異なるスレッドの場合InvalidOperationExceptionの例外をスローします。
public bool CheckAccess()現在のスレッドがDispatcherObjectに紐づけられたスレッドかどうかチェックします。チェックの結果異なるスレッドの場合falseを返します。

これらの機能の使い方についてのサンプルプログラムを以下に示します。 DispatcherObjectは抽象クラスなので、継承してメソッドを1つもつクラスを作成しました。メソッド内では、VerifyAccessメソッドを使って有効なスレッドからのアクセスかどうかを確認して、デバッグウィンドウへメッセージを出力しています。

publicclass DrivedObject : DispatcherObject
{
    publicvoid DoSomething()
    {
        // UIスレッドからのアクセスかチェックするthis.VerifyAccess();
        Debug.WriteLine("DoSomething");
    }
}

Windowに3つのボタンを置いて、UIスレッドからの直接呼出し、UIスレッド以外からの呼び出し、UIスレッド以外からDispatcher経由での呼び出しの3パターンの呼び出しを確認します。

<Window x:Class="DispatcherObjectSample01.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><Button Content="UIスレッドからなのでOK"Click="OKButton_Click" /><Button Content="UIスレッド以外から呼ぶのでNG"Click="NGButton_Click" /><Button Content="UIスレッド以外からDispatcher経由で呼ぶのでOK"Click="DispatcherButton_Click" /></StackPanel></Window>
privatevoid OKButton_Click(object sender, RoutedEventArgs e)
{
    // UIスレッドからの普通の呼び出しなのでOK
    var d = new DrivedObject();
    d.DoSomething();
}

private async void NGButton_Click(object sender, RoutedEventArgs e)
{
    // UIスレッド以外からの呼び出しなので例外が出る
    var d = new DrivedObject();
    try
    {
        await Task.Run(() => d.DoSomething());
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex);
    }
}

private async void DispatcherButton_Click(object sender, RoutedEventArgs e)
{
    // UIスレッド以外だがDispatcher経由での呼び出しなのでOK
    var d = new DrivedObject();
    await Task.Run(async () =>
    {
        if (!d.CheckAccess())
        {
            await d.Dispatcher.InvokeAsync(() => d.DoSomething()); // OK
        }
    });
}

実行して上から順番にボタンを押した結果のデバッグウィンドウを以下に示します。

DoSomething
...省略...
System.InvalidOperationException: このオブジェクトは別のスレッドに所有されているため、呼び出しスレッドはこのオブジェクトにアクセスできません。
...省略...
DoSomething

最初のボタンでは、UIスレッドからの呼び出しなので、エラーもなくデバッグウィンドウに結果が出ています。二番目のボタンでは、UIスレッド以外からの呼び出しでVerifyAccessの箇所でInvalidOperationExceptionが発生しています。三番目のボタンでは、UIスレッド以外からDispatcher経由で、呼び出しているためデバッグウィンドウに結果が出ていることが確認できます。

通常のWPFを使った開発でも、UIスレッド以外からUIを直接または間接的に操作することがあります。そんなときは、このDispatcherを使い今回のサンプルプログラムのように呼び出す必要があります。

過去記事

WPF4.5入門 その42 「WPFのプロパティシステム」

$
0
0

DispatcherObjectの1段下に継承階層をおりると、DependencyObjectというクラスになります。DependencyObjectは、WPFで使われた独自のプロパティシステムを実装しています。この独自のプロパティシステムのことを、依存関係プロパティと添付プロパティといいます。

存関係プロパティ

依存関係プロパティは、通常のCLRのプロパティと比べて、以下の機能を追加で提供します。 - リソースからの値の取得 - データバインディングへの対応 - スタイルによる値の設定 - アニメーション - オーバーライド可能なメタデータ - 親子関係にあるインスタンスでのプロパティ値の継承

依存関係プロパティの定義方法

依存関係プロパティは、DependencyObjectを直接、または間接的に継承したクラスで定義可能です。定義方法は、DependencyPropertyクラスのRegisterメソッドを使用します。DependencyObjectを継承したPersonクラスにNameという依存関係プロパティを定義する方法を以下に示します。

publicclass Person : DependencyObject
{

    publicstaticreadonly DependencyProperty NameProperty =
        DependencyProperty.Register(
            "Name", // プロパティ名を指定typeof(string), // プロパティの型を指定typeof(Person), // プロパティを所有する型を指定new PropertyMetadata("default name")); // メタデータを指定。ここではデフォルト値を設定してる
}

Registerメソッドを使いDependencyPropertyクラスのインスタンスを作成します。作成したインスタンスはpublic static readonlyのフィールドに「プロパティ名Property」の命名規約で格納します。DependencyPropertyの値は、DependencyObjectクラスに定義されているGetValue、SetValueメソッドで取得と設定が可能です。上記クラスを使ってName依存関係プロパティの値の取得と設定をするコード例を以下に示します。

// GetValue, SetValueの使用例
var p = new Person();
// 値を取得
Console.WriteLine(p.GetValue(Person.NameProperty));
// 値を設定
p.SetValue(Person.NameProperty, "おおた");
// 値を取得
Console.WriteLine(p.GetValue(Person.NameProperty));

実行すると、以下のような出力になります。

default name
おおた

GetValueメソッドとSetValueメソッドを使って依存関係プロパティの値の取得と設定が出来ますが、通常のプロパティの使用方法とかけ離れているため、通常は、以下のようなCLRのプロパティのラッパーを作成します。

publicclass Person : DependencyObject
{

    publicstaticreadonly DependencyProperty NameProperty =
        DependencyProperty.Register(
            "Name", // プロパティ名を指定typeof(string), // プロパティの型を指定typeof(Person), // プロパティを所有する型を指定new PropertyMetadata("default name")); // メタデータを指定。ここではデフォルト値を設定してる// 依存関係プロパティのCLRのプロパティのラッパーpublicstring Name
    {
        get { return (string)GetValue(NameProperty); }
        set { SetValue(NameProperty, value); }
    }

}

上記のプロパティを使うと使用する側のコードは自然なC#によるクラスを利用したコードになります。

var p = new Person();
Console.WriteLine(p.Name);
p.Name = "おおた";
Console.WriteLine(p.Name);

デフォルト値の設定

Personクラスの例で示したように、依存関係プロパティは、メタデータを使ってでデフォルト値の設定が出来ます。デフォルト値は、全てのクラスで同じインスタンスが使われます。このようにして、大量のインスタンスが生成されたときにメモリをデフォルト値によって無駄に使うことがないようになっています。その反面、List型などのような参照型の値の場合同じインスタンスを使うと不都合があるケースがあります。

例えば先ほどのPersonクラスにChildrenというList型の依存関係プロパティを追加してデフォルト値にList型のインスタンスを指定したとします。

publicclass Person : DependencyObject
{
    // Nameプロパティは省略publicstaticreadonly DependencyProperty ChildrenProperty =
        DependencyProperty.Register(
            "Children", 
            typeof(List<Person>), 
            typeof(Person), 
            new PropertyMetadata(new List<Person>())); // デフォルト値は共有されるpublic List<Person> Children
    {
        get { return (List<Person>)GetValue(ChildrenProperty); }
        set { SetValue(ChildrenProperty, value); }
    }

}

このようにすると、2つのPersonクラスのインスタンスを作った時に、Childrenプロパティの値が共有されて不都合がおきてしまいます。

// Childrenプロパティの使用
var p1 = new Person();
var p2 = new Person();

p1.Children.Add(new Person());
p2.Children.Add(new Person());

Console.WriteLine("p1.Children.Count = {0}", p1.Children.Count);
Console.WriteLine("p2.Children.Count = {0}", p2.Children.Count);

このプログラムの実行結果はどちらも2が表示されてしまいます。このような問題を避けるためには、通常のプロパティと同じように、デフォルト値をコンストラクタで行う必要があります。

public Person()
{
    // デフォルト値をコンストラクタで指定するようにするthis.Children = new List<Person>();
}

これで問題は起きなくなります。

値の変更の検出

依存関係プロパティのメタデータには、第二引数にプロパティの値に変更があったときに呼ばれるコールバックメソッドを指定することが出来ます。以下のように設定をします。

publicstaticreadonly DependencyProperty NameProperty =
    DependencyProperty.Register(
        "Name", // プロパティ名を指定typeof(string), // プロパティの型を指定typeof(Person), // プロパティを所有する型を指定new PropertyMetadata(
            "default name", // デフォルト値の設定
            NamePropertyChanged)); // プロパティの変更時に呼ばれるコールバックの設定privatestaticvoid NamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    Console.WriteLine("Nameプロパティが{0}から{1}に変わりました", e.OldValue, e.NewValue);
}

DependencyPropertyChangedEventArgsのOldValueプロパティとNewValueプロパティで変更前、変更後の値の取得が可能です。プロパティの値が変わった時に何か処理をしたいときに使用します。注意点としては、staticメソッドで、値が変更されたインスタンスはメソッドの引数にDependencyObjectの形で渡されるという点です。値が変更されたインスタンスに何か操作をしたい場合は、引数で渡されたものをキャストして使用します。

値の矯正

依存関係プロパティには、値が有効範囲にあるかどうかを指定する方法があります。メタデータの第三引数にcoerceValueCallbackという引数を指定することで、値がプロパティにとって正しい範囲にあるかを検証する処理を追加することができます。以下にPersonクラスにAgeというプロパティを追加して、値の範囲が0~120であるように矯正する処理を設定している例を示します。

publicstaticreadonly DependencyProperty AgeProperty =
    DependencyProperty.Register(
        "Age", 
        typeof(int), 
        typeof(Person), 
        new PropertyMetadata(
            0,
            AgeChanged,
            CoerceAgeValue));

privatestaticobject CoerceAgeValue(DependencyObject d, object baseValue)
{
    // 年齢は0-120の間
    var value = (int)baseValue;
    if (value< 0)
    {
        return0;
    }
    if (value> 120)
    {
        return120;
    }
    returnvalue;
}

privatestaticvoid AgeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    Console.WriteLine("Ageプロパティが{0}から{1}に変わりました。", e.OldValue, e.NewValue);
}


publicint Age
{
    get { return (int)GetValue(AgeProperty); }
    set { SetValue(AgeProperty, value); }
}

CoerceAgeValueメソッドが値を矯正している処理になります。範囲外の値が設定された場合は、範囲内の値を返しています。この処理がどのように動くか示すためのコードを以下に示します。

var p = new Person();
p.Age = 10;
p.Age = -10;
p.Age = 150;

実行結果は以下のようになります。

Ageプロパティが0から10に変わりました。
Ageプロパティが10から0に変わりました。
Ageプロパティが0から120に変わりました。

-10を設定したのに0が設定されていることと、150を設定したのに120が設定されていることが確認できます。この値の矯正処理は、プロパティの変更時だけではなくDependencyObjectのCoerceValueメソッドに依存関係プロパティを渡すことでも呼び出すことができます。よく使われる例として、最大値(Maximum)と最小値(Minimum)を指定できるクラスで、このプロパティの値が変わった時にthis.CoerceValue(ValuePeoperty);のように値のプロパティを最大値と最小値の範囲内に矯正する処理を呼び出すといったケースがあります。

プロパティの妥当性検証

プロパティの値の矯正の他に、不正な値が設定されたときに例外をスローする検証処理を記述する方法も提供されています。これはメタデータではなく、Registerメソッドの第5引数として指定します。値を受け取り、その値が妥当な値の場合はtrueを返し、不正な値の場合はfalseを返すようにします。

AgeプロパティがMinValue、MaxValueの場合に不正な値とするコード例を以下に示します。

publicstaticreadonly DependencyProperty AgeProperty =
    DependencyProperty.Register(
        "Age", 
        typeof(int), 
        typeof(Person), 
        new PropertyMetadata(
            0,
            AgeChanged,
            CoerceAgeValue),
        ValidateAgeValue);

privatestaticbool ValidateAgeValue(objectvalue)
{
    // MinValueとMaxValueはやりすぎだろint age = (int)value;
    return age != int.MaxValue && age != int.MinValue;
}

このようにすると、以下のようにMaxValueやMinValueを設定するとArgumentExceptionの例外がスローされます。

var p = new Person();
try
{
    // 不正な値なので例外が出る
    p.Age = int.MinValue;
}
catch (ArgumentException ex)
{
    Console.WriteLine(ex);
}

過去記事


WPF4.5入門 その43 「読み取り専用の依存関係プロパティ」

$
0
0

これまで見てきた依存関係プロパティは全て読み書きできるものでしたが、読み取り専用の依存関係プロパティも定義できます。読み取り専用の依存関係プロパティは、DependencyPropertyKeyというクラスを使用します。

読み取り専用の依存関係プロパティの例を以下に示します。

// RegisterReadOnlyメソッドでDependencyPropertyKeyを取得privatestaticreadonly DependencyPropertyKey BirthdayPropertyKey =
    DependencyProperty.RegisterReadOnly(
        "Birthday",
        typeof(DateTime),
        typeof(Person),
        new PropertyMetadata(DateTime.Now));
// DependencyPropertyは、DependencyPropertyKeyから取得するpublicstaticreadonly DependencyProperty BirthdayProperty = BirthdayPropertyKey.DependencyProperty;

public DateTime Birthday
{
    // getは従来通りget { return (DateTime)GetValue(BirthdayProperty); }
    // setはDependencyPropertyKeyを使って行うprivateset { SetValue(BirthdayPropertyKey, value); }
}

コメントにある通り、DependencyPropertyKeyクラスのインスタンスはDependencyPropertyクラスのRegisterReadOnlyメソッドを使って取得します。このDependencyPropertyKeyクラスのインスタンスは、外部に公開しないように管理します。

DependencyPropertyのインスタンスは、DependencyPropertyKeyクラスのDependencyPropertyプロパティを使って取得します。これは、普通の依存関係プロパティと同じようにpublic static readonlyのフィールドで管理します。あとは、GetValueメソッドとSetValueメソッドを使ったCLRのプロパティのラッパーを作るのですが、このときSetValueではDependencyPropertyKeyのインスタンスを使って設定を行います。DependencyPropertyクラスのインスタンスを使うと例外が発生するので注意してください。また、プロパティのsetterは、読み取り専用の依存関係プロパティでは外部に公開しないように管理します。

このように、値の取得には内部で管理しているDependencyPropertyKeyクラスのインスタンスを使うようにすることで読み取り専用の依存関係プロパティを実現します。DependencyPropertyKeyクラスのインスタンスを外部に公開すると、読み取り専用ではなくなってしまうため実装するさいは注意をして行ってください。

過去記事

拡張されたプロパティメタデータ

$
0
0

依存関係プロパティのメタデータは、PropertyMetadataクラスの他に、PropertyMetadataクラスを継承したUIPropertyMetadataクラスや、FrameworkPropertyMetadataクラスがあります。UIPropertyMetadataクラスは、WPFのアニメーションを無効化にする機能を提供します。UIPropertyMetadataクラスを継承したFrameworkPropertyMetadataクラスは、FrameworkPropertyMetadataOptions列挙体によるレイアウトシステムへの影響の有無の設定や値の継承の有無の設定、データバインディングのデフォルト値の設定などWPFのフレームワークレベルの設定をサポートしています。カスタムコントロールを作成する場合などは通常FrameworkPropertyMetadataクラスを依存関係プロパティのメタデータに使用するといいでしょう。

FrameworkPropertyMetadataクラスの使用方法の一例として、子要素へ継承するプロパティの定義方法を以下に示します。

publicclass Person : FrameworkElement
{

    publicstaticreadonly DependencyProperty FirstNameProperty =
        DependencyProperty.Register(
            "FirstName", 
            typeof(string), 
            typeof(Person),
            new FrameworkPropertyMetadata(null));

    publicstring FirstName
    {
        get { return (string)GetValue(FirstNameProperty); }
        set { SetValue(FirstNameProperty, value); }
    }

    publicstaticreadonly DependencyProperty LastNameProperty =
        DependencyProperty.Register(
            "LastName", 
            typeof(string), 
            typeof(Person),
            // 子要素へ継承するプロパティnew FrameworkPropertyMetadata(
                null,
                FrameworkPropertyMetadataOptions.Inherits));

    publicstring LastName
    {
        get { return (string)GetValue(LastNameProperty); }
        set { SetValue(LastNameProperty, value); }
    }

    publicvoid AddChild(Person child)
    {
        this.AddLogicalChild(child);
    }
}

FrameworkElementという、DependencyObjectを間接的に継承したオブジェクトを継承してPersonクラスを作成しています。Personクラスには、名前を表すFirstName依存関係プロパティと、苗字を表すLastName依存関係プロパティがあります。苗字は、親子関係にある場合は同じものを使用するのが自然なので、FrameworkPropertyMetadataを使って、Inheritsを指定しています。親子関係の構築は、FrameworkElementクラスのAddLogicalChildメソッドを使って指定しています。

Personクラスの使用例を以下に示します。親と子のインスタンスを作って親のほうにだけLastNameプロパティを指定します。その状態で、親と子の両方のLastNameプロパティとFirstNameプロパティを表示しています。

[STAThread] // FrameworkElement使うのに必要staticvoid Main(string[] args)
{
    var parent = new Person { FirstName = "taro", LastName = "tanaka" };
    var child = new Person { FirstName = "jiro" };

    parent.AddChild(child);

    Console.WriteLine("{0} {1}", parent.LastName, parent.FirstName);
    Console.WriteLine("{0} {1}", child.LastName, child.FirstName);
}

実行すると以下のような結果になります。LastNameプロパティの値が継承されていることが確認できます。

tanaka taro
tanaka jiro

ここで紹介したFrameworkPropertyMetadataクラスは、カスタムコントロールを作成する場合でもない限り使用することはありませんが、カスタムコントロールを作成するときには、以下のリンクを読んでおくと役に立ちます。

過去記事

WPF4.5入門 その45 「添付プロパティ」

$
0
0

添付プロパティは、別のDependencyObjectを継承したクラスに対して、任意のプロパティを設定することが出来る機能です。例えば、GridクラスのRow添付プロパティやColumn添付プロパティがあります。これは、Grid内の別コントロールに対して、何行目、何列目に表示するのかを設定するのに使用します。注目すべきなのは、Row添付プロパティとColumn添付プロパティはButtonなどには定義されていませんが、Buttonなどの様々なコントロールに設定可能な点です。これが添付プロパティの特徴になります。

<Grid><Grid.RowDefinitions><RowDefinition Height="Auto" /><RowDefinition Height="Auto" /></Grid.RowDefinitions><!-- Grid.Row添付プロパティの設定例 --><Button Grid.Row="0"Content="Button1" /><Button Grid.Row="1"Content="Button2" /></Grid>

添付プロパティを定義する方法は、基本的に依存関係プロパティと同じような流れになります。コード例を以下に示します。

publicstaticclass Sample
{
    // RegisterAttachedメソッドを使って添付プロパティを作成するpublicstaticreadonly DependencyProperty BirthdayProperty =
        DependencyProperty.RegisterAttached(
            "Birthday", 
            typeof(DateTime), 
            typeof(Sample), 
            new PropertyMetadata(DateTime.MinValue));
}

依存関係プロパティがRegisterメソッドを使っていたのに対して添付プロパティは、RegisterAttachメソッドを使用します。メソッドの引数は基本的にRegisterメソッドと同じです。添付プロパティのもう1つの特徴として、定義するクラス自体にはDependencyObjectクラスの継承は必要ないという点があります。添付プロパティを設定するクラス側でDependencyObjectクラスを継承していれば問題ありません。

添付プロパティの値の取得や設定も、依存関係プロパティと同様にGetValueメソッドとSetValueメソッドを使って行います。

// 依存関係プロパティと同様にSetValue、GetValueで値の設定を取得が可能
var p = new Person();
p.SetValue(Sample.BirthdayProperty, DateTime.Now);
Console.WriteLine(p.GetValue(Sample.BirthdayProperty));

GetValueメソッドとSetValueメソッドを使って値の取得や設定を行うのは、非現実的なため、通常は添付プロパティを定義したクラスにGetプロパティ名、Setプロパティ名という名前の静的メソッドを定義します。上記のBirthday添付プロパティの完全な定義を以下に示します。

publicstaticclass Sample
{
    // RegisterAttachedメソッドを使って添付プロパティを作成するpublicstaticreadonly DependencyProperty BirthdayProperty =
        DependencyProperty.RegisterAttached(
            "Birthday", 
            typeof(DateTime), 
            typeof(Sample), 
            new PropertyMetadata(DateTime.MinValue));

    // プログラムからアクセスするための添付プロパティのラッパーpublicstatic DateTime GetBirthday(DependencyObject obj)
    {
        return (DateTime)obj.GetValue(BirthdayProperty);
    }

    publicstaticvoid SetBirthday(DependencyObject obj, DateTime value)
    {
        obj.SetValue(BirthdayProperty, value);
    }
}

このメソッドを使うと、添付プロパティを使うコードは以下のようになります。

// 通常はラッパーを使ってアクセスする
var p = new Person();
Sample.SetBirthday(p, DateTime.Now);
Console.WriteLine(Sample.GetBirthday(p));

過去記事

C#でPropertyChangedを購読する方法

$
0
0

さて、かなり前に書いた記事があります。

こいつの欠点は購読したあと解除方法がないことですね。辛い。

今ならどうする?

ということで今ならReactive Extensionsという素敵なものがあります。例えばこんなINotifyPropetyChangedを実装したクラスがあるとします。

public class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var h = this.PropertyChanged;
        if (h != null)
        {
            h(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private string name;

    public string Name
    {
        get { return this.name; }
        set { this.name = value; this.OnPropertyChanged(); }
    }

}

PropertyChangedイベントを購読するにはObservableのFromEventを使ってさくっと書けます。

var p = new Person();

Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
    h => (s, e) => h(e),
    h => p.PropertyChanged += h,
    h => p.PropertyChanged -= h)
    .Where(e => e.PropertyName == "Name")
    .Subscribe(_ =>
    {
        Console.WriteLine(p.Name);
    });

p.Name = "tanaka";

Rxの基本的なFromEventの書き方ですね。なかなか慣れない…!!毎回FromEvent書くのはだるいので拡張メソッドにしてやります。

publicstaticclass PropertyChangedExtensions
{
    publicstatic IObservable<PropertyChangedEventArgs> ObserveProperty(this INotifyPropertyChanged self, string propertyName)
    {
        return Observable.FromEvent<PropertyChangedEventHandler, PropertyChangedEventArgs>(
            h => (s, e) => h(e),
            h => self.PropertyChanged += h,
            h => self.PropertyChanged -= h)
            .Where(e => e.PropertyName == propertyName);

    }
}

こうすると、とてもすっきりします。

var p = new Person();

p.ObserveProperty("Name")
    .Subscribe(_ =>
    {
        Console.WriteLine(p.Name);
    });

p.Name = "tanaka";

もうちょっとタイプセーフに行きたいならこんな拡張メソッドも追加してやるといいです。

publicstatic IObservable<PropertyChangedEventArgs> ObserveProperty<TProp>(this INotifyPropertyChanged self, Expression<Func<TProp>> propertyName)
{
    var name = ((MemberExpression)propertyName.Body).Member.Name;
    return self.ObserveProperty(name);
}

もっとすっきり。

var p = new Person();

p.ObserveProperty(() => p.Name)
    .Subscribe(_ =>
    {
        Console.WriteLine(p.Name);
    });

p.Name = "tanaka";

ReactivePropertyには…

こういう感じのことをしてくれる便利メソッドがあります。

// using Codeplex.Reactive.Extensions;
var p = new Person();

p.ObserveProperty(o => o.Name)
    .Subscribe(value =>
    {
        Console.WriteLine(value);
    });

p.Name = "tanaka";

なんとObservePropertyの後ろに流れる値は、プロパティの値という親切設計。ここらへんid:neueccさんのセンスが光ってると思います。細かいところですが。

まとめ

便利メソッドだけ使うということでReactiveProperty入れてみてもいいんではないでしょうかという宣伝でした。

WPF4.5入門 その46 「WPFのイベントシステム」

$
0
0

WPFは、イベントも独自の機構を構築しています。WPFのイベントシステムの特徴を説明する前に、なぜその仕組みが必要になるかというシンプルな例を示したいと思います。以下のようにButtonの中にButtonがあるシンプルなケースでのイベントについて考えてみます。

<StackPanel Margin="10"><Button Click="Button_Click"><Button Content="Button" /></Button></StackPanel>

外側のButtonのClickイベントには、以下のようなMessageBoxを表示するコードを記述しています。

privatevoid Button_Click(object sender, RoutedEventArgs e)
{
    MessageBox.Show("Click");
}

外側のボタンをクリックしたときには、このイベントハンドラが呼び出されてMessageBoxが表示されることは予想できますが、ボタンの中に置かれたボタンをクリックしたときにはどうなるでしょうか?答えは、MessageBoxが表示されます。

WPFは、複雑にコントロールを組み合わせたUIを作ることができます。そのため、上記のような露骨なものではなくても上記の例のようにコントロール内のコントロールによって通常のCLRのイベントではボタンが本来のボタンの役割を果たさない可能性が出てきます。WPFのイベントシステムは、通常のイベントを拡張して、親要素へイベントを伝搬するバブルイベントという仕組みを提供しています。HTMLになじみのある人にとってはおなじみの動きです。

バブルイベントとは、イベント発生元でイベントが処理されなかった場合親要素へイベントを伝搬させる機能をもったイベントです。上記のボタンの例では、ボタンの中に置いたボタンがクリックされたときに、中に置いたボタンでイベントが処理されなかったため、親要素のボタンにクリックイベントが伝搬して、親要素のクリックイベントハンドラが呼び出されてMessageBoxが表示されるという動きになります。

WPFでは、バブルイベントの他にトンネルイベントという形のイベントも提供しています。一般的にPreviewという命名規則で始まるイベントがそれになります。バブルイベントが、イベントの発生元から親要素・親要素…へ伝搬していくのに対して、トンネルイベントはルートの要素からイベント発生元のオブジェクトの順番でイベントが伝搬していきます。(ちょうどトンネルイベントの逆の動きになります)トンネルイベントは、ユーザーの入力を処理するイベントに対して、プログラムが処理前に割り込むポイントを提供するために使用されます。そのため、ユーザーの入力を処理するカスタムコントロールを作成する場合以外は、自分で定義することはないでしょう。

これらの、バブルイベントやトンネルイベントなどのように、イベントの発生元だけでなくWPFのコントロールのツリー上の他のオブジェクトに対しても影響を与えるイベントをルーティングイベントと言います。

ルーティングイベントの定義方法

ルーティングイベントの定義は、EventManagerのRegisterEventメソッドを使って行います。定義の例を以下に示します。

class Person : FrameworkElement
{
    // イベント名Eventの命名規約のstaticフィールドに格納するpublicstatic RoutedEvent ToAgeEvent = EventManager.RegisterRoutedEvent(
        "ToAge", // イベント名
        RoutingStrategy.Tunnel, // イベントタイプtypeof(RoutedEventHandler), // イベントハンドラの型typeof(Person)); // イベントのオーナー// CLRのイベントのラッパーpublicevent RoutedEventHandler ToAge
    {
        add { this.AddHandler(ToAgeEvent, value); }
        remove { this.RemoveHandler(ToAgeEvent, value); }
    }

    // 子を追加するメソッドpublicvoid AddChild(Person child)
    {
        this.AddLogicalChild(child);
    }
}

基本的には、依存関係プロパティなどと同じで専用の登録メソッドを使ってイベントを登録して、それのCLR用のラッパーを作成するという流れになります。第二引数に、トンネルかバブルかを指定します。一般的にトンネルイベントの名前はPreviewイベント名になります。

今回定義したイベントはトンネルイベントなのでイベント発生元から親へ登っていく形になります。以下にイベントを発行するプログラムの例を示します。

var parent = new Person { Name = "parent" };
var child = new Person { Name = "child" };

parent.AddChild(child);

parent.ToAge += (object s, RoutedEventArgs e) =>
{
    Console.WriteLine(((Person)e.Source).Name);
};

parent.RaiseEvent(new RoutedEventArgs(Person.ToAgeEvent));
child.RaiseEvent(new RoutedEventArgs(Person.ToAgeEvent));

まず、トンネルイベントの挙動を確認するための親子関係を構築しています。そして親のオブジェクトのほうでToAgeイベントハンドラの登録を行っています。ルーティングイベントでは、イベントの発生元がsenderとは限りません。(今回の例ではsenderにはparentが入ってきます)イベントの発生元を取得するには、イベント引数のSourceプロパティを利用します。今回の例では、イベントの発生元のNameプロパティの値を表示しています。

最後の2行は、parentとchildで、イベントの発行を行っています。ルーティングイベントの発行は、RaiseEventメソッドにRoutedEventArgsを渡す形で行います。RoutedEventArgsは、イベントの種類を表すRoutedEventを受け取ります。

このプログラムを実行すると以下のように表示されます。

parent
child

childには、イベントハンドラを登録していませんが、親のイベントハンドラが呼び出されてることが確認できます。

イベントのキャンセル

ルーティングイベントは、RoutedEventArgsのHandledプロパティをtrueにすることで、後続のイベントをキャンセルすることが出来ます。この機能を使うと、トンネルイベントやバブルイベントを途中でインターセプトして後続のイベントの処理をキャンセルすることが出来ます。

添付イベント

WPFのイベントシステムは、トンネルイベント、バブルイベントがあることを説明しました。このようなイベントがあるとクリックイベントがボタンで発生したとき、WindowやPanel系コントロールでもClickイベントが発生することになります。このような状況に対応するためにWindowやPanel系コントロールに全てのオブジェクトの全てのイベントを実装するのは現実的ではありません。WPFでは添付イベントという仕組みで、本来そのオブジェクトで定義されてないルーティングイベントを処理する方法を提供しています。

StackPanelにButtonのClickイベントを添付イベントとして設定するXAMLを以下に示します。

<StackPanel Button.Click="StackPanel_Click"><Button Content="Button1" /></StackPanel>

基本的に添付プロパティと同じような記述になります。これと同じことをコードで記述する場合は以下のようになります。stackPanelという変数にButtonが置いてあるStackPanelが入っている場合のコード例です。

this.stackPanel.AddHandler(Button.ClickEvent, new RoutedEventHandler(this.StackPanel_Click));

過去記事

Viewing all 1388 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>