ItemControls don’t properly select items or raise events when bound to a data source
About a month ago, I saw two unrelated questions where an ItemsControl was not behaving correctly when clicking on an item: Events weren’t being raised, items weren’t getting selected; chaos ensued. In both cases, the developer was adding an items container to the data template. (As a refresher, ListBoxItem, ListViewItem, and TreeViewItem are examples of item containers.) They were doing something like the following:
<Grid>
<Grid.Resources>
<src:ItemsForSale x:Key="Data"/>
</Grid.Resources>
<ListBox ItemsSource="{StaticResource Data}">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItemPadding="3,8,3,8" MouseDoubleClick="ListBoxItem_MouseDoubleClick" >
<TextBlock Text="{Binding Path=Description}"/>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
When the correct way to implement the ListBox is something like the following:
<Grid>
<Grid.Resources>
<src:ItemsForSale x:Key="Data"/>
</Grid.Resources>
<ListBox ItemsSource="{StaticResource Data}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Padding" Value="3,8,3,8"/>
<EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Description}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
In the second example, properties and events for item containers are set on a style, and the data template contains only elements that go in each item container. You see, when the ItemsControl is bound to a data source, an items container is implicitly created for each item in the source, so in the first example above two ListBoxItem objects are made for each item: the one that is implicitly created and the one in the data template. In the second example, only the implicitly created ListBoxItem is present, and the Padding and event handler from the style is applied to each ListBoxItem.
For full disclosure, I must say that when I experimented with the first example, I got inconsistent results. Sometimes I couldn’t select an item but I could get the double click event to occur and sometimes I could select the item but not raise the event. So be forewarned: Don’t put item containers in data templates; only weird things happen if you do!
Comments
Anonymous
May 22, 2008
The comment has been removedAnonymous
May 22, 2008
Setting IsSynchronizedWithCurrentItem="True" on the ComboBox solved the problem... :)Anonymous
June 16, 2008
This is a nice point and I'll try to remember it and all, but if the arrangement of code in the "incorrect" example doesn't actually work, then the compiler needs to reject the declaration of event handlers in a data template, at least with a warning that the programmer needs to use a setter instead. Any chance something like that is already in the SP1 Beta?Anonymous
June 30, 2008
The issue isn't that there's an event handler in the DataTemplate, the issue is that there's a ListBoxItem in the DataTemplate. For example, something like this is entirely acceptable and works: <DataTemplate x:Key="ItemTemplate" > <StackPanel Orientation="Horizontal" > <TextBlock Text="Customer Name" Margin="5"/> <TextBox Width="100" Margin="5" Text="{Binding Name}"/> <Button Content="Save Customer" Click="saveCustomer_Click"/> </StackPanel> </DataTemplate> <ListBox Name="customerList" ItemTemplate="{StaticResource ItemTemplate}" ItemsSource="{Binding}"/> I don't know if it's really possible for the compiler to check for the presence of certain elements in a DataTemplate. I'll pass this along to the product team, though. Thanks, CaroleAnonymous
June 30, 2008
The comment has been removed