Trouble updating WPF with UpdateSourceTrigger = PropertyChanged - c#

First (full disclosure), I am a relative beginner with C#, so this is probably an obvious oops. I've reviewed dozens of similar questions and examples here on stackoverflow already and I still can't put my finder on what I am doing wrong. I started with a VS 2017 WPFapp project. Changed the OnStartup so I could load my window manually. Created a class with a timer that increments a value and then used INotifyPropertyChanged to update a TextBox on my MainWindow.
The WPF window loads fine. The TextBlock starts with a value of 5, so the binding is at least pointing to my class and value. You will note in the output that the value is updating with the timer event, and the NotifyPropertyChanged is firing but the TextBlock never changes.
My only thought is that the TextBlock is linked to the wrong instance of my UpdateWPF class, but I don't see how that could be.
Thanks in advance for the help!
Here is my code...
MainWindow.xaml: (Really just dropped a textblock and set the binding)
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock HorizontalAlignment="Left" Height="50" Margin="139,123,0,0" TextWrapping="Wrap" Text="{Binding Path=GetValue,Mode=OneTime,UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="215"/>
</Grid>
</Window>
MainWindow.xaml.cs (Didn't change this code at all)
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
App.xaml (just commented out the StartupUri)
<Application x:Class="WpfApp1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1">
<!--StartupUri="MainWindow.xaml"-->
<Application.Resources>
</Application.Resources>
</Application>
App.xaml.cs (The meat and potatoes)
namespace WpfApp1
{
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
UpdateWPF uWPF = new UpdateWPF();
MainWindow w = new MainWindow();
w.DataContext = uWPF;
w.Show();
}
public class UpdateWPF : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
Console.WriteLine("NotifyPropertyChanged Fired.");
}
private int value = 5;
public UpdateWPF()
{
Timer aTimer = new System.Timers.Timer();
aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
aTimer.Interval = 1000;
aTimer.Enabled = true;
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
value++;
Console.WriteLine("Timer Event Fired. New Value is " + value.ToString());
NotifyPropertyChanged(nameof(GetValue));
}
public string GetValue => (value.ToString());
}
}
}
and the output
Timer Event Fired. New Value is 6
NotifyPropertyChanged Fired.
Timer Event Fired. New Value is 7
NotifyPropertyChanged Fired.
Timer Event Fired. New Value is 8
NotifyPropertyChanged Fired.
Timer Event Fired. New Value is 9
NotifyPropertyChanged Fired.
Timer Event Fired. New Value is 10
NotifyPropertyChanged Fired.
Timer Event Fired. New Value is 11
NotifyPropertyChanged Fired.

ASh found my problem, but he posted it as comment so I'll answer this. Since I've solved a few other things along the way, I post it all here.
The real issue was in my MainWindow.xaml
Text="{Binding Path=GetValue,Mode=OneTime,UpdateSourceTrigger=PropertyChanged}"
should have been
Text="{Binding Path=GetValue,Mode=OneWay}"
which will later become
Text="{Binding Path=Value,Mode=OneWay}"
As ASh pointed out, UpdateSourceTrigger was completely pointless in this case.
Loris156 pointed out some other issues. Some of that suggestion wrong, but I stumbled through the good points and came to this.
Naming Convention
I came from java were everything is GetThis or SetThis with this being the variable.
MS suggests Value(){ get; set;} with value being the variable. loris156 prefers the _value, which I see a lot of people doing. As it so happens "value" is a very bad example because of the use in the auto property setter. Nonetheless, I stuck with MS's way, since I'm using their compiler.
So my getter/setter became "Value".
I'll also point out that Lori156 used an int return, but this didn't work for the textbox I was using.
I had been previously unable to get the [CallerMemberName] to correctly work. This also had to do with my getter/setter. By excluding the setter, I had broken the auto property functionality. See understanding private setters
As I mentioned previously, the int getter doesn't work with a text box so I chose to convert the string back to an int in the setter. It really doesn't matter, since I'll not be using it anyway.
And here is the code changes for App.xaml.cs
private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
Console.WriteLine("NotifyPropertyChanged Fired.");
}
...
public string Value {
get => value.ToString();
private set => this.value = Int32.Parse(value);
}
I'm open to further suggestions if anyone has them.

