ということで、CheckedListBoxを作ってみようと思う。
基本的に、ListBoxの中身の1つの要素の実態のListBoxItemってやつのTemplateをCheckBoxに差し替えるだけで見た目は完成する。
ということで作っていこう。
普通にListBoxを作成
いつもならPersonクラスを作って…とかになるんだけど、今回はめんどくさいのでSystem.StringをそのままListBoxの要素として指定するようにした。
<Window x:Class="WpfCheckedListBox.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ListBox Name="listBox">
<System:String>田中 太郎</System:String>
<System:String>田中 次郎</System:String>
<System:String>田中 三郎</System:String>
<System:String>田中 四郎</System:String>
<System:String>田中 五郎</System:String>
</ListBox>
<Button Content="Dump" Click="Button_Click" />
</StackPanel>
</Window>
ボタンのクリックイベントには、現在選択中の要素の値をメッセージボックスに表示する処理を書いておいた。
using System;
using System.Text;
using System.Windows;
namespace WpfCheckedListBox
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// 選択中の要素をつなげてメッセージボックスに表示
var sb = new StringBuilder();
foreach (var item in listBox.SelectedItems)
{
sb.AppendLine((String) item);
}
MessageBox.Show(sb.ToString());
}
}
}
これを実行すると、おそらく予想通りの結果になる。
田中 三郎を選択した状態でボタンを押すと…
田中 三郎が表示される。
現状複数選択は出来ないが、ListBoxのSelectionModeをMultipleにしておくと、複数選択にも対応できる。CheckedListBoxの動き的には、複数選択できるほうがしっくりくると思うので、Multipleに設定しておこうと思う。
<ListBox Name="listBox" SelectionMode="Multiple">
複数選択も可能。
ボタンを押したときの動作もばっちり。
CheckBox化してみよう
見た目をCheckBoxに差し替えてみようと思う。ListBoxに、ItemContainerStyleというプロパティがあるので、そこにStyleを設定するとListBoxItemにStyleを指定できる。StyleからTemplateをCheckBoxに変えるようにカキカキ。
<Window x:Class="WpfCheckedListBox.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ListBox Name="listBox" SelectionMode="Multiple">
<!-- ContainerStyle -->
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<!-- CheckBoxに見た目を差し替えてContentをバインドする -->
<CheckBox Content="{TemplateBinding Content}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<System:String>田中 太郎</System:String>
<System:String>田中 次郎</System:String>
<System:String>田中 三郎</System:String>
<System:String>田中 四郎</System:String>
<System:String>田中 五郎</System:String>
</ListBox>
<Button Content="Dump" Click="Button_Click" />
</StackPanel>
</Window>
これで実行すると、見た目はばっちり動作はしょんぼりになる。
見た目は出来てそうだけど…
ListBoxの選択状態とCheckBoxのチェックが全然連動できてない。
次は、この問題を解決してみようと思う。
ListBoxの選択状態とCheckBoxのチェックの同期
ListBoxItemの選択状態は、添付プロパティのSelector.IsSelectedで管理されている。ListBoxItem自体にはIsSelectedといったプロパティは定義されてないといった有様だ。
直感的ではないが、まぁ仕方ないのだろう。分離分離。
ということで、Selector.IsSelectedとCheckBoxのIsCheckedプロパティが同期すればよさそうだ。二つのプロパティの値を同期とるってことは…バインドすればOKです。ということでやってみた。
<CheckBox Content="{TemplateBinding Content}"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(Selector.IsSelected)}"/>
IsCheckedは、添付プロパティのSelector.IsSelectedとバインドするため、プロパティ名だけを指定するTemplateBindingではなくて、きちんとBindingでRelativeSourceをTemplatedParentにして使った。Pathは、括弧で囲ってSelector.IsSelectedで1つのプロパティをあらわしていることを明示しないと動かない。
早速実行してみよう。
動きは勿論大丈夫。
選択状態もばっちりいけてる。
最後にStyleに切り出し
最後に、再利用可能なようにStyleに切り出しておく。Application.Resourcesあたりに登録して使うといいだろう。
<Style x:Key="CheckedListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="SelectionMode" Value="Multiple" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<!-- CheckBoxに見た目を差し替えてContentをバインドする -->
<CheckBox Content="{TemplateBinding Content}"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(Selector.IsSelected)}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
使う側はStaticResourceでStyleプロパティに設定するだけでCheckedListBoxとして使えるようになる。
<ListBox Name="listBox" Style="{StaticResource CheckedListBoxStyle}">
....省略...
</ListBox>
めでたしめでたし。