Detect PropertyChanged in custom TextBox - c#

I'm have implemented a custom TextBox:
public class MyTextBox : TextBox
{
// ...
}
that I'm using from XAML:
<MyTextBox Text="{Binding MyProperty}" />
and it's bound to a property in my ViewModel.
public class MyDataContext : INotifyPropertyChanged
{
public string MyProperty
{
get { return _myPropertyBackingField; }
set
{
_myPropertyBackingField = value;
PropertyChanged(this, new PropertyChangedEventArgs("MyProperty"));
}
}
// ...
}
Question: How can I, in MyTextBox, detect that MyProperty is changed?
MyProperty = "NewValue";
Preferably, I would like to distinguish a programmatical change from when the change was triggered by the user editing the value. That is, I don't think overriding OnPropertyChanged works for me.

You can register to the PropertyChanged event of the TextBox's DataContext.
var dataContext = DataContext as MyDataContext;
dataContext.PropertyChanged += dataContext_PropertyChanged;
// check for the propertyname and react
void dataContext_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "MyProperty")
{
// Do things
}
}
So if your viewmodel raises PropertyChanged you textbox also gets notified. But I think that's bad practice. What do you want to achieve?

OP here.
I realised after a while that it's the binding that keeps track of updating the TextBox from the source (DataContext). So a possible path to take would be to call GetBindingExpression(TextProperty) and work something out from that.
However, I solved it by overriding TextBoxBase.OnTextChanged:
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
if (!IsFocused)
{
// Do stuff here
}
}
Since the control is not focused, the change must have been done programatically. This is not perfect since a programatical change might come when the TextBox has focus, but it is good enough for me.

Related

INotifyPropertyChanged does not work from class held in ObservableCollection<Class>

