views:

871

answers:

1

I've noticed a behavior difference between static and dynamic resource on ComboBox.ItemsSource, when the ComboBox gets out the visual tree.

  • in the static exemple the selected destination remains
  • in the dynamic exemple, the underlying object gets a null value

Binding seems OK, because when the comboboxes gets in focus, and have their SelectedIndex changed, the change are properly notified to the other list - both objects implements INotifyProperty - and both List are ObservableCollections.

It's when the dynamic-bound combobox gets out of focus that the strange things happen

XAML

<Window ... xmlns:me = "clr-namespace:WpfComboBoxBug">
    <Window.Resources>
        <me:ShippingList x:Key="sl" />
        <me:DestinationList x:Key="dl" />
    </Window.Resources>
    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="21" />
            <RowDefinition Height="421*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Custom:DataGrid Grid.Row="1"
            ItemsSource="{StaticResource sl}" x:Name="dg" AutoGenerateColumns="False" Grid.RowSpan="2">
        <Custom:DataGrid.Columns>
            <Custom:DataGridTextColumn Header="Reference" Binding="{Binding Reference}" />
            <Custom:DataGridTemplateColumn Header="Destination">
                <Custom:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Destination.Name}"></TextBlock>
                </DataTemplate>
                </Custom:DataGridTemplateColumn.CellTemplate>
                <Custom:DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{StaticResource dl}" SelectedItem="{Binding Destination,Mode=TwoWay}" DisplayMemberPath="Name"/>
                </DataTemplate>
                </Custom:DataGridTemplateColumn.CellEditingTemplate>
            </Custom:DataGridTemplateColumn>
        </Custom:DataGrid.Columns>
        </Custom:DataGrid>
            <Custom:DataGrid Grid.Column="1" Grid.Row="1" ItemsSource="{StaticResource sl}" x:Name="dg2" AutoGenerateColumns="False" Grid.RowSpan="2">
            <Custom:DataGrid.Columns>
                <Custom:DataGridTextColumn Header="Reference" Binding="{Binding Reference}" />
                <Custom:DataGridTemplateColumn Header="Destination">
                    <Custom:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Destination.Name}"></TextBlock>
                        </DataTemplate>
                    </Custom:DataGridTemplateColumn.CellTemplate>
                    <Custom:DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <ComboBox ItemsSource="{DynamicResource dynamicdl}" SelectedItem="{Binding Destination,Mode=TwoWay}" DisplayMemberPath="Name"/>
                        </DataTemplate>
                    </Custom:DataGridTemplateColumn.CellEditingTemplate>
                </Custom:DataGridTemplateColumn>
            </Custom:DataGrid.Columns>
        </Custom:DataGrid>
        <TextBox Height="23"  Name="textBox1" VerticalAlignment="Top" Text="Static" />
        <TextBox Height="23"  Name="textBox2"  VerticalAlignment="Top" Text="Dynamic" Grid.Column="2" />
    </Grid>
</Window>

CS

using System;
/* snip */

namespace WpfComboBoxBug
{
    /// <summary>
    /// Logique d'interaction pour MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            ShippingList sl;
            this.InitializeComponent();
            sl = this.Resources["sl"] as ShippingList;

            ResourceDictionary rd = new ResourceDictionary();
            rd.Add("dynamicdl", this.FindResource("dl"));

            dg2.Resources = rd;


            dg.ItemsSource = CollectionViewSource.GetDefaultView(sl);
            dg2.ItemsSource = CollectionViewSource.GetDefaultView(sl);
        }
    }
}

full source code at : http://dl.free.fr/eI1VtkaB8 ( VS 2008 SP1, .NET 3.5 SP1 )

I expected the dynamic resource to behave like the static resource in this case, beacause I intialize it once in the beginning.

  • Have I found a bug here ?
  • If that' not the case, how would you explain the difference ?
+1  A: 

I'd have to look at your code to be sure, but I would guess that your ComboBox has its SelectedItem or SelectedValue two-way bound to a property.

When you use StaticResource the resource reference is resolved at XAML load time. When you use DynamicResource the resource refrence is resolved later. So what is probably happening is that your ComboBox is starting up with no items, which is forcing its SelectedItem and SelectedValue null. The two-way binding causes the property to update with this value.

Personally I consider ComboBox's inability to gracefully handle this situation to be a bug in the design of ComboBox, not an implementation bug.

For my own projects I frequently use a ComboBox and ListBox enhancement I created to fix this problem: I have additional properties I can use in place of SelectedValue and SelectedItems. My new properties accepts any value until the ItemsSource is set, after which it stays synchronized with SelectedValue or SelectedItem.

You could use a similar technique, or just always make sure ItemsSource is bound / initialized before SelectedValue or SelectedItem.

Update

When the control is removed from the visual tree, everything happens in reverse: ItemsSource is cleared immediately due to the ancestry change, then DataContext is cleared. During the interim, ComboBox has a null SelectedItem which is propagated to the bound property.

An enhanced ComboBox or ListBox class with additional SelectedItem and SelectedValue properites can also solve this: It should keep SelectedItem / SelecteValue in sync with the custom properties whenever ItemsSource is non-null and decouple them whenever ItemsSource is null.

Ray Burns
Thanks for help. but the point here is more at UN-binding time than at binding time
Johan Buret
I see what you mean. I missed that when I read your question. Almost everything I said still applies. I've added some more information on dealing with the problem that occurs during what you describe as "UN-binding" (removing the control from the visual tree).
Ray Burns