Collection 2題:「WPFにバインドできる辞書」と「重複を許す検索set」

CodeZine / 2012年12月26日 11時22分

cherry,banana,apple,appleの順にAddしたところ

 アプリケーション試作中、"あったらいいな"な2つのコレクション:「WPFにバインドできる辞書」と「重複を許す検索set」を、既存クラスの拡張と拡張メソッドによる対IListアルゴリズムの追加で実現しました。

■はじめに

 僕のお仕事は主に開発屋さんたちの後方支援なので、エンドユーザーさんとは直接の関わりが浅く、GUIアプリを書く機会は多くありません。とはいえ開発屋さんからのリクエストで、ちょっとしたGUIツールをこしらえることがたまぁにあります。

 長いことその多くはWindows Formsを使っていたのですが、ようやく(いまさら?)近頃WPFを使うようになりました。お客様の目には触れないアプリケーションだし、なにしろ開発屋さんは僕も含めて基本ワガママですから、使い勝手のよいようにUIの仕様をコロコロ変えてきます。そんな無茶ブリに素早く対応せにゃならんので、View(見てくれ)とModel(本体)とをきっちり分離することが望まれます。

 WPFの強力かつ柔軟なデータ・バインディングのおかげでViewとModelの分離がとても楽になりました。WPFに味をしめた僕は、頼まれたGUIアプリはぜーんぶWPFで書いてやろうと手駒を揃えることにしました。

 そんなわけで、僕が書くアプリのユーザは一般のお客様じゃなく身内の開発屋さんですから、凝ったUIはまずもって不要です。TextBox,ButtonそしてListBox/ListViewあたりが使えれば、あらかたのツールは作れます。ListBoxの小さなサンプルを書いてみますね。



 Viewはこんな感じ、いたってシンプルです。「文字列を追加する」と「選択項目を削除する」のたった2つの機能。XAMLをお見せしましょうか。

list01 MainWindows.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がコチラ。

list02 BindData.cs
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に起こるイベント:追加/削除ボタンのクリックに反応するイベント・ハンドラはコード・ビハインドに置きます。

list03 MainWindows.xaml.cs
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は

list04 Entry.cs
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をバインドします。

list05 MainWindows.xaml
<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.コード・ビハインドはそれぞれ以下のとおり。

list06 BindData.cs
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>(); } }
list07 MainWindows.xaml.cs
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(); } }


■関連記事
データ最新化まで含めた総合ソリューション型 住所検索コンポーネント「JPAddress for .NET 1.0」
Windowsストアアプリのコントラクトの基礎 ――検索、共有、設定、リモート再生
グリッドデータをドラッグ&ドロップで操作し新しい表を作成する.NETアプリケーションを作る
Collection 2題:「WPFにバインドできる辞書」と「重複を許す検索set」
Windowsストアアプリの画面構成部品、コントロール

■記事全文へ

CodeZine

トピックスRSS

ランキング