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

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

Related

WPF Dependency Property - set from XAML

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

Dependency property not getting registered in one specific control in WPF

I have two different controls, both of them have a property called Value. I am using pretty much the same code in both cases. And yet, in one case, I get binding error saying "Binding can be done only for dependency property in dependency object.
Below is the code to implement the dependency property
Public Class Calender
Implements INotifyPropertyChanged
Public Shared ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(String), GetType(Calendar))
Public Property Value() As String
Get
If Not TryCast(Me.GetValue(ValueProperty), String) Is Nothing Then
Return TryCast(Me.GetValue(ValueProperty), String)
Else
Return String.Empty
End If
End Get
Set(value As String)
Me.SetValue(ValueProperty, value)
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
This is the exact same code I am using in another control. It works fine in that control, but in this case, it does not work. Also, when I use this Value property as
Value="{Binding MeasuringTapeCalibrationDue,Mode=TwoWay}"
it displays a squiggly line in XAML editor, saying "Value property was already registered by Calendar". I have the above code registering the value property just once.
You have to replace GetType(Calendar) by GetType(OtherControl) in the dependency property registration in the other control:
Public Class OtherControl
Public Shared ValueProperty As DependencyProperty =
DependencyProperty.Register("Value", GetType(String), GetType(OtherControl))

Converting normal property to dependency property

I have a control that I am using for my new application. This control has a regular property as such.
Public Property Value() As String
Get
If AutoCompleteTextBox.SearchText Is Nothing Then
Return String.Empty
Else
Return AutoCompleteTextBox.SearchText.ToString.Trim
End If
End Get
Set(value As String)
AutoCompleteTextBox.SearchText = value
End Set
End Property
Edit:
So, after multiple tries, I am finally at this stage.
Public Shared ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(String), GetType(AutoCompleteBox))
Public Property Value() As String
Get
Return Me.GetValue(ValueProperty).ToString
End Get
Set(value As String)
Me.SetValue(ValueProperty, value)
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
This is the dependency property. This property is still not binding. No errors are shown in output window for binding.
Text="{Binding RelativeSource={RelativeSource Self}, Path=Value, Mode=TwoWay}"
This is my binding method. I have no idea what else I can do. At least if there was an error, I could have figured out something. Without any error, I am just a headless chicken here.
Please refer to the following url for all the dependency fundamentals
http://www.wpftutorial.net/dependencyproperties.html
Basically, you can get a property changed event of dependency property by providing a FrameworkPropertyMetadata.
new FrameworkPropertyMetadata( [Default Value],
OnCurrentTimePropertyChanged);
And you can get back the target control (DependencyObject) at the event handler and implement your logic over there
private static void OnCurrentTimePropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
AutoCompleteTextBox control = source as AutoCompleteTextBox;
string time = (string)e.NewValue;
// Put some update logic here...
}
Declaring a dependency property in a control is a good thing.
You could make some binding in the xaml (sorry I don't have your XAML - I imagine).
Something like :
<TextBox x:Name="AutoCompleteTextBox"
Text="{Binding RelativeSource={RelativeSource=Self},Path=Value}"/>
Regards
TextBox has a property called Text. When you access Text property it will give you text entered in TextBox. Same is your case. Now why you want to convert it into a DP ? A DP would be useful if you want o bind this DP to some other control.
Extend this control itself. Make a new control and introduce this new DP.
While a DP is used where you want to bind this property to some control. This property then gets updated from control or control gets updated from this DP depending upon binding mode set.
How to do binding :
<TextBox x:Name="UserInput" />
<uc:MyAutoCompleteTextBox ValueDP="{Binding Text, ElementName=UserInput, Mode=OneWay}" />
MyAutoCompleteTextBox is new control which extends(inherits) from your old AutoComplete control.
If you want to apply some filtering logic or anything else, you can apply it in your DP itself like this :
Get
someVariable = TryCast(Me.GetValue(ValueProperty), String)
' apply somg logic to someVariable
' use your old Value property from here
Return someVariable
End Get
There are many WPF Binding tutorials on net.
I recommend :
http://blog.scottlogic.com/2012/04/05/everything-you-wanted-to-know-about-databinding-in-wpf-silverlight-and-wp7-part-one.html
Just change your code with following code and you should be good
your code
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(String), GetType(AutoCompleteBox))
Public Property Value() As String
Get
Return TryCast(Me.GetValue(ValueProperty), String)
End Get
Set(value As String)
Me.SetValue(ValueProperty, value)
End Set
End Property
New code
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(String), GetType(AutoCompleteBox))
Public Property Value() As String
Get
If AutoCompleteTextBox.SearchText Is Nothing Then
Return String.Empty
Else
Return AutoCompleteTextBox.SearchText.ToString.Trim
End If
End Get
Set(value As String)
AutoCompleteTextBox.SearchText = value
End Set
End Property
This DP will do what your Older property was doing. But just think about your requirement there can be a better way of writing the things.
Thanks

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.

Model - ViewModel sync and filtered ObservableCollections

I am working with EF and MVVM.
My Model contains this:
Public Property Visits As DbSet(Of Visit)
The Visit class implements INotifyPropertyChanged and its properties properly raise PropertyChanged events.
My ViewModel contains these properties, used for my views binding (context is my model):
Private _BareVisits As ObservableCollection(Of Visit)
Public Property BareVisits As ObservableCollection(Of Visit)
Get
If _BareVisits Is Nothing Then
_BareVisits = New ObservableCollection(Of Visit)(context.Visits)
AddHandler _BareVisits.CollectionChanged, AddressOf BareVisits_CollectionChanged
End If
Return _BareVisits
End Get
Set(value As ObservableCollection(Of Visit))
_BareVisits = value
End Set
End Property
Private _Visits As ObservableCollection(Of Visit)
Public Property Visits As ObservableCollection(Of Visit)
Get
If _Visits Is Nothing Then
_Visits = New ObservableCollection(Of Visit)(
BareVisits.Where(
Function(V) Not V.IsMediGenerated AndAlso V.IsHistoryParseComplete
)
)
End If
Return _Visits
End Get
Set(value As ObservableCollection(Of Visit))
_Visits = value
End Set
End Property
Private Sub BareVisits_CollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
Select Case e.Action
Case NotifyCollectionChangedAction.Add
context.Visits.AddRange(e.NewItems.OfType(Of Visit))
context.SaveChanges()
Case NotifyCollectionChangedAction.Remove
context.Visits.RemoveRange(e.OldItems.OfType(Of Visit))
context.SaveChanges()
Case Else
Exit Sub
End Select
End Sub
As you see, the BareVisits property is just the DB collection of my Visits.
The Visits property (the one my view is bound to) depends on BareVisits to return filtered records.
Questions:
1) Which is the best method to save a new visit? By using the event handler posted? Or should I talk to the Model directly and somehow bubble this changes to the ViewModel? If I hook the CollectionChanged handler to the Visits property, do I need this BareVisits property at all?
2) Suppose the changes take place in the Model itself, I suppose I should also perform the record adds / delete to the BareVisits property also. What happens to the Visits property? (the filtered one) I have tried everything and its CollectionChanged event is never fired. Should I tamper with it also by hand?
I just want my view to depict the data changes and I am at a loss.
EDIT: I have also tried to use ICollectionView to filter the raw data (instead of a new ObservableCollection property) but it is not a choice since the data in my datagrid is rendered un-editable this way.
Thank you in advance. Any help will be greatly appreciated.

Resources