前回:http://blogs.wankuma.com/kazuki/archive/2008/04/28/135608.aspx
前回からものっそい間があいてしまった。
NetBeans 6.1が出たもんだから、そっちにすごい浮気してました。
でも、たまにはこっちもしないとね!!!
なんたって、個人的な感想では、今のところ一番いけてるGUIまわりのフレームワークですから!
後は、成熟して枯れるのを待つばかり。
その時のためにも、今勉強あるのみ。
さて、今回はBindingを使ってMaster/Detailな画面を作ってみようと思う。
説明の必要は、あんまり無いとは思うけどMaster/Detailな画面ってのは、リスト形式で値が列挙されているような画面で、リストの要素を選択すると、その詳細が下のほうに表示されるとかいう類の画面です。
Bindingを使うと、こういうのも割りと簡単に出来ちゃうから便利です。
さて、とりあえず今回登場するデータの入れ物を作ってしまおう。
どうでもいい話だけど、毎回INotifyPropertyChangedインターフェースを実装するのと、OnPropertyChangedを呼び出すプロパティを作るのは正直めんどくさい。
ということで自分は、下のようなコードスニペットを登録してます。
npc.snippet
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>npc</Title>
<Shortcut>npc</Shortcut>
<Description>INotifyPropertyChangedを実装したクラス</Description>
<Author>Kazuki Ohta</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
</Declarations>
<Code Language="csharp"><![CDATA[public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
nprop.snipet
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>prop</Title>
<Shortcut>nprop</Shortcut>
<Description>NotifyPropertyChangedBaseを実装したクラス用のプロパティ</Description>
<Author>Kazuki Ohta</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>type</ID>
<ToolTip>プロパティの種類</ToolTip>
<Default>int</Default>
</Literal>
<Literal>
<ID>property</ID>
<ToolTip>プロパティ名</ToolTip>
<Default>MyProperty</Default>
</Literal>
<Literal>
<ID>var</ID>
<ToolTip>プロパティの値を格納するフィールド</ToolTip>
<Default>myProperty</Default>
</Literal>
</Declarations>
<Code Language="csharp"><![CDATA[#region $property$
private $type$ _$var$;
public $type$ $property$
{
get { return _$var$; }
set
{
if (_$var$ == value)
{
return;
}
_$var$ = value;
OnPropertyChanged("$property$");
}
}
#endregion
$end$]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
こういうのを登録しといたら、npcを入力してTABでNotifyPropertyChangedBaseクラスが定義される。
npropを入力してTABでプロパティもさくっと作れる。
他に何かいい方法が思いつかないので、とりあえずこれでお茶を濁してる今日この頃でした。
ということで入れ物は、いつも通りEmployeesクラスとDepartmentsクラスです。今回は若干プロパティが多め。
上のスニペットの力もかりてさくっと実装。
using System;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfMasterDetail
{
/// <summary>
/// 毎回実装するのがめんどいので、そろそろ共通化。
/// </summary>
public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
/// <summary>
/// 部署クラスね
/// </summary>
public class Departments : NotifyPropertyChangedBase
{
#region ID
private int _id;
public int ID
{
get { return _id; }
set
{
if (_id == value)
{
return;
}
_id = value;
OnPropertyChanged("ID");
}
}
#endregion
#region Name
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value)
{
return;
}
_name = value;
OnPropertyChanged("Name");
}
}
#endregion
#region Members
private ObservableCollection<Employees> _employees;
public ObservableCollection<Employees> Members
{
get { return _employees; }
set
{
if (_employees == value)
{
return;
}
_employees = value;
OnPropertyChanged("Members");
}
}
#endregion
}
/// <summary>
/// 従業員クラスね
/// </summary>
public class Employees : NotifyPropertyChangedBase
{
#region ID
private int _id;
public int ID
{
get { return _id; }
set
{
if (_id == value)
{
return;
}
_id = value;
OnPropertyChanged("ID");
}
}
#endregion
#region Name
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value)
{
return;
}
_name = value;
OnPropertyChanged("Name");
}
}
#endregion
#region Address
private string _address;
/// <summary>
/// 住所
/// </summary>
public string Address
{
get { return _address; }
set
{
if (_address == value)
{
return;
}
_address = value;
OnPropertyChanged("Address");
}
}
#endregion
}
}
長いけど、まだどうってことはない。
とりあえず、今回は部署データのリストから選択された部署の詳細と、所属している従業員が表示される程度にしておくよ!
んじゃ、画面のDataContextにダミーのデータを突っ込むところから。
さくっと画面のLoadedイベントあたりにかいておきます。
<Window x:Class="WpfMasterDetail.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Loaded="Window_Loaded"><!-- Loadedイベントを追加 -->
<Grid>
</Grid>
</Window>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DataContext = new ObservableCollection<Departments> {
new Departments { ID = 1, Name = "総務部", Members = new ObservableCollection<Employees>{
new Employees { ID = 1, Name = "田中 太郎", Address = "東京都のどっか" },
new Employees { ID = 2, Name = "田中 二郎", Address = "東京都のどっか" },
}},
new Departments { ID = 2, Name = "人事部", Members = new ObservableCollection<Employees>{
new Employees { ID = 3, Name = "田中 三郎", Address = "大阪府のどっか" },
}},
new Departments { ID = 3, Name = "情報システム部", Members = new ObservableCollection<Employees>{
new Employees { ID = 4, Name = "田中 四郎", Address = "大阪府のどっか" },
new Employees { ID = 5, Name = "田中 一郎", Address = "宇宙のどっか" },
new Employees { ID = 6, Name = "田中 五郎", Address = "広島県のどっか" },
}},
};
}
それじゃぁ、まず画面の上部に部署情報をリスト形式で表示させます。
今回は、簡単にListBoxにしようかな。
<Window x:Class="WpfMasterDetail.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfMasterDetail="clr-namespace:WpfMasterDetail"
Title="Window1" Loaded="Window_Loaded">
<Window.Resources>
<DataTemplate x:Key="DepartmentsDataTemplate" DataType="{x:Type WpfMasterDetail:Departments}">
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="部署番号: " />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding ID}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="名前: " />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Name}" />
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="3" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource DepartmentsDataTemplate}"/>
<GridSplitter Grid.Row="1" />
</Grid>
</Window>
ということで、適当なDataTemplateをあてたListBoxを作った。実行すると下のような感じになる。
さて、この下半分に、選択した部署の詳細情報を表示してみる。
まぁ、何も悩むことは無いと思うけど…Membersプロパティをリストボックスにバインドしてやれば、所属する部署のメンバーのリストを詳細情報として出力することが出来る。
おまけに、部署番号や、部署名も一応出しておく。
<Window x:Class="WpfMasterDetail.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfMasterDetail="clr-namespace:WpfMasterDetail"
Title="Window1" Loaded="Window_Loaded">
<Window.Resources>
<DataTemplate x:Key="DepartmentsDataTemplate" DataType="{x:Type WpfMasterDetail:Departments}">
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="部署番号: " />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding ID}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="名前: " />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Name}" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="EmployeesDataTemplate" DataType="{x:Type WpfMasterDetail:Employees}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="従業員番号" />
<TextBlock Grid.Row="0"
Grid.Column="1"
Text=":" />
<TextBlock Grid.Row="0"
Grid.Column="2"
Text="{Binding ID}" />
</Grid>
<Grid Grid.Column="1" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="従業員名" />
<TextBlock Grid.Row="0"
Grid.Column="1"
Text=":" />
<TextBlock Grid.Row="0"
Grid.Column="2"
Text="{Binding Name}" />
</Grid>
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="住所" />
<TextBlock Grid.Column="1" Text=":" />
<TextBlock Grid.Column="2" Text="{Binding Address}" />
</Grid>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="3" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Master -->
<ListBox Grid.Row="0"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource DepartmentsDataTemplate}"/>
<GridSplitter Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<!-- Detail -->
<Border Grid.Row="2" BorderBrush="Orange" BorderThickness="1" CornerRadius="5" Padding="5" Margin="10">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0"
Grid.Column="0"
Content="部署番号" />
<TextBlock Grid.Row="0"
Grid.Column="1"
Text="{Binding ID}" />
<Label Grid.Row="1"
Grid.Column="0"
Content="部署名" />
<TextBlock Grid.Row="1"
Grid.Column="1"
Text="{Binding Name}" />
<ListBox Grid.Row="2"
Grid.ColumnSpan="2"
ItemsSource="{Binding Members}"
ItemTemplate="{StaticResource EmployeesDataTemplate}"/>
</Grid>
</Border>
</Grid>
</Window>
長くなったけど、前半のDataTemplateが長いだけ。Master/Detailに関係するところは最後のほうだけです。
といっても特別なことは何もしてない。Bindingを使えば楽チンにできる。
実行すると、こんな感じ。選択した部署に関する情報が下のオレンジ色の枠に囲まれた部分に表示されてる。
割といい感じだ。