DependencyProperty data binding - c#

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?

Related

Trouble updating WPF with UpdateSourceTrigger = PropertyChanged

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();
}
}
}

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

Windows 8.1 UI not updating when MVVM property set is called by UI element

My problem is, that the UI isn't updating if they call the setter of the property which they binded to.
Here's a sample to make it clear:
Let's say I have a textbox binded to a property like this.
<TextBox PlaceholderText="Task Name..." FontSize="24"
Text="{Binding TaskName, Mode=TwoWay}" />
And this is my property:
public string TaskName
{
get
{
return _taskName;
}
set
{
_taskName = "something";
RaisePropertyChanged();
}
}
If I write something into the textbox then "something" should appear inside of it, after it loses focus, but there isn't any change. However, if I change the value of the property with code, like this:
TaskName = "something";
Then the change will appear on the UI as well.
Some further information.
This is how I implemented the INotifyPropertyChange interface:
public class ViewModelBase : INotifyPropertyChanged
{
public static Navigator NavigationService;
public static void SetNavigationService(Navigator service)
{
NavigationService = service;
}
protected void GoBack()
{
NavigationService.GoBack();
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I really don't know why is it behave like this. I search for it for hours, but can't find anything.
in the setter of the property you need to call
RaisePropertyChanged(x => x.TaskName)

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.

XAML Binding to property

I have check box in my XAML+C# Windows Store application. Also I have bool property: WindowsStoreTestApp.SessionData.RememberUser which is public and static.
I want check box's property IsChecked to be consistent (or binded, or mapped) to this bool property.
I tried this:
XAML
<CheckBox x:Name="chbRemember1" IsChecked="{Binding Mode=TwoWay}"/>
C#
chbRemember1.DataContext = SessionData.RememberUser;
Code for property:
namespace WindowsStoreTestApp
{
public class SessionData
{
public static bool RememberUser { get; set; }
}
}
But it doesn't seem to work. Can you help me?
<CheckBox x:Name="chbRemember1" IsChecked="{Binding Path=RememberUser, Mode=TwoWay}"/>
public class SessionData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
bool _rememberUser;
public bool RememberUser
{
get
{
return _rememberUser;
}
set
{
_rememberUser = value;
NotifyPropertyChanged("RememberUser");
}
}
}
You need to implement some form of change notification in order for your check box to be "aware" of any changes to the property. The best bet is to use one of the many MVVM frameworks out there, if not, implement INotifyPropertyChanged in your ViewModel.
Also, typically in WPF, we do not set the DataContext of individual controls but set the DataContext of the Window or User Control to a ViewModel...
Here is an example of a property with change notification through one of the MVVM frameworks:
private bool createTrigger;
public bool CreateTrigger
{
get { return createTrigger; }
set { createTrigger = value; NotifyPropertyChanged(m => m.CreateTrigger); }
}
As you can see a simple auto-implemented property cannot be used for data binding in WPF...
I'd recommend going through Data Binding Overview over on MSDN...
You cannot bind to static properties as static properties cannot raise the PropertyChanged event. You will, of course, need INotifyPropertyChanged. But that is not relevant with static properties. You simply cannot bind to static properties. (You can in WPF and Silverlight)
Try like this, note that the property is not static but the backing field is:
public class SessionData : INotifyPropertyChanged
{
private static bool _rememberUser;
public event PropertyChangedEventHandler PropertyChanged;
public bool RememberUser
{
get { return _rememberUser; }
set
{
_rememberUser = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
this.DataContext = new SessionData();
<CheckBox x:Name="chbRemember1" IsChecked="{Binding RememberUser, Mode=TwoWay}"/>

Categories