WPFにて、コンボボックスやリストボックスの ItemsSourceは何処に書くべきでしょうか?
パッと思いついた四つのパターン
- XAML内で定義する
- Viewのコードビハインドで設定する
- ViewModelのプロパティとして実装する
- 独自のクラスを定義する
それぞれのパターンについて考えてみます。
1. XAML内で定義する
XAML内で、
<ListBox Name="list1" >
<ListBoxItem Content="item1"/>
<ListBoxItem Content="item2"/>
<ListBoxItem Content="item3"/>
</ListBox>
みたいな感じで定義します。
利点はXAML内で完結すること。難点は動的に定義できない(?)ことでしょうか?
2. Viewのコードビハインドで設定する
Viewのコードビハインドにて、ListBoxなどのコントロールのItemsSouceプロパティに直接データを設定します。
list2.ItemsSource = new string[] { "item1", "item2", "item3" };
利点はView内で完結すること。難点はViewにコードを書くのは一般的にお行儀がよろしくないとされています。
3. ViewModelのプロパティとして実装する
ViewModelにデータバインディング可能なプロパティとして公開します。
public List<string> ItemsSource
{
get { return GetDataBindItem<List<string>>("ItemsSource").Value; }
set { GetDataBindItem<List<string>>("ItemsSource").Value = value; }
}
public MainViewModel()
{
CreateDataBindItem<List<string>>("ItemsSource", new List<string> { "item1", "item2", "item3" });
}
利点としては動的に変更される場合などに対処しやすいということでしょうか? ある項目の値がAの場合はこう、Bの場合はこうといったケースも容易に書けます。
難点は、ViewModel側で特定のコントロール用の属性に依存する項目を持つことの是非でしょうか?
ViewModelでは本来、ある項目がViewでリストボックスやコンボボックスで実装されるのか、テキストボックスで実装されるのか考える必要はありません。したがって、ViewModelでリストボックスやコンボボックスの選択肢であるItemsSourceを保持する必要もない筈です。
ただ、実際にはViewModelで保持した方が記述が簡潔になるとか多々あると思うので一概にはいえないと思いますが。
4. 独自のクラスを定義する
ItemsSourceだけを提供するクラスを定義します。
public class ListSource
{
public string[] ItemsSource { get; private set; }
// コンストラクタ
public ListSource()
{
ItemsSource = new string[] { "item1", "item2", "item3" };
}
}
利点はXAMLで定義するより自由度が高く、Viewのコードビハインドに書くより行儀が良いことでしょうか?
難点はXAMLでの定義同様、動的に変更されるような場合には向かないこと。
この場合、実際には
public class ListProvider<T>
{
public List<T> Items { get; protected set; }
public ListProvider(IEnumerable<T> items)
{
Items = new List<T>(items);
}
}
というようなジェネリックなクラスを用意して、そこから派生させるのが良いんじゃないでしょうか。
どれを採用すべきか?
ケースバイケースとしか言いようがないでしょうが、個人的には可能であれば4の独自クラスを定義する方法、でなければ3のViewModel側で提供する方法を利用するのが良いかなと感じています。
他にも何か良い方法がありますでしょうか?
サンプルコード
// ViewModel
class MainViewModel : WindowViewModelBase
{
public List<string> ItemsSource
{
get { return GetDataBindItem<List<string>>("ItemsSource").Value; }
set { GetDataBindItem<List<string>>("ItemsSource").Value = value; }
}
// コンストラクタ
public MainViewModel()
{
CreateDataBindItem<List<string>>("ItemsSource", new List<string> { "item1", "item2", "item3" });
}
}
// ItemsScoue提供クラス
public class ListSource
{
public string[] ItemsSource { get; private set; }
// コンストラクタ
public ListSource()
{
ItemsSource = new string[] { "item1", "item2", "item3" };
}
}
// View
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
list2.ItemsSource = new string[] { "item1", "item2", "item3" };
}
}
<Window x:Class="ItemsSourceTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ItemsSourceTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:ListSource x:Key="ListSource" />
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<!-- list1 : XAML内で要素を定義する -->
<ListBox Name="list1" >
<ListBoxItem Content="item1"/>
<ListBoxItem Content="item2"/>
<ListBoxItem Content="item3"/>
</ListBox>
<!-- コードビハインドで要素を設定する -->
<ListBox Name="list2" />
<!-- ViewModel で要素を設定する -->
<ListBox Name="list3" ItemsSource="{Binding ItemsSource}" />
<!-- 独自のクラスを定義して設定する -->
<ListBox Name="list4" ItemsSource="{Binding ItemsSource, Source={StaticResource ListSource}}" />
</StackPanel>
</Grid>
</Window>