INotifyPropertyChanged is not working in WPF - wpf

I need to enable and disable a datagrid through button click. This is how I do it:
MainWindow
public bool IsReadOnly = true;
private MainWindowEngine mwe;
public MainWindow()
{
InitializeComponent();
InitialSettings();
DataContext = mwe;
}
private void InitialSettings()
{
_mwe = new MainWindowEngine();
IsReadOnly = bool.Parse(_mwe.IsReadOnly);
DataGridCommands.IsReadOnly = IsReadOnly;
DataGridReaders.IsReadOnly = IsReadOnly;
}
private void EnableEdit(object sender, RoutedEventArgs e)
{
IsReadOnly = !IsReadOnly;
_mwe.IsReadOnly = IsReadOnly.ToString();
}
xaml
<Button
Name="ButtonEdit"
Grid.Column="1"
Content="Edit"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="5 0 0 0"
Width="75" Click="EnableEdit"
/>
class MainWindowEngine : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _isReadOnly = "true";
public string IsReadOnly
{
get => _isReadOnly;
set
{
if (_isReadOnly != value)
{
_isReadOnly = value;
OnIsReadOnlyChanged(_isReadOnly);
}
}
}
protected void OnIsReadOnlyChanged(string value)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(value));
}
}
Through debugger, it is hitting the breakpoints but it doesn't make my datagrid isReadOnly property to false or true.

Change it like shown below. Pass the name of the property instead of its value to the method that fires the PropertyChanged event. Also change the name of the method to something that reflects its general purpose, i.e. to notify about the change of any property, not just IsReadOnly.
public string IsReadOnly
{
get => _isReadOnly;
set
{
if (_isReadOnly != value)
{
_isReadOnly = value;
OnPropertyChanged(nameof(IsReadOnly));
}
}
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
See the documentation -
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?view=net-5.0
You need to notify the name of the property that changed, not the value directly.
Edit -
If you do not want to write out your property names always, you can invoke the PropertyChanged event using the CallerMemberName as below -
public string IsReadOnly
{
get => _isReadOnly;
set
{
if (_isReadOnly != value)
{
_isReadOnly = value;
OnPropertyChanged(); // no longer need to pass the property name
}
}
}
protected void OnPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

PropertyChangedEventArgs as its argument takes a string contanining property name that's changed, not its value.
So correct body of OnIsReadOnlyChanged will look like
protected void OnIsReadOnlyChanged()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsReadOnly)));
}
You can make it even more general by defining method:
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
And use it like
RaisePropertyChanged(nameof(IsReadOnly));

Related

Visibility of StackPanel in MVVM

I'm working with WPF using Prism ( MVVM). I wanted to set visibililty
of StackPanel from ViewModel calss. The StackPanel's visibility is
binded like :
<StackPanel x:Name="spVisibility" Orientation="Horizontal"
Visibility="{Binding spVisibility, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
I've view model class like :
public class SearchId : BindableBase, INotifyPropertyChanged
{
private Visibility _visibility = Visibility.Collapsed;
private DelegateCommand<object> searchCommand;
public event PropertyChangedEventHandler PropertyChanged;
public SearchId()
{
searchCommand = new DelegateCommand<object>(this.SearchData);
}///
public Visibility spVisibility
{
get { return _visibility; }
set
{
if (!string.Equals(_visibility, value))
{
_visibility = value;
RaisePropertyChanged("spVisibility");
}
}
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs (propertyName));
}
}
private async void SearchData(object parameter)
{
_visibility = Visibility.Visible;
}
}
But this not working. Please help me.
_visibility = Visibility.Visible is setting the private property instead of using the public one so RaisePropertyChanged("spVisibility") is being bypassed. You need to use spVisibility = Visibility.Visible.
If you are using MVVM i would recommend using a Boolean value instead of Visibility. The whole purpose of MVVM is seperation of View Logic from DataLogic.
View logic:
<StackPanel Orientation="Horizontal"
Visibility="{Binding ShowStackPanel, Converter={StaticResource BooleanToVisibilityConverter}}">
Use a Converter to convert the boolan to a Visibility Property.. BooleanToVisibilityConverter is part of .NET and can be referenced without defining it manually in the xaml.
public class SearchId : BindableBase, INotifyPropertyChanged
{
private bool _showStackPanel;
private DelegateCommand<object> searchCommand;
public event PropertyChangedEventHandler PropertyChanged;
public SearchByIDVM()
{
searchCommand = new DelegateCommand<object>(this.SearchData);
}///
public bool ShowStackPanel
{
get { return _showStackPanel; }
set
{
if (!Equals(_showStackPanel, value))
{
_showStackPanel= value;
RaisePropertyChanged("ShowStackPanel");
}
}
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs (propertyName));
}
}
private async void SearchData(object parameter)
{
ShowStackPanel= true;
}
}

ListBox bind to ObservableCollection is not updated with collection