I have a class, "BaseClass" that implements INotifyPropertyChanged and has the following:
BaseClass:
private bool isOn;
public bool IsOn
{
get { return isOn; }
set
{
isOn = value;
RaisePropertyChanged("BaseClass:IsOn");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
I then have a class, "DIClass" that also implements INotifyPropertyChanged. It also has an ObservableCollection<BaseClass>:
DIClass:
public ObservableCollection<BaseClass> ClassesOfA;
private string iNPCTest;
public string INPCTest
{
get { return iNPCTest; }
set
{
iNPCTest = value;
RaisePropertyChanged("DIClass: INPCTest");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
My ViewModel holds an intance of "DIClass" and registers to it's PropertyChanged event. When I set the value of INPCTest in "DIClass", the ViewModel 'captures' the event correctly. However when I updated the IsOn property within the ObservableCollection, as below, the event is not picked up in the ViewModel.
ClassesOfA[0].IsOn = true;
Why is the INPC interface not working with the nested property? The question and answer here seems quite relevant, but I can't figure it out.
EDIT: additional explanation and code:
I can register to the PropetyChanged events of the ObservableCollection's items, as such:
ClassesOfA[0].PropertyChanged += DIClass_PropertyChanged;
ClassesOfA[1].PropertyChanged += DIClass_PropertyChanged;
However, this still does not bubble up to notify my ViewModel, that a property of my DIClass's ObservableCollection<BaseClass> has changed. I want to use INPC to bubble up event information / property updates up via MVVM layers. But I want to "wrap" them to make my classes cleaner/ less properties lying around
EDIT:
I add this "sketch" of my problem/scenario, with basic naming to make it easy:
To answer your question: This is by design.
ObservableCollection has two events:
CollectionChanged: Fires when the collection changes, e.g. collection.Add( item )
PropertyChanged: Fires when the property changes, e.g. collection = new ObservablecCollection<T>();
I think you need no ObservableCollection, because - as far as I understand your question - you want to observe the changes of the properties of the items in the collection. To achieve that you need to register to each observed item's PropertyChanged like this:
public List<BaseClass> Collection {get;set;}
public void InitializeCollection( IEnumerable<BaseClass> baseClassCollection){
Collection = new List<BaseClass>();
foreach(var item in baseClassCollection){
item.PropertyChanged += MethodToCallOnPropertyChanges;
Collection.Add( item );
}
}
public void MethodToCallOnPropertyChanges(object sender, PropertyChangedEventArgs e){
//react to any property changes
doSomething();
//react to specific properties
if(e != null && e.PropertyName.Equals("isOn"))
doSomethingOtherStuff();
}
This can be very annoying and can causes some other problems.
If I would come across this, I would think about redesigning the ViewModels and the UI. I would try to have an UI which is bound to each BaseClass item. For example, if I have an ListView I would provide an ItemTemplate in which the BaseClass item is bound. Doing so would prevent the need of registering to each item's PropertyChanged.
My suggestion is that you could create a customized ObservableCollection class that raises a Reset action when a property on a list item changes. It enforces all items to implement INotifyPropertyChanged.
I made a simple demo and you that you could check:
public class DIClass : INotifyPropertyChanged
{
public ExObservableCollection<BaseClass> ClassesOfA
... other code...
}
public sealed class ExObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public ExObservableCollection()
{
CollectionChanged += AllObservableCollectionCollectionChanged;
}
public ExObservableCollection(IEnumerable<T> pItems) : this()
{
foreach (var item in pItems)
{
this.Add(item);
}
}
private void AllObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
}
}
}
private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
OnCollectionChanged(args);
}
}
Then you could use the ExObservableCollection class in the DIClass object. When the properties inside the BaseClass changes, the UI will be updated.
Update:
Finally, I found out the unexpected behavior you mentioned based on the complex sample. The ExObservableCollection class works well and fires the property changed event correctly.
The key point is you think if the property change event in baseclass is fired then it will
trigger the property change event in DIClass as well, right? I have to say that is not correct. The property change event only fires in the current class. It won't pass to the parent class unless you handle it in the parent class. It fired only once and notify the UI when the target property is changed.
If I understand your scenario correctly, you want to change the ToggleButton's status when the same property in BaseClassobject is changed. But the ToggleButtons are bind to VMData objects so that you need to get notified when the BaseClass objects are changed in the DIClass objects. So you want the the property change event of BaseCasss triggers the property change event of the DIClass.
Handling the property changed event of BaseClass in the DIClass object is the correct way to do what you want. It's the same like handling DIClass event in the ViewModel. But you don't want it since there might be many objects.
Then the first version of your sample is the recommended way to achieve what you want by triggering the property changed event of the DIClass on your own.

How to handle a numeric box ValueChanged event in View Model?

