• 検索結果がありません。

42

</Label>

</Border>

43

TwoWay

であるため、Textプロパティには

Getter

Setter

の両方を用意します)。

public class MainWindowViewModel : ViewModel.ViewModelBase {

public object LabelText => "TestLabel";

string text;

public string Text {

get { return text; } set {

if (text != value) { text = value;

}

base.RaisePropertyChanged("Text");

} } }

9 標準で提供されている UI コントロール

WPF

では、以下のように様々な

UI

コントロールが標準で用意されています。

Grid、StackPanel、WrapPanel、DockPanel、Canvas、

Button、Label、ComboBox、TextBox、TextBlock、CheckBox、RadioButton、

ScrollViewer、

ViewBox、

Menu、MenuItem、ContextMenu、

Expander、

Slider、

ListView、DataGrid、

ContentControl、

ItemsControl、Popup、

Border、

UserControl、Window

上記から幾つかピックアップして、利用例を紹介します。

ここで、入門ノートの範囲からは外れるのですが、WPFの描画が遅く、パフォーマンスを

44

意識する必要がある場合、「レイアウトの選択」や「UIの仮想化」を考慮することがあるか も知れません。リンクを

2

つ紹介しておきます。

WPF

アプリケーションのパフォーマンスの最適化

https://msdn.microsoft.com/ja-jp/library/aa970683(v=vs.110).aspx

3

回 WPFアプリケーション・チューニング

http://www.atmarkit.co.jp/fdotnet/chushin/vsperf_03/vsperf_03_02.html

(1) Grid

Grid

について押さえておくべきことは次の

2

つだと思います。

1)

行と列を定義して、何行何列目に配置するといった指定が可能である。

2)

行や列の指定をしないと、Grid内の

UI

コントロールは重ねて描画される。

これらについて例示します。

1)

例えば、3行

4

列を定義し、各行にボタンを配置する例を挙げます。

<!-- MainWindow.xaml -->

<Window x:Class="WpfSample.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">

<Grid>

<Grid.RowDefinitions>

<RowDefinition Height="2.*" />

<RowDefinition Height="3.*" />

<RowDefinition Height="5.*" />

</Grid.RowDefinitions>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="1.*" />

<ColumnDefinition Width="3.*" />

<ColumnDefinition Width="2.*" />

<ColumnDefinition Width="4.*" />

</Grid.ColumnDefinitions>

<Button Grid.Row="0" Grid.Column="2" Content="(1,3)" />

<Button Grid.Row="1" Grid.Column="3" Content="(2,4)" />

<Button Grid.Row="1" Grid.Column="1" Content="(2,2)" />

<Button Grid.Row="2" Grid.Column="0" Content="(3,1)" />

</Grid>

</Window>

45

ここで、図を見ると分かるように、「a.*」の指定は比率を意味しています。

一方、ピクセル指定もできます。

2)

行や列を定義せずに

Grid

Button

2

つ配置してみます。

<!-- MainWindow.xaml -->

<Window x:Class="WpfSample.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">

<Grid>

<Button Width="100" Height="30" Content="TEST1" />

<Button Width="100" Height="30" Content="TEST2" />

</Grid>

</Window>

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

Content

TEST1

のボタンは、Contentが

TEST2

のボタンに上書きされ、イベントも拾 えない状態になります。また、

VisualTree

は上から順に描画されていることも分かります。

46

このままでは使えませんが、例えば、HorizontalAlignmentや

VerticalAlignment

を指定し て使えば、重ねて描画はむしろ記述量が少なくなり便利なことがあります。

<Grid>

<Button Width="100" Height="30" Content="TEST1" HorizontalAlignment="Left" />

<Button Width="100" Height="30" Content="TEST2" HorizontalAlignment="Right" />

</Grid>

(2) Canvas

MSDN

の次の記事

パフォーマンスの最適化:レイアウトとデザイン

https://msdn.microsoft.com/ja-jp/library/bb613542(v=vs.110).aspx

にもあるように、 5 つのパネル(Grid、StackPanel、WrapPanel、DockPanel、Canvas)

の中で最も描画効率がよいのが

Canvas

です。

Canvas

では、座標指定して

UI

コントロールを配置します。他のパネルでは様々なレイ

