In my program I would like to disable a contentPresenter when my other contentPresenter gets focus. Each presenter is represented by a property located in my MainWindowViewModel. This is also where the IsEnabled property is located for both presenters.
Both contentPresenters are created with the following structure: UserControl -> ViewModel -> Data Model.
Right now I am trying to disable the necessary contentPresenter by changing the IsEnabled property in the main window's ViewModel from the code-behind of the contentPresenter that gets focus.
contentPresenter User Control code-behind:
public partial class EditBlockUC : UserControl
{
public EditBlockViewModel ViewModel { get { return DataContext as EditBlockViewModel; } }
public EditBlockUC()
{
InitializeComponent();
}
//Runs when the user control gets focus
private void UserControl_GotFocus(object sender, RoutedEventArgs e)
{
//This UserControl has access to MainWindowViewModel through
//it's own ViewModel, EditBlockViewModel
ViewModel.MainViewModel.LeftWidgetEnabled = false;
}
}
The line: ViewModel.MainViewModel.LeftWidgetEnabled = false; successfully changes the property in the Main window's view model, but the view is not affected. Can I fix this by finding a way to call NotifyPropertyChange()? If so, how would I do that?
If this is the completely wrong solution please let me know, and help me fix it.
Thank you
Update 1:
My complete base class:
public class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public virtual void NotifyPropertyChange<TProperty>(Expression<Func<TProperty>> property)
{
var lambda = (LambdaExpression)property;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = (UnaryExpression)lambda.Body;
memberExpression = (MemberExpression)unaryExpression.Operand;
}
else
memberExpression = (MemberExpression)lambda.Body;
OnPropertyChanged(memberExpression.Member.Name);
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
Update 2:
My LeftWidgetEnabled property:
public bool LeftWidgetEnabled
{
get { return _leftWidgetEnabled; }
set { SetField(ref _leftWidgetEnabled, value, "LeftWidgetEnabled"); }
}
The LeftWidgetEnabled of your ViewModel.MainViewModel class must be like this:
private bool leftWidgetEnabled;
public bool LeftWidgetEnabled
{
get { return leftWidgetEnabled; }
set { SetField(ref leftWidgetEnabled, value, "LeftWidgetEnabled"); }
}
Also, your MainViewModel must implement INotifyPropertyChanged.
You're better off letting the MainViewModel inherit from a ViewModelBase and let ViewModelBase implement INotifyPropertyChanged.
public class MainViewModel : ViewModelBase
{
private bool leftWidgetEnabled;
public bool LeftWidgetEnabled
{
get { return leftWidgetEnabled; }
set { SetField(ref leftWidgetEnabled, value, "LeftWidgetEnabled"); }
}
}
public class ViewModelBase : INotifyPropertyChanged
{
// boiler-plate
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
Update 1
Your ContentPresenter should then be bound like:
<ContentPresenter IsEnabled="{Binding Path=LeftWidgetEnabled}" />
while the DataContext of your UserControl (where the ContentPresenter is on) should be an instance of MainViewModel.
For instance:
<UserControl
x:Class="MyApplication.UserControl1"
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:viewModels="**PATH TO YOUR VIEWMODELS-ASSEMBLY**"
mc:Ignorable="d">
<UserControl.DataContext>
<viewModels:MainViewModel />
</UserControl.DataContext>
<ContentPresenter IsEnabled="{Binding Path=LeftWidgetEnabled}" />
</UserControl>
You implement INotifyPropertyChanged as below
class ViewModel : INotifyPropertyChanged
{
private bool leftWidgetEnabled;
public bool LeftWidgetEnabled
{
get
{
return leftWidgetEnabled;
}
set
{
leftWidgetEnabled=value
OnPropertyChanged("LeftWidgetEnabled");
}
}
public void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Related
I need to set a property in the Business Logic with a method in the Business Logic. If you run my code you can see the first String "Target Location" changes successfully, but the second one "Some Other String" doesn't change its value in the view. "PropertyChanged" in the BusinessLogic.cs is null. I have absolutely no idea WHY it's null! Can someone explain me this behaviour and how I can fix this?
I have the following files in my project:
MainWindow.xaml
<Window x:Class="TestWpf.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:TestWpf"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBox Text="{Binding Path=TargetLocation}"></TextBox>
<TextBox Text="{Binding Path=SomeOtherString}"></TextBox>
<Button Click="ChangeTextButton_Click">Change Target Location</Button>
<Button Click="ChangeSomeOtherStringButton_Click">Change some other string</Button>
</StackPanel>
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
MainViewModel mainViewModel = new MainViewModel();
mainViewModel.TargetLocation = #"A:\Old_Location";
mainViewModel.SomeOtherString = "Old String...";
DataContext = mainViewModel;
}
private void ChangeTextButton_Click(object sender, RoutedEventArgs e)
{
MainViewModel mainViewModel = (MainViewModel)DataContext;
mainViewModel.TargetLocation = #"B:\New_Location";
}
private void ChangeSomeOtherStringButton_Click(object sender, RoutedEventArgs e)
{
MainViewModel mainViewModel = (MainViewModel)DataContext;
mainViewModel.ChangeSomeOtherString();
}
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
private string targetLocation;
public string TargetLocation
{
get
{
return targetLocation;
}
set
{
targetLocation = value;
OnPropertyChanged("TargetLocation");
}
}
public string SomeOtherString
{
get
{
return BusinessLogicClass.GetInstance().SomeOtherString;
}
set
{
BusinessLogicClass.GetInstance().SomeOtherString = value;
OnPropertyChanged("SomeOtherString");
}
}
public void ChangeSomeOtherString()
{
BusinessLogicClass.GetInstance().ChangeSomeOtherString();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
BusinessLogicClass
public class BusinessLogicClass : INotifyPropertyChanged
{
private static BusinessLogicClass instance;
public static BusinessLogicClass GetInstance()
{
if (instance == null)
{
instance = new BusinessLogicClass();
}
return instance;
}
private BusinessLogicClass()
{
}
private string someOtherString;
public string SomeOtherString
{
get
{
return someOtherString;
}
set
{
someOtherString = value;
OnPropertyChanged("SomeOtherString");
}
}
public void ChangeSomeOtherString()
{
SomeOtherString = "New String!";
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
"PropertyChanged" in the BusinessLogic.cs is null. I have absolutely no idea WHY it's null!
PropertyChanged in the BusinessLogic class is null because there are no bindings that use properties in this class as their source. The source properties for both of your bindings are on your MainViewModel class.
WPF doesn't scan through all classes that happen to implement INotifyPropertyChanged. And even if it did, how would it know that a PropertyChanged event fired from your BusinessLogic class means that it needs to update the TextBox bound to the SomeOtherString property on your MainViewModel? WPF can't read your code to find this out.
The simplest fix is to fire a PropertyChanged event inside your ChangeSomeOtherString() method:
public void ChangeSomeOtherString()
{
BusinessLogicClass.GetInstance().ChangeSomeOtherString();
OnPropertyChanged("SomeOtherString"); // Add this line
}
This way WPF knows that the value of the SomeOtherString property has changed and will perform the necessary update to the TextBox.
I'm making a test application to handle a CardReader, I have an enum with the states of the CardReader and a XAML window with a TextBlock, i want that when the state change the onPropertyChanged change the TextBlock with the name of the state.
Here is part of my code:
public class CardControler : INotifyPropertyChanged
{
private CardState state;
public CardState State
{
get { return state; }
set
{
if (state != value)
{
state = value;
OnPropertyChanged(state);
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(CardState state)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(state.ToString()));
}
}
......................................................................
public partial class CardReader : Window
{
public CardControler control { get; set; }
public CardReader(int port)
{
this.DataContext = control;
this.port = port;
InitializeComponent();
ScreenWrite(CardState.Initializing);
Thread thread = new Thread(new ThreadStart(asincControlCreate));
thread.Start();
}
And in the xaml
<TextBlock Name="Screen" Text="{Binding Path=control.state}></TextBlock>
I hope i explained my self correctly and somebody can help me.
Thanks in advance
The following line is incorrect as you should pass propertyName as a parameter instead of state.ToString():
PropertyChanged(this, new PropertyChangedEventArgs(state.ToString()));
So your code should look something like:
public CardState State
{
get { return state; }
set
{
if (state != value)
{
state = value;
OnPropertyChanged("State");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In addition to this keep in mind that xaml is case sensitive so {Binding State} is not the same as {Binding state}.
I think the problem is that you are raising the OnPropertyChanged with the value that is changing, rather than the actual property name (i.e. "State" in this case).
protected void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I suspect you also need to change your XAML to bind to the appropriate property (note, the property name is State not state - the XAML will be case sensitive):
<TextBlock Name="Screen" Text="{Binding Path=control.State}></TextBlock>
You should pass the name of the property that changed, not its value:
PropertyChanged(this, new PropertyChangedEventArgs("State"));
The case of the property in your binding needs to match the public property (State, not state):
<TextBlock Name="Screen" Text="{Binding Path=control.State}></TextBlock>
You have alread set the datacontext of the page to control so your binding
<TextBlock Name="Screen" Text="{Binding Path=control.state}></TextBlock>
will be wrong.
your binding should be
<TextBlock Name="Screen" Text="{Binding Path=State}></TextBlock>
Instead of
public CardControler control { get; set; }
try this:
public CardControler control = new CardControler();
your OnPopertyChanged Event Call is wrong , you have to pass Property name as the argumeny. you can add the code i added below. that way you can avoid passing the parameter name altogether.
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
I need Model to notify ViewModel if any property is changed, because I need to collect the changed model instances in a collection for further processing, also to enable and disable command buttons in the viewmodel.
So I've used ModelBase abstract class and added HasChanges property which I can test against in the viewmodel and catch the changed models.But it is not working and I don't know what i'm missing.
public abstract class ModelBase : INotifyPropertyChanged
{
protected ModelBase()
{
}
private bool _hasChanges;
public bool HasChanges
{
get
{
return _hasChanges;
}
set
{
if (_hasChanges != value)
{
_hasChanges = value;
RaisePropertyChanged("HasChanges");
}
}
}
protected void RaisePropertyChanged(string propertyName)
{
HasChanges = true;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
The Model is wrapped inside the ViewModel and bound to the View which is a DataGrid:
private Model_selectedModel;
public Mode SelectedModel
{
get
{
return _selectedModel;
}
set
{
if (_selectedModel != value)
{
_selectedModel = value;
NotifyPropertyChanged("SelectedModel");
}
}
}
Thanks for valuable help.
I tested your class and it's okay. I think the point is a typo here:
private Model_selectedModel;
public Mode SelectedModel
{
get
{
return _selectedModel;
}
set
{
if (_selectedModel != value)
{
_selectedModel = value;
NotifyPropertyChanged("SelectedModel");
}
}
}
There should be RaisePropertyChanged instead of NotifyPropertyChanged.
Below it's my test:
XAML
<Window x:Class="TestUpdatePropertyChanged.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:TestUpdatePropertyChanged"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<this:TestViewModel />
</Window.DataContext>
<Grid>
<TextBox Width="100" Height="25" Text="{Binding Path=TestString, UpdateSourceTrigger=PropertyChanged}" />
<Button Width="100" Height="30" VerticalAlignment="Top" Content="Click" Click="Button_Click" />
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var testData = this.DataContext as TestViewModel;
testData.TestString = "Yay, it's change!";
if (testData.HasChanges == true)
{
MessageBox.Show("It's Change!");
}
}
}
public class TestViewModel : ModelBase
{
private string _testString = "test";
public string TestString
{
get { return _testString; }
set
{
if (_testString != value)
{
_testString = value;
RaisePropertyChanged("TestString");
}
}
}
}
public abstract class ModelBase : INotifyPropertyChanged
{
protected ModelBase()
{
}
private bool _hasChanges;
public bool HasChanges
{
get
{
return _hasChanges;
}
set
{
if (_hasChanges != value)
{
_hasChanges = value;
RaisePropertyChanged("HasChanges");
}
}
}
protected void RaisePropertyChanged(string propertyName)
{
HasChanges = true;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
I have three C# classes A, B and C where
A is a ViewModel
B is a Model
C is a state class (containing some state information about a device, like e.g. IsConnected)
They are connected such that B has a property of type C. C contains almost 30 properties representing the device state.
I have decided to let B update A via INotifyPropertyChanged, and now I am looking for a way for A to be informed when properties in C changes.
What's the easiest way of achieving this?
Update:
This code will do the trick.
class Gun : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public GunState GunState { get; private set; }
public Gun()
{
GunState = new GunState();
GunState.PropertyChanged += GunStateOnPropertyChanged;
}
private void GunStateOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
NotifyPropertyChanged(propertyChangedEventArgs.PropertyName);
}
protected virtual void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
class GunState : INotifyPropertyChanged
{
private bool _isLoaded;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsLoaded
{
get { return _isLoaded; }
private set
{
if (_isLoaded != value)
{
_isLoaded = value;
NotifyPropertyChanged("IsLoaded");
}
}
}
public void SimulateLoadGun(bool isLoaded)
{
IsLoaded = isLoaded;
}
protected virtual void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
class GunViewModel : INotifyPropertyChanged
{
private readonly Gun _gun;
public GunViewModel()
{
_gun = new Gun();
_gun.PropertyChanged += OnGunOnPropertyChanged;
}
public string IsLoaded
{
get { return _gun.GunState.IsLoaded ? "Gun is loaded!" : "Gun is not loaded."; }
}
private void OnGunOnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
NotifyPropertyChanged(args.PropertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public void LoadGun()
{
_gun.GunState.SimulateLoadGun(!_gun.GunState.IsLoaded);
}
}
XAML:
<Window x:Class="ModelViewModelInteraction.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ModelViewModelInteraction"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:GunViewModel x:Name="_model" />
</Window.DataContext>
<Grid>
<Label Content="{Binding IsLoaded}" Margin="0,0,313,262" />
<Button Content="Load gun" Click="Button_Click_1" Margin="73,83,283,59" />
</Grid>
</Window>
XAML.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
_viewModel = new GunViewModel();
DataContext = _viewModel;
}
private GunViewModel _viewModel;
private void Button_Click_1(object sender, RoutedEventArgs e)
{
_viewModel.LoadGun();
}
}
I would follow the Law of Demeter; A shouldn't need knowledge of what's going on in C. Rather, A should watch for notifications from B when relevant properties in C change.
For example, your model, B could have its own IsConnected property, and you could continue using INotifyPropertyChanged mechanisms.
I have a scenario which is causing strange behavior with WPF data binding and INotifyPropertyChanged. I want a private member of the data binding source to handle the INotifyPropertyChanged.PropertyChanged event.
Here's the source code:
XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="TestApplication.MainWindow"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Height="100" Width="100">
<StackPanel>
<CheckBox IsChecked="{Binding Path=CheckboxIsChecked}" Content="A" />
<CheckBox IsChecked="{Binding Path=CheckboxIsChecked}" Content="B" />
</StackPanel>
</Window>
Normal implementation works
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public bool CheckboxIsChecked
{
get { return this.mCheckboxIsChecked; }
set
{
this.mCheckboxIsChecked = value;
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("CheckboxIsChecked"));
}
}
private bool mCheckboxIsChecked = false;
public MainWindow() { InitializeComponent(); }
}
Desired implementation doesn't work
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged
{
add { lock (this.mHandler) { this.mHandler.PropertyChanged += value; } }
remove { lock (this.mHandler) { this.mHandler.PropertyChanged -= value; } }
}
public bool CheckboxIsChecked
{
get { return this.mHandler.CheckboxIsChecked; }
set { this.mHandler.CheckboxIsChecked = value; }
}
private HandlesPropertyChangeEvents mHandler = new HandlesPropertyChangeEvents();
public MainWindow() { InitializeComponent(); }
public class HandlesPropertyChangeEvents : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public bool CheckboxIsChecked
{
get { return this.mCheckboxIsChecked; }
set
{
this.mCheckboxIsChecked = value;
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("CheckboxIsChecked"));
}
}
private bool mCheckboxIsChecked = false;
}
}
That's just a guess, but I think it might be because the sender parameter passed to the event handler is an instance of HandlesPropertyChangeEvents, when the binding expects an instance of MainWindow.
Try to change your code so that the sender is the MainWindow instance :
private PropertyChangedEventHandler _propertyChanged;
public event PropertyChangedEventHandler PropertyChanged
{
add { lock (this.mHandler) { this._propertyChanged += value; } }
remove { lock (this.mHandler) { this._propertyChanged -= value; } }
}
...
public MainWindow()
{
InitializeComponent();
mHandler.PropertyChanged += mHandler_PropertyChanged;
}
private void mHandler_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var handler = _propertyChanged;
if (handler != null)
_propertyChanged(this, e);
}
My working solution is almost exactly the same as the "desired implementation" in my question, with the addition of the Sender property.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged
{
add { lock (this.mHandler) { this.mHandler.PropertyChanged += value; } }
remove { lock (this.mHandler) { this.mHandler.PropertyChanged -= value; } }
}
public bool CheckboxIsChecked
{
get { return this.mHandler.CheckboxIsChecked; }
set { this.mHandler.CheckboxIsChecked = value; }
}
private HandlesPropertyChangeEvents mHandler = new HandlesPropertyChangeEvents();
public MainWindow()
{
InitializeComponent();
this.mHandler.Sender = this;
}
public class HandlesPropertyChangeEvents : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Sender { get; set; }
public bool CheckboxIsChecked
{
get { return this.mCheckboxIsChecked; }
set
{
this.mCheckboxIsChecked = value;
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
handler(this.Sender, new PropertyChangedEventArgs("CheckboxIsChecked"));
}
}
private bool mCheckboxIsChecked = false;
}
}
This example is a bit artificial, but in my application moving the event handling code outside of the bound class makes sense.