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.