Is it possible to notify changes on a child class? Like the way binding on ValueB is notified when changing ValueA?
The PropertyChangedEventHandler only allows a propertyname to be notified.
The only way I see is adding functionality to the Child class to call notification there (Notify method)..
public class Parent: INotifyPropertyChanged
{
public Child ChildA
{
get; set;
}
public Child ChildB
{
get; set;
}
public int ValueA
{
get
{
return _valueA;
}
set
{
_valueA = value;
OnPropertyChanged(nameof(ValueA));
}
}
public int ValueB
{
get
{
return _valueB;
}
set
{
_valueB = value;
OnPropertyChanged(nameof(ValueA));
OnPropertyChanged(nameof(ValueB));
}
}
public void RefreshBindings()
{
OnPropertyChanged(ChildA.Check);
OnPropertyChanged(ChildB.Check);
}
}
public class Child: INotifyPropertyChanged
{
public void Notify(string property)
{
OnPropertyChanged(property);
}
public bool Check
{
get
{
return // something;
}
}
}
No, it's the source of the binding that should implement the INotifyPropertyChanged interface and raise change notifications for the framework to be able to refresh the bindings "automatically".
So if you bind to ChildA.Check of Parent, it's the object returned by the ChildA property (i.e. the Child class) that should implement INotifyPropertyChanged.
The other option would to bind to properties of Parent that wraps properties of Child, but the Child must still somehow notify the parent when its state changes.
#NawedNabiZada I appreciate you suggestions but they do not work.
Please only suggest it if you know for a fact they work.
Not sure what you tried, but my point is this:
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Child A :"/>
<Label Content="{Binding Path=ChildA.Check}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Child B :"/>
<Label Content="{Binding Path=ChildB.Check}"/>
</StackPanel>
<Button Content="Check/UnCheck" Command="{Binding Path=RefreshBindingCommand}"/>
</StackPanel>
</Grid>
Parent:
public class Parent : INotifyPropertyChanged
{
public Child ChildA
{
get; set;
}
public Child ChildB
{
get; set;
}
public ICommand RefreshBindingCommand { get; }
public Parent()
{
ChildA = new Child(true);
ChildB = new Child(false);
RefreshBindingCommand = new RelayCommand(RefreshBindingCommand_Execute);
}
void RefreshBindingCommand_Execute(object obj)
{
RefreshBindings();
}
public void RefreshBindings()
{
ChildA.Notify(nameof(ChildA.Check));
ChildB.Notify(nameof(ChildB.Check));
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Child:
public class Child : INotifyPropertyChanged
{
private bool _check;
public bool Check
{
get
{
_check = !_check;
return _check;
}
}
public Child(bool check)
{
_check = check;
}
public void Notify(string property)
{
OnPropertyChanged(property);
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Proof that it works:
Related
I have 2 pages, when I navigate from the first one to the other, I send it an id (in NavigationEvent). Then I call a function on its ViewModel with the id passed and which loads an object to the ViewModel's property asynchronously by a service. I binded the properties of the object in my view and try to call PropertyChanged.Invoke in the getters of the object, but it's always null. How can I bind my view to this obejct?
The data class I want to bind:
class Dog: INotifyPropertyChanged
{
private int _name;
public int Name
{
get => _name;
set
{
_name= value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
The ViewModel:
class MyViewModel : ViewModelBase
{
public Dog Dog{ get; set; }
public MyViewModel()
{
Dog = new Dog();
}
public async void LoadDog(string id)
{
var service = newvDogService();
Dog = await service.GetDogAsync(id);
}
}
The view:
public sealed partial class DogPage : Page
{
private string dogId { get; set; }
public DogPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
dogId = e.Parameter.ToString();
ViewModel.LoadDog(dogId);
}
}
In the xaml file:
<Page.DataContext>
<ViewModels:MyViewModel x:Name ="ViewModel"/>
</Page.DataContext>
...
Text="{Binding Dog.Name}"
I suggest you could simplify the binding process. For an object binding, you don’t need to use the ViewModel. When you pass the id in NavigationEvent, you could get the new Dog object directly via the id. At first, you could create a Dog object that Name property is null. Then you could change the previous object Name through the newly created object.
Please refer to the following code.
Xaml code:
<StackPanel DataContext="{x:Bind Dog,Mode=OneWay}">
<TextBlock Text="{Binding Name,Mode=OneWay}"/>
</StackPanel>
Code behind:
public sealed partial class MainPage : Page
{
public Dog Dog { get; set; }
public MainPage()
{
this.InitializeComponent();
Dog = new Dog { Name = "" };
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
dogId = e.Parameter.ToString();
var dog = await service.GetDogAsync(dogId);
Dog.Name=dog.Name;
}
}
public class Dog : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I am new to MVVM. I found this artcle and it resolve my haf of problems.
https://social.technet.microsoft.com/wiki/contents/articles/30898.simple-navigation-technique-in-wpf-using-mvvm.aspx
However, I need to navigate from one usercontrol to another from the button click event of one usercontrol not from the button on the main window.
Usercontrol 1:
Usercontrol 2:
This is what I have tried sofar;
class Usercontrol1ViewModel : INotifyPropertyChanged
{
public ICommand navCommand { get; set; }
public Usercontrol1ViewModel()
{
navCommand = new BaseCommand(navigate);
}
private void navigate(object obj)
{
NavigationViewModel mainViewModel = new NavigationViewModel();
mainViewModel.SelectedViewModel = new Usercontrol2ViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
NavigationView model class
Class NavigationViewModel : INotifyPropertyChanged
{
public ICommand btn1Command { get; set; }
public ICommand btn2Command { get; set; }
private object selectedViewModel;
public object SelectedViewModel
{
get { return selectedViewModel; }
set { selectedViewModel = value; OnPropertyChanged("SelectedViewModel"); }
}
public NavigationViewModel()
{
btn1Command = new BaseCommand(Opencontrl1);
btn2Command = new BaseCommand(Opencontrl2);
}
private void Opencontrl1(object obj)
{
SelectedViewModel = new Usercontrol1ViewModel();
}
private void Opencontrl2(object obj)
{
SelectedViewModel = new Usercontrol2ViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
Main window code behind;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new NavigationViewModel();
}
}
Can somebody guide me how to achieve this with MVVM?
Try the below code. I used the sample code in the article and I am the author of the MSDN article :)
MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type local:EmployeeViewModel}">
<local:EmployeeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:DepartmentViewModel}">
<local:DepartmentView/>
</DataTemplate>
</Window.Resources>
<DockPanel LastChildFill="True">
<ContentControl x:Name="Pages" DockPanel.Dock="Right" Content="{Binding SelectedViewModel}"/>
</DockPanel>
NavigationViewModel
class NavigationViewModel : INotifyPropertyChanged
{
public EmployeeViewModel EmployeeViewModel { get; set; }
public DepartmentViewModel DepartmentViewModel { get; set; }
private object selectedViewModel;
public object SelectedViewModel
{
get { return selectedViewModel; }
set { selectedViewModel = value; OnPropertyChanged("SelectedViewModel"); }
}
public NavigationViewModel()
{
SelectedViewModel = new EmployeeViewModel(OpenEmp);
}
private void OpenEmp(object obj)
{
if (obj.ToString() == "Dept")
{
SelectedViewModel = new DepartmentViewModel();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
EmployeeView
<Grid>
<StackPanel>
<TextBlock Text="This is employee view"/>
<Button Content="Navigate to Dept View" Command="{Binding Navigate}"/>
</StackPanel>
</Grid>
EmployeeViewModel
class EmployeeViewModel
{
private readonly Action<object> navigate;
public ICommand Navigate { get; set; }
public EmployeeViewModel(Action<object> navigate)
{
Navigate = new BaseCommand(OnNavigate);
this.navigate = navigate;
}
private void OnNavigate(object obj)
{
navigate.Invoke("Dept");
}
}
I have a little problem with a ListBox and his binding.
All is good except when I call the function LstExtensionUnSelectAll() because
nothing changed, the checkbox are again checked.
I think it's a stupid thing but I don't see it.
<ListBox ItemsSource="{Binding LstExtension, Mode=TwoWay}" Grid.Row="0">
<ListBox.ItemTemplate>
<HierarchicalDataTemplate>
<CheckBox Content="{Binding Extension}" IsChecked="{Binding Checked}"/>
</HierarchicalDataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here it's the object LstExtension :
public class CustomExtensions
{
public string Extension { get; set; }
public bool Checked { get; set; }
public CustomExtensions(string ext)
{
Extension = ext;
Checked = true;
}
}
private List<CustomExtensions> _LstExtension;
public IEnumerable<CustomExtensions> LstExtension
{
get { return _LstExtension; }
set
{
if (value != _LstExtension)
{
_LstExtension = value.ToList();
NotifyPropertyChanged("LstExtension");
}
}
}
internal void LstExtensionUnSelectAll()
{
_LstExtension?.ForEach(c => c.Checked = false);
NotifyPropertyChanged("LstExtension");
}
you need to update your CustomExtensions class to use INotifyPropertyChanged so that Checked raises the event whenever the value changes.
public class CustomExtensions : INotifyPropertyChanged
{
public string Extension { get; set; }
private bool _checked;
public bool Checked
{
get { return _checked; }
set
{
if (_checked == value) return;
_checked = value;
RaisePropertyChanged("Checked");
}
}
public CustomExtensions(string ext)
{
Extension = ext;
Checked = true;
}
public virtual event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string name)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
My UI is not updating when more data is added to the ObservableCollection. The console output says A first chance exception of type 'System.NullReferenceException' occurred. Should I be using Inotifycollectionchanged instead? Here is some of the code:
<ListView x:Name="ListView2" ItemsSource="{Binding Source={x:Static d:GrabUserConversationModel._Conversation}, UpdateSourceTrigger=PropertyChanged}" SelectionChanged="ListView1_SelectionChanged">
UserConversationModel.cs
public class UserConversationModel : INotifyPropertyChanged
{
public UserConversationModel()
{
}
public string Name
{ get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string Obj)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(Obj));
}
}
}
MainWindow.xaml.cs
public partial class MainWindow
{
static GrabUserConversationModel grabUserConversationModel;
public MainWindow()
{
InitializeComponent();
...
}
static void AddData()
{
grabUserConversationModel.Conversation.Add(new UserConversationModel { Name = "TestName" });
}
GrabUserConversationModel.cs
class GrabUserConversationModel
{
public static ObservableCollection<UserConversationModel> _Conversation = new ObservableCollection<UserConversationModel>();
public ObservableCollection<UserConversationModel> Conversation
{
get { return _Conversation; }
set { _Conversation = value; }
}
...
your property ObservableCollection<UserConversationModel> Conversation is not implementing the INotifyPropertyChanged
public ObservableCollection<UserConversationModel> Conversation
{
get { return _Conversation; }
set { _Conversation = value; OnPropertyChanged("Conversation");}
}
I am working in vs2010.
I have created a DataGrid which is bounded to
ObservableCollection List;
the Class_CMD looks like this :
public class Class_RetrieveCommand
{
public string CMD { get; set; }
public bool C_R_CMD { get; set; }
public bool S_CMD { get; set; }
public bool C_S_CMD { get; set; }
}
i have 4 delegates which i pass to another window, and this window needs to update the list during runtime. During the runtime i can see the string column of the grid updated all the time but the DataGridCheckBoxColumns are never updated.
the DataGrid -
<DataGrid Background="Transparent" x:Name="DataGrid_CMD" Width="450" MaxHeight="450" Height="Auto" ItemsSource="{Binding}" AutoGenerateColumns="True">
one of the delegates which updates the bool is -
public void UpdateC_S_CMD(string Msg)
{
foreach (Class_CMD c in List.ToArray())
{
if (c.CMD.Equals(Msg))
c.C_S_CMD = true;
}
}
I don't understand why the bool columns are not updated....
can anyone help please?
thanks.
Your class Class_RetrieveCommand needs to implement the INotifyPropertyChanged interface. Otherwise the individual rows databound to the instances of the class don't know that the underlying properties have changed. If you change it to something like this, you should see the changes reflected in your grid:
public class Class_RetrieveCommand : INotifyPropertyChanged
{
private bool _cRCmd;
private bool _cSCmd;
private string _cmd;
private bool _sCmd;
public string CMD
{
get { return _cmd; }
set
{
_cmd = value;
InvokePropertyChanged(new PropertyChangedEventArgs("CMD"));
}
}
public bool C_R_CMD
{
get { return _cRCmd; }
set
{
_cRCmd = value;
InvokePropertyChanged(new PropertyChangedEventArgs("C_R_CMD"));
}
}
public bool S_CMD
{
get { return _sCmd; }
set
{
_sCmd = value;
InvokePropertyChanged(new PropertyChangedEventArgs("S_CMD"));
}
}
public bool C_S_CMD
{
get { return _cSCmd; }
set
{
_cSCmd = value;
InvokePropertyChanged(new PropertyChangedEventArgs("C_S_CMD"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
You should implement INotifyPropertyChanged in the Class_RetrieveCommand like this:
public class Class_RetrieveCommand : INotifyPropertyChanged
{
private string _CMD;
public string CMD
{
get { return _CMD; }
set { _CMD = value; OnPropertyChanged("CMD"); }
}
... similar for the other properties
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Unfortunately you can't use auto properties anymore then (except you resort to proxygenerators).