How to setup datacontext and custom usercontrol binding - wpf

I do not understand why binding works for a Textbox but does not work for usercontrol.
On image below you see how it should work. The service can bind to the yellow usercontrol and this usercontrol contains a property of my own class. In my case this property is called Email. Problem is, that this Email is never bind to the yellow usercontrol.
If I replace the usercontrol by simple "TextBox" control, it works proper.
Please can you advice me how to get Binding work?
Codebehind of Silvelright Main Page
#Region "UserProfile"
''' <summary>
''' UserProfile Dependency Property
''' </summary>
Public Shared ReadOnly UserProfileProperty As DependencyProperty = _
DependencyProperty.Register("UserProfile", GetType(ServiceReference1.UserProfile), GetType(MainPage), _
New Windows.PropertyMetadata(Nothing, _
AddressOf OnUserProfileChanged))
''' <summary>
''' Gets or sets the UserProfile property. This dependency property
''' indicates ....
''' </summary>
Public Property UserProfile() As ServiceReference1.UserProfile
Get
Return CType(GetValue(UserProfileProperty), ServiceReference1.UserProfile)
End Get
Set(ByVal value As ServiceReference1.UserProfile)
SetValue(UserProfileProperty, value)
End Set
End Property
''' <summary>
''' Handles changes to the UserProfile property.
''' </summary>
Private Overloads Shared Sub OnUserProfileChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim target As MainPage = CType(d, MainPage)
Dim oldUserProfile As ServiceReference1.UserProfile = CType(e.OldValue, ServiceReference1.UserProfile)
Dim newUserProfile As ServiceReference1.UserProfile = target.UserProfile
target.OnUserProfileChanged(oldUserProfile, newUserProfile)
End Sub
''' <summary>
''' Provides derived classes an opportunity to handle changes to the UserProfile property.
''' </summary>
Protected Overridable Overloads Sub OnUserProfileChanged(ByVal oldUserProfile As ServiceReference1.UserProfile, ByVal newUserProfile As ServiceReference1.UserProfile)
Me.DataContext = newUserProfile
End Sub
#End Region
when tracking the property, the "newUserProfile" item was set successfully in codebehind.
XAML
<UserControl x:Class="CH_App.ucUserEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:my="clr-namespace:CH_App"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<TextBox Text="{Binding Path=Email}"/>
<my:ucDbRow Title="Email" Value="{Binding Path=Email, Mode=TwoWay}" />
</Grid>
</UserControl>
The Texbox with binding of email works like it should and shows the email address.
The usercontrol does not show the email address. The user control shows the Title correct.
UserControl
<UserControl x:Class="CH_App.ucDbRow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:my="clr-namespace:CH_App"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
d:DesignHeight="300" d:DesignWidth="400">
<StackPanel>
<TextBlock x:Name="txtTitle" Text="{Binding Path=Title}" />
<TextBox x:Name="txtValue" Text="{Binding Path=Value, Mode=TwoWay}"/>
</StackPanel>
</UserControl>
Codebehind of usercontrol
#Region "Title"
''' <summary>
''' Title Dependency Property
''' </summary>
Public Shared ReadOnly TitleProperty As DependencyProperty = _
DependencyProperty.Register("Title", GetType(String), GetType(ucDbRow), _
New Windows.PropertyMetadata(""))
''' <summary>
''' Gets or sets the Title property. This dependency property
''' indicates ....
''' </summary>
Public Property Title() As String
Get
Return CType(GetValue(TitleProperty), String)
End Get
Set(ByVal value As String)
SetValue(TitleProperty, value)
End Set
End Property
#End Region
#Region "Value"
''' <summary>
''' Value Dependency Property
''' </summary>
Public Shared ReadOnly ValueProperty As DependencyProperty = _
DependencyProperty.Register("Value", GetType(String), GetType(ucDbRow), _
New Windows.PropertyMetadata(""))
''' <summary>
''' Gets or sets the Value property. This dependency property
''' indicates ....
''' </summary>
Public Property Value() As String
Get
Return CType(GetValue(ValueProperty), Object)
End Get
Set(ByVal value As String)
SetValue(ValueProperty, value)
End Set
End Property
#End Region

Edit (insert):
You're using Silverlight 4, and i was either testing in Silverlight 5 or WPF from which both probably, butWPF certainly supports RelativeSourceBinding that way, however you had it almost right.The output window in Visual Studio gives you the following error if I'm correct.
System.Windows.Data Error: BindingExpression path error:
'Email' property not found on 'VisualBasicSilverlightApplication1.ucDbRow'
'VisualBasicSilverlightApplication1.ucDbRow' (HashCode=72766).
BindingExpression: Path='DataContext.Email'
DataItem='VisualBasicSilverlightApplication1.ucDbRow' (HashCode=72766);
target element is 'VisualBasicSilverlightApplication1.ucDbRow' (Name='');
target property is 'Value' (type 'System.String')..
The DataContext will fall through the usercontrol, except at the toplevel/instance of the usercontrol.
So instead of doing this:
<my:ucDbRow Title="Email" Value="{Binding Path=Email, Mode=TwoWay}" />
The only thing you will have to change in your code is point to the element where you set the DataContext on, which is most of the time the LayoutRoot:
(in ucUserEditor)
<my:ucDbRow Title="Email" Value="{Binding ElementName=LayoutRoot,
Path=DataContext.Email, Mode=TwoWay}" />
Previous answer
You are overriding the binded datacontext with this DataContext="{Binding RelativeSource={RelativeSource Self}}" in CH_App.ucDbRow. Remove it and the Value works because you've got your underlaying DataContext back, however Title is not working anymore.
There is a solution:
Change ucDbRow to this:
<UserControl x:Class="CH_App.ucDbRow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding}">
<StackPanel>
<TextBlock x:Name="txtTitle" Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=Title}" Height="23"/>
<TextBox x:Name="txtValue" Text="{Binding Path=Value, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</UserControl>
Note: Check your ouput window if Bindings don't work, if it failed it's in the output window.
In addition:
I would suggest not to use usercontrol's as control's. In my opinion usercontrols should more be used to represent individual context, not half a context on one and half a context on another. Start looking at usercontrols more as pages and use custom control's and datatemplate's for the detailed work. Also building a better layout is becominging very easy using a Grid which is (again in my opinion) one of the best features in WPF and Silverlight, can't be beaten by ordered usercontrols.

Related

Handling a UserControl's DependencyProperty Command

I am struggling with getting a WPF UserControl to update one of its DependencyProperty when a DependencyProperty Command is invoked.
Here's a an example that can hopefully demonstrate what I am trying to achieve. Basically it's a user control with a button on it. When the button is clicked, I'd like to increment an integer (MyValue) using a command (MyCommand):
User Control
<UserControl x:Class="UserControl1"
x:Name="root"
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:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="100"
d:DesignWidth="200">
<Button x:Name="MyButton"
Content="{Binding MyValue, ElementName=root}"
Command="{Binding MyCommand, ElementName=root}" />
</UserControl>
The Code-behind looks like this so far:
Imports System.ComponentModel
Public Class UserControl1
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("MyValue", GetType(Integer), GetType(UserControl1), New PropertyMetadata(1))
Public Shared ReadOnly CommandProperty As DependencyProperty = DependencyProperty.Register("MyCommand", GetType(ICommand), GetType(UserControl1))
Public Property Value() As Integer
Get
Return GetValue(ValueProperty)
End Get
Set(value As Integer)
SetValue(ValueProperty, value)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Value"))
End Set
End Property
Public Property Command() As ICommand
Get
Return CType(GetValue(CommandProperty), ICommand)
End Get
Set(value As ICommand)
SetValue(CommandProperty, value)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Command"))
End Set
End Property
End Class
Finally, I've added 5 instances of this control to a Window:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="800">
<Grid>
<StackPanel>
<local:UserControl1 Width="40"
Height="40" />
<local:UserControl1 Width="40"
Height="40" />
<local:UserControl1 Width="40"
Height="40" />
<local:UserControl1 Width="40"
Height="40" />
<local:UserControl1 Width="40"
Height="40" />
</StackPanel>
</Grid>
</Window>
What I would like to do is have each control increment its MyValue by 1 when the button is clicked. I've bound the Button's command to MyCommand to do so but I do not know where/how to add code to handle the Command invocation.
What I have tried so far
I can simply handle the Click event on the button:
Private Sub HandleButtonClick() Handles MyButton.Click
Value += 1
End Sub
This works fine but I would like to handle this through the MyCommand binding in an effort to limit code-behind to a minimum.
Another approach I have tried is to create a Command (not as DependencyProperty):
Public Shared Property DirectCommand As ICommand
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
DirectCommand = New RelayCommand(Sub() Value += 1)
End Sub
(RelayCommand class not shown - it's a standard implementation of a delegate command)
This last approach works but since the command is Shared it affects other instances of this user control. For example, if I have 5 instances, clicking 3rd instance will will increment the MyValue on the previous (2nd) instance in the XAML (but not other instances).
Any pointers would be greatly appreciated.
EDIT 1: Going further with non-DP Commands
Following #peter-duniho's advice, I continued down the path of using RelayCommands to handle the button click but I am having no luck getting the button to invoke a command that isn't marked as Shared:
Public Class UserControl1
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("MyValue", GetType(Integer), GetType(UserControl1), New PropertyMetadata(1))
Private _localValue As Integer = 2
Public Shared Property IncrementValueCommand As ICommand
Public Shared Property IncrementLocalValueCommand As ICommand
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
IncrementValueCommand = New RelayCommand(Sub() Value += 1)
IncrementLocalValueCommand = New RelayCommand(Sub() LocalValue += 1)
End Sub
Public Property Value() As Integer
Get
Return GetValue(ValueProperty)
End Get
Set(value As Integer)
SetValue(ValueProperty, value)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Value"))
End Set
End Property
Public Property LocalValue() As Integer
Get
Return _localValue
End Get
Set(value As Integer)
If _localValue <> value Then
_localValue = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("LocalValue"))
End If
End Set
End Property
End Class
I've added a LocalValue to try doing this with no DependencyProperties so I now have two buttons to test both side-by-side:
<UserControl x:Class="UserControl1"
x:Name="root"
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:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="100"
d:DesignWidth="200">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Button Grid.Row="0"
Background="DodgerBlue"
Content="{Binding Value, ElementName=root}"
Command="{Binding IncrementValueCommand, ElementName=root}" />
<Button Grid.Row="1"
Background="Gold"
Content="{Binding LocalValue, ElementName=root}"
Command="{Binding IncrementLocalValueCommand, ElementName=root}" />
</Grid>
</UserControl>
Using Shared commands, both values increment but the result is shown in the user control above the one clicked.
If I remove Shared in my declarations, the values don't update anymore:
Public Property IncrementValueCommand As ICommand
Public Property IncrementLocalValueCommand As ICommand
This is where I am stuck with this approach. If this can be explained to me I would be very grateful.
As far as creating a View Model to handle the User Control's logic, that would be great, I stayed away from that because, from what I have read, it's "code stink" so I was trying to stay away from that approach.
To elaborate a little on my goal: I am trying to make a Label user control that can display two Up/Down controls, one for small increments and one for larger increments. The Label will have many other features like:
Flash when data changes
Support "scrubbing" (hold and move mouse to increment/decrement value)
Have a Highlighted property that changes the label's background color.
The View Model approach seems to make perfect sense to contain all this logic.
Your last attempt is very close to a workable solution. It would have worked, had you simply not made the property a Shared property. Indeed, you could have even just assigned the RelayCommand instance to the existing MyCommand dependency property instead of creating a new property.
That said, it's not clear what you would gain from such an approach. The user control wouldn't wind up being general-purpose, and you could implement that approach with a much-simpler-to-implement event handler for the Button element's Click event. So, here are some other thoughts with respect to your question and the code contained within…
First, it is very unusual for a WPF dependency object to implement INotifyPropertyChanged, and even more unusual for it do so for its dependency properties. Should one decide to do so, instead of doing as you have here, by raising the event from the property setter itself, you must instead include a property-change callback when you register the dependency property, like so:
Public Shared ReadOnly CommandProperty As DependencyProperty =
DependencyProperty.Register("MyCommand", GetType(ICommand), GetType(UserControl1), New PropertyMetadata(AddressOf OnCommandPropertyChanged))
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub _RaisePropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Private Shared Sub OnCommandPropertyChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim userControl As UserControl1 = CType(d, UserControl1)
userControl._RaisePropertyChanged(e.Property.Name)
End Sub
The WPF binding system typically updates a dependency property value directly, without going through the property setter. In the code you posted, this means that the PropertyChanged event would not be raised with the property is updated via a binding. Instead, you need to do it as above, to make sure that any change to the property will result in the PropertyChanged event being raised.
That said, I'd advise not implementing INotifyPropertyChanged for dependency objects. The scenarios where one would make a dependency object are generally mutually exclusive with needing to implement INotifyPropertyChanged, because dependency objects are typically the target of a binding, while INotifyPropertyChanged is used for objects which are the source of a binding. The only component that needs to observe the change of a property value in the target of a binding is the WPF binding system, and it can do that without the dependency object implementing INotifyPropertyChanged.
Second, a more idiomatic way to implement something as you've intended to do here would be to have a separate view model object where the actual value and command would be stored, and bind that view model's properties to the dependency object's properties. In that case, one would have a view model object that looks something like this:
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Public Class UserControlViewModel
Implements INotifyPropertyChanged
Private _value As Integer
Public Property Value() As Integer
Get
Return _value
End Get
Set(value As Integer)
_UpdatePropertyField(_value, value)
End Set
End Property
Private _command As ICommand
Public Property Command() As ICommand
Get
Return _command
End Get
Set(value As ICommand)
_UpdatePropertyField(_command, value)
End Set
End Property
Public Sub New()
Command = New RelayCommand(Sub() Value += 1)
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub _UpdatePropertyField(Of T)(ByRef field As T, newValue As T, <CallerMemberName> Optional propertyName As String = Nothing)
If Not EqualityComparer(Of T).Default.Equals(field, newValue) Then
field = newValue
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End If
End Sub
End Class
(Note: this class includes an _UpdatePropertyField() method which handles the actual property change mechanism. Typically, one would actually put this method into a base class, so you can reuse that logic in any view model object one might write.)
In the example above, the view model sets its own Command property to the RelayCommand object. If this is the only intended scenario one wants to support, then one could just make the property read-only. With the implementation above, one also has the option of replacing the default ICommand value with some other ICommand object of choice (either a different RelayCommand or any other implementation of ICommand).
With this view model object defined, one can then give each user control its own view model as a data context, binding the view model's properties to the user control's dependency properties:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:TestSO58052597CommandProperty"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<l:UserControl1 Width="40" Height="40" MyValue="{Binding Value}" MyCommand="{Binding Command}">
<l:UserControl1.DataContext>
<l:UserControlViewModel Value="1"/>
</l:UserControl1.DataContext>
</l:UserControl1>
<l:UserControl1 Width="40" Height="40" MyValue="{Binding Value}" MyCommand="{Binding Command}">
<l:UserControl1.DataContext>
<l:UserControlViewModel Value="1"/>
</l:UserControl1.DataContext>
</l:UserControl1>
<l:UserControl1 Width="40" Height="40" MyValue="{Binding Value}" MyCommand="{Binding Command}">
<l:UserControl1.DataContext>
<l:UserControlViewModel Value="1"/>
</l:UserControl1.DataContext>
</l:UserControl1>
<l:UserControl1 Width="40" Height="40" MyValue="{Binding Value}" MyCommand="{Binding Command}">
<l:UserControl1.DataContext>
<l:UserControlViewModel Value="1"/>
</l:UserControl1.DataContext>
</l:UserControl1>
<l:UserControl1 Width="40" Height="40" MyValue="{Binding Value}" MyCommand="{Binding Command}">
<l:UserControl1.DataContext>
<l:UserControlViewModel Value="1"/>
</l:UserControl1.DataContext>
</l:UserControl1>
</StackPanel>
</Window>
Each user control object gets its own view model object, initialized the XAML as the DataContext property value. Then the {Binding Value} and {Binding Command} markup cause the view model properties to serve as the source for the dependency property targets for each user control object.
This is a little more idiomatic for WPF. However, it's actually still not really how one would typically go about doing this, because all the view models are hard-coded for the user control objects. When one has a collection of source objects, and wants to represent them visually, one would typically maintain a separation between data and UI through the use of templating and the ItemsControl UI element. For example:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:TestSO58052597CommandProperty"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<x:Array x:Key="data" Type="{x:Type l:UserControlViewModel}">
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
</x:Array>
</Window.Resources>
<ItemsControl ItemsSource="{StaticResource data}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type l:UserControlViewModel}">
<l:UserControl1 Width="40" Height="40" MyValue="{Binding Value}" MyCommand="{Binding Command}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Here, the StackPanel which was previously installed explicitly as an element in the window, is now used as the template for the panel in an ItemsControl element. The data itself is now stored separately. In this example, I've just used a simple array resource, but in a more realistic program this would often be a collection referenced by a top-level view model used as the data context for the window. Either way, the collection gets used as the ItemsSource property value in the ItemsControl.
(Note: for static collections as here, an array suffices. But the ObservableCollection<T> class is very commonly used in WPF, to provide a binding source for collections that may be modified during the execution of the program.)
The ItemsControl object then uses the data template provided in for the ItemTemplate property to visually present the view model object.
In this example, the data template is unique for that ItemsControl object. It might be desirable to provide a different data template elsewhere, either in a different ItemsControl, or when presenting the view model objects individually (e.g. via ContentControl). This approach works well for those kinds of scenarios.
But, it is also possible that one might have a standard visualization for the view model object. In that case, one can define a default template to use, placing that in a resource dictionary somewhere, so that WPF can automatically find it in any context where one might be using the view model object as the data context. Then, no template needs to be specified explicitly in the UI elements where that's the case.
That would look something like this:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:TestSO58052597CommandProperty"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<x:Array x:Key="data" Type="{x:Type l:UserControlViewModel}">
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
</x:Array>
<DataTemplate DataType="{x:Type l:UserControlViewModel}">
<l:UserControl1 Width="40" Height="40" MyValue="{Binding Value}" MyCommand="{Binding Command}"/>
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{StaticResource data}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Window>
This just barely scratches the surface on topics in WPF like dependency properties, data binding, templating, etc. Some key points in my view to keep in mind are:
Dependency objects are generally the target of bindings
Data should be independent of visualization
Don't repeat yourself.
That last one is a crucial point in all programming, and is at the heart of OOP, and even simpler scenarios where you can make reusable data structures and functions. But in frameworks like WPF, there is a whole new range of dimensions in which there's the opportunity for reusing your code. If you find yourself copy/pasting anything related to your program, you're probably violating this very important principle. :)

How to bind to properties of a UserControl inside another UserControl?

I have a simple UserControl that displays an icon and text:
<UserControl x:Class="IconLabel"
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="26" d:DesignWidth="200" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Image x:Name="imgIcon" Source="{Binding Path=IconPath}" Stretch="UniformToFill" Width="26" Height="26" Margin="3,0" />
<Label Content="{Binding Path=LabelText}" Margin="5,0" Grid.Column="1" />
</Grid>
</UserControl>
The code-behind defines two DependencyProperties that are meant to be bound from the outside:
Public Class IconLabel
Public Property IconPath As String
Get
Return GetValue(IconPathProperty)
End Get
Set(ByVal value As String)
SetValue(IconPathProperty, value)
End Set
End Property
Public Shared ReadOnly IconPathProperty As DependencyProperty = DependencyProperty.Register("IconPath", GetType(String), GetType(IconLabel), New PropertyMetadata(""))
Public Property LabelText As String
Get
Return GetValue(LabelTextProperty)
End Get
Set(ByVal value As String)
SetValue(LabelTextProperty, value)
End Set
End Property
Public Shared ReadOnly LabelTextProperty As DependencyProperty = DependencyProperty.Register("LabelText", GetType(String), GetType(IconLabel), New PropertyMetadata("LabelText"))
End Class
That's working fine so far. I can set its properties in XAML and they are getting used properly:
<local:IconLabel LabelText="Test"/>
However, I'd now like to re-use this control in another UserControl that slightly expands its functionality by showing a progress bar next to it (I've kept this short for the sake of the example):
<UserControl x:Class="IconLabelProgress"
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:myApp"
mc:Ignorable="d"
d:DesignHeight="26" d:DesignWidth="600" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*" MaxWidth="300"/>
<ColumnDefinition Width="6*"/>
</Grid.ColumnDefinitions>
<local:IconLabel IconPath="{Binding Path=IconPath}" LabelText="{Binding Path=PropName}" />
<ProgressBar Value="{Binding Path=ActualValue}" Minimum="0" Maximum="10" Margin="5" Height="16" VerticalAlignment="Top" Grid.Column="1" />
</Grid>
</UserControl>
with the following code-behind:
Public Class IconLabelProgress
'These are just meant to be passed along to the IconLabel
Public Property IconPath As String
Get
Return GetValue(IconPathProperty)
End Get
Set(ByVal value As String)
SetValue(IconPathProperty, value)
End Set
End Property
Public Shared ReadOnly IconPathProperty As DependencyProperty = DependencyProperty.Register("IconPath", GetType(String), GetType(IconLabelProgress), New PropertyMetadata(""))
Public Property PropName As String
Get
Return GetValue(PropNameProperty)
End Get
Set(ByVal value As String)
SetValue(PropNameProperty, value)
End Set
End Property
Public Shared ReadOnly PropNameProperty As DependencyProperty = DependencyProperty.Register("PropName", GetType(String), GetType(IconLabelProgress), New PropertyMetadata("PropName"))
'This one is new
Public Property ActualValue As Double
Get
Return GetValue(ActualValueProperty)
End Get
Set(ByVal value As Double)
SetValue(ActualValueProperty, value)
End Set
End Property
Public Shared ReadOnly ActualValueProperty As DependencyProperty = DependencyProperty.Register("ActualValue", GetType(Double), GetType(IconLabelProgress), New PropertyMetadata(0.0))
End Class
If I now try to instantiate this control and pass in a value for the label of the inner IconLabel control, like this:
<local:IconLabelProgress x:Name="ilp1" PropName="Test" ActualValue="5.0" />
then it won't show "Test" on its label and instead fall back to its default that was specified via PropertyMetadata("LabelText"). The ActualValue is used correctly, though.
How can I make the outer control pass the value to the nested one?
As a general rule, never explicitly set the DataContext property of a UserControl as you do with
<UserControl x:Class="IconLabel" ...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
Doing so effectively prevents inheriting a DataContext from the UserControl's parent, e.g. here
<local:IconLabel LabelText="{Binding Path=PropName}" ... />
where PropName is expected to be a property in the parent DataContext.
Instead of explicitly setting a UserControl's DataContext, write its "internal" Bindings with a RelativeSource like
<Label Content="{Binding Path=LabelText,
RelativeSource={RelativeSource AncestorType=UserControl}}" ... />
By default (and you didn't specify anything else) the binding is resolved from the objects DataContext. So your IconLabel searches a property with the name IconPath on its DataContext.
To specify that the place to search for the property is the outer control, you can add ElementName to the binding and set a name property on the IconLabelProgress or you specify a RelativeSource like in the second example of the accepted answer in How do I use WPF bindings with RelativeSource.
Hope it helps.

Silverlight How to create a usercontrol whose properties are customizable

I'm sure this is a real beginner question; I'm just having trouble figuring out how to search for it.
I have a simple UserControl (MyNewControl) that only has three controls, one of which is the following label:
<sdk:Label x:Name="Title" />
In another control, then, I want to use MyNewControl, like this:
<local:MyNewControl Grid.Column="1" x:Name="MyNewGuy" />
What do I need to do so that this second control can, for example, set a gradient background for my Title label?
First you define the desired dependency property in your UserControl:
public partial class MyUserControl : UserControl
{
public Brush LabelBackground
{
get { return (Brush)GetValue(LabelBackgroundProperty); }
set { SetValue(LabelBackgroundProperty, value); }
}
public static readonly DependencyProperty LabelBackgroundProperty =
DependencyProperty.Register("LabelBackground", typeof(Brush), typeof(MyUserControl), new PropertyMetadata(null));
public MyUserControl()
{
InitializeComponent();
}
}
To assign the value of your property to the child label, you can bind using the ElementName property of the binding:
<UserControl x:Class="SilverlightApplication1.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
d:DesignHeight="300"
d:DesignWidth="400"
mc:Ignorable="d"
x:Name="UserControl"
>
<Grid x:Name="LayoutRoot">
<sdk:Label x:Name="Title"
HorizontalAlignment="Center"
VerticalAlignment="Center" Content="Title" Background="{Binding LabelBackground, ElementName=UserControl}" />
</Grid>
</UserControl>
As you are using Silverlight 5, you can also set a RelativeSource to your binding, instead of internally naming your UserControl:
<sdk:Label Background="{Binding LabelBackground, RelativeSource={RelativeSource AncestorType=UserControl}}" />
Then, when using your UserControl, you just set (or bind) the LabelBackground to the desired value:
<local:MyUserControl LabelBackground="Red"/>
Just to note, you can also create a CustomControl instead of a UserControl, add the dependency property to it the same way and use a TemplateBinding when defining its template.
You can do that using dependency property in your custom control . Say you defined LableBG as an dependency property in your custom control and do the binding with an Background of your defined Label control in xaml . And when you use your custom control in another control you can set the LableBG of it from xaml or else from code behind.
Note : the type of your defined dependency property should be of Brush
For eg :
Defining Dependency Property in cs file of your custom control :
/1. Declare the dependency property as static, readonly field in your class.
public static readonly DependencyProperty LableBGProperty = DependencyProperty.Register(
"LableBG", //Property name
typeof(Brush), //Property type
typeof(MySilverlightControl), //Type of the dependency property provider
null );//Callback invoked on property value has changes
<sdk:Label x:Name="Title" Background="{Binding LableBG }" /> (Custom Control)
<local:MyNewControl Grid.Column="1" x:Name="MyNewGuy" LableBG="Red" /> (Another control)

Using DataContext inside custom control

I have a custom control with a ContentTemplate to display child controls. The data context isn't passing through my DataTemplate and so when I bind my child control, I'm not able to retrieve that value. I'm pretty sure that I'm not implementing this correctly specifically with respect to the DataTemplate, so I would appreciate any help. I've broken the problem down into as small a scenario as I can.
First, the Page:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ResourceDictionary>
<local:MainWindowViewModel x:Key="ViewModel" />
</ResourceDictionary>
</Window.Resources>
<Grid DataContext="{StaticResource ViewModel}">
<local:MyControl>
<local:MyControl.MainContent>
<DataTemplate>
<TextBlock Text="{Binding TextValue}" />
</DataTemplate>
</local:MyControl.MainContent>
</local:MyControl>
</Grid>
Next, the ViewModel:
Public Class MainWindowViewModel
Implements INotifyPropertyChanged
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Private _textValue As String
Public Property TextValue() As String
Get
If String.IsNullOrEmpty(_textValue) Then
_textValue = "A default value"
End If
Return _textValue
End Get
Set(ByVal value As String)
_textValue = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("TextValue"))
End Set
End Property
End Class
And now my custom control code-behind:
Public Class MyControl
Inherits System.Windows.Controls.Control
Shared Sub New()
DefaultStyleKeyProperty.OverrideMetadata(GetType(MyControl), New FrameworkPropertyMetadata(GetType(MyControl)))
End Sub
Public Property MainContent As DataTemplate
Get
Return GetValue(MainContentProperty)
End Get
Set(ByVal value As DataTemplate)
SetValue(MainContentProperty, value)
End Set
End Property
Public Shared ReadOnly MainContentProperty As DependencyProperty = _
DependencyProperty.Register("MainContent", _
GetType(DataTemplate), GetType(MyControl), _
New FrameworkPropertyMetadata(Nothing))
End Class
And finally, my custom control definition:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1">
<Style TargetType="{x:Type local:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyControl}">
<StackPanel>
<TextBlock Text="Hello, World!" />
<ContentControl x:Name="MainContentArea" ContentTemplate="{TemplateBinding MainContent}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
When this is run, the value from the ViewModel (TextValue) is not bound.
ContentControl is a little special in regards to DataContext. The DataContext within the DataTemplate is the Content of the ContentControl and not its DataContext. I didn't try your code but after a quick look I have a feelings that's your problem
You could try to bind the Content of the ContentControl to its DataContext with
<ContentControl x:Name="MainContentArea" Content="{Binding}" ...

DependencyProperty in UserControl doesn't get updated from binding

Here is the reproduction of my problem:
Create a WPF Application
Add a new UserControl to the project
Replace its content with the following
<UserControl
x:Class="UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding MyText}"/>
</UserControl>
Public Class UserControl1
Public Property MyText As String
Get
Return GetValue(MyTextProperty)
End Get
Set(ByVal value As String)
SetValue(MyTextProperty, value)
End Set
End Property
Public Shared ReadOnly MyTextProperty As DependencyProperty =
DependencyProperty.Register("MyText", GetType(String), GetType(UserControl1))
End Class
Replace the following in the MainWindow.xaml file:
<Window
x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:WpfApplication1"
Title="MainWindow"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel >
<TextBox Text="{Binding Title, UpdateSourceTrigger=PropertyChanged}"/>
<src:UserControl1 MyText="{Binding Title}"/>
</StackPanel>
</Window>
As you can see, the UserControl1.MyText property doesn't get updated when the MainWindow.Title changes.
What am I missing? I want the user control to be connected to the parent property, is there a xamly way to do it?
Okay, when I recreate the app (I'm using C#, so I don't think this will make any difference), I notice in my output the binding is failing because UserControl does not have a Title property.
Add ElementName=this to your UserControl1 binding. And set the Name property on the Window to this and that should fix it.
Something like this:
<src:UserControl1 MyText="{Binding Title, ElementName=this}" />
The binding works for me when I do that. Hope that helps!

Resources