I created a DependencyObject with properties in it, I also have it inherit INotifyPropertyChanged.
But when it is implemented as a DependencyProperty, it does not trigger PropertyChangedCallback when I change a single property from the DependencyObject both in Design and Code.
This is the Dependency Object that I will use for my CustomControl.
public class Basemap : DependencyObject, INotifyPropertyChanged
{
private string identifier;
private string name;
private string alias;
private string url;
private Map.DetailRange detailrange;
private Map.Attribution attribution;
public string Identifier
{
get => identifier;
set
{
identifier = value;
OnPropertyChanged("Identifier");
}
}
public string Name
{
get => name;
set
{
name = value;
OnPropertyChanged("Name");
}
}
public string Alias
{
get => alias;
set
{
alias = value;
OnPropertyChanged("Alias");
}
}
public string URL
{
get => url;
set
{
url = value;
OnPropertyChanged("URL");
}
}
public Map.DetailRange DetailRange
{
get => detailrange;
set
{
detailrange = value;
OnPropertyChanged("DetailRange");
}
}
public Map.Attribution Attribution
{
get => attribution;
set
{
attribution = value;
OnPropertyChanged("Attribution");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
It has a PropertyChangedEventHandler and Invoking it whenever the OnPropertyChanged is called, just like in this GUIDE
This it the DependecyProperty I implimented into the CustomControl that has a PropertyChangedCallback.
public static DependencyProperty BasemapProperty = DependencyProperty.Register("Basemap", typeof(Basemap), typeof(Browser), new PropertyMetadata(null, BasemapChanged));
private static void BasemapChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("Basemap Changed");
}
But the PropertyChangedCallback is not triggered when an individual property's value is changed, but it is triggered only when the whole DependecyObject is updated or inserted. Both in Design and Code
<Window.Resources>
<mMAP:Basemap x:Key="OpenStreetMap"
Identifier="OpenStreetMap.Standard"
Name="OpenStreetMap Standard"
Alias="OpenStreetMap"
URL="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
DetailRange="0,0,18"
Attribution="© OpenStreetMap contributors, CC-BY-SA" />
</Window.Resources>
Can anyone suggest any fixes to this?
Thank you.
Edit:
I assignend the PropertyChanged event from the DependecyObject to the control. Basemap.PropertyChanged += Basemap_PropertyChanged; so it can be triggered every time any member of the DependecyObject class. It may be sacrilegious to do it to WPF, but works for me.
PS:
I needed it to be grouped, for it is a configuration for the CustomControl.
You are not binding to the Dependency Property you defined in the control, that is why the PropertyChangedCallback BasemapChanged is not triggered.
While it works on some cases, I try to avoid implementing the INotifyPropertyChanged Interface on Controls and use only Dependency Properties instead when binding is required.
public static DependencyProperty AliasProperty = DependencyProperty.Register(
nameof(Alias),
typeof(string),
typeof(Basemap),
new PropertyMetadata(null, AliasPropertyChangedCallback));
public string Alias
{
get { return (string)GetValue(Alias); }
set { SetValue(Alias, value); }
}
private static void AliasPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine($"New Value of Alias: {e.NewValue}");
}
See: Dependency properties overview (WPF .NET)
Related
A DependencyProperty which is bound to a property in a data context is not having its PropertyChangedCallback invoked when the property in the data context is changed.
I have setup a test program to explore this functionality in which I have two classes; one named "DependencyObjectTest" that inherits DependencyObject and another named "ViewModelTest" that implements INotifyPropertyChanged.
The code of DependencyObjectTest is as follows:
public class DependencyObjectTest : DependencyObject
{
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value",
typeof(double), typeof(DependencyObjectTest), new PropertyMetadata(0d, ValuePropertyChanged));
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
private static void ValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is DependencyObjectTest test && e.NewValue is double value)
{
System.Diagnostics.Debugger.Break();
}
}
}
And the code of ViewModelTest is as follows:
public class ViewModelTest : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private double value = 0d;
public double Value
{
get { return value; }
set
{
if (this.value != value)
{
this.value = value;
NotifyPropertyChanged(nameof(Value));
}
}
}
private void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
PropertyChanged?.Invoke(this, e);
}
}
Here's how I have my binding set up in XAML:
<Window.DataContext>
<local:ViewModelTest/>
</Window.DataContext>
<Window.Resources>
<local:DependencyObjectTest x:Key="testObject" Value="{Binding Value}"/>
</Window.Resources>
I then have a slider whose Value is bound to Value of the data context. This should update the Value property of testObject but it does not fire the PropertyChangedCallback however, when I manually set the binding in the codebehind, it is triggered.
if (DataContext is ViewModelTest test)
{
string propName = nameof(test.Value);
Binding binding = new Binding(propName) { Source = test };
if (Resources["testObject"] is DependencyObjectTest depTest)
BindingOperations.SetBinding(depTest, DependencyObjectTest.ValueProperty, binding);
}
Is there something that I'm missing in my data binding setup? All help is appreciated, thank you.
I'm trying to bind the Title property of a Window to a custom property of this Window. The XAML looks like this:
Title="{Binding Path=WindowTitle, RelativeSource={RelativeSource Mode=Self}}"
The code behind like this:
public string WindowTitle
{
get
{
string title = (string)GetValue(WindowTitleProperty);
title = string.IsNullOrEmpty(title) ? "Editor" : title;
return title;
}
set
{
SetValue(WindowTitleProperty, value);
}
}
public static readonly DependencyProperty WindowTitleProperty =
DependencyProperty.Register("WindowTitle", typeof(string), typeof(Editor), new UIPropertyMetadata(null));
This works good after the property WindowTitle was set to a new value. But unfortunately upon loading the Window I don't get any title. The getter of WindowTitle isn't even called. As far as I can say, it never gets called. What am I doing wrong? Why is the getter never called (even when the title is set correctly)? Can I set a defaultvalue on any other way?
Your property looks strange. Why is it defined as a dependency property to begin with? You might as well use a CLR property and implement the INotifyPropertyChanged interface. This works fine for me:
public partial class Window13 : Window, INotifyPropertyChanged
{
public Window13()
{
InitializeComponent();
}
private string _windowTitle = "default title...";
public string WindowTitle
{
get { return _windowTitle; }
set { _windowTitle = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The CLR getter of a dependency property should only call the GetValue method and not contain any other logic.
Edit:
If you do want a dependency property for some reason, it should be implemented like this:
public string WindowTitle
{
get
{
return (string)GetValue(WindowTitleProperty);
}
set
{
SetValue(WindowTitleProperty, value);
}
}
public static readonly DependencyProperty WindowTitleProperty =
DependencyProperty.Register("WindowTitle", typeof(string), typeof(Editor), new UIPropertyMetadata("Editor"));
Note that you specify the default value when you register the property.
I am working on a custom input control inherited from TextBox that will be used to enter in a Social Security Number. The end goal is to use a mask and display bullet characters instead of the actual numbers while storing the actual value in a dependency property called ActualValue.
I'm attempting to set the TextBox.Text value to the actual value (later to be swapped out for formatted hidden characters) and I am receiving an exception at the kernel level (no symbols available if I set to managed and native debugging) if I have an event handler on the TextChanging event and bind the value.
no TextChanging eventhandler - no exception
do not set Text in callback - no exception
use literal (e.g. 123456) in markup instead of binding - no exception
I have used this pattern before (even in UWP applications), but the only difference that I can see in this example is that it is in new project with both minimum and target versions set to the Anniversary Update version... I'm not sure if that has anything to do with it.
I am not looking for a solution to the end goal... just help with why it is throwing an exception, please.
here is the class:
public class SSNInputBox : TextBox
{
private static readonly string SSNMask = "___-__-____";
public SSNInputBox()
{
DefaultStyleKey = typeof(TextBox);
TextChanging += OnTextChanging;
}
#region Properties
private void OnTextChanging(TextBox sender, TextBoxTextChangingEventArgs args)
{
}
public string ActualValue
{
get { return (string)GetValue(ActualValueProperty); }
set { SetValue(ActualValueProperty, value); }
}
// Using a DependencyProperty as the backing store for ActualValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ActualValueProperty =
DependencyProperty.Register("ActualValue", typeof(string), typeof(SSNInputBox), new PropertyMetadata(string.Empty, onActualValueChanged));
private static void onActualValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((SSNInputBox)d).OnActualValueChanged(e);
}
protected virtual void OnActualValueChanged(DependencyPropertyChangedEventArgs e)
{
Text = e.NewValue.ToString();
}
#endregion
}
and here is the markup:
<local:SSNInputBox ActualValue="{Binding Path=CurrentApplicant.SSN, Mode=TwoWay}" />
UPDATE
If I move the SSN to the main viewmodel as opposed to being a property of an INotifyPropertyChanged object in the viewmodel, it also works.
INP class:
public class Applicant : INotifyPropertyChanged
{
private string _ssn;
public string SSN
{
get { return _ssn; }
set { Set(ref _ssn, value); }
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected bool Set<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (Equals(storage, value)) return false;
storage = value;
this.RaisePropertyChanged(propertyName);
return true;
}
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
ViewModel (methods are called from code behind of mainpage during development)
public class MainViewModel : ViewModelBase
{
private Applicant _currentApplicant = new Applicant();
public Applicant CurrentApplicant
{
get { return _currentApplicant; }
set { Set(ref _currentApplicant, value); }
}
private string _ssn = "123-45-6789";
public string SSN
{
get { return _ssn; }
set { Set(ref _ssn, value); }
}
public void LoadApplicant()
{
CurrentApplicant.SSN = "123-45-6789";
}
public void ChangeSSN()
{
CurrentApplicant.SSN = SSN = "987-65-4321";
}
}
In developing some UserControls for internal use I followed this exmaple from MSDN http://msdn.microsoft.com/en-us/library/vstudio/ee712573(v=vs.100).aspx
The public value of one control is used by another control. The way I have this working currently is hooking into an event that is fired in the first control through code-behind. I am thinking that making one or both of the properties DependencyProperties which would eliminate the need for the code-behind.
public partial class UserControl1 : UserControl
{
private DataModel1 dm;
public UserControl1()
{
this.DataContext = new DataModel1();
dm = (DataModel1)DataContext;
InitializeComponent();
}
public DataValue CurrentValue
{
get { return dm.CurrentValue; }
set { dm.CurrentValue = value; }
}
}
public class DataModel1 : INotifyPropertyChanged
{
private DataValue _myData = new DataValue();
public DataValue CurrentValue
{
get { return _myData; }
set { if (_myData != value) {_myData = value OnPropertyChanged("CurrentValue"); }
}
// INotifyPropertyChanged Section....
}
The property is just a pass through from the DataModel1 class.
Both UserControls are very similar in their structure and have the same public properties. I would like to replace the code behind eventhandler with a Binding similar, I think to:
<my:UserControl1 Name="UserControl1" />
<my:UserControl2 CurrentValue={Binding ElementName="UserControl1", Path="CurrentValue"} />
but the standard examples of DependencyProperties have getters and setter that use the GetValue and SetValue functions which use a generated backing object instead of allowing a pass through.
public DataValue CurrentValue
{
get { return (DataValue)GetValue(CurrentValueProperty); }
set { SetValue(CurrentValueProperty, value); }
}
I think the DP should look like:
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(DataValue), typeof(UserControl1));
How can I change the definition of the public backing property to support the databinding pass through?
I found that jumping into the OnPropertyChanged event allowed me to pass the data through to the DataModel1. I am not 100% sure that this is the correct answer but it gets the job done.
Here is the corrected code:
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(DataValue), typeof(UserControl1),
new PropertyMetadata(new PropertyChangedCallback(OnCurrenValueChanged)));
private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl1 uc = d as UserControl1;
if (e.NewValue != null)
{
uc.dm.CurrentValue = e.NewValue as DataValue;
}
}
public DataValue CurrentValue
{
get { return GetValue(CurrentValueProperty) as DataValue; }
set { SetValue(CurrentValueProperty, value); }
}
I used a Multibinding to bind some properties and use the INotifyPropertyChanged interface to notify these properties'changes.But sadly,it seems that the INotifyPropertyChanged didn't work. The PropertyChangedEventHandler "PropertyChanged" was null all the time.
Questions:
A. Could you please tell me why the event is null?In my mind,there should be a default method for the event PropertyChangedEventHandler,am I wrong?(Resolved,thanks!)
B. Just like some friends said,I tried again without using the INotifyPropertyChanged.But the target's property's value seems to be not changed...
Properties
public static readonly DependencyProperty LeftOffsetProperty = DependencyProperty.Register("LeftOffset", typeof(double), typeof(NetworkTaskLable), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty TopOffsetProperty = DependencyProperty.Register("TopOffset", typeof(double), typeof(NetworkTaskLable), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsRender));
public double LeftOffset
{
get { return (double)GetValue(LeftOffsetProperty); }
set { SetValue(LeftOffsetProperty, value); }
}
public double TopOffset
{
get { return (double)GetValue(TopOffsetProperty); }
set { SetValue(TopOffsetProperty, value); }
}
Multibinding (It seems work well.By using the converter to calculate a location which is affected by "TopOffset" and "LeftOffset")
var multibinding = new MultiBinding() { Converter = new BeginAndStartDateToLeftConverter_NetworkTaskLable(), ConverterParameter = NetworkView };
multibinding.Bindings.Add(new Binding("Start"));
multibinding.Bindings.Add(new Binding("StartDate") { Source = NetworkView });
multibinding.Bindings.Add(new Binding("LeftOffset") { Source = this });
MainCanvas.SetBinding(LeftProperty, multibinding);
INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void CallPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)//It seems to be null all the time!!!And the properties'changes were never notified!!!
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
Notify the change
SetValue(LeftOffsetProperty, moveAdorner.LeftOffset);
CallPropertyChanged("LeftOffset");
SetValue(TopOffsetProperty, moveAdorner.TopOffset);
CallPropertyChanged("TopOffset");
You don't need to implement INotifyPropertyChanged for dependency properties. Binding will track changes of these properties automatically.
Set Mode=TwoWay anywhere in your binding ?
You need to override OnPropertyChanged and check to see if the property is the one you are looking for.
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.Property == MyProperty)
{
// do something
}
base.OnPropertyChanged(e);
}