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);
}
Related
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)
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 creating dynamic control in code behind and setting it's visibility property binding to the property in the code behind. But when the property value is changed, it's not updating visibility of the control.
Binding:
Binding assetsVisibilityBinding = new Binding();
assetsVisibilityBinding.Source = this;
assetsVisibilityBinding.Path = new PropertyPath("IsLocalSearchEnabled");
assetsVisibilityBinding.Mode = BindingMode.TwoWay;
assetsVisibilityBinding.Converter = Resources["BooleanToVisibilityConverter"] as IValueConverter;
assetsStackPanel.SetBinding(StackPanel.VisibilityProperty, assetsVisibilityBinding);
Property(Using fody):
public bool IsLocalSearchEnabled { get; set; }
maybe your class which contains the property needs to implement the interface
INotifyPropertyChanged
Let's assume your class name be A
then snippet will be
class A : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public bool isLocalSearchEnabled = false;
public bool IsLocalSearchEnabled
{
get { return isLocalSearchEnabled ;}
set { isLocalSearchEnabled = value; this.OnPropertyChanged("IsLocalSearchEnabled");
}
}
What happens here when you implement INotifyPropertyChanged is the event PropertyChanged
is triggered when the value of isLocalSearchEnabled is set (regardless of old value and new value) and OnPropertyChanged is called with the name of Public property
It seems you have not implemented INotifyPropertyChanged interface, please see the detailed example INotifyPropertyChanged
Did you set the assetsStackPanel DataContext the binding need the source ,that you should set the DataContext ont only set the source.
If you set the property in this in xaml.cs that you should make it public.
Binding assetsVisibilityBinding = new Binding();
assetsVisibilityBinding.Source = this;
assetsVisibilityBinding.Path = new PropertyPath("IsLocalSearchEnabled");
assetsVisibilityBinding.Mode = BindingMode.TwoWay;
assetsVisibilityBinding.Converter = Resources["BooleanToVisibilityConverter"] as IValueConverter;
assetsStackPanel.DataContex=this;
assetsStackPanel.SetBinding(StackPanel.VisibilityProperty, assetsVisibilityBinding);
For I have not read frameWork ,I think you can try use property by INotifyPropertyChanged to know whether code is right.
And you can use BindingOperations.SetBinding
Try use resharper in xaml and write Visibility="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=IsLocalSearchEnabled}",mode=TwoWay.If it can work that is mean the FrameWork can work.
Thanks. But as mentioned in the post I am actually using
Fody(github.com/Fody/PropertyChanged). Which automatically implements
that
I have checked the complied class, by using Fody PropertyChanged the property changed notification wasn't successfully implemented.
[ImplementPropertyChanged]
public sealed partial class MainPage : Page
{
public bool IsLocalSearchEnabled { get; set; }
public MainPage()
{
this.InitializeComponent();
SetBinding();
this.DataContext = this;
}
public void SetBinding()
{
Binding assetsVisibilityBinding = new Binding();
assetsVisibilityBinding.Source = this;
assetsVisibilityBinding.Path = new PropertyPath("IsLocalSearchEnabled");
assetsVisibilityBinding.Mode = BindingMode.TwoWay;
assetsVisibilityBinding.Converter = Resources["BooleanToVisibilityConverter"] as IValueConverter;
assetsStackPanel.SetBinding(StackPanel.VisibilityProperty, assetsVisibilityBinding);
}
}
I would suggest you reporting issue to Fody to fix it.
The standard way as follows:
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
bool isLocalSearchEnabled;
public bool IsLocalSearchEnabled
{
get { return isLocalSearchEnabled; }
set
{
if (value != isLocalSearchEnabled)
{
isLocalSearchEnabled = value;
OnPropertyChanged("IsLocalSearchEnabled");
}
}
}
public MainPage()
{
this.InitializeComponent();
SetBinding();
this.DataContext = this;
}
public void SetBinding()
{
Binding assetsVisibilityBinding = new Binding();
assetsVisibilityBinding.Source = this;
assetsVisibilityBinding.Path = new PropertyPath("IsLocalSearchEnabled");
assetsVisibilityBinding.Mode = BindingMode.TwoWay;
assetsVisibilityBinding.Converter = Resources["BooleanToVisibilityConverter"] as IValueConverter;
assetsStackPanel.SetBinding(StackPanel.VisibilityProperty, assetsVisibilityBinding);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Or you can easily use a wrapper class: BindableBase
I am relatively new to WPF and having a problem with data binding. I am binding a dependency property of a user control to a class property in my code behind. During intantiation of the class entity in my code behind the UI is sucessfully updated through INotifyPropertyChanged. However when subsequently changing the value in my code behind the OnPropertyChangedEventHandler fires, but the OnPropertyChanged method does no longer answer to this. Below the details. It would be great if someone could give me some hints what I am doing wrong.
I implemented a user control that I am binding to a property CurrentAccProp.DiscountRate of my partial class in code behind:
<local:doubleUEdit x:Name="InterestRate" LabelField="Interest rate" MinimumValue="0" MaximumValue="1" FormatStringForNumbers="P2" IncrementSize="0.01" UncertainValue="{Binding ElementName=RibbonWindow, Path=CurrentAccProp.DiscountRate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The class of which CurrentAccProp is an instance implements INotifyPropertyChanged to inform the UI about value changes
//Event to inform data grid about changes
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
OnPropertyChanged is called in the setter for the DiscountRate property:
doubleU discountingrate;
public doubleU DiscountRate
{
get {return discountingrate;}
set
{
discountingrate = value;
OnPropertyChanged("DiscountingRate");
}
}
The property of my user control that I am binding to is implemented as a dependency property:
//Property for data binding to doubleU
[Description("The formatstring for the double boxes"), Category("Default")]
public doubleU UncertainValue
{
get { return new doubleU(0, 0, (double)doubleUSupremum.Value, (double)doubleUSupremum.Value); }
set { doubleURangeSlider.LowerValue = value.Interval.Infimum; doubleURangeSlider.HigherValue = value.Interval.Supremum; doubleUInfimum.Value = value.Interval.Infimum; doubleUSupremum.Value = value.Interval.Supremum; }
}
public static readonly DependencyProperty UncertainValueProperty =
DependencyProperty.Register(
"UncertainValue",
typeof(doubleU),
typeof(doubleUEdit),
new PropertyMetadata(default(doubleU), OnItemsPropertyChanged));
private static void OnItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
doubleUEdit MydblUEdt = d as doubleUEdit;
MydblUEdt.UncertainValue = e.NewValue as doubleU;
}
When I am instantiating CurrentAccProp in my code behind the OnPropertyChanged informs the UI and the value is updated.
AccountingProperties currentaccprop = new AccountingProperties(new doubleU(0.0));
public AccountingProperties CurrentAccProp { get { return currentaccprop; } set { currentaccprop = value; } }
However, when I later update the value of DiscountRate
CurrentAccProp.DiscountRate = new doubleU(1.0);
OnPropertyChanged gets executed, but the UI is no longer updated. Does anyone have a clue what I am doing wrong here?
The typo pointed out by HighCore and zaknotzach was indeed the problem. Thanks for your help! I implemented the approach in the thread referenced by HighCore to avoid this and it works like a charm. Below the changed AccountingProperties class from which CurrentAccProp is instantiated for reference:
public class AccountingProperties : INotifyPropertyChanged
{
doubleU discountrate;
public doubleU DiscountRate
{
get {return discountrate;}
set { SetField(ref discountrate, value, () => DiscountRate); }
}
//------------------------------------------------
//constructors
public AccountingProperties(doubleU discountrate)
{
DiscountRate = discountrate;
}
//Event to inform data grid about changes
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
if (selectorExpression == null)
throw new ArgumentNullException("selectorExpression");
MemberExpression body = selectorExpression.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
OnPropertyChanged(body.Member.Name);
}
protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(selectorExpression);
return true;
}
}
You need to first change the string in
OnPropertyChanged("DiscountingRate");
to "DiscountRate". The string you are giving your OnPropertyChanged function must match the property name. That is most likely the issue you are having.
As already answered, the problem is OnPropertyChanged("DiscountingRate"); providing the event with an incorrect property name.
In order to prevent errors like this, you can avoid using string literals all together. In your OnPropertyChanged parameter, use CallerMemberName. You can modify your OnPropertyChanged signature to
public void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
// Do your stuff
}
Then in your setters, you just call this.OnPropertyChanged();. The method will be given the property name that was changed.
public doubleU DiscountRate
{
get {return discountingrate;}
set
{
discountingrate = value;
OnPropertyChanged();
}
}
The benefit to this is that you can refactor your code and not worry about breaking your property changed events.