I am looking for an implementation of RelayCommand. The original implementation that I considered was the classic one (lets call it implementation A)
public class RelayCommand : ICommand
{
private readonly Predicate<object> canExecute;
private readonly Action<object> execute;
private EventHandler canExecuteEventhandler;
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
this.canExecuteEventhandler += value;
}
remove
{
this.canExecuteEventhandler -= value;
}
}
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return this.canExecute == null ? true : this.canExecute(parameter);
}
[DebuggerStepThrough]
public void Execute(object parameter)
{
this.execute(parameter);
}
public void InvokeCanExecuteChanged()
{
if (this.canExecute != null)
{
if (this.canExecuteEventhandler != null)
{
this.canExecuteEventhandler(this, EventArgs.Empty);
}
}
}
}
This is the implementation that I have used since I started developing in Silverlight around 2009. I have also used it in WPF applications.
Lately I understood that it has a memory leak problem in cases where the views that bind to the command have shorter life span than the command itself. Apparently when a button binds to the command, it of course registers to the CanExecuteChanged event handler but never unregistered. The default event handlers hold strong reference to the delegate, which holds a strong reference to the button itself, therefore the RelayCommand keeps the button alive and that's a memory leak.
Another implementation that I have found uses the CommandManager. The CommandManager exposes a a RequerySuggested event and internally only hold weak references to the delegates. So the definition of the event can be implemented as follows (implementation B)
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
So that every delegate is passed to the static event handler instead of being held by the relay command itself. My problem with this implementation is that it relies on the CommandManager to know when to raise the event. Also, when RaiseCanExecuteChanged is called, the command manager raises this event for all RelayCommands, not specifically the one that initiated the event.
The last implementation I found was from MvvmLight where the event is defined as such (implementation C):
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
{
// add event handler to local handler backing field in a thread safe manner
EventHandler handler2;
EventHandler canExecuteChanged = _requerySuggestedLocal;
do
{
handler2 = canExecuteChanged;
EventHandler handler3 = (EventHandler)Delegate.Combine(handler2, value);
canExecuteChanged = System.Threading.Interlocked.CompareExchange<EventHandler>(
ref _requerySuggestedLocal,
handler3,
handler2);
}
while (canExecuteChanged != handler2);
CommandManager.RequerySuggested += value;
}
}
remove
{
if (_canExecute != null)
{
// removes an event handler from local backing field in a thread safe manner
EventHandler handler2;
EventHandler canExecuteChanged = this._requerySuggestedLocal;
do
{
handler2 = canExecuteChanged;
EventHandler handler3 = (EventHandler)Delegate.Remove(handler2, value);
canExecuteChanged = System.Threading.Interlocked.CompareExchange<EventHandler>(
ref this._requerySuggestedLocal,
handler3,
handler2);
}
while (canExecuteChanged != handler2);
CommandManager.RequerySuggested -= value;
}
}
}
So in addition to the command manager it also holds the delegate locally and does some magic trick to support thread safety.
My questions are:
Which of these implementations actually solve the memory leak problem.
Is there an implementation that solves the problem without relying on the CommandManager?
Is the trick that is done in implementation C really necessary to avoid thread safety related bugs and how does it solve it?
You can use the WeakEventManager.
public event EventHandler CanExecuteChanged
{
add
{
RelayCommandWeakEventManager.AddHandler(this, value);
}
remove
{
RelayCommandWeakEventManager.RemoveHandler(this, value);
}
}
private class RelayCommandWeakEventManager : WeakEventManager
{
private RelayCommandWeakEventManager()
{
}
public static void AddHandler(RelayCommand source, EventHandler handler)
{
if (source == null)
throw new ArgumentNullException("source");
if (handler == null)
throw new ArgumentNullException("handler");
CurrentManager.ProtectedAddHandler(source, handler);
}
public static void RemoveHandler(RelayCommand source,
EventHandler handler)
{
if (source == null)
throw new ArgumentNullException("source");
if (handler == null)
throw new ArgumentNullException("handler");
CurrentManager.ProtectedRemoveHandler(source, handler);
}
private static RelayCommandWeakEventManager CurrentManager
{
get
{
Type managerType = typeof(RelayCommandWeakEventManager);
RelayCommandWeakEventManager manager =
(RelayCommandWeakEventManager)GetCurrentManager(managerType);
// at first use, create and register a new manager
if (manager == null)
{
manager = new RelayCommandWeakEventManager();
SetCurrentManager(managerType, manager);
}
return manager;
}
}
/// <summary>
/// Return a new list to hold listeners to the event.
/// </summary>
protected override ListenerList NewListenerList()
{
return new ListenerList<EventArgs>();
}
/// <summary>
/// Listen to the given source for the event.
/// </summary>
protected override void StartListening(object source)
{
EventSource typedSource = (RelayCommand) source;
typedSource.canExecuteEventhandler += new EventHandler(OnSomeEvent);
}
/// <summary>
/// Stop listening to the given source for the event.
/// </summary>
protected override void StopListening(object source)
{
EventSource typedSource = (RelayCommand) source;
typedSource.canExecuteEventhandler -= new EventHandler(OnSomeEvent);
}
/// <summary>
/// Event handler for the SomeEvent event.
/// </summary>
void OnSomeEvent(object sender, EventArgs e)
{
DeliverEvent(sender, e);
}
}
This code was shamelessly lifted (and adapted) from https://msdn.microsoft.com/en-us/library/aa970850%28v=vs.110%29.aspx
Based on Aron's answer, I went with a solution that involves weak events, but developed differently, in order to reduce the amounts of code and make the building blocks a little more reusable.
The following implementation mixes the "classic" one, with some ideas taken from MvvmLight and I am using a WeakEvent class that is developed according to a pattern introduced in the following (excellent!!!) article by Daniel Grunwald. http://www.codeproject.com/Articles/29922/Weak-Events-in-C
The RelayCommand itself is implemented as follows:
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
private WeakEvent<EventHandler> _canExecuteChanged;
/// <summary>
/// Initializes a new instance of the RelayCommand class that
/// can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <exception cref="ArgumentNullException">If the execute argument is null.</exception>
public RelayCommand(Action execute)
: this(execute, null)
{
}
/// <summary>
/// Initializes a new instance of the RelayCommand class.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
/// <exception cref="ArgumentNullException">If the execute argument is null.</exception>
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
_canExecuteChanged = new WeakEvent<EventHandler>();
}
/// <summary>
/// Occurs when changes occur that affect whether the command should execute.
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
_canExecuteChanged.Add(value);
}
remove
{
_canExecuteChanged.Remove(value);
}
}
/// <summary>
/// Raises the <see cref="CanExecuteChanged" /> event.
/// </summary>
[SuppressMessage(
"Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "The this keyword is used in the Silverlight version")]
[SuppressMessage(
"Microsoft.Design",
"CA1030:UseEventsWhereAppropriate",
Justification = "This cannot be an event")]
public void RaiseCanExecuteChanged()
{
_canExecuteChanged.Raise(this, EventArgs.Empty);
}
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">This parameter will always be ignored.</param>
/// <returns>true if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object parameter)
{
return (_canExecute == null) || (_canExecute());
}
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">This parameter will always be ignored.</param>
public virtual void Execute(object parameter)
{
if (CanExecute(parameter))
{
_execute();
}
}
}
Note that I am not holding a weak refernces to the _execute and _canExecute delegates. Using weak references to delegates causes all sorts of problems when the delegates are closures since their target object is not referenced by any object and they "die" instantly. I expect these delegates to have the owner of the RelayCommand anyway, so their lifespan is expected to be the same as the RelayCommand's.
The CanExecuteChanged event is implemented using the WeakEvent, so even if the listeners do not unregister, the relay command does not affect their lifespan.
Related
This question already has answers here:
What's the best way to pass event to ViewModel?
(3 answers)
Closed 8 years ago.
I am trying to find a simple example on how to bind some TextBox events (PreviewTextInput and PreviewKeyDown) to a Commands, however I can't find any clear example and all the exmaples I found so far enforce me to use some MVVM framework (Light toolkit, Prism, etc.), however currently I don't want to use a framework because I want to understand more deeply how the business works.
Can anyone please supply a simple example on how this can be achieved?
Is it absolutely necessary to use MVVM framework?
Thanks in advance.
The easy way is to attach a common event handler to the event in XAML, and invoke the Command in code-behind. Such an event handler could look like the following:
private void TextBox_OnTextChanged(object sender, EventArgs e)
{
var viewmodel = this.DataContext as MyViewmodel;
if (viewmodel != null)
{
viewmodel.SomeCommand.Execute();
}
}
An alternative that works without any code in the code-behind (but is a bit tricky to implement, and works only on .NET 4.5) is to implement your own MarkupExtension, such that you can code something like
<TextBox TextChanged="{myMarkupExtension:CommandBinding SomeCommand]">...</TextBox>
There are a few articles out there describing this approach, for example this one
You can inherit TextBox and implement ICommandSource. I have done the same, my implementation looked like this. You should be able to extend this to work on PreviewTextInput.
public class CommandTextBox : TextBox, ICommandSource
{
private bool _canExecute;
private EventHandler _canExecuteChanged;
/// <summary>
/// DependencyProperty for Command property.
/// </summary>
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandTextBox), new PropertyMetadata(OnCommandChanged));
/// <summary>
/// Gets or sets the command to invoke when the enter key is pressed.
/// </summary>
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
/// <summary>
/// DependencyProperty for CommandParameter property.
/// </summary>
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(CommandTextBox));
/// <summary>
/// Gets or sets the parameter to pass to the Command property.
/// </summary>
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
/// <summary>
/// Gets or sets a value that indicates whether the command resets the text property.
/// </summary>
public bool CommandResetsText { get; set; }
/// <summary>
/// DependencyProperty for CommandTarget property.
/// </summary>
public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(CommandTextBox));
/// <summary>
/// Gets or sets the element on which to raise the specified command.
/// </summary>
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
/// <summary>
/// Gets a value that becomes the return value of
/// System.Windows.UIElement.IsEnabled in derived classes.
/// </summary>
protected override bool IsEnabledCore
{
get { return base.IsEnabledCore && _canExecute; }
}
/// <summary>
/// Command dependency property change callback.
/// </summary>
/// <param name="d">Dependency Object</param>
/// <param name="e">Event Args</param>
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandTextBox tb = (CommandTextBox)d;
tb.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
}
/// <summary>
/// If Command is defined, pressing the enter key will invoke the command;
/// Otherwise, the textbox will behave normally.
/// </summary>
/// <param name="e">Provides data about the event.</param>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Enter && Command != null)
{
RoutedCommand command = Command as RoutedCommand;
if (command != null)
command.Execute(CommandParameter, CommandTarget);
else
Command.Execute(CommandParameter);
if (CommandResetsText)
this.Text = String.Empty;
}
}
/// <summary>
/// Add a command to the Command Property.
/// </summary>
/// <param name="command">Command</param>
private void AddCommand(ICommand command)
{
var handler = new EventHandler(CanExecuteChanged);
_canExecuteChanged = handler;
if (command != null)
command.CanExecuteChanged += _canExecuteChanged;
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (Command != null)
{
RoutedCommand command = Command as RoutedCommand;
// If a RoutedCommand.
if (command != null)
_canExecute = command.CanExecute(CommandParameter, CommandTarget);
else
_canExecute = Command.CanExecute(CommandParameter);
}
CoerceValue(UIElement.IsEnabledProperty);
}
/// <summary>
/// Add a new command to the Command Property.
/// </summary>
/// <param name="oldCommand">Old Command</param>
/// <param name="newCommand">New Command</param>
private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
{
// If oldCommand is not null, then we need to remove the handlers.
if (oldCommand != null)
RemoveCommand(oldCommand);
AddCommand(newCommand);
}
/// <summary>
/// Remove a command from the Command Property.
/// </summary>
/// <param name="command">Command</param>
private void RemoveCommand(ICommand command)
{
EventHandler handler = CanExecuteChanged;
command.CanExecuteChanged -= handler;
}
}
My ViewModelBase class is:
public abstract class ViewModelBase:INotifyPropertyChanged, IDisposable, INotifyCollectionChanged
{
#region Constructor
protected ViewModelBase()
{
}
#endregion // Constructor
#region DisplayName
/// <summary>
/// Returns the user-friendly name of this object.
/// Child classes can set this property to a new value,
/// or override it to determine the value on-demand.
/// </summary>
public virtual string DisplayName { get; protected set; }
#endregion // DisplayName
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
#region INotifyPropertyChanged Members
/// <summary>
/// raised when property of this object has some new value
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion
#region IDisposable Members
public void Dispose()
{
this.OnDispose();
}
/// <summary>
/// child classes can override this method to perform cleanup logic,like removing eventhandlers and disposing objects
/// Anindya
/// </summary>
protected virtual void OnDispose()
{
//no implementation has been done here
//intentionhally I have done so
//so that this method will be only used for the overriding of this method
//by default nothing I have kept in this method
}
#endregion
#region INotifyCollectionChanged Members
/// <summary>
/// Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.
/// </summary>
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected virtual void OnCollectionChanged(CollectionChangeEventArgs ccevent)
{
//NotifyCollectionChangedEventHandler handler = this.CollectionChanged;
//if (handler != null)
//{
// var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction
//}
}
#endregion
}
and my WorkSpaceViewModel is inheriting from ViewModelBase as followes:
public abstract class WorkspaceViewModel:ViewModelBase
{
#region Fields
RelayCommand _closeCommand;
#endregion // Fields
#region Constructor
protected WorkspaceViewModel()
{
}
#endregion // Constructor
#region CloseCommand
/// <summary>
/// Returns the command that, when invoked, attempts
/// to remove this workspace from the user interface.
/// </summary>
public ICommand CloseCommand
{
get
{
if (_closeCommand == null)
_closeCommand = new RelayCommand(param => this.OnRequestClose());
return _closeCommand;
}
}
private void CanDoSomeImportantMethod()
{
}
#endregion // CloseCommand
#region RequestClose [event]
/// <summary>
/// Raised when this workspace should be removed from the UI.
/// </summary>
public event EventHandler RequestClose;
void OnRequestClose()
{
EventHandler handler = this.RequestClose;
if (handler != null)
handler(this, EventArgs.Empty);
}
#endregion // RequestClose [event]
}
My ViewModel is inheriting from WorkSpaceViewModel as followes:
public class MainWindowViewModel:WorkspaceViewModel,INotifyCollectionChanged
{
//
RelayCommand _loadCommand;
MatchBLL matchBLL = new MatchBLL();
EfesBetServiceReference.EfesBetClient proxy = new EfesBetClient();
public MainWindowViewModel()
{
_matchObsCollection = new ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC>();
Load();
_matchObsCollection.CollectionChanged += new NotifyCollectionChangedEventHandler(_matchObsCollection_CollectionChanged);
}
/// <summary>
/// This will get called when the collection is changed(for reference see http://stackoverflow.com/questions/1427471/observablecollection-not-noticing-when-item-in-it-changes-even-with-inotifyprop)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void _matchObsCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
}
protected override void OnPropertyChanged(string propertyName)
{
base.OnPropertyChanged(propertyName);
}
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
{
_loadCommand = new RelayCommand(
param => this.Load(),
param => this.CanLoad
);
}
return _loadCommand;
}
}
List<EfesBet.DataContract.GetMatchDetailsDC> matchList;
ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC> _matchObsCollection;
public ObservableCollection<EfesBet.DataContract.GetMatchDetailsDC> MatchObsCollection
{
get { return _matchObsCollection; }
set
{
_matchObsCollection = value;
OnPropertyChanged("MatchObsCollection");
}
}
public void Load()
{
matchList = new List<GetMatchDetailsDC>();
matchList = proxy.GetMatch().ToList();
foreach (EfesBet.DataContract.GetMatchDetailsDC match in matchList)
{
_matchObsCollection.Add(match);
}
//ajebaje code
PopulateSahibiKonuk();
}
bool CanLoad
{
get { return true; }
}
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
Now I am having a DataGrid in UI and I want OnCollectionChanged of my ObservableCollection. How NotifyCollectionChangedAction like add, move, remove, replace, reset my viewmodel should fire. But I do not know how to implement or what I have to do in my base classes or in my viewmodel. Please provide me some useful code or urls or suggestion regarding this.
Thanking you in advance.
Typically, a view model would not implement the INotifyCollectionChanged interface... that is for collection classes to implement. I have a massive WPF project and I haven't needed to implement that interface once.
I generally use custom collection classes that extend ObservableCollection<T> and these collections already implement the INotifyCollectionChanged interface. Therefore, when I need to display collections, I just add properties for these collections in my view model classes. If I then need to monitor changes in the collection, I would add a handler for the CollectionChanged event.
This is not something that can be built into the base view model unless you are certain that every view model will have a collection of a particular type. Even then, what would you do when you need more than one collection in a view model? You'd have to add a collection property and extra handlers, so why don't you just do that whenever you need to?
Could you please provide me some samples where DataGrid in WPF updates live.
I am trying to write an app, which will be updating a LIST regularly and that i want to show on a DataGrid using WPF.
Following is the code snippet.
MainWindow.XAMl
Model _model = new Model();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = _model;
}
DataGrid Xaml
<DataGrid
Height="214"
HorizontalAlignment="Left"
Margin="12,135,0,0"
Name="resultDataGrid"
VerticalAlignment="Top"
Width="720"
ItemsSource="{Binding Path=Results, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
/>
Code where I am updating the Results.
public class Model : INotifyPropertyChanged
{
ObservableCollection<Result> _results = new ObservableCollection<Result>();
public void X()
{
foreach (var file in Files)
{
_results.Add(new Result() { File = file, Status = "passsed" });
}
}
public ObservableCollection<Result> Results
{
get { return _results; }
set { _results = value; OnPropertyChanged("Results"); }
}
}
When I am adding to _results collection Live update is not happening.
Use databinding (by binding DataGrid.ItemsSource to your collection of items) and remember to fire INotifyPropertyChanged.PropertyChanged when an item is updated. Or if it the collection of items and not individual items that change fire INotifyCollectionChanged.CollectionChanged. Obviously you need to databind to classes that implement these interfaces for this to work.
Try using an Observable Collection instead of a normal list. This collection implements INotifyCollectionChanged already.
Note that this will work for adding or removing items in the list, but if you change the item's properties themselves and want to update the ObservableCollection, you need to have an ObservableCollection of ViewModels, which are implementing INotifyPropertyChanged on each property.
EDIT
This may be a silly question but where are you actually calling that x method? I copied your code pretty much exactly, created my own Result class, and implemented INotifyPropertyChanged and created an implementation of the RelayCommand pattern, bound that to the command of the button and it all worked. When I click the button, the datagrid changes.
All I can think of is that you haven't actually implemented the INotifyPropertyChanged, or you aren't running the x method.
here is the code I did:
public class Model : INotifyPropertyChanged
{
ObservableCollection<Result> _results = new ObservableCollection<Result>();
private List<string> Files;
public void X()
{
foreach (var file in Files)
{
_results.Add(new Result() { File = file, Status = "passsed" });
}
_results.Add(new Result() { File = DateTime.Now.ToString(), Status = "passed" });
}
public ObservableCollection<Result> Results
{
get { return _results; }
set { _results = value; OnPropertyChanged("Results"); }
}
public ICommand XCmd { get; protected set; }
private void InitializeCommands()
{
this.XCmd = new RelayCommand((param) => { this.X(); },
(param) => { return true; });
}
public Model()
{
Files = new List<string>();
Files.Add("ONE");
Files.Add("TWO");
Files.Add("THREE");
Files.Add("FOUR");
_results.Add(new Result() { File = "ZERO", Status = "Pending" });
_results.Add(new Result() { File = DateTime.Now.ToString(), Status = "Pending" });
InitializeCommands();
}
#region INotifyPropertyChanged Members
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
Note that the INotifyPropertyChanged Members region implements the PropertyChanged event, and the Debugging aids region just checks that the property specified in the OnPropertyChanged handler actually exists.
Here is the xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<DataGrid
HorizontalAlignment="Stretch"
Name="resultDataGrid"
VerticalAlignment="Stretch"
ItemsSource="{Binding Path=Results, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
/>
<Button Grid.Row="2" Command="{Binding XCmd}" Margin="5,5,5,5">click</Button>
</Grid>
Its not pretty I know but you can style it as you please
And here is the relaycommand implementation that I linked you earlier:
public class RelayCommand : ICommand
{
#region Private Accessor Fields
/// <summary>
/// A boolean function that contains the code to enable/disable the command and the associated UI elements.
/// </summary>
private readonly Func<object, bool> _canExecute = null;
/// <summary>
/// A generic delegate that will contain the code to execute.
/// </summary>
private readonly Action<object> _executeAction = null;
#endregion //Private Accessor Fields
#region Constructor
/// <summary>
/// Initializes a new instance of the RelayCommannd class
/// </summary>
/// <param name="executeAction">The execute action.</param>
/// <param name="canExecute">The can execute.</param>
public RelayCommand(Action<object> executeAction, Func<object, bool> canExecute)
{
this._executeAction = executeAction;
this._canExecute = canExecute;
}
#endregion
//Modified on 15 August 2011. CanExecuteChanged
#region Implementation of ICommand
/// <summary>
/// Occurs when changes occur that affect whether or not the command should execute.
/// </summary>
public event EventHandler CanExecuteChanged
{
//RequerySuggested occurs when the CommandManager detects conditions that might
//change the ability of a command to execute.
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed,
/// this object can be null.</param>
/// <returns>true if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object parameter)
{
if (this._canExecute == null)
{
return true;
}
return this._canExecute(parameter);
}
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed,
/// this object can be set to null</param>
public void Execute(object parameter)
{
if (this._executeAction != null)
{
this._executeAction(parameter);
}
}
#endregion
If this doesn't work you will have to show me more of your code, because I really don't know why it's not working.
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 can I create a UI that responds to Drag/Drop events of a usercontrol by usinng the Command pattern in WPF?
On the User Control
Implement a command that has a parameter. I use ICommand with Josh Smiths RelayCommand, but i extend it to give it a parameter. (code at the end of this answer)
/// <summary>
/// Gets and Sets the ICommand that manages dragging and dropping.
/// </summary>
/// <remarks>The CanExecute will be called to determin if a drop can take place, the Executed is called when a drop takes place</remarks>
public ICommand DragDropCommand {
get { return (ICommand)GetValue(DragDropCommandProperty); }
set { SetValue(DragDropCommandProperty, value); }
now you can bind your view model to this command.
set another property for our entity drag type (you could hard code this) but i reuse this user control for different things and i dont want one control to accept the wrong entity type on a drop.
/// <summary>
/// Gets and Sets the Name of the items we are dragging
/// </summary>
public String DragEntityType {
get { return (String)GetValue(DragEntityTypeProperty); }
set { SetValue(DragEntityTypeProperty, value); }
}
Override the OnPreviewLeftMouseButtonDown
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) {
//find the item the mouse is over, i.e. the one you want to drag.
var itemToDrag = FindItem(e);
//move the selected items, using the drag entity type
DataObject data = new DataObject(this.DragEntityType, itemToDrag);
//use the helper class to initiate the drag
DragDropEffects de = DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
//call the base
base.OnPreviewMouseLeftButtonDown(e);
}
when you call DragDrop.DoDragDrop, the below methods will be called at the approriate time
Override the OnDragOver and OnDragDrop methods, and use a command to ask if we can drag and we can drop
protected override void OnDragOver(DragEventArgs e) {
//if we can accept the drop
if (this.DragDropCommand != null && this.DragDropCommand.CanExecute(e.Data)) {
// Console.WriteLine(true);
}
//otherwise
else {
e.Effects = DragDropEffects.None;
e.Handled = true;
}
base.OnDragOver(e);
}
protected override void OnDrop(DragEventArgs e) {
if (this.DragDropCommand == null) { }
//if we dont allow dropping on ourselves and we are trying to do it
//else if (this.AllowSelfDrop == false && e.Source == this) { }
else {
this.DragDropCommand.Execute(e.Data);
}
base.OnDrop(e);
}
In the View Model
then when you are setting up your command in the view model use something like this, then bind the command to your user control
this.MyDropCommand = new ExtendedRelayCommand((Object o) => AddItem(o), (Object o) => { return ItemCanBeDragged(o); });
usually you are dragging from one user control to another so you would set up one command for one user control and one for the other, each having a different DragEntityType that you would accept. Two user controls one to drag from, one to drop on, and vica versa. each user control has a different DragEntityType so you can tell which one the drag originated from.
private Boolean ItemCanBeDragged(object o) {
Boolean returnValue = false;
//do they have permissions to dragt
if (this.HasPermissionToDrag) {
IDataObject data = o as IDataObject;
if (data == null) { }
//this line looks up the DragEntityType
else if (data.GetDataPresent("ItemDragEntityTypeForItemWeAreDragging")) {
returnValue = true;
}
}
return returnValue;
}
and when we drop
private void AddItem(object o) {
IDataObject data = o as IDataObject;
if (data == null) { }
else {
MyDataObject myData = data.GetData("ItemDragEntityTypeForItemWeAreDroppingHere") as MyDataObject ;
if (myData == null) { }
else {
//do something with the dropped data
}
}
}
I might have missed something, but this technique lets me ask the view model if i can drag an item, and lets me ask the view model if i can drop (if the view model will accept the item) its bindable, and it seperates view/ view model nicely. If you have any questions feel free to ask.
Extended Relay Command, thanks Josh Smith...
/// <summary>
/// A command whose sole purpose is to
/// relay its ExtendedFunctionality to other
/// objects by invoking delegates. The
/// default return value for the CanExecute
/// method is 'true'.
/// </summary>
public class ExtendedRelayCommand : ICommand {
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public ExtendedRelayCommand(Action<Object> execute)
: this(execute, null) {
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public ExtendedRelayCommand(Action<Object> execute, Func<Object, bool> canExecute) {
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter) {
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged {
add {
if (_canExecute != null)
CommandManager.RequerySuggested += value;
}
remove {
if (_canExecute != null)
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter) {
_execute(parameter);
}
#endregion // ICommand Members
#region Fields
readonly Action<Object> _execute;
readonly Func<Object, bool> _canExecute;
#endregion // Fields
}