I want to bind some properties (FooClass.FooString) of a custom class (FooClass) to my MainWindow. Now below (Working known behavior) is the default working solution if binding some data to a gui.
What I want to do is in the second code block (Not working, but desired behavior). Expose some properties of another class objectto the gui and update it.
**Problem**: TheTestStringis not getting updated (on the gui, code behind works). ThePropertyChangedeventis alsonull` (not subscribed?!).
Is this the wrong way how to bind data?
If I bind the complete FooClass object to the gui and set Path (of TextBlock) to Foo.FooString, the gui and string is updated. But I don't want to do it this way.
Is this the way how to solve it?
Working known behavior
public partial class MainWindow : Window
{
public FooClass Foo { get; } = new FooClass();
public MainWindow()
{
DataContext = this;
InitializeComponent();
Loaded += _OnLoaded;
}
private async void _OnLoaded(object sender, RoutedEventArgs e)
{
await Task.Delay(1000);
Foo.ChangeTheProperty();
}
}
public class FooClass : INotifyPropertyChanged
{
public string FooString
{
get => _FooString;
set
{
if (_FooString == value) return;
_FooString = value;
OnPropertyChanged();
}
}
private string _FooString = "empty";
public void ChangeTheProperty()
{
FooString = "Loaded";
}
// ##############################################################################################################################
// PropertyChanged
// ##############################################################################################################################
#region PropertyChanged
/// <summary>
/// The PropertyChanged Eventhandler
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise/invoke the propertyChanged event!
/// </summary>
/// <param name="propertyName"></param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
MainWindow.xaml
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:MainWindow}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock Text="{Binding Path=Foo.FooString}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Window>
Not working, but desired behavior
public partial class MainWindow : Window
{
public string TestString => _Foo.FooString;
private readonly FooClass _Foo;
public MainWindow()
{
_Foo = new FooClass();
DataContext = this;
InitializeComponent();
Loaded += _OnLoaded;
}
private async void _OnLoaded(object sender, RoutedEventArgs e)
{
await Task.Delay(1000);
_Foo.ChangeTheProperty();
}
}
public class FooClass : INotifyPropertyChanged
{
public string FooString
{
get => _FooString;
set
{
if (_FooString == value) return;
_FooString = value;
OnPropertyChanged();
}
}
private string _FooString = "empty";
public void ChangeTheProperty()
{
FooString = "Loaded";
}
// ##############################################################################################################################
// PropertyChanged
// ##############################################################################################################################
#region PropertyChanged
/// <summary>
/// The PropertyChanged Eventhandler
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise/invoke the propertyChanged event!
/// </summary>
/// <param name="propertyName"></param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
MainWindow.xaml
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:MainWindow}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock Text="{Binding Path=TestString}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Window>
Solution 1
Subscribe to the Foo.PropertyChanged event and route it to MainWindow.PropertyChanged.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public FooClass Foo { get; } = new FooClass();
public MainWindow()
{
Foo.PropertyChanged += (sender, args) => OnPropertyChanged(args.PropertyName);
DataContext = this;
InitializeComponent();
Loaded += _OnLoaded;
}
private async void _OnLoaded(object sender, RoutedEventArgs e)
{
await Task.Delay(1000);
Foo.ChangeTheProperty();
}
// ##############################################################################################################################
// PropertyChanged
// ##############################################################################################################################
#region PropertyChanged
/// <summary>
/// The PropertyChanged Eventhandler
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise/invoke the propertyChanged event!
/// </summary>
/// <param name="propertyName"></param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
I might not have fully understood what you want, but here is a working example of data binding, that is somewhat close to your example.
The two main changes were:
Set the datacontext to the VM and not the code behind
Actually give OnPropertyChanged the argument it needs to correctly trigger the refresh, the name of the property.
Result:
MainWindow.xaml
<Window x:Class="ListViewColor.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"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock Text="{Binding Foo.FooString}" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Aqua"/>
</Grid>
</Window>
MainWindow.xaml.cs
namespace ListViewColor
{
public partial class MainWindow : Window
{
public FooClass Foo { get; } = new FooClass();
public MainWindow()
{
DataContext = this;
InitializeComponent();
Loaded += _OnLoaded;
}
private async void _OnLoaded(object sender, RoutedEventArgs e)
{
await Task.Delay(1000);
Foo.ChangeTheProperty();
}
}
}
FooClass.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class FooClass : INotifyPropertyChanged
{
private string _FooString = "Empty";
public string FooString
{
get
{
return _FooString;
}
set
{
if (_FooString == value) return;
_FooString = value;
OnPropertyChanged();
}
}
public void ChangeTheProperty()
{
FooString = "Loaded";
}
#region PropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
I hope that helps!
I have two List<ColumnClass>. one for left side listview and another for right side list view. these listviews are in a pop up box. I am modifying the List of both the Listviews and again assigning that to the Listview's ItemsSource. But this doesn't reflect in the UI immediatly. When I close the popup and open again it reflects the changes. What am I missing?
You should replace the List<T> with ObservableCollection<T>, ObservableCollections will update your ListView whenever an Item is removed, If you are just modifing properties your ColumnClass make sure your ColumnClass implements INotifyPropertyChanged this will allow the UI to update when a property changes.
Example:
Code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MyColumns.Add(new ColumnClass { Name = "Column1" });
MyColumns.Add(new ColumnClass { Name = "Column2" });
MyColumns.Add(new ColumnClass { Name = "Column3" });
}
private ObservableCollection<ColumnClass> _myColumns = new ObservableCollection<ColumnClass>();
public ObservableCollection<ColumnClass> MyColumns
{
get { return _myColumns; }
set { _myColumns = value; }
}
}
xaml:
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WpfApplication8" Height="368" Width="486" Name="UI" >
<Grid>
<ListView ItemsSource="{Binding ElementName=UI, Path=MyColumns}" DisplayMemberPath="Name" />
</Grid>
</Window>
Model:
public class ColumnClass : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; NotifyPropertyChanged("Name"); }
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies the property changed.
/// </summary>
/// <param name="property">The info.</param>
public void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
You should change List<T> to ObservableCollection<T> or BindingList<T>.
Reason, List doesnot implement INotifyPropertyChanged.
I have a textbox which I need to bind a string to.
<TextBox Name="txtDoc" Margin="5" Text ="{Binding Source={x:Static local:DocumentViewModel.FileText}, Path=FileText}">
The FileText property is changed on a different class:
DocumentViewModel.GetInstance().FileText = File.ReadAllText(document.Path);
The DocumentViewModel is a class with Singleton:
public class DocumentViewModel : INotifyPropertyChanged
{
private static string fileText;
public string FileText
{
get { return fileText; }
set
{
fileText = value; // Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("FileText");
}
}
private void OnPropertyChanged(string filetext)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(filetext));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private static DocumentViewModel instance = new DocumentViewModel();
private DocumentViewModel() { }
public static DocumentViewModel GetInstance()
{
return instance;
}
}
I need to be able to change the value of the FileText property and reflect this change in the textbox.
It's not working.
I tried using TextBox as a static property but then the Onp
Try to set the source to your viewmodel instead of the property itself, and set the instance property to public? {Binding Source={x:Static local:DocumentViewModel.instance}, Path=FileText}
Edit: Included a complete example, that working for me:
Xaml:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded">
<TextBox Name="txtDoc" Margin="5"
Text="{Binding Source={x:Static local:DocumentViewModel.Instance}, Path=FileText}" />
</Window>
Code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DocumentViewModel.Instance.FileText = "Hello world!";
}
}
public class DocumentViewModel : INotifyPropertyChanged
{
#region Singleton implementation
// Static constructor to create the singleton instance.
static DocumentViewModel()
{
DocumentViewModel.Instance = new DocumentViewModel();
}
public static DocumentViewModel Instance { get; private set; }
#endregion
private static string fileText;
public string FileText
{
get { return fileText; }
set
{
if (fileText != value)
{
fileText = value;
OnPropertyChanged("FileText");
}
}
}
#region INotifyPropertyChanged
private void OnPropertyChanged(string filetext)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(filetext));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I know I'm probably missing something simple and obvious, but at the moment it eludes me.
I'm attempting to use the MVVM pattern.
How do you update a reference to a command in a viewmodel that is linked to a child viewmodel?
I've got a view (MainView) bound to a viewmodel (MainViewModel).
On MainView, I've got an instance of another view (SummaryView) bound to a viewmodel (SummaryViewModel). SummaryViewModel contains a collection of a third viewmodel (SummaryFilterViewModel).
On SummaryView, there is a TabControl and each tab on it is bound to one of the SummaryFilterViewModel instances in the SummaryViewodel collection.
On MainView there is a button that is bound to a command in MainViewModel.
What I want to happen is for the command logic to live within the SummaryFilterViewModel class. So, whichever tab is currently displayed needs to be wired up to the command that the button on MainView fires.
What I tried to do was this:
The individual SummaryFilterViewModel objects stored in the collection in SummaryViewModel hold the actual implementations of the ShoutCommand.
A CommandReference object in the XAML of MainView binds to a ShoutCommand property of the MainViewModel
The ShoutCommand property of the MainViewModel returns a reference to the ShoutCommand property of the SummaryViewModel object stored in MainViewModel.
The ShoutCommand property of the SummaryViewModel returns a reference to the ShoutCommand property of whichever is the currently selected SummaryFilterViewModel.
What happens, is that the command does not get updated when the user changes tabs.
Am I way off base in how to implement this?
Do I need to move the implementation of the command into the SummaryViewModel class?
Thanks in advance for any help!
The source to my solution is listed below:
ViewModels
SummaryView.xaml
<UserControl x:Class="NestedCommands.Views.SummaryView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="309" d:DesignWidth="476">
<Grid>
<TabControl SelectedIndex="{Binding SelectedTabIndex}">
<TabItem DataContext="{Binding Filters[0]}" Header="{Binding FilterName}">
<ListBox ItemsSource="{Binding ListData}" />
</TabItem>
<TabItem DataContext="{Binding Filters[1]}" Header="{Binding FilterName}">
<ListBox ItemsSource="{Binding ListData}" />
</TabItem>
<TabItem DataContext="{Binding Filters[2]}" Header="{Binding FilterName}">
<ListBox ItemsSource="{Binding ListData}" />
</TabItem>
</TabControl>
</Grid>
MainView.xaml
<Window x:Class="NestedCommands.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:NestedCommands.Views"
xmlns:c="clr-namespace:NestedCommands.Commands"
Title="MainView" Height="336" Width="420">
<Window.Resources>
<c:CommandReference x:Key="ShoutCommandReference" Command="{Binding ShoutCommand}" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<v:SummaryView Grid.Row="0"
DataContext="{Binding SummaryViewModel}" />
<Button Content="Shout Command"
Grid.Row="1"
Command="{StaticResource ShoutCommandReference}" />
</Grid>
Command Classes
CommandReference.cs
using System;
using System.Windows;
using System.Windows.Input;
namespace NestedCommands.Commands
{
/// <summary>
/// This class facilitates associating a key binding in XAML markup to a command
/// defined in a View Model by exposing a Command dependency property.
/// The class derives from Freezable to work around a limitation in WPF when data-binding from XAML.
/// </summary>
public class CommandReference : Freezable, ICommand
{
public CommandReference()
{
// Blank
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandReference), new PropertyMetadata(new PropertyChangedCallback(OnCommandChanged)));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
#region ICommand Members
public bool CanExecute(object parameter)
{
if (Command != null)
return Command.CanExecute(parameter);
return false;
}
public void Execute(object parameter)
{
Command.Execute(parameter);
}
public event EventHandler CanExecuteChanged;
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandReference commandReference = d as CommandReference;
ICommand oldCommand = e.OldValue as ICommand;
ICommand newCommand = e.NewValue as ICommand;
if (oldCommand != null)
{
oldCommand.CanExecuteChanged -= commandReference.CanExecuteChanged;
}
if (newCommand != null)
{
newCommand.CanExecuteChanged += commandReference.CanExecuteChanged;
}
}
#endregion
#region Freezable
protected override Freezable CreateInstanceCore()
{
throw new NotImplementedException();
}
#endregion
}
}
DelegateCommand.cs
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
namespace NestedCommands.Commands
{
/// <summary>
/// This class allows delegating the commanding logic to methods passed as parameters,
/// and enables a View to bind commands to objects that are not part of the element tree.
/// </summary>
public class DelegateCommand : ICommand
{
#region Constructors
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action executeMethod)
: this(executeMethod, null, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
: this(executeMethod, canExecuteMethod, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
{
if (executeMethod == null)
{
throw new ArgumentNullException("executeMethod");
}
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
_isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
}
#endregion
#region Public Methods
/// <summary>
/// Method to determine if the command can be executed
/// </summary>
public bool CanExecute()
{
if (_canExecuteMethod != null)
{
return _canExecuteMethod();
}
return true;
}
/// <summary>
/// Execution of the command
/// </summary>
public void Execute()
{
if (_executeMethod != null)
{
_executeMethod();
}
}
/// <summary>
/// Property to enable or disable CommandManager's automatic requery on this command
/// </summary>
public bool IsAutomaticRequeryDisabled
{
get
{
return _isAutomaticRequeryDisabled;
}
set
{
if (_isAutomaticRequeryDisabled != value)
{
if (value)
{
CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
}
else
{
CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
}
_isAutomaticRequeryDisabled = value;
}
}
}
/// <summary>
/// Raises the CanExecuteChaged event
/// </summary>
public void RaiseCanExecuteChanged()
{
OnCanExecuteChanged();
}
/// <summary>
/// Protected virtual method to raise CanExecuteChanged event
/// </summary>
protected virtual void OnCanExecuteChanged()
{
CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
}
#endregion
#region ICommand Members
/// <summary>
/// ICommand.CanExecuteChanged implementation
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested += value;
}
CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2);
}
remove
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested -= value;
}
CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value);
}
}
bool ICommand.CanExecute(object parameter)
{
return CanExecute();
}
void ICommand.Execute(object parameter)
{
Execute();
}
#endregion
#region Data
private readonly Action _executeMethod = null;
private readonly Func<bool> _canExecuteMethod = null;
private bool _isAutomaticRequeryDisabled = false;
private List<WeakReference> _canExecuteChangedHandlers;
#endregion
}
/// <summary>
/// This class allows delegating the commanding logic to methods passed as parameters,
/// and enables a View to bind commands to objects that are not part of the element tree.
/// </summary>
/// <typeparam name="T">Type of the parameter passed to the delegates</typeparam>
public class DelegateCommand<T> : ICommand
{
#region Constructors
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action<T> executeMethod)
: this(executeMethod, null, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
: this(executeMethod, canExecuteMethod, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
{
if (executeMethod == null)
{
throw new ArgumentNullException("executeMethod");
}
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
_isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
}
#endregion
#region Public Methods
/// <summary>
/// Method to determine if the command can be executed
/// </summary>
public bool CanExecute(T parameter)
{
if (_canExecuteMethod != null)
{
return _canExecuteMethod(parameter);
}
return true;
}
/// <summary>
/// Execution of the command
/// </summary>
public void Execute(T parameter)
{
if (_executeMethod != null)
{
_executeMethod(parameter);
}
}
/// <summary>
/// Raises the CanExecuteChaged event
/// </summary>
public void RaiseCanExecuteChanged()
{
OnCanExecuteChanged();
}
/// <summary>
/// Protected virtual method to raise CanExecuteChanged event
/// </summary>
protected virtual void OnCanExecuteChanged()
{
CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
}
/// <summary>
/// Property to enable or disable CommandManager's automatic requery on this command
/// </summary>
public bool IsAutomaticRequeryDisabled
{
get
{
return _isAutomaticRequeryDisabled;
}
set
{
if (_isAutomaticRequeryDisabled != value)
{
if (value)
{
CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
}
else
{
CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
}
_isAutomaticRequeryDisabled = value;
}
}
}
#endregion
#region ICommand Members
/// <summary>
/// ICommand.CanExecuteChanged implementation
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested += value;
}
CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2);
}
remove
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested -= value;
}
CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value);
}
}
bool ICommand.CanExecute(object parameter)
{
// if T is of value type and the parameter is not
// set yet, then return false if CanExecute delegate
// exists, else return true
if (parameter == null &&
typeof(T).IsValueType)
{
return (_canExecuteMethod == null);
}
return CanExecute((T)parameter);
}
void ICommand.Execute(object parameter)
{
Execute((T)parameter);
}
#endregion
#region Data
private readonly Action<T> _executeMethod = null;
private readonly Func<T, bool> _canExecuteMethod = null;
private bool _isAutomaticRequeryDisabled = false;
private List<WeakReference> _canExecuteChangedHandlers;
#endregion
}
/// <summary>
/// This class contains methods for the CommandManager that help avoid memory leaks by
/// using weak references.
/// </summary>
internal class CommandManagerHelper
{
internal static void CallWeakReferenceHandlers(List<WeakReference> handlers)
{
if (handlers != null)
{
// Take a snapshot of the handlers before we call out to them since the handlers
// could cause the array to me modified while we are reading it.
EventHandler[] callees = new EventHandler[handlers.Count];
int count = 0;
for (int i = handlers.Count - 1; i >= 0; i--)
{
WeakReference reference = handlers[i];
EventHandler handler = reference.Target as EventHandler;
if (handler == null)
{
// Clean up old handlers that have been collected
handlers.RemoveAt(i);
}
else
{
callees[count] = handler;
count++;
}
}
// Call the handlers that we snapshotted
for (int i = 0; i < count; i++)
{
EventHandler handler = callees[i];
handler(null, EventArgs.Empty);
}
}
}
internal static void AddHandlersToRequerySuggested(List<WeakReference> handlers)
{
if (handlers != null)
{
foreach (WeakReference handlerRef in handlers)
{
EventHandler handler = handlerRef.Target as EventHandler;
if (handler != null)
{
CommandManager.RequerySuggested += handler;
}
}
}
}
internal static void RemoveHandlersFromRequerySuggested(List<WeakReference> handlers)
{
if (handlers != null)
{
foreach (WeakReference handlerRef in handlers)
{
EventHandler handler = handlerRef.Target as EventHandler;
if (handler != null)
{
CommandManager.RequerySuggested -= handler;
}
}
}
}
internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler)
{
AddWeakReferenceHandler(ref handlers, handler, -1);
}
internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler, int defaultListSize)
{
if (handlers == null)
{
handlers = (defaultListSize > 0 ? new List<WeakReference>(defaultListSize) : new List<WeakReference>());
}
handlers.Add(new WeakReference(handler));
}
internal static void RemoveWeakReferenceHandler(List<WeakReference> handlers, EventHandler handler)
{
if (handlers != null)
{
for (int i = handlers.Count - 1; i >= 0; i--)
{
WeakReference reference = handlers[i];
EventHandler existingHandler = reference.Target as EventHandler;
if ((existingHandler == null) || (existingHandler == handler))
{
// Clean up old handlers that have been collected
// in addition to the handler that is to be removed.
handlers.RemoveAt(i);
}
}
}
}
}
}
View Models
ViewModelBase.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace NestedCommands.ViewModels
{
class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(object sender, string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
}
}
}
MainViewModel.cs
using System;
using System.Windows.Input;
namespace NestedCommands.ViewModels
{
class MainViewModel : ViewModelBase
{
public MainViewModel()
{
_SummaryViewModel = new SummaryViewModel();
}
private SummaryViewModel _SummaryViewModel;
public SummaryViewModel SummaryViewModel
{
get { return _SummaryViewModel; }
set
{
_SummaryViewModel = value;
OnPropertyChanged(this, "SummaryViewModel");
}
}
public ICommand ShoutCommand
{
get { return _SummaryViewModel.ShoutCommand; }
}
}
}
SummaryViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NestedCommands.Commands;
using System.Windows.Input;
namespace NestedCommands.ViewModels
{
class SummaryViewModel : ViewModelBase
{
#region Constructor
public SummaryViewModel()
{
List<SummaryFilterViewModel> filters = new List<SummaryFilterViewModel>();
filters.Add(new SummaryFilterViewModel("Filter 1"));
filters.Add(new SummaryFilterViewModel("Filter 2"));
filters.Add(new SummaryFilterViewModel("Filter 3"));
Filters = filters;
}
#endregion
#region Properties
private List<SummaryFilterViewModel> _Filters;
public List<SummaryFilterViewModel> Filters
{
get { return _Filters; }
set
{
_Filters = value;
OnPropertyChanged(this, "Filters");
}
}
private int _SelectedTabIndex;
public int SelectedTabIndex
{
get { return _SelectedTabIndex; }
set
{
_SelectedTabIndex = value;
OnPropertyChanged(this, "SelectedTabIndex");
}
}
#endregion
#region Command References
public ICommand ShoutCommand
{
get { return Filters[SelectedTabIndex].ShoutCommand; }
}
#endregion
}
}
SummaryFilterViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NestedCommands.Commands;
using System.Windows.Input;
namespace NestedCommands.ViewModels
{
class SummaryFilterViewModel : ViewModelBase
{
#region Constructor
public SummaryFilterViewModel(string FilterName)
{
this.FilterName = FilterName;
List<string> listData = new List<string>();
for (int i = 1; i < 10; i++)
{
listData.Add(string.Format("{0}: {1}", FilterName, i));
}
ListData = listData;
}
#endregion
#region Properties
private string _FilterName;
public string FilterName
{
get { return _FilterName; }
set
{
_FilterName = value;
OnPropertyChanged(this, "FilterName");
}
}
private List<string> _ListData;
public List<string> ListData
{
get { return _ListData; }
set
{
_ListData = value;
OnPropertyChanged(this, "ListData");
}
}
#endregion
#region Shout Command
private DelegateCommand _ShoutCommand;
public ICommand ShoutCommand
{
get { return _ShoutCommand ?? (_ShoutCommand = new DelegateCommand(Shout, CanShout)); }
}
private void Shout()
{
System.Windows.MessageBox.Show(string.Format("Called from SummaryFilterViewModel: {0}", FilterName));
}
private bool CanShout()
{
return true;
}
#endregion
}
}
I think the path you are going down is quickly going to end up complex and tightly coupled. You should probably take a look at using the Mediator Pattern to facilitate communication of changes in your SummaryFilterViewModel to your MainViewModel.
Using the mediator pattern, you can implement a means of subscribing to and publishing messages that allows one view model to communicate with another view model without ending up with tightly coupled view models.
Basically, when your tab selection changes, the summary view model would publish the change with the message payload containing the reference object or other data. The main view model would be subscribed to publication of this message and modify its state accordingly.
Some resources on the Mediator Pattern you can take a look at:
http://www.eggheadcafe.com/tutorials/aspnet/ec832ac7-6e4c-4ea8-81ab-7374d3da3425/wpf-and-the-model-view-vi.aspx
http://marlongrech.wordpress.com/2008/03/20/more-than-just-mvc-for-wpf/
http://marlongrech.wordpress.com/2009/04/16/mediator-v2-for-mvvm-wpf-and-silverlight-applications/
I've made some changes to my sample solution in response to some suggestions that were made by Kent Boogaart. Kent, thank you again for your reply it gave me a new direction to move.
I'll try to keep this as short as possible.
The MainView is basically a frameset that houses the application's main command interface. In the sample the SummaryView is embedded directly in MainView's XAML. In the real solution it's a content control that may contain different types of child views. Each type of child view may or may not implement the command.
I was able to wire the SelectedIndex to a property so that I wouldn't need a dependency on the System.Windows.Control library. When that property changes, I also call OnPropertyChanged for the ShoutCommand property.
This, however, did not relay that change to the MainView object. So, in MainViewModel, I listen for the _SummaryViewModel.PropertyChanged event.
When MainView hears that the _SummaryViewModel.PropertyChanged event fired, I call OnPropertyChanged(this, "ShoutCommand") which propagates the change to the MainView.
So, I guess I want to know if it's necessary for the MainViewModel to listen to the _SummaryViewModel's PropertyChanged event like I'm doing, or if there is a cleaner way to do it.
My code is listed below: (I tried to take out as much as I could)
Thanks!
MainView
<v:SummaryView Grid.Row="0"
DataContext="{Binding SummaryViewModel}" />
<Button Content="Shout Command"
Grid.Row="1"
Command="{Binding ShoutCommand}" />
MainViewModel
public MainViewModel()
{
_SummaryViewModel = new SummaryViewModel();
_SummaryViewModel.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(_SummaryViewModel_PropertyChanged);
}
void _SummaryViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "ShoutCommand":
OnPropertyChanged(this, "ShoutCommand");
break;
}
}
private SummaryViewModel _SummaryViewModel;
public SummaryViewModel SummaryViewModel {...}
public ICommand ShoutCommand
{
get { return _SummaryViewModel.ShoutCommand; }
}
SummaryView
<TabControl SelectedIndex="{Binding SelectedTabIndex}">
<TabItem DataContext="{Binding Filters[0]}" Header="{Binding FilterName}">
<ListBox ItemsSource="{Binding ListData}" />
</TabItem>
<!-- TabItem repeated two more times -->
</TabControl>
SummaryViewModel
private List<SummaryFilterViewModel> _Filters;
public List<SummaryFilterViewModel> Filters {...}
private int _SelectedTabIndex;
public int SelectedTabIndex
{
get { return _SelectedTabIndex; }
set
{
_SelectedTabIndex = value;
OnPropertyChanged(this, "SelectedTabIndex");
OnPropertyChanged(this, "ShoutCommand");
}
}
public ICommand ShoutCommand
{
get {
int selectedTabIndex = SelectedTabIndex;
return (selectedTabIndex == -1) ? null : Filters[SelectedTabIndex].ShoutCommand;
}
}
Your post was long and I confess I didn't fully read it. However, I don't understand the purpose of CommandReference. Why not just bind directly to MainViewModel.ShoutCommand? Consider:
Bind the ItemsSource of the TabControl to the collection of child view models
Bind the SelectedItem of the TabControl to another property that tracks the selected child view model
When the aforementioned property changes, raise the PropertyChanged event for the ShoutCommand property, too
In the getter for ShoutCommand property, simply return the ShoutCommand of the selected child view model
How to disable all the properties or some of the properties PropertyChanged event for some time when we are using INotifypropertyChanged?
In order for INotifyPropertyChanged to work, you need to raise the PropertyChanged event. Therefore, to make it not work, you just don't raise that event.
Here's a small example class:
public class NPCExample : INotifyPropertyChanged
{
public NPCExample()
{
}
private string mSomeProperty = "Set Property";
public string SomeProperty
{
get { return mSomeProperty; }
set
{
mSomeProperty = value;
if (mUseNotifyPropertyChanged)
NotifyPropertyChanged("SomeProperty");
}
}
private Boolean mUseNotifyPropertyChanged = true;
public Boolean UseNotifyPropertyChanged
{
get { return mUseNotifyPropertyChanged; }
set
{
mUseNotifyPropertyChanged = value;
NotifyPropertyChanged("UseNotifyPropertyChanged");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
In this class, each property calls the common "NotifyPropertyChanged" method for raising the PropertyChanged event. There is an additional variable defined (here, I used a public Property so I could bind it to a checkbox) that tells whether or not to raise the event, as used in the SomeProperty event.
Here's a small, quick-n-dirty program to show this in action:
XAML
<Window x:Class="MyNamespace.SelectiveNotifyPropertyChanged"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SelectiveNotifyPropertyChanged" Height="300" Width="300">
<StackPanel>
<TextBlock Text="{Binding SomeProperty}" />
<CheckBox x:Name="chkINPCEnabled"
Content="Enable INotifyPropertyChanged"
IsChecked="{Binding UseNotifyPropertyChanged}"></CheckBox>
<StackPanel Orientation="Horizontal">
<TextBox x:Name="txtIsProperty"
Text="Set Property" />
<Button x:Name="btnSetProperty"
Content="Set Property" />
</StackPanel>
</StackPanel>
</Window>
Code Behind
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace MyNamespace
{
/// <summary>
/// Interaction logic for SelectiveNotifyPropertyChanged.xaml
/// </summary>
public partial class SelectiveNotifyPropertyChanged : Window
{
public SelectiveNotifyPropertyChanged()
{
InitializeComponent();
NPCExample example = new NPCExample();
this.DataContext = example;
btnSetProperty.Click +=
(s, e) => example.SomeProperty = txtIsProperty.Text;
}
}
public class NPCExample : INotifyPropertyChanged
{
public NPCExample()
{
}
private string mSomeProperty = "Set Property";
public string SomeProperty
{
get { return mSomeProperty; }
set
{
mSomeProperty = value;
if (mUseNotifyPropertyChanged)
NotifyPropertyChanged("SomeProperty");
}
}
private Boolean mUseNotifyPropertyChanged = true;
public Boolean UseNotifyPropertyChanged
{
get { return mUseNotifyPropertyChanged; }
set
{
mUseNotifyPropertyChanged = value;
NotifyPropertyChanged("UseNotifyPropertyChanged");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
If you are referring to a binding, you can set the UpdateSourceTrigger to Explicit, which means any changes won't get saved until you explicitly tell it to update
<TextBox Text="{Binding SomeValue, UpdateSourceTrigger=Explicit}" />
Based on your comment to Rachel it sounds like you might want to set the private property backing member sometimes. Could you expose a public method in your underlying class that would set the private member but not call NotifyPropertyChaged?
Public Class SomeClass
... define property SomeProp and m_SomeProp
Public Sub SetSomeProp(val as string)
m_SomePreop=val
End Sub
End Class