C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

XAML: 同じ ItemsSource を持つ複数のコントロールの効率的な定義の仕方、あるいは ItemsSource を StaticResource で別定義する方法

例えば同じ要素軍を選択肢として提示する複数のコンボボックスを配置したいとしよう。以下のコード例は何れも <Window> の内部に定義している。

<!--ダサいがシンプルで間違いは無い実装。しかしダサい。-->
<ComboBox Name="CB1">
  <ComboBoxItem Content="選択肢1"/>
  <ComboBoxItem Content="選択肢2"/>
  <ComboBoxItem Content="選択肢3"/>
</ComboBox>
<ComboBox Name="CB2">
  <ComboBoxItem Content="選択肢1"/>
  <ComboBoxItem Content="選択肢2"/>
  <ComboBoxItem Content="選択肢3"/>
</ComboBox>

↑ダサい。

そこで XAML 初心者のわたしは先ず次のようにしてみた。

<!--これは期待動作しません。-->
<ComboBox Name="CB1">
  <ComboBoxItem Content="選択肢1"/>
  <ComboBoxItem Content="選択肢2"/>
  <ComboBoxItem Content="選択肢3"/>
</ComboBox>
<ComboBox Name="CB2" ItemsSource="{Binding ItemsSource,ElementName=CB1"/>

↑これはコンパイルはできるが実行してもCB2の選択肢が何も出てこない。

<!--これも期待動作しません。-->
<ComboBox Name="CB1">
  <ComboBoxItem Content="選択肢1"/>
  <ComboBoxItem Content="選択肢2"/>
  <ComboBoxItem Content="選択肢3"/>
</ComboBox>
<ComboBox Name="CB2" ItemsSource="{Binding Items,ElementName=CB1"/>

↑これはコンパイルはできるし、実行するとCB2にも選択肢がでるのだが、CB2でドロップダウンされた選択肢を選択してもCB1の値へ適用されるという怪奇現象を引き起こした。

<Window
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

↑と x が使用可能として、

<!--期待動作する例-->
<Window.Resources>
  <x:Array Type="ComboBoxItem" x:Key="CBItems">
    <ComboBoxItem Content="選択肢1"/>
    <ComboBoxItem Content="選択肢2"/>
    <ComboBoxItem Content="選択肢3"/>
  </x:Array>
</Window.Resources>
<ComboBox Name="CB1" ItemsSource="{StaticResource CBItems}"/>
<ComboBox Name="CB2" ItemsSource="{StaticResource CBItems}"/>

↑期待動作する😃

ちなみに、 <ComboBoxItem Content="hoge" Tag="fuga"/> など Content 属性の他にも設定したい何かしらがある場合はさておき、そうではないただの文字列のリストの StaticResource を定義して使えればよい場合には、

<Window.Resources>
  <x:Array xmlns:s="clr-namespace:System;assembly=mscorlib" Type="s:String" x:Key="StringItems">
    <s:String>梅干し</s:String>
    <s:String>生姜</s:String>
    <s:String>小葱</s:String>
  </x:Array>
</Window.Resources>

↑など定義し、これも ComboBoxItemsSourceStaticRerouce としてぶちこめる。この場合には ComboBox ではなく ListBox にも放り込めるようになる。

なお、 XAML で完結する必要が無ければ XAML 側では ItemsSource{Binding MyDataContextProperty} としておいてコードで WindowDataContextIEnumerable< string >MyDataContextProperty を持つオブジェクトを放り込めばよい。この方法は ItemsSource に列挙する選択肢が実行中に変化する場合にはより適切だろう。