2

Preface The control I am giving as an example is an sample work for a larger project. I have already had some help from the community on Stackoverflow ironing out some of the finer points of bindings within the control. The surprise has been that I am having an issue binding in the control's hosting form.

I have read and researched around DependencyProperty for a lot of hours. I was not a WPF developer at the start of the year but I am now covering the role because of a death in the business, and I accept this is a big hill to climb.

The question is what is missing here in my:

The hosting form's XAML code

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:AControl="clr-namespace:AControl;assembly=AControl" x:Class="DependencySampler.MainWindow"
    Title="MainWindow" Height="350" Width="525">
<Grid>

    <AControl:UserControl1 x:Name="cboBob" HorizontalAlignment="Left" Margin="100,118,0,0" VerticalAlignment="Top" Width="200" Height="29" SelectedColor="{Binding Path=BeSelected, Mode=OneWayToSource}"/>

</Grid>

The code behind

    public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new viewModelBinding();
        BeSelected =  new modelMain("Yellow", "#FFFFE0");
    }

    public modelMain BeSelected
    { 
        get { return ((viewModelBinding)DataContext).Selected; }
        set { ((viewModelBinding)DataContext).Selected = value; }
    }

}

The ViewModel

    public class viewModelBinding :ViewModelBase
{
    modelMain sel = new modelMain("Red", "#FF0000");
    public modelMain Selected
    {
        get { return sel; }
        set { SetProperty(ref this.sel, value, "Selected"); }
    }
}

The next section is the control itself.

The Model

    public class modelMain:ViewModelBase
{
    public modelMain(string colName, string hexval)
    {
        ColorName = colName;
        HexValue = hexval;
    }

    string colorName;
    public string ColorName
    {
        get { return colorName; }
        set { SetProperty(ref this.colorName, value, "ColorName"); }
    }

    string hexValue;
    public string HexValue
    {
        get { return hexValue; }
        set { SetProperty(ref this.hexValue, value, "HexValue"); }
    }
}

The ViewModel

    public class viewModelMain:ViewModelBase
{
    ObservableCollection<modelMain> val = new ObservableCollection<modelMain>();
    public ObservableCollection<modelMain> ColorsList
    {
        get { return val; }
        set { SetProperty(ref this.val, value, "Colors"); }
    }


    modelMain selectedColor;
    public modelMain SelectedColour
    {          
        get{return selectedColor;}
        set { SetProperty(ref this.selectedColor, value, "SelectedColour"); }
    }

    public void SetCurrentColor(modelMain col)
    {
        SelectedColour = this.val.Where(x => x.ColorName == col.ColorName).FirstOrDefault(); 
    }

    public viewModelMain()
    {
        val.Add(new modelMain("Red", "#FF0000"));
        val.Add(new modelMain("Blue", "#0000FF"));
        val.Add(new modelMain("Green", "#008000"));
        val.Add(new modelMain("Yellow", "#FFFFE0"));

        SelectedColour = new modelMain("Blue", "#0000FF");
    }
}

The UserControl XAML

<UserControl x:Class="AControl.UserControl1"
         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" 
         mc:Ignorable="d" 
         d:DesignHeight="32" d:DesignWidth="190">
<Grid>
    <ComboBox x:Name="cboValue"
              SelectionChanged="cboValue_SelectionChanged"
              HorizontalAlignment="Stretch"
              VerticalAlignment="Stretch"
              ItemsSource="{Binding ColorList, RelativeSource={RelativeSource AncestorType=UserControl}}"
              SelectedValue="{Binding SelectedColor, RelativeSource={RelativeSource AncestorType=UserControl}}">
        <ComboBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Width="10"
                               Height="10"
                               Margin="5"
                               Background="{Binding ColorName}"/>
                    <TextBlock Width="35"
                               Height="15"
                               Margin="5"
                               Text="{Binding ColorName}"/>
                </StackPanel>
            </DataTemplate>
        </ComboBox.ItemTemplate>
    </ComboBox> 
</Grid>