アウトロジックが実行されて位置が決まり、慣れない内は表示したい場所に表示できない ことがあるかも知れません。Canvasの場合は、そんなことはなくシンプルです。

<Canvas>

<Button Canvas.Left="10" Canvas.Top="20" Width="100" Height="30" Content="(10,20)" />

<Button Canvas.Left="200" Canvas.Top="50" Width="100" Height="30" Content="(200,50)" />

<Button Canvas.Left="70" Canvas.Top="150" Width="100" Height="30" Content="(70,150)" />

</Canvas>

ちなみに、C#コード上で

button

インスタンスの

Left

の値を設定するには、次のように書 きます。

Canvas.SetLeft(button, 100d);

47 (3) Label

Label

は目に見えない伸縮性のアメーバのように使えて、拾えなかったイベントを拾う、

といったことができます。例えば、次のように

Label

を配置したとします。

<Grid>

<Canvas>

<Button Canvas.Left="10" Canvas.Top="20" Width="100" Height="30"

Content="(10,20)" />

<Button Canvas.Left="200" Canvas.Top="50" Width="100" Height="30"

Content="(200,50)" Click="Button_Click" />

<Button Canvas.Left="70" Canvas.Top="150" Width="100" Height="30"

Content="(70,150)" />

</Canvas>

<Label MouseLeftButtonDown="Label_MouseLeftButtonDown" />

</Grid>

このように記述すると、Canvasが描画された後に

Label

が描画され、かつ、Gridで行と列 を定義していないので、Grid 全体を

Label

が覆うことになり、ボタンのクリックイベント が呼ばれなくなります(Label_MouseLeftButtonDownハンドラが呼ばれます)。

一方、次のように

Label

Canvas

より前に宣言したとします。

<Grid>

<Label MouseLeftButtonDown="Label_MouseLeftButtonDown" />

<Canvas>

<Button Canvas.Left="10" Canvas.Top="20" Width="100" Height="30"

Content="(10,20)" />

<Button Canvas.Left="200" Canvas.Top="50" Width="100" Height="30"

Content="(200,50)" Click="Button_Click" />

<Button Canvas.Left="70" Canvas.Top="150" Width="100" Height="30"

Content="(70,150)" />

</Canvas>

</Grid>

すると、ボタンをクリックすればボタンのクリックイベントが呼ばれ、余白をクリックすれ

48

Label_MouseLeftButtonDown

ハンドラが呼ばれます。

(4) ComboBox

「Key: Value」のコレクションを並べる例を挙げます。

STEP 1/3:KeyValue

クラスを作成します。

namespace WpfSample.Models {

public class KeyValue {

public string Key { get; set; } public string Value { get; set; }

public KeyValue(string key, string value) {

this.Key = key;

this.Value = value;

}

public override string ToString() => $"{Key}: {Value}";

} }

STEP 2/3:Xaml

ComboBox

ItemsSource

依存関係プロパティに

ComboItems

をパス で指定します。

<ComboBox ItemsSource="{Binding Path=ComboItems}" Width="150" Height="25" />

STEP 3/3:VM

ComboItems

を定義します。

// using WpfSample.Models;

public IEnumerable<KeyValue> ComboItems =>

new[] {

new KeyValue(key:"k1", value:"v1"), new KeyValue(key:"k2", value:"v2"), };

49 (5) ScrollViewer

基本的な使い方は、

HorizontalScrollBarVisibility

VerticalScrollBarVisibility

の各依存関 係プロパティに値をセットして次のように使います。

<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"

Width="300" Height="200">

<StackPanel>

<StackPanel.Resources>

<Style TargetType="{x:Type Button}">

<Style.Setters>

<Setter Property="Width" Value="100" />

<Setter Property="Height" Value="30" />

</Style.Setters>

</Style>

</StackPanel.Resources>

<Button Content="{Binding Path=ButtonContents[0], Mode=OneWay}" />

<Button Content="{Binding Path=ButtonContents[1], Mode=OneWay}" />

<Button Content="{Binding Path=ButtonContents[2], Mode=OneWay}" />

<Button Content="{Binding Path=ButtonContents[3], Mode=OneWay}" />

<Button Content="{Binding Path=ButtonContents[4], Mode=OneWay}" />