I'm trying to detect when a numeric box value was changed by a user and handle it in my view model.
The numeric DoubleBox is defined in XAML like this:
<numeric:DoubleBox Value="{Binding LeadR}" Grid.Column="1" MinValue="0" MaxValue="1000" IsEnabled="{Binding IsNotMeasuring}" ValueChanged="{Binding DoubleBox_ValueChanged}"/>
In my ViewModel.cs:
private void DoubleBox_ValueChanged(object sender, ValueChangedEventArgs<double?> e)
{
// Omitted Code: Insert code that does something whenever the text changes...
}
When I right click DoubleBox_ValueChanged in XAML and "Go to definition", it will navigate to the method in WM. But when I run the code, Visual Studio shows this error:
System.Windows.Markup.XamlParseException: ''Provide value on 'System.Windows.Data.Binding' threw an exception.' Line number '123' and line position '162'.'
Can anyone tell me how to solve this?
You're using the code-behind and not the viewmodel; the error means that you have not associated a DataContext to the Window/UserControl/whatever the DoubleBox is contained in. You have to setup a ViewModel and bind it to the Container of the DoubleBox to make binding work. I'll give you a quick example.
public class ViewModel : INotifyPropertyChanged
{
private double _leadR;
public double LeadR
{
get
{
return _leadR;
}
set
{
_leadR = value;
OnPropertyChanged(nameof(LeadR));
OnLeadRChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void OnLeadRChanged()
{
//Do whatever you want with the new value of LeadR
}
}
Then in your container you can set the DataContext even in the constructor like
public class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
Hope this helps with your question.
If you want to react on a value change on DoubleBox, simply do it in the setter of LeadR.
private double _leadr;
public double LeadR
{
get => _leadr;
set
{
if (Math.Abs(_leadr - value) > 10E-12)
{
_leadr = value;
OnPropertyChanged();
// The value changed, do something with it here
}
}
}
You do not need and should not handle the ValueChanged event in the view model. Other options are writing an attached property, a tigger action or a behavior, but that might be to complex for what you want to achieve.
The binding exception that you get seems to originate from using a wrong type, since the inner exception of the XamlParseException is an InvalidCastException.

UWP - Change button.Visibility from another class

I am trying to change multiple buttons visibility from another class. This is supposed to be easy, but I just don't understand it.
The xaml part is straight forward:
<button x:Name="whatever" Visibility="{Binding whateverName}"
The view-model could be something like this?
private Visibility vis;
public Visibility Vis
{
get { return vis; }
set { vis = value; }
}
But if that is the case, how do I pass my button name?
To go a bit further, a services file is trying to modify the visibility value..
Thanks in advance.
Since you're using Bindings, you don't need the button name identifier.
The connection is made in the Binding part of the XAML:
<Button x:Name="whatever" Visibility="{Binding whateverName}"/>
What is happening there is that you are saying the Visibility property of the whatever button will be bound to the whateverName property value in your view model.
So your View model needs to look like this:
private Visibility vis;
public Visibility whateverName
{
get { return vis; }
set { vis = value; }
}
To change the visibility of your button you need to change the value of whateverName in your view model.
However, if you try, you'll notice that that won't work. The reason is that in order for the change to take effect on the button, the View model must notify the view that its property has changed. This is done with the INotifyPropertyChanged interface.
So your view model will need to look something like this:
public class Viewmodel : INotifyPropertyChanged
{
private Visibility vis;
public Visibility whateverName
{
get { return vis; }
set
{
vis = value;
OnPropertyChanged("whateverName");
}
}
public void OnPropertyChanged(string pName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(pName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
In the PropertyChanged event you must pass the property name that you want to notify. In my example I just used a string value that matches the property name but there are various techniques to eliminate that "magic string".
Here's one SO question that has good answers.

WPF, set DataContext to property of same class

I have a class Device that uses the INotifyPropertyChanged, it is tested and it works.
Now I have a deviceMonitor that is the UI representation of this device. In the code I have a reference to Device and I want to link changes in the device to changes in the UI (two way is not needed, but clicking the deviceMonitor should call a certain function of the device)
I'm using expression Blend with VS2015 so guidance based on where to click to get it to work would be extremely welcome.
this is a mockup of the device
public class Device : INotifyPropertyChanged
{
public string Name { ... } //uses NotifyPropertyChanged in the set
// other properties and their relative private vars.
}
Then the xaml.cs for the GUI, here I have a reference to the dll containing the Device:
public partial class DeviceControl : UserControl
{
public Device myDevice = new Device();
public DeviceControl()
{
InitializeComponent();
// here I tried setting the datacontest to the myDevice
// also tried to set the dataContext in Blend and here grab a
// reference to it and store it in myDevice. But nothing workerd
}
public void ChangeDevName()
{
this.myDevice.DeviceName = "Test";
//UI Representation of deviceName never changed
}
}
Then the XAML
<UserControl>
<UserControl.DataContext>
<recoveriX:RecoverixDevice DeviceName="thisIsAName"/>
</UserControl.DataContext>
<Grid>
<TextBlock x:Name="title" Text="{Binding DeviceName}"/>
</Grid>
</UserControl>
This might work:
In your DeviceControl UserControl, wire up events for OnLoaded and OnUnloaded of the control.
In the code-behind for the event handlers, subscribe/unsubscribe to the PropertyChanged event of UserControl's DataContext (this.DataContext) ; like so:
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (this.DataContext is INotifyPropertyChanged)
{
((INotifyPropertyChanged)this.DataContext).PropertyChanged += OnDataContextPropertyChanged;
}
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
if (this.DataContext is INotifyPropertyChanged)
{
((INotifyPropertyChanged)this.DataContext).PropertyChanged -= OnDataContextPropertyChanged;
}
}
private void OnDataContextPropertyChanged(object sender, PropertyChangedEventArgs e)
{
// You could also just update every time something is changed.
// As an example you could check for the "Name" property being changed.
if (e.PropertyName == nameof(Device.Name))
{
title.Text = this.DataContext.Name;
}
}
An important note to make is the if (this.myDevice is INotifyPropertyChanged) check.
It ensures the Device class inherits from INotifyPropertyChanged.
Providing it does, it casts the Device being your DataContext (this.DataContext) to (INotifyPropertyChanged) so you can subscribe to the PropertyChanged event from the INotifyPropertyChanged interface.
Then, when a property on the DataContext is changed, your handler will be fired. Obviously you can put what you want to do in the code of OnMyDevicePropertyChanged, I've just used "Name" as an example.
Hope this helps!
EDIT
Furthermore; you could also store a private field of type Device in the UserControl's code-behind. A bit like so:
private Device _viewModel; // You could also use the interface (like 'IDevice'), too.
Then in your `OnLoaded' event, store it in the field:
if (this.DataContext is INotifyPropertyChanged)
{
this.viewModel = this.DataContext;
// Wire up your PropertyChanged handler as before.
}
And on your OnUnloaded event, just unsubscribe from the viewModel if it is not null:
if (this.viewModel != null)
{
this.viewModel.PropertyChanged -= OnDataContextPropertyChanged;
}
This also gives you a bit more flexibility when you've got the DataContext stored as a field, as you can use it within other methods (if you use any more in your code behind - you shouldn't...; but it saves CPU time casting it to INotifyPropertyChanged all the time.
For future reference I would look at Implementing MVVM Practices into your projects.
Good luck!
Problem was overwriting the private device, setting the datacontext fixed the thing.
This is the final class:
public partial class DeviceControl : UserControl
private Device _device = new Device();
public DeviceControl()
{
InitializeComponnents();
this.DataContext = _device;
}
public void SetDevice(Device d)
{
//This fails:
//_device = d;
//This works
this.DataContext = d;
}

C# WPF MVVM TextBox value doesn't change

I am a beginner to use MVVM in WPF and found that it seem impossible to change the value of a textbox or a label. Here is an example.
In Xaml:
The original value of Name is "Peter".
But after I press a button which invoke a command in the ViewModel and change the value of Name to be
"John". So, suppose the value of the text box will be changed to John as well. However, it doesn't change.
I have found a lot of example in the net and found that none of them implemented this kind of functions. What I have learnt from them is to use Command and ItemsSource of ListView.
The value of ListView will change when I use button to raise command to change the ItemsSource of the view. Its value will change automatically when the Binding to ItemsSource changed.
However, I cannot make the value of TextBox or Label change even the value of the bindings to them are changed already.
Actually, I am really quite young in MVVM. I think I still have so much that I don't know.
Could you give me an example of how exactly I should do to make change to textbox after a button click? By the way, I am not quite sure how to make command for button. It seem to involve so much codes that I found in the sample from the net. Is there any simplier way?
Thank you very much.
Your ViewModel needs to implement INotifyPropertyChanged .
Documentation see here
public class Bar : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string foo;
public string Foo
{
get { return this.foo; }
set
{
if(value==this.foo)
return;
this.foo = value;
this.OnPropertyChanged("Foo");
}
}
private void OnPropertyChanged(string propertyName)
{
if(this.PropertyChanged!=null)
this.PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
}
}
Your view model should implement INotifyPropertyChanged so that WPF knows that you've altered a value of a property.
Here is an example from
// This is a simple customer class that
// implements the IPropertyChange interface.
public class DemoCustomer : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private string customerNameValue = String.Empty;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
var listeners = PropertyChanged;
if (listeners != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
if (value != this.customerNameValue)
{
this.customerNameValue = value;
NotifyPropertyChanged("CustomerName");
}
}
}
}

Categories