0
votes

I've got a really simple UserControl I'm trying to create that contains a list of US states. I am trying to expose the selected state via a "SelectedState" property. However, I'm having trouble trying to get this binding to fire once it's hooked up in another UserControl / form.

The XAML for the user control looks like this:

<UserControl x:Class="Sample.Desktop.UserControls.StateDropdown"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:Sample.Desktop.UserControls"
         mc:Ignorable="d" 
         Width="170" Height="28"
         d:DesignHeight="28" d:DesignWidth="170">
<ComboBox x:Name="cboState" 
          ItemsSource="{Binding StateList, RelativeSource={RelativeSource AncestorType=UserControl}}" 
          SelectedValue="{Binding SelectedState, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}" 
          >
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <Label Content="{Binding Abbreviation}"></Label>
                <Label> - </Label>
                <Label Content="{Binding Name}"></Label>
            </StackPanel>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

In the code-behind, I have this code:

    public static readonly DependencyProperty SelectedStateProperty = DependencyProperty.Register("SelectedState", 
                                                                                                  typeof(USState), 
                                                                                                  typeof(StateDropdown), 
                                                                                                  new UIPropertyMetadata(null, 
                                                                                                                         new PropertyChangedCallback(OnSelectedStateChanged), 
                                                                                                                         new CoerceValueCallback(OnCoerceSelectedState)));

    private static object OnCoerceSelectedState(DependencyObject o, object value)
    {
        StateDropdown stateDropdown = o as StateDropdown;
        if (stateDropdown != null)
            return stateDropdown.OnCoerceSelectedState((USState)value);
        else
            return value;
    }

    private static void OnSelectedStateChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        StateDropdown stateDropdown = o as StateDropdown;
        if (stateDropdown != null)
            stateDropdown.OnSelectedStateChanged((USState)e.OldValue, (USState)e.NewValue);
    }

    protected virtual USState OnCoerceSelectedState(USState value)
    {
        // TODO: Keep the proposed value within the desired range.
        return value;
    }

    protected virtual void OnSelectedStateChanged(USState oldValue, USState newValue)
    {
        // TODO: Add your property changed side-effects. Descendants can override as well.
    }

    public USState SelectedState
    {
        // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
        get
        {
            return (USState)GetValue(SelectedStateProperty);
        }
        set
        {
            SetValue(SelectedStateProperty, value);
        }
    }

I wasn't able to get the SelectedValue bound property of SelectedState to fire, so I ended up hooking up the SelectionChanged event.

    private void cboState_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.AddedItems?.Count > 0)
        {
            SelectedState = (USState)e.AddedItems[0];
        }
    }

In my other user control, I have this in the XAML:

<uc:StateDropdown Margin="10,0,0,0" SelectedState="{Binding SelectedState}" ></uc:StateDropdown>

And the ViewModel (I'm using Caliburn Micro), I have this property:

    protected USState _selectedState;
    public USState SelectedState
    {
        get { return _selectedState; }
        set
        {
            _selectedState = value;
            NotifyOfPropertyChange(() => SelectedState);                
        }
    }

The combo is populated as expected. However, SelectedState is never fired/updated when I change the selection.

I had also previously tried using SelectedItem instead of SelectedValue, with the same results.

I'm sure I'm missing something obvious, but I'm having trouble seeing where I went wrong.

EDIT: Here's what fixed the binding.

I removed the SelectionChanged event. Then I modified my "hosting page" usercontrol to set TwoWay binding:

<uc:StateDropdown Margin="10,0,0,0" SelectedState="{Binding SelectedState, Mode=TwoWay}" ></uc:StateDropdown>

As soon as I added that, SelectedState started being updated when I changed the ComboBox value.

1
SelectedState = (USState)e.AddedItems[0]; that might ruin your binding, while inside your usercontrol use SetCurrentValue method of dependency object. - 3615
That was only added after I couldn't get the normal binding to work. - Paul Mrozowski

1 Answers

1
votes

The only things I see, is this line :

 SelectedValue="{Binding SelectedState, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}" 

You don't need it, because of the SelectionChanged event. And it can cause the problem.

Also I would bind the SelectedState of the UserControl using a TwoWay binding.

Hope that will help you.