はじめに
僕のお仕事は主に開発屋さんたちの後方支援なので、エンドユーザーさんとは直接の関わりが浅く、GUIアプリを書く機会は多くありません。とはいえ開発屋さんからのリクエストで、ちょっとしたGUIツールをこしらえることがたまぁにあります。
長いことその多くはWindows Formsを使っていたのですが、ようやく(いまさら?)近頃WPFを使うようになりました。お客様の目には触れないアプリケーションだし、なにしろ開発屋さんは僕も含めて基本ワガママですから、使い勝手のよいようにUIの仕様をコロコロ変えてきます。そんな無茶ブリに素早く対応せにゃならんので、View(見てくれ)とModel(本体)とをきっちり分離することが望まれます。
WPFの強力かつ柔軟なデータ・バインディングのおかげでViewとModelの分離がとても楽になりました。WPFに味をしめた僕は、頼まれたGUIアプリはぜーんぶWPFで書いてやろうと手駒を揃えることにしました。
そんなわけで、僕が書くアプリのユーザは一般のお客様じゃなく身内の開発屋さんですから、凝ったUIはまずもって不要です。TextBox,ButtonそしてListBox/ListViewあたりが使えれば、あらかたのツールは作れます。ListBoxの小さなサンプルを書いてみますね。
Viewはこんな感じ、いたってシンプルです。「文字列を追加する」と「選択項目を削除する」のたった2つの機能。XAMLをお見せしましょうか。
<Window x:Class="ListBox_binding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListBox binding" Height="298" Width="309">
<Grid>
<ListBox Height="165" HorizontalAlignment="Left" Margin="12,12,0,0" Name="listBox" VerticalAlignment="Top" Width="255" ItemsSource="{Binding Path=Items}" />
<TextBox Height="28" HorizontalAlignment="Left" Margin="12,183,0,0" Name="textBox" VerticalAlignment="Top" Width="176" />
<Button Content="追加" Height="26" HorizontalAlignment="Left" Margin="219,183,0,0" VerticalAlignment="Top" Width="48" Click="add_Click" />
<Label Content="を" Height="28" HorizontalAlignment="Left" Margin="194,185,0,0" VerticalAlignment="Top" />
<Label Content="選択した項目を" Height="28" HorizontalAlignment="Left" Margin="127,219,0,0" VerticalAlignment="Top" />
<Button Content="削除" Height="26" HorizontalAlignment="Left" Margin="219,0,0,12" VerticalAlignment="Bottom" Width="48" Click="remove_Click" />
</Grid>
</Window>
<ListBox>に並ぶ属性のItemsSource="{Binding Path=Items}"でItemsプロパティにバインドしています。
このViewに反映されるデータを提供するModelであるBindDataがコチラ。
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace ListBox_binding {
public class BindData {
public void add(string str) { data_.Add(str); }
public void remove_at(int n) { if ( n >= 0 && n < data_.Count ) data_.RemoveAt(n); }
public ObservableCollection<string> Items { get { return data_; } }
private ObservableCollection<string> data_ = new ObservableCollection<string>();
}
}
XAML側とバインドしたプロパティ:Itemsの型はObservableCollection<string>です。ObservableCollection<T>は、可変長配列List<T>のバリエーションで、要素の追加/変更/削除に応じて画面を更新してくれます。
Viewに起こるイベント:追加/削除ボタンのクリックに反応するイベント・ハンドラはコード・ビハインドに置きます。
using System.Windows;
using System.Collections;
namespace ListBox_binding {
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = data_;
}
private void add_Click(object sender, RoutedEventArgs e) {
data_.add(textBox.Text);
}
private void remove_Click(object sender, RoutedEventArgs e) {
data_.remove_at(listBox.SelectedIndex);
}
private BindData data_ = new BindData();
}
}
ModelであるBindDataのインスタンスを1つ用意し、コンストラクト時にDataContextに与えています。追加/削除ボタンのハンドラではそれぞれModelのadd/remove_atを呼ぶだけ。これだけで動いてくれます。楽ですねー♪
ListViewで表を描くのもListBoxと大差ありません。
表のカラムごとに表示するデータをバインドします。名前(Name)と連絡先(Contact)の表であれば、1つのレコードを表現するクラス:Entryは
namespace ListView_binding {
public class Entry {
public Entry(string k, string v) { Name = k; Contact = v; }
public string Name { get; set; }
public string Contact { get; set; }
}
}
XAML上のListViewに対応するEntryの集合はOvservableCollection<Entry>となります。ListViewのItemsSource属性でEntry集合とバインドし、加えて各カラムにEntryのプロパティ:Name,Contactをバインドします。
<Window x:Class="ListView_binding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListView binding" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="382" SizeToContent="WidthAndHeight" d:DesignWidth="444">
<Grid>
<ListView ItemsSource="{Binding Path=Items}" Height="212" HorizontalAlignment="Left" Margin="16,16,0,0" Name="lstEntry" VerticalAlignment="Top" Width="360" >
<ListView.View>
<GridView>
<GridViewColumn Header="名前" DisplayMemberBinding="{Binding Path=Name}" />
<GridViewColumn Header="連絡先" DisplayMemberBinding="{Binding Path=Contact}" />
</GridView>
</ListView.View>
</ListView>
<TextBox Height="24" HorizontalAlignment="Left" Margin="56,244,0,0" Name="txtName" VerticalAlignment="Top" Width="80" />
<TextBox Height="24" HorizontalAlignment="Left" Margin="190,245,0,0" Name="txtContact" VerticalAlignment="Top" Width="79" />
<Button Content="追加" Height="23" HorizontalAlignment="Left" Margin="301,245,0,0" VerticalAlignment="Top" Width="75" Click="add_Click" />
<Label Content="名前" Height="24" HorizontalAlignment="Left" Margin="16,246,0,0" Name="label1" VerticalAlignment="Top" />
<Label Content="連絡先" Height="24" HorizontalAlignment="Left" Margin="142,246,0,0" Name="label2" VerticalAlignment="Top" />
<Label Content="を" Height="24" HorizontalAlignment="Left" Margin="275,244,0,0" Name="label3" VerticalAlignment="Top" />
<Button Content="削除" Height="23" HorizontalAlignment="Left" Margin="301,282,0,0" VerticalAlignment="Top" Width="75" Click="remove_Click" />
<Label Content="選択した項目を" Height="24" HorizontalAlignment="Left" IsEnabled="True" Margin="208,281,0,0" Name="label4" VerticalAlignment="Top" />
</Grid>
</Window>
Model.コード・ビハインドはそれぞれ以下のとおり。
using System.Collections.ObjectModel;
namespace ListView_binding {
public class BindData {
public void addPair(string k, string v) { data_.Add(new Entry(k,v)); }
public void remove_at(int n) { if ( n >= 0 && n < data_.Count ) data_.RemoveAt(n); }
public ObservableCollection<Entry> Items { get { return data_; } }
private ObservableCollection<Entry> data_= new ObservableCollection<Entry>();
}
}
using System.Windows;
namespace ListBox_binding {
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = data_;
}
private void add_Click(object sender, RoutedEventArgs e) {
data_.add(textBox.Text);
}
private void remove_Click(object sender, RoutedEventArgs e) {
data_.remove_at(listBox.SelectedIndex);
}
private BindData data_ = new BindData();
}
}
