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.
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)
I am new to the binding concept and got stuck with the following.
public sealed partial class MainPage : Page
{
Model model;
public MainPage()
{
this.InitializeComponent();
model = new Model();
this.DataContext = model;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
model.Name = "My New Name";
}
}
class Model : DependencyObject
{
public static DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Model), new PropertyMetadata("My Name"));
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
I have bound the Name property to Text property of TextView. All I need to do is, on the button click I want to update the Name value that will have to update the text box value. I thought, if I use dependency property instead of normal CLR property, I dont need to implement INotifyPropertyChanged.
But the value in the UI is not updating as expected. Am I missing something?
Thanks in advance.
There are a couple things that need to be addressed with your question. First of all, your model does not need to inherit from DependencyObject, rather it should implement INotifyPropertyChanged:
public class Model : INotifyPropertyChanged
{
string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
NotifyPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
An object that implements INotifyProperty can then be used as a DependencyProperty in your page/window/object:
public static readonly DependencyProperty ModelProperty = DependencyProperty.Register("Model",
typeof(Model), typeof(MainWindow));
public Model Model
{
get { return (Model)GetValue(ModelProperty); }
set { SetValue(ModelProperty, value); }
}
Finally, then, you can bind your TextBox.Text property to that in the XAML:
<Grid>
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Name}"/>
<Button Click="Button_Click">Click</Button>
</StackPanel>
</Grid>
The INotifyPropertyChanged is still necessary here because there needs to be a way for the UI to know that the model object has been updated.
I am writing terimnal application in WPF. I receive characters from an embedded device and I update a TextBox.Text property that is bounded to my ViewModel.
The problem is that the TextBox Caret is reset when I update the text property. what I would like to do is hold a Caret parameter in my viewModel and bind it to the Caret property of the TextBox, however the TextBox Caret is not a dependency property and I don't want to access the view directly from my view model.
Are you familiar with a proper solutionthat does not break the MVVM pattern?
Thanks in advance.
You can add attached property to bind non-dependency property. below example i have created it for CaretIndex property of the textbox.
<Window x:Class="Converter_Learning.Window7"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Converter_Learning"
Title="Window7" Height="500" Width="500">
<Grid FocusManager.FocusedElement="{Binding ElementName=txt}">
<TextBox x:Name="txt" Text="Hiiiiiiiiiiiiiii" local:TextBoxHelper.Caret="{Binding Caret}" />
</Grid>
public partial class Window7 : Window
{
public Window7()
{
InitializeComponent();
this.DataContext= new CaretViewModel();
}
}
public class CaretViewModel : INotifyPropertyChanged
{
private int myVar;
public int Caret
{
get { return myVar; }
set { myVar = value; Notify("Caret"); }
}
public CaretViewModel()
{
Caret = 5;
}
public event PropertyChangedEventHandler PropertyChanged;
void Notify(string property)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public static class TextBoxHelper
{
public static int GetCaret(DependencyObject obj)
{
return (int)obj.GetValue(CaretProperty);
}
public static void SetCaret(DependencyObject obj, int value)
{
obj.SetValue(CaretProperty, value);
}
public static readonly DependencyProperty CaretProperty =
DependencyProperty.RegisterAttached(
"Caret",
typeof(int),
typeof(TextBoxHelper),
new FrameworkPropertyMetadata(0, CaretChanged));
private static void CaretChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TextBox tb = obj as TextBox;
if (tb != null)
{
int newValue = (int)e.NewValue;
if (newValue != tb.CaretIndex)
{
tb.CaretIndex = (int)newValue;
}
}
}
}
I am using the free version of the DevExpress Silverlight Menu (AgMenu 8.4). Sadly the MenuItems of this menu have no "Command" and "CommandParameter" properties.
I decided to inherit from the MenuItem class and implement two DependencyProperties, "Command" and "CommandProperty".
The code for this looks like this:
public partial class MenuItem : DevExpress.AgMenu.AgMenuItem
{
public MenuItem()
{
InitializeComponent();
}
private Object _CommandParameter = null;
public Object CommandParameter
{
get { return _CommandParameter; }
set { _CommandParameter = value; } //This one is triggered. This is ok.
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(Object), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandParameterChanged));
private static void OnCommandParameterChanged(object sender, DependencyPropertyChangedEventArgs args)
{
//CommandParameter Object is arriving here. That is ok.
}
private ICommand _Command = null;
public ICommand Command
{
get { return _Command; }
set
{
//HERE is the problem.
//This one is NOT triggered. I dont' know why....?
_Command = value;
}
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandChanged));
private static void OnCommandChanged(object sender, DependencyPropertyChangedEventArgs args)
{
//ICommand Object is arriving here. That is also ok.
//I don't understand, why the ICommand Object is not arriving in the set value prop
}
}
Now I am using this two DPs in my XAML. This looks like this for one MenuItem:
<cc:MenuItem x:Name ="_mnuItemLogout"
DataContext ="{Binding Source={StaticResource ViewModel}}"
Header ="{Binding Source={StaticResource MenuProvider}, Path=GetSingleton.LogoutText, Mode=OneWay}"
IsEnabled ="{Binding Source={StaticResource MenuProvider}, Path=GetSingleton.LogoutEnabled, Mode=OneWay}"
Command ="{Binding Source={StaticResource ViewModel}, Path=Command_FormOpen}"
CommandParameter ="{gui:FormOpen e=Login}"
IsCheckable ="False"
>
</cc:MenuItem>
When I am testing my silverlight application, I assume that both, the "Command" and "CommandParameter" set value properties are called, and the values are set to _Command and _CommandParameter, but only the CommandParameter set value is called.
Strangely, both static procedures "OnCommandChanged" and "OnCommandParameterChanged" are called. While debugging, I can see, both expected objects (ICommand and CommandParameter) are arriving in this two procedures.
So my question is:
What am I doing wrong, that the ICommand Object is not set in the "Set ICommand" property?
Thank you.
This case is solved. What I needed to do, is not using DependencyProperties, but attached DependencyProperties.
The MenuItem of the DevExpress Silverlight menu now accepts Command and CommandParameter objects. The Command is triggered, when the LeftMouseButtonUp event is fired. The following is the working code. The XAML stays the same (see above). You just need to make a silverlight user control and inherit from DevExpress.AgMenu.AgMenuItem. You then use this as MenuItem, instead of the original.
using System;
using System.Windows;
using System.Windows.Input;
namespace Gui.CustomControls
{
public partial class MenuItem : DevExpress.AgMenu.AgMenuItem
{
public MenuItem()
{
InitializeComponent();
}
#region CommandParameter DependencyProperty
public static Object GetCommandParameter(DependencyObject obj)
{
return (Object)obj.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(DependencyObject obj, Object value)
{
obj.SetValue(CommandParameterProperty, value);
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter", typeof(Object), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandParameterChanged) );
private static void OnCommandParameterChanged(object sender, DependencyPropertyChangedEventArgs args)
{
DependencyObject _DependencyObject = (DependencyObject)sender;
if ((args.NewValue != null) && (_DependencyObject != null))
{
MenuItem.SetCommandParameter(_DependencyObject, args.NewValue);
}
}
#endregion
#region Command
private static void OnCommandChanged(object sender, DependencyPropertyChangedEventArgs args)
{
DependencyObject _DependencyObject = (DependencyObject)sender;
ICommand _ICommand = (ICommand)args.NewValue;
if ((_ICommand != null) && (_DependencyObject != null))
{
SetCommand(_DependencyObject, _ICommand);
}
}
public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}
// Using a DependencyProperty as the backing store for Command. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(Gui.CustomControls.MenuItem), new PropertyMetadata(OnCommandChanged));
#endregion
#region LeftMouseButtonUp (Command Trigger)
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
ICommand _ICommand = MenuItem.GetCommand(this);
Object _CommandParameter = MenuItem.GetCommandParameter(this);
if (_ICommand != null)
{
_ICommand.Execute(_CommandParameter);
}
}
#endregion
}
}
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);
}