While investigating on a seemingly unrelated issue, I've hit some unexpected binding behaviour. Having
class StringRecord : INotifyPropertyChanged
{
public string Key {get; set; } // real INPC implementation is omitted
public string Value { get; set; } // real INPC implementation is omitted
...
}
class Container
{
public ObservableKeyedCollection<string, StringRecord> Params { get; set; }
...
{
Now, when a TextBox is bound to one of the collection items in obvious way
<TextBox Text="{Binding Params[APN_HOST].Value}" />
the PropertyChanged event of the StringRecord's instance doesn't fire upon editing the text. But, rewriting it as
<TextBox DataContext="{Binding Params[APN_HOST]}" Text="{Binding Value}" />
makes the miracle, and the event begins to fire correctly.
Why?
In the second xaml sample the binding is observing a StringRecord which implements INotifyPropertyChanged and thus is notified of changes to the object.
In the first xaml sample it isn't clear what you are binding to.
If you set the DataContext to Container the binding is observing an object that doesn't implement the INotifyPropertyChanged interface. Because the path is still correct the Value property can still be read but you are missing out on the notifications.
The ObservableKeyedCollection class needs to fire PropertyChanged events as well as CollectionChanged events if you want the binding system to know about changes to properties accessed via string indexes.
To do this, make ObservableKeyedCollection implement INotifyPropertyChanged, and then add the following code to OnCollectionChanged:
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Item[]"));
}
See also this answer: PropertyChanged for indexer property.
Related
I have Car.xaml:
<CheckBox Name="carA" IsChecked="{Binding CarACheck, Mode=TwoWay, FallbackValue=true}" />
Then in Car.xaml.cs:
public Car()
{
//...
DataContext = this;
}
public bool CarACheck
{
get => carA.IsChecked;
set => carA.IsChecked = value;
}
When I run it and click on CheckBox, the app crashes with the error below:
set => carA.IsChecked = value;
System.StackOverflowException
An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll
Cause of Error
The reason is that your setter is called over and over.
The CheckBox is clicked.
The IsChecked property of the CheckBox is set.
The binding sets value on the bound property CarACheck.
The setter of CarACheck sets the IsChecked property of the CheckBox.
Go to 3.
In the long run this causes the stack to overflow and the application crashes.
An MVVM Solution
It seems that you are trying to build a view that displays data about cars. What I see from your sample is that you mix your data and most likely business logic with the user interface code. You should not do that, because it harms code quality and maintainability in the long run. A better and sustainable approach is to separate your view from data and business logic. This is what the MVVM pattern is about.
In your sample code, we have a view, which we would call CarView. This is where the CheckBox is defined along with the rest of the user interface to represent a car. The data is exposed to the view through a separate view model type called CarViewModel. This view model contains properties that are bound from the view.
When using simple properties, regardless of having a backing field or not, the bindings are not able to determine changes to properties from the view model side. This is why you have to implement the INotifyPropertyChanged interface, which provides an event for this purpose. The event is raised, whenever a property is changed. Usually this is in done in the setter.
public class CarViewModel : INotifyPropertyChanged
{
private bool _carACheck;
public bool CarACheck
{
get => _carACheck;
set
{
if (_carACheck == value)
return;
_carACheck = value;
OnPropertyChanged(nameof(_carACheck));
}
}
// ...other code, maybe even setting CarACheck.
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In the view, simply assign an instance of this view model to the DataContext in XAML or code-behind, e.g.:
public Car()
{
// ...other code.
DataContext = new CarViewModel();
}
In your view, you do not need a name for the CheckBox, as there is no explicit reference to it. The Mode=TwoWay declaration can be removed, too, because the IsChecked property binds two-way by default.
<CheckBox IsChecked="{Binding CarACheck, FallbackValue=true}"/>
Remarks about your original code: I hope that you can see the benefits of the MVVM pattern. Of course, after reviewing this solution, you could simply add a backing field and INotifyPropertyChanged to your current code to make it work, too, but the lesson to learn here is that this separation is valuable and worth investing although it might seem more verbose. See also:
Data binding overview (WPF .NET)
How to: Implement Property Change Notification
WPF project + Prism 7 + (Pure MVVM pattern)
Simple, I have TextBox which need to be cleared when some button is pressed (without the violation to the MVVM pattern)
<Button Command="{Binding ClearCommand}"/>
<TextBox Text="{Binding File}">
<i:Interaction.Behaviors>
<local:ClearTextBehavior ClearTextCommand="{Binding ClearCommand, Mode=OneWayToSource}" />
</i:Interaction.Behaviors>
</TextBox>
ViewModel
public class ViewModel {
public ICommand ClearCommand { get; set; }
}
Behavior
public class ClearTextBehavior : Behavior<TextBox>
{
public ICommand ClearTextCommand
{
get { return (ICommand)GetValue(ClearTextCommandProperty); }
set
{
SetValue(ClearTextCommandProperty, value);
RaisePropertyChanged();
}
}
public static readonly DependencyProperty ClearTextCommandProperty =
DependencyProperty.Register(nameof(ClearTextCommand), typeof(ICommand), typeof(ClearTextBehavior));
public ClearTextBehavior()
{
ClearTextCommand = new DelegateCommand(ClearTextCommandExecuted);
}
private void ClearTextCommandExecuted()
{
this.AssociatedObject.Clear();
}
}
The problem is the command in the ViewModel is always null (it did not bound to the command in the Behavior), Although I made sure that it is initialized in the behavior class.
NOTE: please do NOT suggest to set the File property to empty string, because this is just an example, In my real case, I need to select all the Text, so I really need an access to the AssociatedObject of the behavior
If i understood your Question correctly, you want to know why the ICommand in the ViewModel is not set to the DelegateCommand defined in the Behaviour.
The Problem is, that the ICommand and the DelegateCommand do not have a direct connection. I assume you may misunderstood how a Binding works and what happens by using those.
First of all, the ICommand is 'comes' from a Class and is therefore a reference Type.
Second, the reference to the ICommand is saved within the DependencyProperty ClearTextCommandProperty.
Third, by using a Binding in the XAML something like this happens as C# code:
Binding binding = new Binding();
binding.Path = new PropertyPath("ClearTextCommand");
binding.Source = ClearCommand;
BindingOperations.SetBinding(TextBox.ClearTextCommandProperty, binding);
Now the important thing: I don't know exactly which assignment comes first, but both lines will override the Value reference in the ClearTextCommandProperty!
//either here
SetBinding(TextBox.ClearTextCommandProperty, binding);
//or here
ClearTextCommand = new DelegateCommand(ClearTextCommandExecuted);
//Which could be written as
SetValue(ClearTextCommandProperty, new DelegateCommand(ClearTextCommandExecuted));
At no point there is an assignment like this:
ViewModel.ClearCommand = SomeICommand;
Therefore it is Null, as #Andy mentioned
Edited to match select all Text
Additionally, i suggest you drop this complex stuff and use the full potential of the Interactivity Package like this:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<utils:SelectAllText TargetName="TextToSelect"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<TextBox x:Name="TextToSelect" Text="{Binding File}"/>
And the SelectAllText
public class SelectAllText : TargetedTriggerAction<TextBox>
{
protected override void Invoke(object parameter)
{
if (Target == null) return;
Target.SelectAll();
Target.Focus();
}
}
If you take a look at this sample here:
https://social.technet.microsoft.com/wiki/contents/articles/31915.wpf-mvvm-step-by-step-1.aspx
You will notice that I have an ICommand there and it's set up to run a method.
If it was just an ICommand with a Get and Set like you have there then it would be NULL. There's a property but it's null until it is set to something.
This a very clunky way to implement an ICommand but relies on no external libraries or anything.
If you take a look at the second article in that series, it uses mvvmlight and relaycommand so creating a command is rather less clunky.
https://social.technet.microsoft.com/wiki/contents/articles/32164.wpf-mvvm-step-by-step-2.aspx
public RelayCommand AddListCommand { get; set; }
public MainWindowViewModel()
{
AddListCommand = new RelayCommand(AddList);
}
private void AddList()
{
stringList.Add(myString));
}
If you look at that code AddListCommand is initially null.
It is set in the constructor to a new RelayCommand which means it is then not null.
This is fairly simple but the code for the command is in a different place to the property so a more elegant approach is usual. As shown here: https://msdn.microsoft.com/en-gb/magazine/dn237302.aspx
Having said all that.
Selecting all text is something to do in the view, not the viewmodel.
You shouldn't really be passing a piece of UI from the view into a viewmodel.
Rather than a command it could well be that you should be binding a bool which is set in the viewmodel and acted on in the behaviour.
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}"
The application I'm currently writing is using MVVM with the ViewModel-first pattern. I have XAML similar to the following:
<ContentControl Content="{Binding FooViewModel.BarViewModel.View, Mode=OneWay}"/>
Every VM is a DependencyObject. Every property is a DependencyProperty. Depending upon the state of the application, the value of the BarViewModel property of the FooViewModel can change, thus changing the value of the View property. Unfortunately when this happens, the new view is not displayed, and the old one remains.
This is extremely frustrating. I thought that if any part of a path expression changed, the binding would update, but that doesn't appear to be the case. When I've used shallower path expressions, such as FooViewModel.View and I've changed the value of the FooViewModel property, that has updated the ContentControl to which it's bound, but not in this case.
If your solution is that I abandon ViewModel-first, that is not an option, though I appreciate your advice. I must get this working as is.
CLARIFICATION
This is a question about data binding, and not about MVVM or how to implement it. You can safely ignore the MVVM aspects of this if it helps you to think about the problem, or if you have a different idea about how MVVM should be implemented. This is a large, existing project in which the MVVM design pattern cannot be changed. (It is far too late for that.)
So, with that said, the correct question to be answering is the following:
Given a binding path expression in which every element is a DependencyProperty and the final property is a view bound to a ContentControl, why does a change in a property in the middle of the path not cause the binding to update?
Although I would expect this to work, there are several problems with your approach.
Firstly, your view models should not use DependencyObject or DependencyProperty, this ties them in to WPF. They should instead implement INotifyPropertyChanged. This makes your view models reusable in other presentation technologies such as Silverlight.
Secondly, your view models shouldn't have references to your views, so you shouldn't require a View property on your view models.
I would seriously consider using an MVVM framework for view composition - Caliburn.Micro, for example, makes view model first development extremely straightforward, and already provides a view model base class which implements INotifyPropertyChanged, and a mechanism for building view compositions with conventions.
I.e. you can have a conductor view model which has an ActiveItem property, and you simply place a ContentControl on your view with the same name as the property:
<ContentControl x:Name="ActiveItem" />
You can use the ActivateItem() method to change the current active item.
Caliburn.Micro also has a host of other features, such as being able to place a Button control with x:Name="Save" on your view, and your Save method on your view model will automatically be invoked when the button is clicked.
Every VM is a DependencyObject. Every property is a
DependencyProperty.
why? a viewmodel should be a simple class with INotifyPropertyChanged and the Properties should be simple properties.
and if you want your different viewmodel be rendered in a different way - you should use DataTemplate.
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyViewModelA}>
<MyViewA/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:MyViewModelB}>
<MyViewB/>
</DataTemplate>
</Windows.Resources>
<Grid>
<ContentControl Content="{Binding MyActualVM}"/>
</Grid>
</Window>
EDIT: btw you always bind to the last Property: FooViewModel.BarViewModel.View --> so the INotifyPropertyChanged (if raised) just work for the .View
EDIT2: another approach could be to get the BindingExpression of your content control and call.
System.Windows.Data.BindingExpression expr = //get it from your contentcontrol
expr.UpdateTarget();
EDIT3: and a simple mvvm way - just use INotifyPropertyChanged
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.MyFooVM = new FooVM();
this.MyFooVM.MyBarVM = new BarVM(){View = "erster"};
this.DataContext = this;
}
public FooVM MyFooVM { get; set; }
private void Button_Click(object sender, RoutedEventArgs e)
{
this.MyFooVM.MyBarVM = new BarVM(){View = "zweiter"};
}
}
public class INPC : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropChanged(string property)
{
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs(property));
}
#endregion
}
public class FooVM:INPC
{
private BarVM _myBarVm;
public BarVM MyBarVM
{
get { return _myBarVm; }
set { _myBarVm = value;OnPropChanged("MyBarVM"); }
}
}
public class BarVM : INPC
{
private string _view;
public string View
{
get { return _view; }
set { _view = value;OnPropChanged("View"); }
}
}
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.