I have next model:
public class MyModel
{
public ObservableCollection<MyObject> MyList {get; set;}
}
public class MyObject
{
MyObservableDictionary MyDictionary {get; set;}
}
public class MyObservableDictionary : ObservableCollection<EnymValue>
{
}
public class EnymValue : INotifyPropertyChanged
{
private MyEnum key;
private string value;
public MyEnum Key
{
get
{
return this.key;
}
set
{
this.key = value;
NotifyPropertyChanged("Key");
}
}
public string Value
{
get
{
return this.value;
}
set
{
this.value = value;
NotifyPropertyChanged("Value");
}
}
public LanguageValue(MyEnum key, string value)
{
this.Key = key;
this.Value = value;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName]string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public enum MyEnum
{
}
And on View I have a ListBox:
<ListBox x:Name="MyList" SelectionMode="Single" ItemsSource="{Binding Path=MyList, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=MyDictionary, Mode=OneWay, Converter={StaticResource myEnumToTextConverter}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
(myEnumToTextConverter converter is just selects first element from collection and return it's value, or some specified constant if collection is null or empty)
I want my Model's list box to be updated on view, when any EnymValue values are changed.
Is it possible somehow to implement this?
Currently the view is not updated when Value changed.
I've tried to inherit EnymValue from INotifyPropertyChanged, but this didn't helped. Looks like PropertyChanged == null on EnymValue.NotifyPropertyChanged when property updated.
ObservableCollection is able to notify UI about changes when collection itself is changed(elemends are added or deleted). But ObservableCollection is not aware of changes that are happening when you modify one of it's items. To solve the problem you may subscribe to CollectionChange event of observable collection, and when new item is added, subscribe to new items's PropertyChanged. When PropertyChanged event is raised, you can trigger notification on your list OnPropertyChanged(()=>MyItems); You should be careful implementing this solution and remember to unsubscribe from the event's to avoid memory leaks.
An example of what I mean you can see in this answer.
Your MyDictionary should force a refresh. Easiest way is to re-assign its old value, and implement INPC in MyObject like below :
public class MyObject: INotifyPropertyChanged
{
MyObservableDictionary _myDictionary;
public MyObservableDictionary MyDictionary {
get
{
return _myDictionary;
}
set
{
_myDictionary = value;
OnPropertyChanged("MyDictionary");
}
}
public MyObject()
{
MyDictionary = new MyObservableDictionary();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Sample code to change Value :
private void Button_Click(object sender, RoutedEventArgs e)
{
// vm is ViewModel instance, vm is DataContext set for Window
var old = vm.MyList[0].MyDictionary;
vm.MyList[0].MyDictionary[0].Value = "aaaa";
vm.MyList[0].MyDictionary = old;
}
I tested this, and it displays changed value as "aaaa".

Silverlight MVVM:Updating parent view model based on status of child View model

I am new to Silverlight MVVM.
I have one requirement to show checkbox in a parent child hierarchy.
While loading the page if the child is checked then parent checkbox should also get checked.
I have created a ViewModel as below
public class TestViewModel : INotifyPropertyChanged
{
private string name;
private string percent;
private bool isChecked;
internal event EventHandler CheckboxStateChanged = delegate { };
private List<TestViewModel> testViewModel;
public List<TestViewModel> TestViewModel1
{
get { return testViewModel; }
set
{
testViewModel = value;
NotifyPropertyChanged("TestViewModel1");
}
}
public TestViewModel()
{
//IsChecked = true;
//Name = "Hello";
//Percent = "10";
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
NotifyPropertyChanged("IsChecked");
CheckboxStateChanged(this, new EventArgs());
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In my main.xaml.cs I have created recursive method which will create the parent child hierarchy of checkboxes.
On clicking the child checkbox, parent checkbox is getting checked as I have added eventhandler in my VM (CheckboxStateChanged ) for that.But while on page load if child is checked then parent also get checked,I am unable to do that..Pls help.
Note I can not make parents checked until I get the status of child and once I get child status m not sure how to go back to parent.
Parent VM contains list of same VM as children(i.e public List TestViewModel1)
If I understand your question correct you are looking for a way to bouble up checkbox values from the children to its parent checkbox.
I've done a similar solution for a tree view. This code works but needs some event detaching if the collection changes.
The following is the set of classes that is used to run the ViewModel part of this solution.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class StructureViewModel : ViewModelBase
{
private bool? _isChecked = false;
public bool? IsChecked
{
get { return _isChecked; }
set
{
if (_isChecked != value)
{
_isChecked = value;
RaisePropertyChanged("IsChecked");
}
}
}
public string Name { get; set; }
}
public class ChildViewModel : StructureViewModel
{
}
public class ParentViewModel : StructureViewModel
{
public ParentViewModel()
{
Children = new List<ChildViewModel>();
}
public ICollection<ChildViewModel> Children { get; set; }
}
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Parents = new List<ParentViewModel>();
var parent = new ParentViewModel { Name = "Parent" };
parent.Children.Add(new ChildViewModel
{
Name = "Child1"
});
parent.Children.Add(new ChildViewModel
{
Name = "Child2"
});
Parents.Add(parent);
}
public ICollection<ParentViewModel> Parents { get; set; }
}
To display this I use the following markup:
<TreeView ItemsSource="{Binding Parents}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" >
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" Content="{Binding Name}">
<i:Interaction.Behaviors>
<local:CheckParentBehavior Children="{Binding Children}" />
</i:Interaction.Behaviors>
</CheckBox>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The magic that fixes the checkboxes are the CheckParentBehavior:
public class CheckParentBehavior : Behavior<CheckBox>
{
public IEnumerable<StructureViewModel> Children
{
get { return (IEnumerable<StructureViewModel>)GetValue(ChildrenProperty); }
set { SetValue(ChildrenProperty, value); }
}
public static readonly DependencyProperty ChildrenProperty =
DependencyProperty.Register("Children", typeof(IEnumerable<StructureViewModel>), typeof(CheckParentBehavior), new PropertyMetadata(OnChildrenChanged));
protected override void OnAttached()
{
if (Children != null)
AssociatedObject.IsChecked = GetCheck(Children);
}
private static void OnChildrenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
foreach (var child in e.NewValue as IEnumerable<StructureViewModel>)
child.PropertyChanged += (_, args) => OnChildPropertyChanged(d as CheckParentBehavior, args);
}
}
private static void OnChildPropertyChanged(CheckParentBehavior behavior, PropertyChangedEventArgs args)
{
if (args.PropertyName == "IsChecked")
behavior.AssociatedObject.IsChecked = GetCheck(behavior.Children);
}
public static bool? GetCheck(IEnumerable<StructureViewModel> children)
{
if (children.All(c => c.IsChecked.GetValueOrDefault()))
return true;
else if (children.Any(c => c.IsChecked.GetValueOrDefault()))
return null;
else
return false;
}
}
What happens is that it listens to each childs propertychanged event and if it changes the ischecked property it will change the parents accordingly.
Hopefully you can use some of this code to solve your problem.

TreeView IsSelected property returns the ItemsSource collection as 'This' pointer

I have a TreeView which binds to a datasource like this
<TreeView ItemsSource="{Binding Data}" Width="190" >
JinkData is defined in my ViewModel as a property of a class. The property is defined like this
public Collection<JinkData> Data { get; set; }
I have a property IsSelected which tells me which node of the TreeView is currently selected and then I can use the 'this' pointer to get the selected node using the following code.
private static object _selectedItem = null;
// This is public get-only here but you could implement a public setter which also selects the item.
// Also this should be moved to an instance property on a VM for the whole tree, otherwise there will be conflicts for more than one tree.
public static object SelectedItem
{
get { return _selectedItem; }
private set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnSelectedItemChanged();
}
}
}
static virtual void onselecteditemchanged()
{
// raise event / do other things
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
if (_isSelected)
{
SelectedItem = this;
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
The problem that I am facing is 'this' pointer in Set of IsSelected is the collection JinkData but I wanted this pointer to be the selectedJinkData insead of the whole collection. How can I get the currently selected JinkData from the TreeView?
How will I do that?

WPF Databinding a Custom Control

So I've spent about two hours pounding my head against the desk trying everything I can think of to bind to a property on a custom control and none of it works. If I have something like this:
<Grid Name="Form1">
<mine:SomeControl MyProp="{Binding ElementName=Form1, Path=DataContext.Enable}"/>
<Button Click="toggleEnabled_Click"/>
</Grid>
public class TestPage : Page
{
private TestForm _form;
public TestPage()
{
InitializeComponent();
_form = new TestForm();
Form1.DataContext = _form;
}
public void toggleEnabled_Click(object sender, RoutedEventArgs e)
{
_form.Enable = !_form.Enable;
}
}
TestForm looks like:
public class TestForm
{
private bool _enable;
public event PropertyChangedEventHandler PropertyChanged;
public bool Enable
{
get { return _enable; }
set { _enable = value; OnPropertyChanged("Enable"); }
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
And my control looks like:
<UserControl>
<TextBox Name="TestBox"/>
</UserControl>
public class SomeControl : UserControl
{
public static readonly DependencyProperty MyPropProperty =
DependencyProperty.Register("MyProp", typeof(bool), typeof(SomeControl));
public bool MyProp
{
get { return (bool)GetValue(MyPropProperty); }
set { SetValue(MyPropProperty, value); }
}
public SomeControl()
{
InitializeComponent();
DependencyPropertyDescriptor.FromProperty(MyPropProperty)
.AddValueChanged(this, Enable);
}
public void Enable(object sender, EventArgs e)
{
TestBox.IsEnabled = (bool)GetValue(MyPropProperty);
}
}
Absolutely nothing happens when I click the toggle button. If I put a breakpoint inside of the Enable callback it is never hit, whats the deal?
If the Enabled method does not do any more than setting the propertou you could drop it and bind the TextBox.IsEnabled directly:
<UserControl Name="control">
<TextBox IsEnabled="{Binding MyProp, ElementName=control}"/>
</UserControl>
If you want to keep such a method you should register a property changed callback via UIPropertyMetadata for the dependency property.
Also this binding is redundant:
{Binding ElementName=Form1, Path=DataContext.Enable}
The DataContext is inherited (if you don't set it in the UserControl (which you should never do!)), so you can just use:
{Binding Enable}
Further if there is trouble with any of the bindings: There are ways to debug them.

Resources