DependencyProperty fires only in ViewModel.ctor
-
Hello, I'd tried to reduce the problem. I've this UserControl
public class TestControl : Label
{
public static readonly DependencyProperty dependencyPropertyTestInfo =
DependencyProperty.Register("TestInfo", typeof(string), typeof(TestControl),
new PropertyMetadata("", new PropertyChangedCallback(ChangeTestInfo)));public string TestInfo { get => (string)GetValue(dependencyPropertyTestInfo); set => SetValue(dependencyPropertyTestInfo, value); } private static void ChangeTestInfo(DependencyObject d, DependencyPropertyChangedEventArgs e) { var testControl = (TestControl)d; testControl.TestInfo = (string)e.NewValue; }
}
This is the essential part of MainWindow.xaml:
Its bounded in MainWindow.xaml.cs to DataContext with this ViewModel:
public class MainWindowViewModel : BindableBase
{
private int counter;
private string _testInfoView;
public string TestInfoView
{
get { return _testInfoView; }
set { SetProperty(ref _testInfoView, value); }
}
public DelegateCommand NewTestInfoCommand { get; }
public MainWindowViewModel()
{
NewTestInfoCommand = new DelegateCommand(OnNewTestInfoCommand);
TestInfoView = "Info from ctor";
}
private void OnNewTestInfoCommand()
{
string newInfo = $"Info No. {++counter}";
Debug.WriteLine($"{MethodBase.GetCurrentMethod().Name}: {nameof(TestInfoView)}=«{TestInfoView}» -> «{newInfo}»");
TestInfoView = newInfo;
}
}I've added a "Debug.WriteLine" in MainWindow.xaml.cs
private void BtnShow_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine($"{MethodBase.GetCurrentMethod().Name}: {nameof(tct.TestInfo)}={tct.TestInfo}");
}After starting the app 1. I've clicked "BtnShow" this is the debug-info: BtnShow_Click: TestInfo=Info from ctor 2. I've clicked "BtnNewTestInfo" this is the debug-info: OnNewTestInfoCommand: TestInfoView=«Info from ctor» -> «Info No. 1» 3. I've clicked "BtnShow" this is the debug-info: BtnShow_Click: TestInfo=Info from ctor Now my question is: why View
-
Hello, I'd tried to reduce the problem. I've this UserControl
public class TestControl : Label
{
public static readonly DependencyProperty dependencyPropertyTestInfo =
DependencyProperty.Register("TestInfo", typeof(string), typeof(TestControl),
new PropertyMetadata("", new PropertyChangedCallback(ChangeTestInfo)));public string TestInfo { get => (string)GetValue(dependencyPropertyTestInfo); set => SetValue(dependencyPropertyTestInfo, value); } private static void ChangeTestInfo(DependencyObject d, DependencyPropertyChangedEventArgs e) { var testControl = (TestControl)d; testControl.TestInfo = (string)e.NewValue; }
}
This is the essential part of MainWindow.xaml:
Its bounded in MainWindow.xaml.cs to DataContext with this ViewModel:
public class MainWindowViewModel : BindableBase
{
private int counter;
private string _testInfoView;
public string TestInfoView
{
get { return _testInfoView; }
set { SetProperty(ref _testInfoView, value); }
}
public DelegateCommand NewTestInfoCommand { get; }
public MainWindowViewModel()
{
NewTestInfoCommand = new DelegateCommand(OnNewTestInfoCommand);
TestInfoView = "Info from ctor";
}
private void OnNewTestInfoCommand()
{
string newInfo = $"Info No. {++counter}";
Debug.WriteLine($"{MethodBase.GetCurrentMethod().Name}: {nameof(TestInfoView)}=«{TestInfoView}» -> «{newInfo}»");
TestInfoView = newInfo;
}
}I've added a "Debug.WriteLine" in MainWindow.xaml.cs
private void BtnShow_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine($"{MethodBase.GetCurrentMethod().Name}: {nameof(tct.TestInfo)}={tct.TestInfo}");
}After starting the app 1. I've clicked "BtnShow" this is the debug-info: BtnShow_Click: TestInfo=Info from ctor 2. I've clicked "BtnNewTestInfo" this is the debug-info: OnNewTestInfoCommand: TestInfoView=«Info from ctor» -> «Info No. 1» 3. I've clicked "BtnShow" this is the debug-info: BtnShow_Click: TestInfo=Info from ctor Now my question is: why View
XAML data-binding to dependency properties does not use the property getter or setter. It calls
SetValue
directly on theDependencyProperty
instance. Any code that needs to execute when the property value changes needs to be in thePropertyChangedCallback
method. #118 – Don’t Add Code to Dependency Property Getter/Setter | 2,000 Things You Should Know About WPF[^] You've also forgotten to raise thePropertyChanged
event from your viewmodel. Assuming the usual definition ofBindableBase
, you probably meant to write something like:private string _testInfoView;
public string TestInfoView
{
get { return _testInfoView; }
set { SetProperty(ref _testInfoView, value); }
}The
SetProperty
method will update the field and raise thePropertyChanged
event for you.
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
XAML data-binding to dependency properties does not use the property getter or setter. It calls
SetValue
directly on theDependencyProperty
instance. Any code that needs to execute when the property value changes needs to be in thePropertyChangedCallback
method. #118 – Don’t Add Code to Dependency Property Getter/Setter | 2,000 Things You Should Know About WPF[^] You've also forgotten to raise thePropertyChanged
event from your viewmodel. Assuming the usual definition ofBindableBase
, you probably meant to write something like:private string _testInfoView;
public string TestInfoView
{
get { return _testInfoView; }
set { SetProperty(ref _testInfoView, value); }
}The
SetProperty
method will update the field and raise thePropertyChanged
event for you.
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
Thank you for your answer. I have changed my test project and adapted the original message. 1. removed all Debug.Writeline in TestControl 2. in ViewModel
set { SetProperty(ref _testInfoView, value); }
3. Added a button BtnShow to see in it's Click in xaml.cs the content of TestInfo property In the original post under "After starting the app" I'd explained what I've done. But the ViewModel-command didn't change the property. I've added the code to GitHub - iwangoll/TestBinding: ViewModel-command didn't change the DependencyProperty.[^]
-
Thank you for your answer. I have changed my test project and adapted the original message. 1. removed all Debug.Writeline in TestControl 2. in ViewModel
set { SetProperty(ref _testInfoView, value); }
3. Added a button BtnShow to see in it's Click in xaml.cs the content of TestInfo property In the original post under "After starting the app" I'd explained what I've done. But the ViewModel-command didn't change the property. I've added the code to GitHub - iwangoll/TestBinding: ViewModel-command didn't change the DependencyProperty.[^]
That's a strange way of sharing code - uploading a 7zip file to GitHub, rather than uploading your code itself. :laugh: The first thing that jumps out is the
PropertyChangedCallback
:private static void ChangeTestInfo(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var testControl = (TestControl)d;
testControl.TestInfo = (string)e.NewValue;
}Thankfully, WPF doesn't call the "property changed" callback if you set the property to the same value it's already set; otherwise, you'd end up with a stack overflow here. As far as I can see, you want to log the fact that the property has changed, not change the property again:
private static void ChangeTestInfo(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine($"TestInfo changed from '{e.OldValue}' to '{e.NewValue}'.");
}Beyond that, your code works as expected. Clicking the "set new TestInfo" button shows the new value being set in the debug console. Clicking the "show TestInfo by Button-Click in xaml.cs" button shows the expected new value in the debug console. Perhaps you were expecting the content of your test control to update? But that's not going to happen: you've set it to the fixed string "TestControl", which isn't bound to anything. If you want to show the value on the screen, then change your markup to:
{Binding Path=TestInfo, ElementName=tct}" x:Name="tct" TestInfo="{Binding TestInfoView}"/>
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
That's a strange way of sharing code - uploading a 7zip file to GitHub, rather than uploading your code itself. :laugh: The first thing that jumps out is the
PropertyChangedCallback
:private static void ChangeTestInfo(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var testControl = (TestControl)d;
testControl.TestInfo = (string)e.NewValue;
}Thankfully, WPF doesn't call the "property changed" callback if you set the property to the same value it's already set; otherwise, you'd end up with a stack overflow here. As far as I can see, you want to log the fact that the property has changed, not change the property again:
private static void ChangeTestInfo(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine($"TestInfo changed from '{e.OldValue}' to '{e.NewValue}'.");
}Beyond that, your code works as expected. Clicking the "set new TestInfo" button shows the new value being set in the debug console. Clicking the "show TestInfo by Button-Click in xaml.cs" button shows the expected new value in the debug console. Perhaps you were expecting the content of your test control to update? But that's not going to happen: you've set it to the fixed string "TestControl", which isn't bound to anything. If you want to show the value on the screen, then change your markup to:
{Binding Path=TestInfo, ElementName=tct}" x:Name="tct" TestInfo="{Binding TestInfoView}"/>
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer