Two way Binding in WPF does not work with static members - wpf

Matt Hamilton told me an interesting fact about WPF: binding in two way mode with a static variable is possible in version 4.5.
Unfortunately V4.5 ist still beta, I decided to change my code to get my app finally run correct.
But - still I have similar problems, here we go:
I have a very simple class 'RecallConnectionSettings'. This member of this class should be accessible from everywhere in the code, so I decided to make them static (like this):
public class RecallConnectionSettings
{
private static string Server {get;set;}
}
As you can see: there is only one variable 'Server'.
Now what I want is to make 2WayMode binding from a TextBox Text-property to that 'Server' value.
So I tried this:
<UserControl....>
<UserControl.Resources>
<local:RecallConnectionSettings x:Key="recallConf"/>
</UserControl.Resources>
<TextBox Text="{Binding Source={StaticResource recallConf}, Path=Server,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ... Name="txtServerAdress" />
</UserControl>
This works great when I change the value in the textbox - but not from the other side.
If I change the 'Server' value (by hand), the text-property in my textbox will not update.
Of course not - as I now know I have to implement INotifyProperty in my RecallConnectionSettings-class.
Then it looks like this:
public class RecallConnectionSettings : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private static string s_server;
public static string Server
{
get { return s_server; }
set
{
s_server = value;
OnPropertyChanged("Server");
}
}
public static event PropertyChangedEventHandler PropertyChanged;
protected static void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
Well - this can't work too. Because there are only static methods, I can't use the class instance to call the event:
PropertyChanged(this, new PropertyChangedEventArgs(name));
So - what to do now?
I thought about using a singleton, so I did this:
public class RecallConnectionSettings : INotifyPropertyChanged
{
private static RecallConnectionSettings instance;
private RecallConnectionSettings(){}
public static RecallConnectionSettings Instance
{
get
{
if(instance == null)
{
instance = new RecallConnectionSettings();
}
return instance;
}
}
// ... here comes the other stuff
}
To make it work, I also have to prepare my UserControl, so I did this:
...
<UserControl.DataContext>
<local:RecallConnectionSettings/>
</UserControl.DataContext>
...
At this point there is no need to go on trying, because for doing this, the default constructor must be public.
No matter what I am doing: it does not work.
Seems to me that I still do not understand how that works - would you be so kind and show me the trick ?

Keep the singleton solution and replace this:
...
<UserControl>
<UserControl.DataContext>
<local:RecallConnectionSettings/>
</UserControl.DataContext>
...
</UserControl>
...
By this:
...
<UserControl DataContext="{x:Static local:RecallConnectionSettings.Instance}">
...
</UserControl>
...

WOW -
thanks Nicolas, that works !
For the other readers - here is what you have to code for the textbox now:
<TextBox Text="{Binding Path=Server, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Name="txtServerAdresse"/>

Related

WPF C# INotifyPropertyChanged doesn't fire up