<Button Content="{Binding Path=ButtonContents[5], Mode=OneWay}" />

<Button Content="{Binding Path=ButtonContents[6], Mode=OneWay}" />

<Button Content="{Binding Path=ButtonContents[7], Mode=OneWay}" />

</StackPanel>

</ScrollViewer>

ちなみに、VMでの

ButtonContents

の定義は次の通りです。

public string[] ButtonContents =>

new[] { "0", "1", "2", "3", "4", "5", "6", "7", };

50

もう少し細かい制御をしたい場合は、System.Windows.Controls.ScrollViewerは

ScrollInfo

プロパティを

protected internal

で持っているため、ScrollViewerを継承して カスタムの

ScrollViewer

を作成するといいと思います。

(6) DataGrid

System.Data.DataTable

ItemsSource

に次のように設定して使えます。Xaml側は例え ば次の通りです。

<DataGrid ItemsSource="{Binding Path=DataGridSource}" />

VM

に定義するプロパティは、例えば次の通りです。

DataTable dt;

public DataTable DataGridSource {

get { return dt; } set {

if (dt != value) { dt = value;

base.RaisePropertyChanged("DataGridSource");

} } }

System.Data.DataTable

の使い方は

Windows

フォームと同様です。例えば、ビューのビハ インドで次のようにデータを作成し、VMの

DataGridSource

プロパティに設定します。

var dt = new DataTable("TestTable");

dt.Columns.AddRange(new[] {

new DataColumn(columnName:"Key"), new DataColumn(columnName:"Value"), new DataColumn(columnName:"Date"), });

dt.Rows.Add("k1", "v1", "2017/3/12");

dt.Rows.Add("k2", "v2", "2017/3/12");

dt.Rows.Add("k3", "v3", "2017/3/12");

51

System.Windows.Controls.DataGrid

には、CellStyle や

ColumnHeaderStyle

の各依存関係 プロパティなどがあり、見た目のカスタマイズを行う方法が提供されています。

(7) ContentControl

System.Windows.Controls.ContentControl

は、object 型の

Content

依存関係プロパティ を持っています。例えば、

Content

にパネルを設定することにし、マウス左ボタン押下でパ ネル

A

(StackPanel)、マウス右ボタン押下でパネル

B

(WrapPanel)を設定する例を挙げま す。

Xaml

は次の通りです。

<ContentControl Content="{Binding Path=CustomContent}" />

VM

は次の通りです。

object customContent;

public object CustomContent {

get { return customContent; } set {

if (customContent != value) { customContent = value;

base.RaisePropertyChanged("CustomContent");

} } }

ビューのビハインドのハンドラは次の通りです。

protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {

base.OnMouseLeftButtonDown(e);

var vm = this.DataContext as MainWindowViewModel;

var stackPanel = new StackPanel();

var textBlock = new TextBlock { Text = "SampleText", Width = 100d, Height = 30d };

var button = new Button { Content = "Content", Width = 100d, Height = 30d };

stackPanel.Children.Add(textBlock);

stackPanel.Children.Add(button);

vm.CustomContent = stackPanel;

}

protected override void OnMouseRightButtonDown(MouseButtonEventArgs e) {

base.OnMouseRightButtonDown(e);

var vm = this.DataContext as MainWindowViewModel;

52

var wrapPanel = new WrapPanel();

var buttons = new Button[20];

for (var i = 0; i < buttons.Length; ++i) {

buttons[i] = new Button { Content = i, Width = 100d, Height = 30d };

wrapPanel.Children.Add(buttons[i]);

}

vm.CustomContent = wrapPanel;

}

10 カスタムコントロール

WPF

の大きな特徴の

1

つは、標準で提供されているUIコントロールを組み合わせて、カ

スタムでコントロールを柔軟に作成することができることです。

カスタムで

UI

コントロールを作成するには、次の

3

つの方法があります。

(1) UI

コントロールの

Template

プロパティに

ControlTemplate

を実装します。

(2) System.Windows.Controls.UserControl

を継承して作成します (UIは主に

Xaml

で 記述します)。

(3) System.Windows.ContentControl

