I've been using databinding in several simple situations with pretty good success. Usually I just use INotifyPropertyChanged to enable my codebehind to modify the GUI values on screen, rather than implement dependency properties for everything.
I am playing with an LED control to learn more about databinding in user controls, and was forced to use dependency properties because VS2008 told me I had to. My application is straightforward -- I have a window that displays several LED controls, each with a number above it and optionally, one to its side. The LEDs should be definable with a default color, as well as change state.
I started by writing an LED control, which seemed to go perfectly fine. First, I started with code like this:
LED.xaml
<UserControl x:Class="LEDControl.LED"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="Auto" Width="Auto">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- LED portion -->
<Ellipse Grid.Column="0" Margin="3" Height="{Binding LEDSize}" Width="{Binding LEDSize}" Fill="{Binding LEDColor}" StrokeThickness="2" Stroke="DarkGray" />
<Ellipse Grid.Column="0" Margin="3" Height="{Binding LEDSize}" Width="{Binding LEDSize}">
<Ellipse.Fill>
<RadialGradientBrush GradientOrigin="0.5,1.0">
<RadialGradientBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform CenterX="0.5" CenterY="0.5" ScaleX="1.5" ScaleY="1.5"/>
<TranslateTransform X="0.02" Y="0.3"/>
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop Offset="1" Color="#00000000"/>
<GradientStop Offset="0.4" Color="#FFFFFFFF"/>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
<!-- label -->
<TextBlock Grid.Column="1" Margin="3" VerticalAlignment="Center" Text="{Binding LEDLabel}" />
</Grid>
</UserControl>
This draws an LED just fine. I then bound LEDSize, LEDLabel, and LEDColor to the Ellipse properties by setting this.DataContext = this
like I always do:
LED.xaml.cs
/// <summary>
/// Interaction logic for LED.xaml
/// </summary>
public partial class LED : UserControl, INotifyPropertyChanged
{
private Brush state_color_;
public Brush LEDColor
{
get { return state_color_; }
set {
state_color_ = value;
OnPropertyChanged( "LEDColor");
}
}
private int led_size_;
public int LEDSize
{
get { return led_size_; }
set {
led_size_ = value;
OnPropertyChanged( "LEDSize");
}
}
private string led_label_;
public string LEDLabel
{
get { return led_label_; }
set {
led_label_ = value;
OnPropertyChanged( "LEDLabel");
}
}
public LED()
{
InitializeComponent();
this.DataContext = this;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged( string property_name)
{
if( PropertyChanged != null)
PropertyChanged( this, new PropertyChangedEventArgs( property_name));
}
#endregion
}
At this point, I can change the property values and see that the LED changes size, color and its label. Great!
I want the LED control to be reusable in other widgets that I write over time, and the next step for me was to create another UserControl (in a separate assembly), called IOView
. IOView
is pretty basic at this point:
IOView.xaml
<UserControl x:Class="IOWidget.IOView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:led="clr-namespace:LEDControl;assembly=LEDControl"
Height="Auto" Width="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" HorizontalAlignment="Center" Text="{Binding Path=Index}" />
<led:LED Grid.Row="1" HorizontalContentAlignment="Center" HorizontalAlignment="Center" LEDSize="30" LEDColor="Green" LEDLabel="Test" />
</Grid>
</UserControl>
Notice that I can modify the LED properties in XAML at design time and everything works as expected:
I then blindly tried to databind LEDColor to my IOView, and VS2008 kindly told me "A 'Binding' cannot be set on the 'LEDColor' property of type 'LED'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject." Oops! I hadn't even realized that since I haven't made my own GUI controls before. Since the LEDColor
is already databound to the Ellipse, I added a DependencyProperty called Color.
LED.xaml.cs
public static DependencyProperty ColorProperty = DependencyProperty.Register( "Color", typeof(Brush), typeof(LED));
public Brush Color
{
get { return (Brush)GetValue(ColorProperty); }
set {
SetValue( ColorProperty, value);
LEDColor = value;
}
}
Note that I set the property LEDColor
in the setter, since that's how the Ellipse knows what color it should be.
The next baby step involved setting the color of the LED in my IOView by binding to IOView.InputColor:
IOView.xaml.cs:
/// <summary>
/// Interaction logic for IOView.xaml
/// </summary>
public partial class IOView : UserControl, INotifyPropertyChanged
{
private Int32 index_;
public Int32 Index
{
get { return index_; }
set {
index_ = value;
OnPropertyChanged( "Index");
}
}
private Brush color_;
public Brush InputColor
{
get { return color_; }
set {
color_ = value;
OnPropertyChanged( "InputColor");
}
}
private Boolean state_;
public Boolean State
{
get { return state_; }
set {
state_ = value;
OnPropertyChanged( "State");
}
}
public IOView()
{
InitializeComponent();
this.DataContext = this;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged( string property_name)
{
if( PropertyChanged != null)
PropertyChanged( this, new PropertyChangedEventArgs( property_name));
}
#endregion
}
and in IOView.xaml, I changed the LED to this:
<led:LED Grid.Row="1" HorizontalContentAlignment="Center" HorizontalAlignment="Center" LEDSize="30" Color="{Binding InputColor}" />
But it's not working, because of the following error in the Output window:
BindingExpression path error: 'InputColor' property not found on 'object' ''LED' (Name='')'. BindingExpression:Path=InputColor; DataItem='LED' (Name=''); target element is 'LED' (Name=''); target property is 'Color' (type 'Brush')
Hmm... so for some reason, my DataBinding is messed up. I can get the LED to work on its own with databinding, but once I wrap it in another control and set its datacontext, it doesn't work. I'm not sure what to try at this point.
I'd love to get as detailed an answer as possible. I know that I could have just retemplated a CheckBox to get the same results, but this is an experiment for me and I'm trying to understand how to databind to controls' descendants.