The UserControl Code behind

    public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
    }

    ObservableCollection<modelMain> colorList = new viewModelMain().ColorsList;
    public ObservableCollection<modelMain> ColorList
    {
        get { return colorList; }
        set { colorList = value; }
    }


    public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register(
        "SelectedColor",
        typeof(modelMain),
        typeof(UserControl1),
        new FrameworkPropertyMetadata(
            null, 
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
            new PropertyChangedCallback(OnSelectedColorChanged),
            new CoerceValueCallback(CoerceSelectedColorCallback)));


    private static void OnSelectedColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        UserControl1 uc = (UserControl1)d;
        uc.SelectedColor = (modelMain)e.NewValue;
    }

    private static object CoerceSelectedColorCallback(DependencyObject d, object value)
    {
        return (modelMain)value;
    }


    public modelMain SelectedColor
    {
        get { return (modelMain)GetValue(SelectedColorProperty); }
        set { SetValue(SelectedColorProperty, value); }
    }

    private void cboValue_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var dat = sender as ComboBox;
        SelectedColor = (modelMain)dat.SelectedValue;
    }

    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
        //var dat = sender as ComboBox;
        ////SelectedColor = (modelMain)dat.SelectedValue;
        //SelectedColor = (modelMain)this.SelectedColor;
    }
}

Please note that in the code behind there is unused code but within the sample I have used then for placing break points

I understand that no DataContext should exist in the UserControl because it precludes one in the hosting form.

The Question I was expecting the this line would be sufficient in the hosting form.

<AControl:UserControl1 x:Name="cboBob" HorizontalAlignment="Left" Margin="100,118,0,0" VerticalAlignment="Top" Width="200" Height="29" SelectedColor="{Binding Path=BeSelected, Mode=OneWayToSource}"/>

But it does not seem to do what I expected. I can see the BeSelected be initialised and it is holding a value but when the form loads I am expecting the colour yellow to enter the UserControl's and set DependencyProperty SelectedColor. This is not happening why and how can I get it to happen?

5
  • Try removing OneWayToSource? Are you trying to set SelectedColor when BeSelected changes? In this case, I think SelectedColor is the target, and BeSelected is the source. Sounds like you want to use just "OneWay" Commented Aug 24, 2016 at 10:32
  • You are using different instances of modelMain and I don't see IEquatable<T> being implemented, so your selected object cannot be selected in the combobox because it does not exist in the colorlist you are creating from the perspective of a computer that is. Commented Aug 24, 2016 at 10:49
  • Joe, I am trying to set the initial value on load of the combobox via binding. When I change the selected value the whole process works very well. However I will try the suggestion of OneWayToSource Commented Aug 24, 2016 at 12:36
  • Janne, The modelMain in both cases is the same class. I just bring in the namespace from the UserControl into the hosting form's project. I would not expect a IEquatable<T> in this particular scenario. Would you agree? Commented Aug 24, 2016 at 12:38
  • @AngryBobb Think it like this way, you take 2 decks of cards and pull out 7 of clubs from each of the decks, are they the same card or not (and I mean physically)? No, but they are equatable because they represent the same thing. Commented Aug 25, 2016 at 5:21

1 Answer 1

1

To get you example working, do the following (for the most part, implement what the commenters said):

The hosting form's XAML code

<AControl:UserControl1 x:Name="cboBob" HorizontalAlignment="Left" Margin="100,118,0,0" VerticalAlignment="Top" Width="200" Height="29" SelectedColor="{Binding Path=BeSelected, RelativeSource={RelativeSource AncestorType=Window}}}"/>

The Mode doesn't really matter since MainWindow doesn't implement INPC nor does it ever know when ((viewModelBinding)DataContext).Selected (and therefor, BeSelected) is changed. Actually, like Joe stated, OneWayToSource doesn't work... RelativeSource was needed because BeSelected is a property of the MainWindow - not MainWindow's DataContext.

modelMain

modelMain needs to implement IEquatable (like Janne commented). Why? Because BeSelected = new modelMain(...) creates a new modelMain which is not one of the items in the ComboBox's ItemsSource (ColorList). The new object may have the same property values as one of the items but that doesn't make them equal (different objects = different address in memory). IEquatable gives you the opportunity to override that.

public class modelMain : ViewModelBase, IEquatable<modelMain>
{
  ...
  public bool Equals(modelMain other)
  {
    return (HexValue == other.HexValue);
  }
}

viewModelMain's ColorList's setter is calling SetProperty with property name "Colors" when it should be "ColorsList". It's not being used so it doesn't stop your example from working but it's still wrong.

Sign up to request clarification or add additional context in comments.

1 Comment

I would also like to make a gracious nod to Joe and Janne for their points. I had something of a think on what was said and in review whilst the class instantiated is the same the instances are not. Thank you for expanding my understanding.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.