0

I have a nearly working control written in C# and WPF using a DependencyProperty (dp). I can return data from the control's dp but I am having a problem setting the initial value. This control is a simplified version of the final product for the purpose of being given here.

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, "ColorsList"); }
    }


    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"));
    }
}

The XAML

<Grid>
    <ComboBox x:Name="cboValue"
              SelectionChanged="cboValue_SelectionChanged"
              HorizontalAlignment="Stretch"
              VerticalAlignment="Stretch"
              ItemsSource="{Binding ColorList, RelativeSource={RelativeSource AncestorType=UserControl}}"
              SelectedValue="{Binding ColorsList.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 code behind

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

    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;
    }

}

I can get most of this working, so when I change a colour the dp fires OnSelectedColorChanged so that is all good. I set the DataContext after the initialise. But when I use a data binding of type modelMain in a testing form nothing happens.

I suspect that the issue is here in the code but after 10s of hours reading around the subject on any number of sites I have come to the conclusion that documentation is lacking or located somewhere in obvious. My ideal solution is a pointer to that information's location and a brief why and how it works. Give an man a fish or teach him to fish and all that. Stack Overflow is pretty much my last place to ask questions so don't be suprised if no searches are recorded here but if those that like ot say no want proof of effort please ask :@)

How I useand bind to the control

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

This the class bound to

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

The inherited ViewModelBase has most of the normal stuff in it INotifyPropertyChanged and IDisposable etc..

I changed my control to read

    <Grid>
    <ComboBox x:Name="cboValue"
              SelectionChanged="cboValue_SelectionChanged"
              HorizontalAlignment="Stretch"
              VerticalAlignment="Stretch"
              ItemsSource="{Binding ColorsList.ColorList, RelativeSource={RelativeSource AncestorType=UserControl}}"
              SelectedValue="{Binding ColorsList.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>

and changed the code behind in this manner

        public UserControl1()
    {
        InitializeComponent();
        //DataContext = new viewModelMain();
    }

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

The colorList collection populates as expected but the list in the control does not. Why is this the case? I know the removal of the DataContext broke the linkage but I had thought using the same process as supplied would work. The examples given demonstrated were very simple this is slightly more complex but not so much so.

The main windows code

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

The code behind

        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 view model used for main window.

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

I have finally got to the bottom of the problem here. Clemens whom I would like to thank for providing to me an enormous leg up into the technology. The remaining issue is one that was not foreseen in the examples provided by Clemens. The assumption in those examples is that I would be passing system types rather than user defined types. The solution to this question is the use of IEquatable that allows values to be passed in.

Graham
  • 7,431
  • 18
  • 59
  • 84
Angry Bobb
  • 35
  • 8
  • Most certainly unrelated to your problem, still worth a note: You have a `ColorsList` property with the string "Colors" as argument to SetProperty. It's also a little confusing how you mix `color` and `colour`. You should probably choose only one way of writing that name. – Clemens Aug 22 '16 at 14:49
  • Besides that, it isn't necessary and doesn't make sense to set the value of the SelectedColor property in a PropertyChangedCallback of the same property. That code is entirely redundant. SelectedColor is already and always equal to e.NewValue. – Clemens Aug 22 '16 at 14:54
  • How are you creating a data binding when you use your UserControl? The problem here is that you explicitly set the UserControl's DataContext in its constructor. You shouldn't do that, because it effectively prevents inheriting a DataContext from the control's parent (e.g. the main window). See e.g. here: http://stackoverflow.com/a/35198693/1136211 or here: http://stackoverflow.com/a/28982771/1136211 – Clemens Aug 22 '16 at 15:01
  • I have added the XAMl above and I use DataContext = new ViewModel which contains only one item of model from the control – Angry Bobb Aug 22 '16 at 18:43
  • As said, that binding won't work as you expect when you explicitly set the UserControl's DataContext in its constructor. Take a look at the posts i've linked. – Clemens Aug 22 '16 at 19:03
  • I am in the UK, I will give this a try first thing. But I think I understand what you are saying. The internal DataContext declaration and explicit declaration prevents the explicit declaration of an external one. Therefore the internal must become implicit by reference from the control and its host. – Angry Bobb Aug 22 '16 at 20:55
  • Please review the code, the remaining question is now that the DataContext is not used how is the list of items populated in the combo box. I have used the model as for selected item but this does not seem to work. Could you please clarify what you would expect? – Angry Bobb Aug 23 '16 at 12:29
  • You're now using the property paths `ColorsList.ColorList` and `ColorsList.SelectedColor` in the RelativeSource bindings. What is `ColorsList`? The paths should certainly be just `ColorList` and `SelectedColor` instead. You should have seen binding error messages in the Output Window in Visual Studio. – Clemens Aug 23 '16 at 12:33
  • Sorry there Clemens I forgot to update the ViewModel I changed a property name to make it more obvious. The ColorsList is an ObservableCollection of modelMain. What I need to do now is attach this to the combobox. I looked at your listbox example and I understood it but the source is applied via code and does not have near the complexity of what I am trying to do, not that it should be complex. A list of displayed colors with their name. The real project takes all of the windows palette and populates from that but this example is true enough for this sample work. – Angry Bobb Aug 23 '16 at 14:07
  • Hi Clemens, I seem to have fathomed with your help the loading of the list see modified code above. Ok so now to the SelectedValue, I will update things as I go and I may have further questions. I have spend tens of hours reading around this and you have been the first source to even mention this Microsoft or not. – Angry Bobb Aug 23 '16 at 15:40
  • Sorry this still has not addressed the issue which is how do I select an initial value for the combo by binding. – Angry Bobb Aug 24 '16 at 08:22
  • I have appended the code above so you can see how I am binding. This really is the final piece to the puzzle for me. – Angry Bobb Aug 24 '16 at 08:29
  • Better write a new question. – Clemens Aug 24 '16 at 09:09
  • New Question raised – Angry Bobb Aug 24 '16 at 10:28

0 Answers0