I'm trying to get some WPF concepts down, so I've put together a simple example of what I'm trying to do. I would like to set a custom property of a user control, and have it be used by an element within the control.
I've been researching and experimenting, but I'm not fully understanding everything here. Any help would be appreciated.
The user control for this example is a simple square with a circle inside of it:
<UserControl x:Class="CircleInSquare"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="100" Height="100" >
<Grid Background="#000000">
<Ellipse Name="Circle"
Margin="10"
Fill="?????????"
>
</Ellipse>
</Grid>
</UserControl>
The VB Code Behind it:
Partial Public Class CircleInSquare
Private _CircleColor As Color
Public Property CircleColor() As Color
Get
Return _CircleColor
End Get
Set(ByVal value As Color)
_CircleColor = value
End Set
End Property
End Class
When I use this user control, how can I apply a CircleColor to the control, and have it be shown as the Ellipse's fill color? Even better... can I give it a default color that shows up in the VS2008 Designer?
So... if I place one of these into my window XAML like this:
<app:CircleInSquare CircleColor="Blue" />
I would like the circle to display as Blue (or any other color I choose for that instance)
Sorry to repost, but After re-reading you post, I think that you might be better off with templating. I've attached some samples in VB
Window.xaml
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:WpfApplicationVB1"
Title="Window1" Height="300" Width="300">
<Grid>
<app:CircleInSquare Height="50" Width="50" CircleColor="Blue" SquareColor="Red" />
</Grid>
</Window>
CircleInSquare.xaml.vb
Partial Public Class CircleInSquare
Public Property CircleColor() As Brush
Get
Return GetValue(CircleColorProperty)
End Get
Set(ByVal value As Brush)
SetValue(CircleColorProperty, value)
End Set
End Property
Public Shared ReadOnly CircleColorProperty As DependencyProperty = _
DependencyProperty.Register("CircleColor", _
GetType(Brush), GetType(CircleInSquare), _
New FrameworkPropertyMetadata(Brushes.Black))
Public Property SquareColor() As Brush
Get
Return GetValue(SquareColorProperty)
End Get
Set(ByVal value As Brush)
SetValue(SquareColorProperty, value)
End Set
End Property
Public Shared ReadOnly SquareColorProperty As DependencyProperty = _
DependencyProperty.Register("SquareColor", _
GetType(Brush), GetType(CircleInSquare), _
New FrameworkPropertyMetadata(Brushes.Gray))
End Class
CircleInSquare.xaml
<UserControl x:Class="CircleInSquare"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:app="clr-namespace:WpfApplicationVB1"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Template>
<ControlTemplate>
<Border x:Name="PART_Square" Height="{TemplateBinding Height}" Width="{TemplateBinding Width}" Background="{TemplateBinding app:CircleInSquare.SquareColor}">
<Ellipse x:Name="PART_Ellipse" Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}" Fill="{TemplateBinding app:CircleInSquare.CircleColor}" />
</Border>
</ControlTemplate>
</UserControl.Template>
</UserControl>
Set the DataContext for the ellipse to an instance of your CircleInSquare class. And make sure you use implement INotifyProperychanged on this class to make it property change enabled. check this link if you need more info on Propertychange
<Ellipse Name="Circle"
Margin="10"
Fill="{Binding Path= CircleColor}"
>
</Ellipse>
You set up a dependency property like this:
Public Shared ReadOnly MouseOverBrushProperty As DependencyProperty = DependencyProperty.Register("MouseOverBrush", GetType(Brush), GetType(BrushableComboBox), New UIPropertyMetadata())
Public Property MouseOverBrush() As Brush
Get
Return CType(GetValue(MouseOverBrushProperty), Brush)
End Get
Set(ByVal value As Brush)
SetValue(MouseOverBrushProperty, value)
End Set
End Property
And then in your xaml you do something like this
Background="{TemplateBinding MouseOverBrush}"
and you can set a default style outside of the control template like this:
<Style TargetType="{x:Type local:BrushableComboBox}">
<Setter Property="MouseOverBrush" Value="Blue" />
...
You might also be able to do it with a normal property but dependency properties support binding which makes styling much easier later.
HTH
You need to create a DependencyProperty in your CircleInSquare class. Do some googling on that. The concept of using the property for your circle class like below is called AttachedProperties, and you will probably need to handle the CircleColorChanged event to do what you need.
<Ellipse app:CircleInSquare.CircleColor="Blue" />
Related
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. :)
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.
I have a resource that need to be a different color depending on where it is used, so I use this attached property:
public static class AssetProperties
{
public static Brush GetFillBrush(DependencyObject obj)
{
return (Brush)obj.GetValue(FillBrushProperty);
}
public static void SetFillBrush(DependencyObject obj, Brush value)
{
obj.SetValue(FillBrushProperty, value);
}
public static readonly DependencyProperty FillBrushProperty =
DependencyProperty.RegisterAttached("FillBrush",
typeof(Brush),
typeof(AssetProperties),
new FrameworkPropertyMetadata(new BrushConverter().ConvertFrom("#FFE41300"), FrameworkPropertyMetadataOptions.Inherits));
}
We define the symbol and use it something like this in a window or user control (this is of course a lot simplified, the resource is for example defined in a separate file) :
<Grid>
<Grid.Resources>
<ResourceDictionary>
<Rectangle x:Key="SomeColorfulSymbol" x:Shared="False" Width="10" Height="10"
Fill="{Binding (main:AssetProperties.FillBrush), RelativeSource={RelativeSource Self}}" />
</ResourceDictionary>
</Grid.Resources>
<ContentControl Content="{StaticResource SomeColorfulSymbol}" main:AssetProperties.FillBrush="Blue"/>
</Grid>
This works as intended, a nice blue rectangle appears. Without setting the attached property, the rectangle is the default red of the FillBrush attached property.
The problem is when we try to use the symbol inside a custom user control defined like this:
OuterControl.xaml:
<UserControl x:Class="AttachedPropertyResourceTest.OuterControl"
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="300" d:DesignWidth="300">
<Grid>
<StackPanel>
<TextBlock Text="Some title"/>
<ContentControl Content="{Binding InnerContent, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}"/>
</StackPanel>
</Grid>
</UserControl>
OuterControl.xaml.cs:
[ContentProperty("InnerContent")]
public partial class OuterControl
{
public FrameworkElement InnerContent
{
get { return (FrameworkElement)GetValue(InnerContentProperty); }
set { SetValue(InnerContentProperty, value); }
}
public static readonly DependencyProperty InnerContentProperty =
DependencyProperty.Register("InnerContent", typeof(FrameworkElement), typeof(OuterControl), new FrameworkPropertyMetadata(null));
public OuterControl()
{
InitializeComponent();
}
}
Now if I wrap the ContentControl in the above snippet like this instead:
<main:OuterControl>
<ContentControl Content="{StaticResource SomeColorfulSymbol}"/>
</main:OuterControl>
it looks good in the VS designer, a title plus a rectangle that is the default red of FillBrush. In runtime however we only get the title. The rectangle gets no color (UnsetValue) and we get this binding error:
System.Windows.Data Error: 40 : BindingExpression path error:
'(main:AssetProperties.FillBrush)' property not found on 'object'
''Rectangle' (Name='')'.
BindingExpression:Path=(main:AssetProperties.FillBrush);
DataItem='Rectangle' (Name=''); target element is 'Rectangle'
(Name=''); target property is 'Fill' (type 'Brush')
If I add an invisible instance of the symbol before the wrapped one, it works again, i.e., a red rectangle appears:
<ContentControl Content="{StaticResource SomeColorfulSymbol}" Visibility="Collapsed"/>
<main:OuterControl>
<ContentControl Content="{StaticResource SomeColorfulSymbol}"/>
</main:OuterControl>
One problem is that the attached property is not registered, when I put a breakpoint on the RegisterAttached method it is not called without the extra invisible ContentControl. This is however only a part of the problem, for example forcing the registration like this does not work:
<StackPanel>
<TextBlock Text="I'm red!" Background="{Binding (main:AssetProperties.FillBrush), RelativeSource={RelativeSource Self}}"/>
<main:OuterControl>
<ContentControl Content="{StaticResource SomeColorfulSymbol}"/>
</main:OuterControl>
</StackPanel>
The text "I'm red" is actually red and the attached property is registered, but we get the exact same binding error.
I also tried without the ContentProperty["InnerContent"], setting the InnerContent attribute explicitly in xaml, with the same result.
Could someone shed some light on this?
Maybe using a control template instead of OuterControl wouldn't have this problem (?), but there is a lot of behavior associated with OuterControl and I would prefer this approach.
To prevent the following issue, try specifying the path property explicitly like:{Binding Path=(main:....}
<Rectangle x:Key="SomeColorfulSymbol" x:Shared="False" Width="10" Height="10" Fill="{Binding Path=(main:AssetProperties.FillBrush), RelativeSource={RelativeSource Self}}" />
I'm creating a simple User Control combining a popup with a text view, nothing crazy. When I set it up in a window at first to style it all out, it worked perfectly, but when I moved it into a User Control to actually finish it up, it won't work correctly any more.
I pass a min and max value into the control and then it automatically creates a list of numbers to pick from in that range. In the User Control, the list of numbers doesn't get bound correctly, who knows why. Maybe someone can take a look at my code.
I've read a bunch of other questions about this, but don't really know what's happening. I'm not getting any errors in my output window, so no clues there. Anyway, here's the code -
UserControl.xaml
<UserControl x:Class="UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Me"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<TextBox Name="myTextbox"
Height="30"
Margin="0"
FontSize="14"
IsReadOnly="True"
Padding="5,2"
Text="{Binding Value}" />
<Popup x:Name="myPopup"
PlacementTarget="{Binding ElementName=myTextbox}"
StaysOpen="True">
<Popup.Style>
<Style TargetType="{x:Type Popup}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myTextbox, Path=IsFocused}" Value="True">
<Setter Property="IsOpen" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</Popup.Style>
<StackPanel>
<ListView Name="myListView"
Height="100"
MaxHeight="300"
ItemsSource="{Binding List,
UpdateSourceTrigger=PropertyChanged}"
SelectionChanged="ListView_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<Label Width="100"
Height="30"
Margin="0"
Content="{Binding}"
FontFamily="Segoe UI"
FontSize="14"
Padding="5,2" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Width="200" Height="30" />
</StackPanel>
</Popup>
</StackPanel>
UserControl.xaml.vb
Imports System.ComponentModel
Imports System.Linq.Expressions
Imports System.Collections.ObjectModel
Class UserControl1
' Dependency Properties
Public Shared ReadOnly ListProperty As DependencyProperty = DependencyProperty.Register("List", GetType(ObservableCollection(Of Integer)), GetType(MainWindow))
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Integer), GetType(MainWindow))
Public Shared ReadOnly MaxValueProperty As DependencyProperty = DependencyProperty.Register("MaxValue", GetType(Integer), GetType(MainWindow))
Public Shared ReadOnly MinValueProperty As DependencyProperty = DependencyProperty.Register("MinValue", GetType(Integer), GetType(MainWindow))
' Properties
Public Property List As ObservableCollection(Of Integer)
Get
Return DirectCast(GetValue(ListProperty), ObservableCollection(Of Integer))
End Get
Set(value As ObservableCollection(Of Integer))
SetValue(ListProperty, value)
End Set
End Property
Public Property Value As Integer
Get
Return DirectCast(GetValue(ValueProperty), Integer)
End Get
Set(value As Integer)
SetValue(ValueProperty, value)
End Set
End Property
Public Property MaxValue As Integer
Get
Return DirectCast(GetValue(MaxValueProperty), Integer)
End Get
Set(value As Integer)
SetValue(MaxValueProperty, value)
End Set
End Property
Public Property MinValue As Integer
Get
Return DirectCast(GetValue(MinValueProperty), Integer)
End Get
Set(value As Integer)
SetValue(MinValueProperty, value)
End Set
End Property
Private Sub ListView_SelectionChanged(sender As System.Object, e As System.Windows.Controls.SelectionChangedEventArgs)
Value = List(myListView.SelectedIndex)
End Sub
Private Sub UserControl1_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
List = New ObservableCollection(Of Integer)
' Add all available numbers into the list
For iCounter As Integer = MinValue To MaxValue
List.Add(iCounter)
Next
' Set the selected index on the list for the value
myListView.SelectedIndex = Value - MinValue
End Sub
End Class
Just for reference, when I tested this out, I set the Min and Max values myself in both the window and usercontrol setup.
I think that you have made the same mistake that I used to when I first started learning WPF. I can't guarantee that this is the cause of your problem, but as it's the only one that I can see, I'll address it. Unfortunately, there are so many poor tutorials and quick examples that show the connecting of a UserControl.DataContext to itself:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Or:
DataContext = this;
Now this is perfectly acceptable if you don't want to Bind to the UserControl externally because it's a quick and easy way to connect with properties defined in the code behind. However, when you want to Bind to the properties from outside the control, you'll find problems. In these instances (if not on all occasions), you should use a RelativeSource Binding to Bind to your code behind properties:
<TextBox Name="myTextbox" Height="30" Margin="0" FontSize="14" IsReadOnly="True"
Padding="5,2" Text="{Binding Value, RelativeSource={RelativeSource AncestorType={
x:Type UserControl1}}}" />
In this Binding, UserControl1 is the name of the UserControl that declared the properties. This should be done on all of the internal Bindings. This way, the DataContext is not set to the UserControl, but the Bindings still find the properties.
You can find out more about RelativeSource from the RelativeSource MarkupExtension page on MSDN.
As I cant provide a comment to Sheridan good answer I have to provide a new answer, sorry for this.
While I love this solution
DataContext="{Binding RelativeSource={RelativeSource Self}}"
it fails (as Sheridan pointed out already) fast.
What you can do is just set the DataContext of the content of your User Control
<UserControl x:Class="Example.View.Controls.MyUserControl"
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:controls="clr-namespace:Example.View.Controls"
mc:Ignorable="d">
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:MyUserControl}}}">
</Grid>
In this way all following Bindings have less Boilerplate code as you can just bind directly to your DP from the code-behind like:
<Label Content="{Binding MyLabel}"/>
As for Windows Apps (Windows 8 and Windows 10 UWP) the way to go is to give your control a name and reference it within your XAML file using Path and ElementName:
<UserControl
x:Class="MyControl"
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"
x:Name="Control"
mc:Ignorable="d" >
<Grid Height="240" VerticalAlignment="Top">
<Rectangle Fill="{Binding ElementName=Control, Path=Background}" />
</Grid>
</UserControl>
``
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}" ...