を継承したクラスを継承して作成します (UIは

主に

C#コードで記述します)。

(1)

の例

例えば、

Label

をタグ付きのボタンとして扱うように

ControlTemplate

を定義することが できます。RelativeSourceで

TemplateParent

を指定していることにご注意ください。

<Label Tag="タグ" Content="TEST">

<Label.Style>

<Style TargetType="{x:Type Label}">

<Style.Setters>

<Setter Property="Template">

<Setter.Value>

<ControlTemplate>

<StackPanel Orientation="Horizontal">

<TextBlock Text="{Binding Path=Tag,

RelativeSource={RelativeSource TemplatedParent}}"

Height="25" Margin="0 0 8 0" />

53

<Button Content="{Binding Path=Content,

RelativeSource={RelativeSource TemplatedParent}}"

Width="100" Height="25" />

</StackPanel>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style.Setters>

</Style>

</Label.Style>

</Label>

他にも、上下

2

段にテキストを表示する

Label、文字数に応じて文字サイズを自動でスケー

リングする

TextBox

くらいのカスタマイズなら

ControlTemplate

の枠組みで実現できます。

どうやって実現するかに興味がある方は、実現方法を考えてみて下さい。

ControlTemplate

をより実際的に使う状況があります。それは、WPF標準で提供されて

いる

ComboBox

Menu

などの

UI

コントロールをカスタマイズする場合です。例えば、

ComboBox

のデフォルトのスタイルとテンプレートは、MSDNの次のサイトで公開されて

います。

ComboBox

のスタイルとテンプレート

https://msdn.microsoft.com/ja-jp/library/ms752094(v=vs.110).aspx

このコードを見ると、ComboBox は幾つもの

ControlTemplate

によって実現されているこ とがよく分かります。また、StoryBoard によるアニメーションがどこで使われているかと いうことや、

ComboBox

のドロップダウンが、System.Windows.Controls.Primitives.Popup に よ っ て 構 成 さ れ て い る こ と な ど も 分 か り ま す 。 こ の コ ー ド を ダ ウ ン ロ ー ド し 、

ResourceDictionary

として

ComboBox.xaml

のように作成したファイルにコピーして、

ControlTemplate

をカスタマイズすると、既存の

ComboBox

を変えることができます。

ちなみに、StoryBoard によるアニメーションを削除するだけでパフォーマンスの改善が 可能です。

(2)

の例

例えば、カスタムのステータスバーを

UserControl

で作成する例を挙げます。部品として 切り出すことで他のビューで利用することができ、

VisualTree

がメンテしやすくなります。

STEP 1/3:VisualStudio

で、[追加]→[新しい項目]→[ユーザーコントロール (WPF)] を 選択し、CustomStatusBar.xamlファイルを作成し、次のように記載します。

54

<!-- CustomStatusBar.xaml -->

<UserControl x:Class="WpfSample.CustomStatusBar"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Height="24">

<DockPanel LastChildFill="True">

<StackPanel Orientation="Horizontal" DockPanel.Dock="Left" Margin="4 0 0 0">

<TextBlock Text="ステータス:" />

<TextBlock Text="{Binding Path=StatusText}" />

</StackPanel>

<StackPanel Orientation="Horizontal" DockPanel.Dock="Right" Margin="0 0 4 0">

<TextBlock Text="メッセージ:" />

<TextBlock Text="{Binding Path=MessageText}" />

</StackPanel>

<Label />

</DockPanel>

</UserControl>

STEP 2/3:MainWindow.xaml

に、作成した

CustomStatusBar.xaml

を埋め込みます。

ここで、特に明示しなければ、CustomStatusBarの

DataContext

は、MainWindowの

DataContext

を継承することに注意しておきます。

<!-- MainWindow.xaml -->

<Window x:Class="WpfSample.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:wpfs="clr-namespace:WpfSample"

Title="MainWindow" Height="350" Width="525">

<Window.DataContext>

<wpfs:MainWindowViewModel />

</Window.DataContext>

<DockPanel LastChildFill="True">

<wpfs:CustomStatusBar DockPanel.Dock="Bottom" Background="LightGray" />

<Label />

</DockPanel>

</Window>

STEP 3/3:MainWindowViewModel

に、StatusTextと

MessageText

の各プロパティを 定義し、それらのプロパティに値を設定する処理を書きます。

関連したドキュメント