Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF ComboBox SelectedValue binding with null value shows up blank

I have a problem while trying to bind 2 or more Comboboxes SelectedValue to a property, that is null. Only 1 of the comboboxes bound to this property will show the real value.

Below is my Xaml where i use DataTemplate to select a Combobox for presentation of the viewModel.

Xaml:

<Window x:Class="Test.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Test"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <DataTemplate DataType="{x:Type local:PropertyValueViewModel}">
        <ComboBox SelectedValue="{Binding Value}" ItemsSource="{Binding SelectableValues}" DisplayMemberPath="Description" SelectedValuePath="Value"/>
    </DataTemplate>
</Window.Resources>
<StackPanel>
    <Label Content="These uses template:"></Label>
    <ContentPresenter Content="{Binding ValueSelector}"></ContentPresenter>
    <ContentPresenter Content="{Binding ValueSelector}"></ContentPresenter>
    <ContentPresenter Content="{Binding ValueSelector}"></ContentPresenter>
</StackPanel>

And the code behind:

 public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        ValueSelector = new PropertyValueViewModel()
        {
            SelectableValues = new List<SelectableValue>()
            {
                new SelectableValue("NULL", null),
                new SelectableValue("1", 1)
            },
            Value = null
        };

        DataContext = this;
    }

    public static readonly DependencyProperty ValueSelectorProperty = DependencyProperty.Register(
        "ValueSelector", typeof(PropertyValueViewModel), typeof(MainWindow), new PropertyMetadata(default(PropertyValueViewModel)));

    public PropertyValueViewModel ValueSelector
    {
        get { return (PropertyValueViewModel)GetValue(ValueSelectorProperty); }
        set { SetValue(ValueSelectorProperty, value); }
    }
}

/// <summary>
/// My viewModel
/// </summary>
public class PropertyValueViewModel
{
    public object Value { get; set; }
    public object SelectableValues { get; set; }
}

/// <summary>
/// The items in the combobox
/// </summary>
public class SelectableValue
{
    public SelectableValue(string header, object value)
    {
        Value = value;
        Description = header;
    }

    public object Value { get; set; }

    public string Description { get; set; }
}

Now i am wondering why only 1 of them can show the NULL value at startup? I can change the value in any of them, and they will all sync with the value in the property - if i select 1 and then back to NULL, they will all show NULL. It seems like its only the initial value is not shown correctly.

If i avoid using DataTemplate the binding works too. Does anyone know why the DAtaTemplate behaves this way?

like image 864
Patrick Avatar asked Jun 24 '15 00:06

Patrick


1 Answers

Interesting problem.

Fundamentally, this appears to be caused by your choice to use null as one of the selectable values. null, of course, has special meaning, for C#, .NET, and WPF. The problem also involves the order in which the initialization of the ComboBox element is done. The SelectedValuePath property is initialized after the SelectedValue property.

This means that as your program is starting up and the ComboBox elements are created, when null is assigned to the SelectedValue property through its binding, the ComboBox does not yet have enough information to handle that value as a legitimate item selection. Instead, it interprets it as no selection at all.

Why does the last ComboBox still get initialized the way you want? I'm not really sure…I didn't investigate very far regarding that. I could speculate, but the odds of my guessing correctly seem low so I won't bother. Since it's the anomaly and not necessarily in keeping with expected behavior (based on above, even if the behavior is the desired behavior) I'll chalk it up to one of WPF's many "quirks". :)

I found several work-arounds for the issue:

  • Don't use null as a selectable value. If every selectable value is non-null, then the non-null value used to initialize each element's SelectedValue property is retained and when the SelectedValuePath is initialized, the current selection for the ComboBox is set correctly.
  • Don't use SelectedValuePath. Instead, just bind to SelectedItem and initialize the Value property with the desired SelectableValue class instance (e.g. the first one in the list).
  • In the ComboBox's Loaded event, refresh the target of the binding.

The first two are significant departures from your current design. Personally, if at all possible I would go with one or the other. It seems to me that there's a clear danger in using null as a selectable value in a ComboBox, and this may not be the only oddity you run into. In the long run, maintenance of this part of the code may cost a lot more if you continue to use null.

That said, the third option does work, and if you're lucky, the only real hazard in using null is on initialization. My proposed work-around for that option would look something like this:

XAML:

<DataTemplate DataType="{x:Type local:PropertyValueViewModel}">
    <ComboBox SelectedValue="{Binding Value}"
              ItemsSource="{Binding SelectableValues}"
              DisplayMemberPath="Description"
              SelectedValuePath="Value"
              Loaded="comboBox_Loaded"/>
</DataTemplate>

C#:

private void comboBox_Loaded(object sender, RoutedEventArgs e)
{
    ComboBox comboBox = (ComboBox)e.OriginalSource;

    BindingOperations.GetBindingExpression(comboBox, ComboBox.SelectedValueProperty)
                     .UpdateTarget();
}

This forces WPF to update the target (i.e. the SelectedValue property of the control). Since at this point, the SelectedValuePath has been set, assigning null to the property this time correctly updates the selected item for the ComboBox.


By the way, I would strongly recommend that you disambiguate the names of the Value properties in your models. Having two different Value properties used for bindings in a single XAML element is very confusing. I would use, for example, SelectedValue and ItemValue, for the PropertyValueViewModel class and the SelectableValue class, respectively.

like image 64
Peter Duniho Avatar answered Sep 29 '22 07:09

Peter Duniho