As already said in the comment above, you have to set your UpdateSourceTrigger correctly. I also do not recommend the way you implemented your properties. They should look like this:
private int _value;
public int Value
{
get => _value;
set
{
_value = value;
OnPropertyChanged(nameof(Value));
}
}
Now you can just say Value = 0; instead of raising the PropertyChanged event manually.
If you change your NotifyPropertyChanged method to this:
private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgw(propertyName));
Console.WriteLine("NotifyPropertyChanged Fired.");
}
you don't have to pass the property name to NotifyPropertyChanged. The runtime will do this automatically, because of the CallerMemberNameAttribute.
Greetings
Loris
EDIT:
My property example was just an example for a property, not yours. But you just have to change the type and it will work.
This should work for you:
public class UpdateWPF : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
Console.WriteLine("NotifyPropertyChanged Fired.");
}
private int _value = 5;
public UpdateWPF()
{
Timer aTimer = new System.Timers.Timer();
aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
aTimer.Interval = 1000;
aTimer.Enabled = true;
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
var i = Convert.ToInt32(value);
Value = ++i;
}
public string Value
{
get => _value;
set
{
_value = value;
NotifyPropertyChanged();
}
}
}

Related

Questions about codes in the setter of a DependencyProperty in a user control

I am making a display of time/clock as a user control (ClockControl) on a page, the actual time model is driven from a DateTime object from another class (ClockTime). I have 2 textblocks on the ClockControl:
Textblock 'Second_update_by_binding' is bound to a dependency property 'Second_binded' which in turn is bound to model ClockTime 'Second'.
Textblock 'Second_update_by_manipulating' is updated by manipulating the value of the model ClockTime 'Second' so that it adds '0' at front if the 'Second' is only 1 digit (or less than 10)
I managed to achieve what I want regardless if its the best approach. However, I have come across a few questions that I don't quite understand why they happen. In particular, its the logic behind the code inside the getter/setter within the dependency property in the Clock user control that I am most confused of.
MainPage.xaml:
<Page x:Class="App1.MainPage" ...">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<local:ClockControl x:Name="useControl"
Second_binded="{Binding Second}"
Second_manipulated="{Binding Second}" />
</Grid>
</Page>
MainPage.cs:
public sealed partial class MainPage : Page
{
ClockTime clock;
DispatcherTimer Timer;
public MainPage()
{
clock = new ClockTime();
this.InitializeComponent();
this.DataContext = clock;
// I am adding this DispatchTimer to force the update of the text
// 'Second_update_by_manipulating' on the ClockControl since the
// Binding of Second_manipulated doesnt work
Timer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 1) };
Timer.Tick += Timer_Tick;
Timer.Start();
}
private void Timer_Tick(object sender, object e)
{
useControl.Second_manipulated = clock.Second.ToString();
}
}
ClockTime model:
class ClockTime : INotifyPropertyChanged
{
public ClockTime()
{
var Timer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 1) };
Timer.Tick += Timer_Tick;
Timer.Start();
}
private void Timer_Tick(object sender, object e)
{
Second = DateTime.Now.Second;
}
//===================================================
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
//===================================================
private int _second;
public int Second
{
get { return this._second; }
set
{
if (value != this._second)
{
this._second = value;
NotifyPropertyChanged("Second");
}
}
}
}
ClockControl.xaml:
<UserControl x:Class="App1.ClockControl" ...>
<Grid>
<StackPanel>
<TextBlock x:Name="Second_update_by_manipulating" />
<TextBlock x:Name="Second_update_by_binding" Text="{Binding Second_binded}" />
</StackPanel>
</Grid>
</UserControl>
ClockControl.cs:
public sealed partial class ClockControl : UserControl
{
public ClockControl()
{
this.InitializeComponent();
(this.Content as FrameworkElement).DataContext = this;
}
//==================================
public String Second_binded
{
get {
return (String)GetValue(Second_bindedProperty);
}
set {
Debug.WriteLine(" in Second_binded ");
SetValue(Second_bindedProperty, value);
}
}
public static readonly DependencyProperty Second_bindedProperty =
DependencyProperty.Register(
"Second_binded",
typeof(string),
typeof(ClockControl),
new PropertyMetadata(""));
//==================================
public String Second_manipulated
{
get
{
return (String)GetValue(Second_manipulatedProperty);
}
set
{
Second_update_by_manipulating.Text = (Convert.ToInt32(value)<10) ? "0"+value : value;
Debug.WriteLine(" in Second_manipulated ");
SetValue(Second_manipulatedProperty, value);
}
}
public static readonly DependencyProperty Second_manipulatedProperty =
DependencyProperty.Register(
"Second_manipulated",
typeof(string),
typeof(ClockControl),
new PropertyMetadata(""));
//==================================
}
so here are my questions:
Why the debugging code Debug.WriteLine(" in Second_binded "); within the setter of Second_binded dependency property in ClockControl is never called when the 'Second' in model ClockTime is being updated.
The code Debug.WriteLine(" in Second_manipulated "); within the setter of Second_manipulated dependency property in ClockControl is called and the code Value_1.Text = (Convert.ToInt32(value)<10) ? "0"+value : value; is executed to change the text on the ClockControl, but it only works after adding another DispatchTimer withtin the MainPage.cs to force the code useControl.Second_manipulated = clock.Second.ToString(); to update the time. Why I have to do it this way to update Second_manipulated, even though I have already set Second_manipulated binded to Second in the MainPage.xaml?
Any ideas and comments to enlighten my knowledge on C# is very welcomed.
thanks
Ken
If you want to track changes in a DependencyProperty, you have to register a PropertyChangedCallback handler. The property setter is not triggered by the system when the value of a binding is updated.
public static readonly DependencyProperty Second_bindedProperty =
DependencyProperty.Register(
"Second_binded",
typeof(string),
typeof(ClockControl),
new PropertyMetadata("", PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
Debug.WriteLine(" in Second_binded callback");
}
The reason why your setter is hit in the second property is because you force it yourself with useControl.Second_manipulated = clock.Second.ToString();, which is not the correct way of using bindings.

wpf-like trigger in code behind

I'm writing a simple game in c# using wpf. In my xaml.cs I create a Game object that does all the work.
I need to be able to reload the window on a certain propertyChange in the Game object. I already have this in my xaml:
<TextBlock x:Name="PhaseTB" Text="{Binding Phase, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center"/>
and Phase is part of the Game object:
public class Game : INotifyPropertyChanged
{
private static Game _instance;
private GamePhase phase;
public static Game Instance
{
get
{
if (_instance == null)
_instance = new Game(10, 10);
return _instance;
}
}
public GamePhase Phase
{
get { return phase; }
set
{
phase = value;
OnPropertyChanged("Phase");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
That all works fine and the textbox is updated according to the Phase value.
My question is: How do I call on a method whenever the Phase value changes? The text changes just fine, but I have no idea how to call a code-behind method.
(sorry for noob-question. I have inherited the code and don't really understand how the whole thing works)
You need to subscribe to the event PropertyChanged:
`<yourDataContext>`.PropertyChanged += propertyChangedHandler;
where <yourDataContext> is your DataContext and propertyChangedHandler is the event handler.
Note - You can access the Data Context like this:
((Game)textBox1.DataContext).PropertyChanged += propertyChangedHandler;
or
((Game)this.DataContext).PropertyChanged += propertyChangedHandler;
if your TextBox inherits the DataContext from the Window/Page's main class.
That event exists precisely for the very purpose you mentioned.
And as for where this code should be put, I would generally put it in the constructor since it's assigning event handlers:
public class MainWindow() {
public MainWindow() {
InitializeComponent();
// Here go the event handlers....
}
}
More info:
Property Changed Event
Data Context

TextBox leave causes PropertyChanged get fired twice

When the Text property of a TextBox is bound to an object property which that object implements INotifyPropertyChanged, the event PropertyChanged may fire two times while having the same value:
1) when the text is changed inside the TextBox 2) when the control is leaving from it.
Consider these methods of a form:
private void Form1_Load(object sender, EventArgs e)
{
TextBox textBox = new TextBox();
TextBox secondTextBox = new TextBox();
secondTextBox.Location = new Point(0, 100);
this.Controls.Add(textBox);
this.Controls.Add(secondTextBox);
MyClass instance = new MyClass();
instance.PropertyChanged += instance_PropertyChanged;
textBox.DataBindings.Add("Text", instance, "Id", true, DataSourceUpdateMode.OnPropertyChanged);
}
private void instance_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine(e.PropertyName + " changed");
}
and the back-end class:
private class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
int _id;
public int Id
{
get
{
return _id;
}
set
{
_id = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Id"));
}
}
}
To reproduce the problem, type something in the upper textbox, check the console, and then enter the lower textbox and check again the console. Upon leaving, a property change is reported. Why?
The default value of Binding.DataSourceUpdateMode property is OnValidation. In this configuration the data source is being only updated when Validating event occurs. In your example you use OnPropertyChanged mode, so you additionally request update of the data source whenever the text is changed inside the TextBox.
It is the default behaviour i.e. the Binding class was implemented in this way. If you want more details, you can examine Binding.Target_PropertyChanged and Binding.Target_Validate methods with a reflector.
From my perspective this behaviour isn't a problem but you need to change the implementation of the setter in the following way:
set
{
if(_id != value)
{
_id = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Id"));
}
}
Even if we assume that the implementation of Binding class is wrong, I think that it is a good practise to check whether a value has changed before generating PropertyChanged event.
Based on Michal's answer, I found the solution in switching off the CausesValidation property of TextBox as:
textBox.CausesValidation = false;

Updating UI based on property change in a view model

I'm new to WPF, so there's probably something basic I'm missing here. I have an application that looks like this:
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test Application" Height="647" Width="723" Background="#88B0FF">
<DockPanel Name="MainDock">
<Button DockPanel.Dock="Top" Margin="5,0,5,0" x:Name="PingButton" Click="PingButton_OnClick">Ping</Button>
<TextBox Text="{Binding Path=Output}" />
</DockPanel>
</Window>
The code-behind is like this:
public partial class MainWindow : Window
{
private Model _applicationModel = new Model();
public Model ApplicationModel {
get { return _applicationModel; }
set { _applicationModel = value; }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = ApplicationModel;
ApplicationModel.Output = "Not clicked";
}
private void PingButton_OnClick(object sender, RoutedEventArgs e)
{
ApplicationModel.Output = "Clicked";
}
}
I have a small class called Model that implements INotifyPropertyChanged.
public class Model : INotifyPropertyChanged
{
public string Output { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I run this application, and the text box displays the text "Not clicked". When I click the button, I would expect that the text would change. It does not. The "ApplicationModel" object is updated, and this is reflected in the DataContext; I have a breakpoint in the OnPropertyChanged() method, however, and it appears that it's never being called.
What am I doing wrong?
OnPropertyChanged() isn't being called because you're not calling it.
There is no special magic that wires up calls to OnPropertyChanged by itself, so you need to do it yourself.
Specifically, you should modify your Output property to call it when it changes (and it wouldn't hurt to do the same for your ApplicationModel property:
private string output;
public string Output
{
get { return output; }
set
{
if (output != value)
{
output = value;
OnPropertyChanged("Output");
}
}
}
If you're targeting .NET 4.5 you can utilize the CallerMemberName attribute to reduce boilerplate code; This article explains how to do so. Then you'll have something like this:
private string output;
public string Output
{
get { return output; }
set { SetProperty(ref output, value); }
}
If you're using .NET 4.0 or below, you can use expression trees, as described in this answer.

DependencyProperty data binding

I try to bind my UI to custom DependencyProperty:
<Window.Resources>
<local:Localization x:Key="Localization" xmlns:x="#unknown" xmlns:local="#unknown"/>
</Window.Resources>
<Grid Name="mainStack" DataContext="{StaticResource Localization}">
<Button Padding="10,3" Margin="5" Content="{Binding BtnAdd}" Command="New"/>
</Grid>
Also I have class "Localization":
class Localization : DependencyObject, INotifyPropertyChanged
{
public static DependencyProperty BtnAddProperty;
static Localization()
{
BtnAddProperty = DependencyProperty.Register("BtnAdd", typeof(string), typeof(Localization));
}
public string BtnAdd
{
set
{
SetValue(BtnAddProperty, value);
}
get
{
return (string)GetValue(BtnAddProperty);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
handler.Invoke(this, e);
}
}
public Localization()
{
BtnAdd = MainWindowRes.BtnAdd;
}
public void SwitchLanguage()
{
BtnAdd = MainWindowRes.BtnAdd;
OnPropertyChanged("BtnAdd");
}
}
First time my UI element gets my property value. But when I use my method SwitchLanguage(), property gets new data, and UI still have first value.
Can someone help me please?
P.S.
Sorry, for my English.
Eugene
I tried your example, everything seems to work.
But there are some pitfalls:
There's a framework class called Localization, so make sure you don't mix up!
How do you call SwitchLanguage()? You have to call this on the right instance! (For example in the Code Behind:
var res = (Localization)Resources["Localization"];
res.SwitchLanguage();
Can't really spot any mistake which would make the binding not update, but there are some other things that need to be fixed, the DP field should be readonly and you should not call any property change notifications for DPs as they have an internal mechanism for notifications (inside SetValue).
Are you sure the value of MainWindowRes.BtnAdd is actually different in SwitchLanguage from the value it has in the constructor?

Categories