はじめに
僕のお仕事は主に開発屋さんたちの後方支援なので、エンドユーザーさんとは直接の関わりが浅く、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(); } }