WPF中的ItemsControl定義了ItemContainerStyle這一屬性,顧名思義,該屬性用來給ItemsControl中包含的每一個Item的容器定義樣式。
比如在ListBox中這個容器就是ListBoxItem,在TabControl中這個容器就是TabItem。
下面是ItemContainerStyle的一種簡單應用:
XAML:
<Window ......> <StackPanel> <ListBox Name="itemsControl" ItemsSource="{Binding}"> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneTime}"/> </Style> </ListBox.ItemContainerStyle> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Text}"/> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </StackPanel></Window>
在這段XAML中定義了一個ListBox,在其ItemTemplate中有一個TextBlock綁定到資料實體的Text屬性上。在其ItemContainerStyle中將其每個Item的IsSelected屬性綁定到資料實體的IsSelected上。其資料實體的產生在下面的代碼中:
public partial class ComboBoxTest : Window { public ComboBoxTest() { InitializeComponent(); itemsControl.DataContext = GetData(); } private object GetData() { Collection<object> data = new Collection<object>(); for (int i = 1; i <= 10; i++) { data.Add(new { Text = i.ToString(), IsSelected = i == 5 }); } return data; } }
為了簡單,沒有單獨定義實體類而是用了匿名對象。一共產生十個匿名實體,把其中第五個的IsSelected設定為true,把這十個實體放入一個Collection中賦值給控制項的DataContext,這樣XAML中對ItemsSource的綁定(ItemsSource="{Binding}")就會起效。當然,直接把這個Collection賦值給ItemsSource也可以。
運行一下,結果和預期的一樣,第五項被選中了。
試試把XAML中的ListBox換成TabControl,更換之後的XAML如下:
<Window ......> <StackPanel> <TabControl Name="itemsControl" ItemsSource="{Binding}"> <TabControl.ItemContainerStyle> <Style TargetType="TabItem"> <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneTime}"/> </Style> </TabControl.ItemContainerStyle> <TabControl.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Text}"/> </DataTemplate> </TabControl.ItemTemplate> </TabControl> </StackPanel></Window>
僅僅是把ListBox換成了TabControl,把ListBoxItem換成了TabItem而已,C#代碼沒有改。試著運行一下,結果還是和預期的一樣,第五項會被選中。
ListBox和TabControl都是間接繼承自ItemsControl而直接繼承自Selector的,那是不是所有Selector的子類都會有如上的行為呢?
實際上不是,把Selector的另一個子類ComboBox拿出來試試。
仍然是只改XAML,不改C#代碼,改完之後的XAML如下:
<Window ......> <StackPanel> <ComboBox Name="itemsControl" ItemsSource="{Binding}"> <ComboBox.ItemContainerStyle> <Style TargetType="ComboBoxItem"> <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneTime}"/> </Style> </ComboBox.ItemContainerStyle> <ComboBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Text}"/> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> </StackPanel></Window>
運行之後的效果如下:
可見啟動後沒有任何選中項。而只有當用滑鼠將ComboBox展開時第五項才會被選中。對這種現象,我的猜測是因為ItemContainerStyle只有在所有Item載入之後才會開始應用,而ComboBox預設情況下並不會把其Items展示出來,所以直到用滑鼠將ComboBox展開時才會有選中效果。
對這種情況有一個不太完美的解決方案,把C#代碼中的GetData方法修改如下:
private object GetData() { Collection<object> data = new Collection<object>(); for (int i = 1; i <= 10; i++) { data.Add(new { Text = i.ToString() }); } return new { Data = data, SelectedData = data[4] }; }
上面的代碼中再次應用了匿名對象,把整個實體集合賦值給新的匿名對象中的Data屬性,並把集合的第五項賦值給新的匿名對象的SelectedData屬性。這樣GetData方法的傳回值--也就是這個新的匿名對象就會被賦值到控制項的DataContext上去。
然後修改XAML,把ComboBox的ItemsSource綁定到匿名對象的Data屬性,把SelectedValue綁定到匿名對象的SelectedData屬性。修改後的XAML如下:
<Window ......> <StackPanel> <ComboBox Name="itemsControl" ItemsSource="{Binding Data}" SelectedValue="{Binding SelectedData, Mode=OneTime}"> <ComboBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Text}"/> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> </StackPanel></Window>
這次直接綁定SelectedValue,就不用像使用Style一樣等到Item載入之後才會生效了。
再運行,啟動效果如下: