WPF Dependency Property - set from XAML - wpf

I am trying to achieve the following in a WPF personal finance app:
In various places I want to display a user control giving details of a asset holding (usually a share, bond etc), the target asset may be changed dynamically by the user in which case the control must be refreshed. Each Asset has a unique identifier, AssetId.
I am using MVVM and I've developed a single window with a View Model that takes AssetID as a parameter (property) and retrieves the relevant details for binding to the View. This work fine. What I'd like to do is make a generic user control with the same functionality so I can basically drop that 'window' inside other windows.
So I pretty much copy-pasted the XAML from that form into a User Control, where I'm struggling is passing in the AssetId from the parent window to the child control.
Google tells me I need a dependency property and here's where I am
Public Class HoldingView
Private _AssetId As Integer
Public AssetIdProperty As DependencyProperty = DependencyProperty.Register("AssetId",
GetType(Integer),
GetType(HoldingView),
New FrameworkPropertyMetadata(New PropertyChangedCallback(AddressOf AssetIDChanged)))
Public Property AssetId As Integer
Get
Return GetValue(AssetIdProperty)
End Get
Set(value As Integer)
SetValue(AssetIdProperty, value)
End Set
End Property
Private Sub AssetIDChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim NewAssetId As Integer
NewAssetId = e.NewValue
Me.DataContext.AssetId = NewAssetId
End Sub
Public Sub New()
' This call is required by the designer.
InitializeComponent()
Me.DataContext = New HoldingViewmodel
End Sub
End Class
Called like this:
<Grid>
<local:HoldingView AssetId="{Binding AssetId}"/>
</Grid>
The code compiles and runs but when I try and load the window that has the user control, the app crashes with this message:
A 'Binding' cannot be set on the 'AssetId' property of type 'HoldingView'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
Which is not that helpful. From my Googling, you can also get this message if the syntax of the DP registration is not spot on, but it looks Ok to my inexperienced eye...
Anybody else had this?

Public AssetIdProperty As DependencyProperty
should be
Public Shared ReadOnly AssetIdProperty As DependencyProperty
Please take a look at Custom Dependency Properties.
Also remove
Me.DataContext = New HoldingViewmodel
because that will effectively break any DataContext-based Bindings like
AssetId="{Binding AssetId}"
where the source property is supposed to be owned by the object in the inherited DataContext, which usually is an object in the application's view model.
Controls should never have their own, "private" view model, but instead handle property changes in code behind. In case of UserControls, there could simply be UI elements in their XAML that would be bound to the UserConrol's own properties.
Hence
Me.DataContext.AssetId = NewAssetId
in the PropertyChangedCallback is pointless and should be removed, as well as
Private _AssetId As Integer
To summarize, it should look like this:
Public Class HoldingView
Public Shared ReadOnly AssetIdProperty As DependencyProperty =
DependencyProperty.Register(
"AssetId",
GetType(Integer),
GetType(HoldingView),
New FrameworkPropertyMetadata(
New PropertyChangedCallback(AddressOf AssetIdPropertyChanged)))
Public Property AssetId As Integer
Get
Return GetValue(AssetIdProperty)
End Get
Set(value As Integer)
SetValue(AssetIdProperty, value)
End Set
End Property
Private Shared Sub AssetIdPropertyChanged(
d As DependencyObject, e As DependencyPropertyChangedEventArgs)
CType(d, HoldingView).AssetIdChanged(e.NewValue)
End Sub
Private Sub AssetIdChanged(id As Integer)
...
End Sub
Public Sub New()
InitializeComponent()
End Sub
End Class

Related

Getting Depedenccy Property Error in XAML only

I am adding an extended property to an existing user control, it works fine at design time and runtime with no errors. I do however get an error showing in design mode in the XAML which is:
A 'Binding' cannot be set on the 'SfdName' property of type 'ExtendedSfMaskedEdit'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
How do I clear this please?
XAML below:
<local:ExtendedSfMaskedEdit SfdName="{Binding TestTag}" />
Code below:
Public SfdNameProperty As DependencyProperty = DependencyProperty.Register("SfdName", GetType(String), GetType(ExtendedSfMaskedEdit), New UIPropertyMetadata("", AddressOf SfdNameChanged))
Private Sub SfdNameChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
End Sub
<Bindable(True)>
Public Property SfdName As String
Get
Return DirectCast(GetValue(SfdNameProperty), String)
End Get
Set
SetValue(SfdNameProperty, Value)
End Set
End Property

vb.net programmatically added binding not working

I am trying to bind a variable value to a button's content property. i created a button named "button" inside a dockpanel of my main window in XAML.
<Button x:Name="button" Content="Button" Height="100"
VerticalAlignment="Top" Width="75"/>
Then I want to add a binding to a public variable test programmatically.
The initial value (400) is displayed correctly at runtime, but when I hit the "NextTurn" button to raise the Click event, the bound value isn't updated.
Imports System.Windows.Data
Class MainWindow
Public test As Integer
Public Sub New()
InitializeComponent()
Dim usr As New UserNS.User
mainUser = usr
test = 400
Dim btest As New Binding()
btest.Source = test
button.SetBinding(Button.ContentProperty, btest)
End Sub
Private Sub NextTurn_Click(sender As Object, e As RoutedEventArgs) Handles NextTurn.Click
test = test - 10
End Sub
End Class
Could you please help me?
Thank you very much!
First of all, fields cannot be bound, only properties.
The binding source should be an object which has the property you would like to bind.
Ideally it is not the form class itself but a separate class (aka. view model).
E.g. the main window (named MainWindow) can have a view model named MainViewModel.
This object must implement the INotifyPropertyChanged interface.
In the property setter you have to call a method which raises the PropertyChanged event that comes with INotifyPropertyChanged interface.
In my example it is:
Private Sub NotifyPropertyChanged(...)
IMPORTANT: VB.NET works in case-insensitive mode so avoid naming a Button control as button. Also if you implement a full property the backing field should have a different name. You cannot have a test field and a Test property at the same time. That's why I chose the _Test name for the field.
Here is a working example:
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Class MainWindow
Implements INotifyPropertyChanged
Public Sub New()
' Actually we can initialize the Test property here as well.
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Test = 400
Dim bindingTest As New Binding() With {
.Source = Me, ' The object which has the property we want to bind.
.Mode = BindingMode.OneWay, ' We declare that the UI will accept changes from the object's property but not vica-versa.
.Path = New PropertyPath("Test") 'We have to pass the name of the property as a String value.
}
TestButton.SetBinding(Button.ContentProperty, bindingTest)
' We could also initialize the Test property here.
End Sub
' We can also initialize only the field instead of the property
' But new values must be set through the property setter.
Private _Test As Integer
Public Property Test() As Integer
Get
Return _Test
End Get
Set(ByVal value As Integer)
_Test = value
NotifyPropertyChanged()
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
' We use CallerMemberName attribute so that we do not need to pass the name of the property.
' The compiler will automatically pass the name of the caller property. In our case: "Test"
' To get it work we declare the parameter as Optional so that we really do not have to pass a parameter value.
Private Sub NotifyPropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = "")
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Private Sub NextTurnButton_Click(sender As Object, e As RoutedEventArgs)
' You must set the property's value instead of the backing field!
Test = Test - 10
End Sub
End Class

Accessing the class object from the Dependancy Proprty Changed Callback

I am having trouble accessing a lable in my custom WPF control. I have implemented the PropertyChangedCallback but from there I can't get access to the class.
The class is a simple control with a lable (Name="lblDataName") that I want to change the text to match the DataName property.
Here is the code behind:
Public Class DataNameControl
Public Property DataName As String
Get
Return GetValue(DataNameProperty)
End Get
Set(ByVal value As String)
SetValue(DataNameProperty, value)
End Set
End Property
Public Shared ReadOnly DataNameProperty As DependencyProperty = _
DependencyProperty.Register("DataName", _
GetType(String), GetType(GraphData), _
New PropertyMetadata("KPI", AddressOf OnDataNameChanged))
Public Shared Function OnDataNameChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs) As String
Dim dnc As DataNameControl = CType(d, DataNameControl)
dnc.lblDataName.Text = e.NewValue.ToString
'' I want to access the lable on my class right here.
Return e.NewValue.ToString
End Function
End class
I would hope that setting the property in XAML would update the label accordingly, but nada!
<Controls:DataNameControl DataName="BCWP" Margin="0"/>
There are tons of answer on how to change the property itself, but none seem to answer this specifically, and if been tinkering for 20 hours on this one issue. Time ask for help!
The third parameter of the Register method is wrong. It must be GetType(DataNameControl) instead of GetType(GraphData):
Public Shared ReadOnly DataNameProperty As DependencyProperty = _
DependencyProperty.Register( _
"DataName", GetType(String), GetType(DataNameControl), _
New PropertyMetadata("KPI", AddressOf OnDataNameChanged))
The PropertyChangedCallback should not return a value:
Public Shared Sub OnDataNameChanged( _
ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim dnc As DataNameControl = CType(d, DataNameControl)
...
End Sub
Newbies (like me),
If you are reading this looking for a solution to your problem, the primary lesson here is, "make sure you are passing the proper parameters to your property registration." the VS widget will sort of help you through the process but only the first time, after that, if you change the name of your class, VS won't automagically change the class in the registration - that was the source of my problem.
SO a generic example based on the widget code added by VS2013 :
Public Shared ReadOnly Prop1Property As DependencyProperty = _
DependencyProperty.Register("Prop1", _
GetType(String), GetType(Window1), _
New PropertyMetadata(Nothing))
Prop1Property is your property name plus the suffix property.
"Prop1" is that property name.
1st GetType(String) is the variable type is the type of YOUR property. VS enters string as a default.
2nd GetType(Window1) is simply the class in which your property exists. VS enters "Window1" as a default. This is likely never correct and its not linked to anything else in the widget so you will have to change it yourself. I say simply, but this is where I messed up.
New PropertyMetadata has 6 overloads depending on how you planning on handling the values of the property.
In this case, "Nothing" explicitly states the default value is nothing.
Options include Default values, Property change callbacks, CoerceValueCallback and an IsValidValueCallback. All of that is a little beyond, my problem. I have to sort of figure them out to get my code working, so if anybody has questions, reply and I might add some info.
If you are new to Dependency Properties, which the two 10-minute videos.
Youtube: WPF Tutorial 21 - Dependency Properties
They are simple and enough to get you up and running.

How to make the command property of a wpf button a dependency property of a user control

I am trying to teach myself the basics of creating user controls in wpf. To that end I have been experimenting with building a data navigation control to allow navigation through the records being retrieved by various view models. My long term plan is for a completely self contained custom control, but I'd like to master the smaller points first so to that end I'd like to know how I can make the Command, and Command Parameter properties ( as well as the Is Enabled property) of buttons that form part of my user control dependency properties of the user control itself.
I have succeeded to make the various image and image height and width properties of the various buttons dependency properties of the overall user control but thus far have not had any success with the Command, Command Parameter and is Enabled properties.
I'd welcome any suggestions anyone could proffer.
I have the following already (I set for each button in my user control):
#Region "Next Button"
Public Property ImageNext() As ImageSource
Get
Return DirectCast(GetValue(ImageNextProperty), ImageSource)
End Get
Set(value As ImageSource)
SetValue(ImageNextProperty, value)
End Set
End Property
Public Shared ReadOnly ImageNextProperty As DependencyProperty = DependencyProperty.Register("ImageNext", GetType(ImageSource), GetType(DataNavigator), New UIPropertyMetadata(Nothing))
Public Property ImageNextWidth() As Double
Get
Return CDbl(GetValue(ImageNextWidthProperty))
End Get
Set(value As Double)
SetValue(ImageNextWidthProperty, value)
End Set
End Property
Public Shared ReadOnly ImageNextWidthProperty As DependencyProperty = DependencyProperty.Register("ImageNextWidth", GetType(Double), GetType(DataNavigator), New UIPropertyMetadata(16.0))
Public Property ImageNextHeight() As Double
Get
Return CDbl(GetValue(ImageNextHeightProperty))
End Get
Set(value As Double)
SetValue(ImageNextHeightProperty, value)
End Set
End Property
Public Shared ReadOnly ImageNextHeightProperty As DependencyProperty = DependencyProperty.Register("ImageNextHeight", GetType(Double), GetType(DataNavigator), New UIPropertyMetadata(16.0))
This however has been adding properties to standard wpf buttons, now what I want to do is access properties of those buttons that already exist and bind to them (via my user control) from my viewmodels
It is the same as any other dependency property.
You declare the DP like this:
Public Shared ReadOnly ThisCommandProperty As DependencyProperty = _
DependencyProperty.Register("ThisCommand", GetType(ICommand), _
GetType(thiscontrol), Nothing)
Public Property ThisCommand As ICommand
Get
Return CType(GetValue(ThisCommandProperty), ICommand)
End Get
Set(ByVal value As ICommand)
SetValue(ThisCommandProperty, value)
End Set
End Property
and in the XAML of your user control:
<UserControl ...>
<Button Command={Binding ThisCommand} ... />
</UserControl>
You set the parameter in the same way, but with type object, and you have to cast it so the correct type in your command handler.
When you use the UserControl, it is like this:
<local:thisControl ThisCommand={Binding whateverCommandYouWantToBindTo},
ThisCommandParameter={Binding whateverParameterYouWant)>
It is really just the same as any other DP, except for the type. Of course, whateverCommandYouWantToBindTo has to be set up as an ICommand too.
People might also tell you that defining usercontrols is bad and to use templates instead, and it's probably a better approach in most cases. But if you want to learn about DPs, I say learn.
Here is an example that I have working in front of me:
Public Shared ReadOnly EditButtonCommandProperty As DependencyProperty = _
DependencyProperty.Register("EditButtonCommand", _
GetType(ICommand), GetType(PersonListControl), Nothing)
Public Property EditButtonCommand As ICommand
Get
Return CType(GetValue(EditButtonCommandProperty), ICommand)
End Get
Set(ByVal value As ICommand)
SetValue(EditButtonCommandProperty, value)
End Set
End Property
Public Shared ReadOnly EditButtonCommandParameterProperty As DependencyProperty = _
DependencyProperty.Register("EditButtonCommandParameter", GetType(Object), _
GetType(PersonListControl), Nothing);
Public Property EditButtonCommandParameter As Object
Get
Return CType(GetValue(EditButtonCommandParameterProperty), Object)
End Get
Set(ByVal value As Object)
SetValue(EditButtonCommandParameterProperty, value)
End Set
End Property
And in the UserControl XAML:
<StackPanel>
<ListBox ... />
<Button
...
Command="{Binding EditButtonCommand}"
CommandParameter="{Binding EditButtonCommandParameter}"/>
</StackPanel>
And I use this UserControl like this:
<local:PersonListControl
...
EditButtonCommand="{Binding PersonListEditCommand}"
EditButtonCommandParameter="{Binding Parents}"/>

Dependency Property Changed Event not fireing on Collection Property in design Time

I have a custom control that has a property that is an ObservableCollection of another custom control. I can't seem to get the DependencyProperty change event to fire in design time. I have tried to use CoerceValueCallback this doesn't fire either. can anyone give me some direction. Every thing else is working just fine in runtime i just can't get this to fire so i can update the control in designtime. Many thanks in advance.
Public Shared ReadOnly ArcsProperty As DependencyProperty = DependencyProperty.Register("Arcs", GetType(ObservableCollection(Of OPCWPF.OPCWPFArcControl)), GetType(OPCWPFPie), New PropertyMetadata(New ObservableCollection(Of OPCWPF.OPCWPFArcControl), New PropertyChangedCallback(AddressOf ArcsPropertyChanged), New CoerceValueCallback(AddressOf CoerceArcs)))
' Arc Quantity
<Description("Collection of Arcs"), _
Category("Common Properties")> _
Public Property Arcs() As ObservableCollection(Of OPCWPF.OPCWPFArcControl)
Get
Return DirectCast(Me.GetValue(ArcsProperty), ObservableCollection(Of OPCWPF.OPCWPFArcControl))
End Get
Set(ByVal value As ObservableCollection(Of OPCWPF.OPCWPFArcControl))
Me.SetValue(ArcsProperty, value)
End Set
End Property
Via the event you register with the dependency property metadata you are just subscribing change on the property itself, not changes to the collection (adding/removing items) that need to be subscribed by registering for the event CollectionChanged exposed by ObservableCollection<T>
The simple solution (maybe not the best) but it got me what i was looking for.
Public Overrides Sub OnApplyTemplate()
MyBase.OnApplyTemplate()
AddHandler Arcs.CollectionChanged, AddressOf UpdateControl
End Sub

Resources