I am building a WPF application that as part of its flow, checks for network connectivity and display the IP address in a TextBlock.
Now I am trying to update the TextBlock Text property everytime the IP address changes for whatever reason.
I have the IP address change working fine, but i could not get INotifyPropertyChanged to work.
I read all the possible solutions and implementations but I couldn't come up with a working code.
The public property gets the value from a static string from the Network Helper class.
So, the code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public string ipAddress
{
get { return NetworkStatus.localIP; }
set
{
if (value != NetworkStatus.localIP)
{
NetworkStatus.localIP = value;
NotifyIPChanged("IpAddress");
}
}
}
private void NotifyIPChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
XAML:
<TextBlock x:Name="ipTxt"
TextWrapping="Wrap"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{Binding DataContext.ipAddress}"
Height="30"
Width="110"
Margin="-30,10,0,-10"
/>
UPDATE
NetWorkStatus.cs -- static bool IsNetworkAvailable()
...
if (statistics.BytesReceived > 0 || statistics.BytesSent > 0)
{
IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
localIP = host.AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToString();
return true;
}
As you can see this method sets a static string "localIP". This is then evaluated by IpAddress property.
Why the TextBlock Text property doesn't get updated when the IP Address changes?
Rename the property to IpAddress so that it adheres to widely accepted naming conventions.
public string IpAddress
{
get { return NetworkStatus.localIP; }
set
{
if (value != NetworkStatus.localIP)
{
NetworkStatus.localIP = value;
NotifyPropertyChanged();
}
}
Use the CallerMemberName attribute on the propertyName parameter of your notification method, so that you do not have to write the name explicitly.
using System.Runtime.CompilerServices;
...
private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Bind it correctly. The current DataContext is already used as source object of the Binding. You must not add it to the property path.
<TextBlock Text="{Binding IpAddress}" ... />
In a possible next step you might want to separate the view from the view model and put the property in a separate class:
public class ViewModel : INotifyPropertyChanged
{
public string IpAddress
{
get ...
set ...
}
...
}
and assign the Window's DataContext to an instance of the view model class:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
I think you need to take a closer look to how WPF works.
As a remark, there is no need to implement INotifyPropertyChanged in code behind. If you are using events, then you can automatically refresh the properties of the targeted UI element.
However, using code behind is not a good practice in our days. You should take a look at MVVM pattern. You have there a Model, View and ViewModel. The ViewModel should implement INotifyPropertyChanged.
The fact is that your code is in my opinion absolutely wrong. The naming is not fine : when you implement INotifyPropertyChanged you should not implement it only for a property, and the name should not look like : NotifyIPChanged, instead you should use RaisePropertyChanged, NotifyPropertyChanged or OnPropertyChanged. In setters you should not refresh something else, but only the property that you are targeting, because otherwise the Single Responsability principle is violated, as in your case. Also a bad practice is to bind to Code Behind.
I hope this post would make you to read more about MVVM and WPF. Good luck!
is it possible that the Event doesnt react, because the first letter of IpAdress is an Upper?
NotifyIPChanged("IpAddress");
public string ipAddress
Text="{Binding DataContext.ipAddress}"

access TextBoxes inside DataTemplate

I know question similar to this has asked several times in SO. But non of them address my issue and have some difficulty with understanding those answers. This is my situation; I have a ItemsControl I have used ItemTemplate and bound some data.
<Window.Resources>
<DataTemplate x:Key="AdditionalFieldTemlate">
<Grid>
<TextBlock Text="{Binding InfoName}"/>
<TextBox Text="{Binding InfoValue,Mode=TwoWay}" Name="CustomValue"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding AdditionalInformation}" x:Name="additionalInfo" ItemTemplate="{DynamicResource AdditionalFieldTemlate}"/>
</Grid>
I need to set TextBox text to empty(all the textboxes text inside datatemplate) once click on a Button. Don't know how to access these textboxes. Please help me.
You don't normally access the TextBoxes (the look) ....you access the data that is being bound to.
So you can just alter the "data" in your collection as follows:
foreach (var item in AdditionalInformation)
{
item.InfoValue = "";
}
The "TextBoxes" will then be emptied.
Make sure you have implemented INotifyPropertyChanged on the class being used by AdditionalInformation....so that when the InfoValue property is altered it raises a notification.
The text in the textboxes is databound to the InfoValue property of your class. Implement the class and proprty like this:
class InfoClass: INotifyPropertyChanged
{
private string _infoValue;
...
public string InfoValue
{
get { return _infoValue; }
set
{
_infoValue = value;
OnNotifyPropertyChanged("InfoValue")
}
}
...
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
Then do what colinsmith suggests in your button click handler (or command if you went with the MVVM approach). The binding will be notified to the change and the view will be updated.

A weird behavior of PropertyChanged UpdataSourceTrigger in WPF

I have an entity like this:
public class Person
{
public string Name { get; set; }
public Person()
{
Name = "Godspeed";
}
}
Then I have three textbox and a button in XAML:
<Window x:Class="WpfApplication19.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication19"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:Person />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding Name,UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Name,UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Name,UpdateSourceTrigger=PropertyChanged}" />
<Button Click="Button_Click">Click</Button>
</StackPanel>
</Window>
The weird thing is that, the entity "Person" doesn't implement the INotifyPropertyChanged, but when one text box is changed, it modifies the source(Person object), but we didn't raised the property changed event but the rest two textboxes automatically changed.
When the button clicks, we update the source directly by code like:
((Person)DataContext).Name = "Godspeed";
It doesn't update. So what I think is that if the Person class implement the INotifyPropertyChanged, this behavior is normal, but now the class doesn't implement the interface, but it update the interface too. Please info me the reason if you have some clue. Thanks.
The reason is PropertyDescriptor, see the following thread,
the same question is being asked: How does the data binding system know when a property is changed?
Here is two of the answers
I think the magic lies in the binding system's use of
PropertyDescriptor (SetValue presumably raises a ValueChanged - the
PropertyDescriptor is likely shared, while the events are raised on a
per-object basis).
I'm not at Microsoft, but I can confirm it. If PropertyDescriptor is
used to update the value, as it will be, then relevant change
notifications are automatically propagated.
Edit
You can verify this by naming the Person DataContext object
<Window.DataContext>
<local:Person x:Name="person"/>
</Window.DataContext>
and add the following code to the MainWindow ctor
public MainWindow()
{
InitializeComponent();
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(person);
PropertyDescriptor nameProperty = properties[0];
nameProperty.AddValueChanged(person, OnPropertyChanged);
}
void OnPropertyChanged(object sender, EventArgs e)
{
MessageBox.Show("Name Changed");
}
Once you change the value on any of the three TextBoxes, you'll end up in the event handler OnPropertyChanged.
Well, as you said, you just have to implement INotifyPropertyChanged
The PropertyChanged event is used when you set a property from code and need to reflect this change to your UI (UI will cath the PropertyChanged event, and thanks to your UpdateSourceTrigger the UI will be updated). The other side (changing from UI) does not need any PropertyChanged, this is why you get this behavior
Just try it like that:
public class Person : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
/// <summary>
/// Property Changed Event
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Property Changed
/// </summary>
/// <param name="propertyName"></param>
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");
}
}
}
Using this code, when you set the Name, the PropertyChanged event will be fired and therefore update UI accordingly :)
It works not only with updatesourcetrigger=propertychanged, but with default (lost focus) value too. In addition to what #Meleak said, I want to point that it is good behaviour. Any changes made by ui are propagated to all binding targets. Binding engine wants to propagate this changes to all controls at once. If you make changes through code, and not implement INotifyPropertyChanged - changes made from code are not reflected at all. Again, for all controls with the same binding source. All controls works in the synchronized way with such implementation.

