1つ前の記事でWindowsFormを使って作ったものと似たようなものをWPFのListViewを使って作ってみようと思う。WPFのListViewのほうは、さくっとやるだけじゃ編集とかはサポートされないので、とりあえず読み取り専用で作る。
ついでに、2つ前の記事でやったようにWindows FormにWPFのコントロールを埋め込む形で1つ前の記事のWindows Formアプリケーションに埋め込んでWPFとDataGridViewの見た目の比較もできるように作ってみようと思う。
まずは、WPFのUserControlを作成する。名前はDataSetViewerにした。名前に反して汎用的な部品じゃないのを作る予定なのでご了承を。とりあえず、簡単にEmployeesテーブルの中身を出すところまでやってみる。
特に悩むことも無い。DataContextにEmployeesDataTableが設定されているという前提でぽちぽちListViewを組み立てただけだ。
<UserControl x:Class="DatabaseApp.DataSetViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<ListView ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" />
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="DeptID" DisplayMemberBinding="{Binding DeptID}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>
これを、フォームの下側に貼り付ける。
フォームのロードイベントで、WPFのユーザコントロールのDataContextにEmployeesDataTableのインスタンスを設定するコードを追加する。
private void Form1_Load(object sender, EventArgs e)
{
// データ取得
this.departmentsTableAdapter.Fill(this.empMngDataSet.Departments);
this.customersTableAdapter.Fill(this.empMngDataSet.Customers);
this.employeesTableAdapter.Fill(this.empMngDataSet.Employees);
dataSetViewer1.DataContext = empMngDataSet.Employees;
}
そして、実行するとEmployeesDataTableのデータがWPFのListViewのほうにも出てる!
次は右側に、選択した従業員の顧客の情報も出してみようと思う。
これは、顧客情報を表示する側のBindingのPathに何を渡すかがわかれば簡単。DataTableのRelationの名前を渡せばOK。DataSetデザイナは、外部キーの名前をRelationの名前にしてくれるので、FK_CustomersEmployeesをBindingのPathに設定すればいける。
<UserControl x:Class="DatabaseApp.DataSetViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.ColumnDefinitions>
<!-- 従業員データ表示用の列 -->
<ColumnDefinition />
<!-- 顧客データ表示用の列 -->
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" />
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="DeptID" DisplayMemberBinding="{Binding DeptID}" />
</GridView>
</ListView.View>
</ListView>
<!-- DataTableのリレーション名をパスに指定することでバインドができる -->
<ListView Grid.Column="1" ItemsSource="{Binding FK_CustomersEmployees}" IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" />
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="EmployeeID" DisplayMemberBinding="{Binding DeptID}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>
ばっちり出た。いい感じ。
最後に、従業員の所属する部署名を出してみようと思う。
正直、これにちょっと苦労した。というのも、ListViewのItemsSourceにはEmployeesDataTableを指定してるから、EmployeesRowがListViewの各行にあたるだろうと思ってたら実はDataRowViewのインスタンスだった。なので、何も考えずにBindingのPathにDataSetデザイナが作ってくれるDepartmentsRowプロパティのNameプロパティを指定しても、うまいこと表示されない。
DataRowViewのRowプロパティのDepartmentsRowのNameプロパティという感じでPathを指定することでうまくいった。メモメモ
<UserControl x:Class="DatabaseApp.DataSetViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.ColumnDefinitions>
<!-- 従業員データ表示用の列 -->
<ColumnDefinition />
<!-- 顧客データ表示用の列 -->
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" />
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="DeptID" DisplayMemberBinding="{Binding DeptID}" />
<!-- DataRowViewに対してBindingのPathを書く! -->
<GridViewColumn Header="DeptName" DisplayMemberBinding="{Binding Row.DepartmentsRow.Name}" />
</GridView>
</ListView.View>
</ListView>
<!-- DataTableのリレーション名をパスに指定することでバインドができる -->
<ListView Grid.Column="1" ItemsSource="{Binding FK_CustomersEmployees}" IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" />
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="EmployeeID" DisplayMemberBinding="{Binding DeptID}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>
実行結果。ばっちり表示できてる。

ということで、DataTableをWPFにバインドするときのメモでした。
ただ、注意しないといけないのは、DataGridViewからデータの追加をしたときにはWPFのほうも追従してくれるけど、従業員名の変更とかは追従してくれないということ。要注意や!
何か、巷の見たことあるサンプルだとMaster/Detailパターンとかで1対多の関係にあるもののデータを表示してしまおう!とかいうのはよく見るような気がする。
文章で説明するのは苦手なので、よく見るパターンから作っていこうと思う。
DatabaseAppという名前でプロジェクトを作ってEmpMng.sdfという名前でローカルデータベースを作成する。ついでにDataSetも作るか?的なことを聞かれるので一緒に作っておく。
EmpMng.sdfにDataSetデザイナにドロップしたら下のような感じになるテーブルを作成する。
部署があって、部署に所属する従業員がいて、従業員に紐づくお客さんがいる。といったイメージ。
フォームにDataSetと、BindingSourceと、BindingNavigator、DataGridViewを置いてEmployeesテーブルの中身を画面に表示するように構成する。
実行すると、Employeesテーブルのデータが画面に表示される。(データは適当に入れました)
ありがちな例は、選択した従業員に紐づくお客さんを表示するといったような感じのものが多い。これは、DataGridViewを、もう1つ置いてDataSourceにbindingSourceEmployeesのFK_CustomerEmployeesを選択すればOKだ。実行すると、左側で選択した従業員に紐づく顧客の情報が表示される。
いい感じだ。
んで、今まで悩んでたのが、従業員テーブルのデータを表示してるDataGridViewに部署情報を表示したい!というとき。
そんでもって見た目は、なるべく普通のテキストを表示するセルと同じにしたい。というものだった。
やり方は、わかってしまえば簡単。DataGridViewComboBoxColumnで、データソースに部署情報、DisplayMemberにNameを指定して、ValueMemberにIDを指定する。
バインド先のプロパティはもちろん、EmployeesテーブルのDeptIDだ。
これだと、普通のドロップダウンになってしまうので、最後にDisplayStyleをNothingにする。これで見た目がぐぐっと普通のセルっぽくなる。
編集中はドロップダウンで、普段の見た目は普通のセルっぽいのが完成。ReadOnlyにすれば同然のことながら普通のセルと区別がつかないかんじになる。