PropertyChangedEventHandler always null in silverlight binding

I've been trying to resolve this issue for some time.
I'm trying to bind a TextBlock's text to a string property using the INotifyPropertyChanged interface. For some reason the PropertyChangedEventHandler is always null when the value of the property is changed thus the target never gets updated...
Any suggestions?
Code below:
XAML code:
<UserControl x:Class="MoleDashboard.MainPage"
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:basics="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:datacontrols="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Data"
xmlns:prop="clr-namespace:MoleDashboard"
<UserControl.Resources>
<prop:YearScreen x:Key="YearScreenProps"/>
</UserControl.Resource>
<TextBlock Margin="10 5" x:Name="DataGridLabel" Visibility="Visible" Text="{Binding YearProperty, Source={StaticResource YearScreenProps}, Mode=OneWay}"/>
Bound property code:
public class YearScreen : INotifyPropertyChanged
{
private string metricProperty;
private string yearProperty;
public event PropertyChangedEventHandler PropertyChanged;
public YearScreen()
{
}
public string YearProperty
{
get { return yearProperty; }
set { yearProperty = value; this.OnPropertyChanged("YearProperty"); }
}
public string MetricProperty
{
get { return metricProperty; }
set { metricProperty = value; this.OnPropertyChanged("MetricProperty"); }
}
public void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
This is based on the comments provided and not just the question above.
Basically the problem is that you are creating updating a second instance of your ViewModel (called a YearScreen in your code) and updating that.
A YearScreen is already being created and bound to your Xaml, by the inclusion of:
<UserControl.Resources>
<prop:YearScreen x:Key="YearScreenProps"/>
</UserControl.Resource>
You are then creating a second ViewScreen elsewhere in code (via new ViewScreen()) and updating that, however there is no connection between the 2 ViewScreen instances, so nothing will update in the Xaml page.
One Possible (quick) solution:
Create your YearScreen as a singleton. That is add a static accessor of type YearScreen in the class and set it from the constructor.
public class YearScreen : INotifyPropertyChanged
{
private static YearScreen _This;
public static YearScreen This { get { return _This; } }
[snip]
public YearScreen()
{
_This = this;
}
The you can access the "single" instance of your YearScreen from elsewhere using the static singleton accessor e.g.:
YearScreen.This.YearProperty = DateTime.Now.ToString():
There are better patterns for sharing ViewModels than singletons, but that will get you going.
The pattern you started with is ViewFirst creation (the view creates the ViewModel). ModelFirst does the opposite, but is bad as the model knows how it is displayed. Using a controller object to create the View and ViewModel and connect them is a better alternative, but that is then getting quite complicated. Using injection of single instance objects is a better option, but involves a whole load of new concepts. Lookup Silverlight Prism after you solve your current problems.
Instead of creating the ViewModel in the resources you should set it into the DataContext of the view from external code.
If you really want to put it in the Resources like that you can get it out of the resources in the code behind Loaded method or in the constructor after the initializecomponent call. Like so:
private YearScreen model;
public MainPage()
{
this.Loaded += MainPage_Loaded;
this.InitializeComponent();
}
void MainPage_Loaded(object sender, EventArgs e)
{
this.model = (YearScreen)this.Resources["YearScreenProps"];
}
Maybe expose it as a property so you can then access it externally. But personally I'd rather create the model externally than pass it into the View instead. Put it into the DataContext.

silverlight 3 collection binding

Someone please help me understand why this binding does not work...
I have a class called SelectionManager with a property called 'dates' which is populated by a WCF service. The property is an array of structs which bundles a DateTime and an integer count of business objects.
public class SelectionManager : INotifyPropertyChanged {
... other properties ...
public DQMServiceDateCountPair[] dates { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName) {
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); }
}
I have another class called DateSelector which has a DependencyProperty called 'pairs' setup to be the binding target of 'dates'.
public partial class DateSelector : UserControl {
... other stuff ...
public static readonly DependencyProperty pairsProperty = DependencyProperty.Register(
"pairs",
typeof(DQMServiceDateCountPair[]),
typeof(DateSelector),
new PropertyMetadata(new DQMServiceDateCountPair[0])
);
public DQMServiceDateCountPair[] pairs {
get { return (DQMServiceDateCountPair[])GetValue(pairsProperty); }
set {
Debug.WriteLine("adding dates");
SetValue(pairsProperty, value);
dateMode = DateMode.Years;
}
}
}
In my MainPage.xaml, I have a line like this:
<date:DateSelector x:Name="dateSelector" pairs="{Binding dates}" />
It's weird, because all my other bindings in MainPage.xaml update correctly, including a ComboBox bound to 'dates'. My UserControl however, will not update. The Debug.Writeline doesn't get called in the set statement of the 'pairs' property.
In playing around with it, I've tried making the DQMServiceDateCountPair[] property into an ObservableCollection and implementing INotifyCollectionChanged, but that doesn't help.
If I leave either the source property or the target property as an array, and make the other an ObservableCollection, then I get a binding error that says it can't automatically convert one to the other, so Silverlight seems aware of the binding, it just doesn't update it.
Can anyone help?
P.S. I'm using Silverlight 3.
Try changing your code as follows:
1. Add DataMember/DataContract attributes
2. Make "set" public
[DataContract]
public class SelectionManager : INotifyPropertyChanged {
[DataMember]
public DQMServiceDateCountPair[] dates { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName) {
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); }
}
Whats actually wierd is that the other stuff is working when you've coded your class the way you have. My guess is that the dates array gets set by some code that runs internally in your selection manager on completion of a WCF request.
Howerver whilst you have implemented INotifyPropertyChanged you aren't actually raising the event that it defines. You can't really combine INotifyPropertyChanged with the Auto-property C# syntax. You need this:-
public SelectionManager : INotifyPropertyChanged
{
private DQMServiceDateCountPair[] myDates;
public DQMServiceDateCountPair[] dates
{
get { return myDates; }
set
{
myDates = value;
NotifyPropertyChanged("dates");
}
// rest of your code
}
So, here's what what going on. The binding has been working perfectly well this whole time. For the past week I've been struggling with this, it's been happily updating along--but because of a faulty assumption on my part, I could never see it.
In case anyone else harbors this faulty assumption, let me spell it out:
The GetValue and SetValue calls are not made automatically by virtue of the fact that you are declaring a Dependency Property. The "new PropertyMetadata()" part of the declaration has an overload that takes a callback method. In this callback method, you have to set the property value yourself. For instance, in my code, I made this the PropertyMetadata call:
new PropertyMetadata(new PropertyChangedCallback(OnPairsPropertyChanged))
and the callback method reads like this:
private static void OnPairsPropertyChanged( DependencyObject d, DependencyPropertyChangedEventArgs e ) {
((DateSelector)d).pairs = (DQMServiceDateCountPair[])e.NewValue;
}
Thanks to everyone who tried to help!

Resources