Updating a reference to a command of a nested ViewModel? - wpf

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

Related

How to implement INotifyCollectionChanged in MVVM for Reset, Add,Remove,Move,Replace of NotifyCollectionChangedAction

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?

Samples on Live Updating Data Grid WPF

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.

How binding in xaml?

now I'm doing so Binding:
field:
private readonly RestaurantContext m_context = new RestaurantContext();
init:
m_context.Load(m_context.GetGroupQuery());
this.dataGridGroup.DataContext = m_context.Groups;
How do this in xaml ?
Juste expose your m_context, ensure that the class that encapsulate this property is set as the datacontext of your view and bind your dataGridGroup datacontext to your prperty.
For example :
public partial class Window1
{
public Window1()
{
InitializeComponent();
DataContext = new WindowViewModel();//this will set the WindowViewModel object below as the datacontext of the window
}
}
public class WindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
public WindowViewModel()
{
restContext = new RestaurantContext();//init 1
restContext.Load(restContext.GetGroupQuery());//init 2
InvokePropertyChanged(new PropertyChangedEventArgs("RestContext"));//notify the view th update datacontext
}
private RestaurantContext restContext;
/// <summary>
/// Gets or sets the RestContext (which will be vound to the datagrid datacontext)
/// </summary>
public RestaurantContext RestContext
{
get { return restContext; }
set
{
if (RestContext != value)
{
restContext = value;
InvokePropertyChanged(new PropertyChangedEventArgs("RestContext"));
}
}
}
}
/// <summary>
/// Whatever class
/// </summary>
public class RestaurantContext
{
public void Load(object getGroupQuery)
{
//Whatever here
}
public object GetGroupQuery()
{
//Whatever here
return new object();
}
IEnumerable Groups { get; set; }
}
XAML :
<Window x:Class="StackOverflow.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Width="100" Height="100" >
<Grid>
<DataGrid DataContex="{Binding RestContext.Groups}"></DataGrid>
</Grid>
</Window>
In your XAML:
<DataGrid x:Name="dataGridGroup" DataContext={Binding Groups} />
It will automatically bind to the Groups property of your ViewModel

wpf mvvm error validation

I have a simple wpf application and I am trying to deactivate the save button if the form has errors.
The Problem is that, although the validation it looks to works perfect, I don't know why but I am getting all the time false from the method which is responsible to check the errors.
Let me make it more clear by providing the code.
This is the code from MainWindow.Xaml.cs
private readonly HashSet<ValidationError> errors = new HashSet<ValidationError>();
private Lazy<MainWindowViewModel> viewModel;
public MainWindow() {
InitializeComponent();
InitializeValidaton();
}
void InitializeValidaton() {
viewModel = new Lazy<MainWindowViewModel>();
Validation.AddErrorHandler(this, ErrorChangedHandler);
}
private void ErrorChangedHandler(object sender, ValidationErrorEventArgs e) {
if (e.Action == ValidationErrorEventAction.Added) {
errors.Add(e.Error);
} else {
errors.Remove(e.Error);
}
//I set a breakpoint here and it returns the correct value. False if it has errors and True if not
viewModel.Value.IsValid = !errors.Any();
}
This is the command for the button
public ICommand SaveItem {
get { return new RelayCommand(SaveItemExecute,CanSaveItem); }
}
private bool CanSaveItem() {
return IsValid;
}
//I set up here a breakpoint and it returns the correct value just once.
//The application looked up on CanSaveItem all the time and except the first time, it returns wrong value
private bool _isValid;
public bool IsValid {
get { return _isValid; }
set {
_isValid = value;
RaisePropertyChanged("IsValid");
}
}
Validation Rules
[Required(ErrorMessage = "Please enter Title")]
[StringLength(100, ErrorMessage = "The maximum length is 100")]
string Name { get; set; }
I don't know if it makes any sense, but the button I want to deactivate is in a UserControl.
I can't understand why the canExecute method which is in a userControl, triggered more than once. What ever of method if I used, it has the same reaction. I mention the userControl, because if I use the same method(which in part of ICommand) in the mainWindow, it triggered just once
I will appreciate if could anyone help me with this.
Thanks
I would rather post a working example of validation using your model. Adjust this example as you need, and probably you will find difference that produce an incorrect work.
MainWindow.xaml
<StackPanel>
<TextBox x:Name="ValidatedTextBox" Width="200">
<TextBox.Text>
<Binding Path="EnteredText" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
<Binding.ValidationRules>
<local:NotEmptyInputRule ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button Content="Save" Width="60" IsEnabled="{Binding IsValid}" />
</StackPanel>
Property EnteredText must exist in the ViewModel:
class MainWindowViewModel : INotifyPropertyChanged
{
public ICommand SaveItem
{
get { return new SimpleCommand(SaveItemExecute, CanSaveItem); }
}
public void SaveItemExecute()
{
//save
}
private bool CanSaveItem()
{
return IsValid;
}
//I set up here a breakpoint and it returns the correct value just once.
//The application looked up on CanSaveItem all the time and except the first time, it returns wrong value
private bool _isValid;
public bool IsValid
{
get { return _isValid; }
set
{
_isValid = value;
OnPropertyChanged("IsValid");
}
}
public string EnteredText { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And don't forget to set DataContext in the MainWindow.
public MainWindow()
{
InitializeComponent();
InitializeValidaton();
this.DataContext = viewModel.Value;
}
There are also the Command class and the validation rule.
public class SimpleCommand : ICommand
{
/// <summary>
/// Gets or sets the Predicate to execute when the CanExecute of the command gets called
/// </summary>
public Predicate<object> CanExecuteDelegate { get; set; }
/// <summary>
/// Gets or sets the action to be called when the Execute method of the command gets called
/// </summary>
public Action<object> ExecuteDelegate { get; set; }
public SimpleCommand(Action execute, Func<bool> canExecute)
{
this.ExecuteDelegate = _ => execute();
this.CanExecuteDelegate = _ => canExecute();
}
#region ICommand Members
/// <summary>
/// Checks if the command Execute method can run
/// </summary>
/// <param name="parameter">THe command parameter to be passed</param>
/// <returns>Returns true if the command can execute. By default true is returned so that if the user of SimpleCommand does not specify a CanExecuteCommand delegate the command still executes.</returns>
public bool CanExecute(object parameter)
{
if (CanExecuteDelegate != null)
return CanExecuteDelegate(parameter);
return true;// if there is no can execute default to true
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// Executes the actual command
/// </summary>
/// <param name="parameter">THe command parameter to be passed</param>
public void Execute(object parameter)
{
if (ExecuteDelegate != null)
ExecuteDelegate(parameter);
}
#endregion
}
 
class NotEmptyInputRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (value != null)
{
string input = value as string;
if (input.Length > 0)
return new ValidationResult(true, null);
}
return new ValidationResult(false, "Validation error. Field input required.");
}
}

Can a custom WPF control implement the IsDefault property

I have a custom button control that does not derive from Button. Is it possible for me to implement the equivalent of IsDefault so that the command associated with my control will be invoked. I was hoping that this was an attached property that I could add to any control but as far as I can tell it doesn't seem to be. Am I out of luck if my control does not derive from Button or is there at least a reasonable workaround?
UPDATE:
I just took a peek with reflector at how this is being done underneath for Button and I must say it isn't the most self explanitory code I've seen. It appears that there are at least 3 dependency properties a few custom types just for the purpose of handling the concept of a Button being default. Since there doesn't seem to be an existing way to borrow the IsDefault functionality I suppose I'll have to narrow down what I'm trying to achieve so that I can at least get default focus and access key handling to work and just ignore the complexity invloved in the Button.IsDefault implementation.
UPDATE:
Added the following code example showing my uncessful attempt at trying itowlson's suggestions.
MyButton.xaml
<UserControl x:Class="IsDefault.MyButton"
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"
Height="28"
Width="117">
<Grid>
<Button Click="Button_Click">
<Button.Template>
<ControlTemplate>
<Border BorderThickness="2"
CornerRadius="12"
Background="DarkSlateBlue">
<TextBlock Foreground="WhiteSmoke"
HorizontalAlignment="Center"
VerticalAlignment="Center">Some Text</TextBlock>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</UserControl>
MyButton.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace IsDefault
{
/// <summary>
/// Interaction logic for MyButton.xaml
/// </summary>
public partial class MyButton : UserControl
{
// Provide CLR accessors for the event
public event RoutedEventHandler Click
{
add { AddHandler(ClickEvent, value); }
remove { RemoveHandler(ClickEvent, value); }
}
// Using a RoutedEvent
public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent(
"Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButton));
public bool IsDefault
{
get { return (bool)GetValue(IsDefaultProperty); }
set { SetValue(IsDefaultProperty, value); }
}
public static readonly DependencyProperty IsDefaultProperty =
DependencyProperty.Register(
"IsDefault",
typeof(bool),
typeof(MyButton),
new PropertyMetadata(false, IsDefault_PropertyChangedCallback, null));
public MyButton()
{
InitializeComponent();
}
protected override void OnAccessKey(AccessKeyEventArgs e)
{
base.OnAccessKey(e);
if (e.Key == "\r")
{
if (e.IsMultiple)
{
// There are multiple controls that are currently handling the Enter key
MessageBox.Show("there are multiple controls handling the Enter key.");
}
else
{
RaiseEvent(new RoutedEventArgs(ClickEvent, this));
}
}
}
private static void IsDefault_PropertyChangedCallback(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var button = d as MyButton;
var isDefault = (bool)e.NewValue;
if (isDefault)
{
AccessKeyManager.Register("\r", button);
}
else
{
AccessKeyManager.Unregister("\r", button);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(ClickEvent));
}
}
}
MainWindow.xaml
<Window x:Class="IsDefault.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:my="clr-namespace:IsDefault">
<Grid>
<Button Content="Button"
Height="23"
HorizontalAlignment="Left"
Margin="224,24,0,0"
Name="button1"
VerticalAlignment="Top"
Width="75" />
<TextBox Height="23"
HorizontalAlignment="Left"
Margin="208,94,0,0"
Name="textBox1"
VerticalAlignment="Top"
Width="120" />
<my:MyButton Height="28"
HorizontalAlignment="Left"
Margin="232,154,0,0"
x:Name="myButton1"
VerticalAlignment="Top"
Width="117"
Click="myButton1_Click"
IsDefault="True"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace IsDefault
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void myButton1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("My button was clicked, yay!");
}
}
}
All that setting Button.IsDefault does is call AccessKeyManager.Register("\r", this) (or Unregister if setting to false). (Actually, it does a little bit of extra work around focus management, but that's probably not crucial for you.)
So to achieve a similar effect yourself:
Create an IsDefault dependency property in the usual way.
In your IsDefault PropertyChangedCallback, call AccessKeyManager.Register or AccessKeyManager.Unregister according to the new value, passing "\r" (the Enter string) as the key and the control instance as the element.
Override OnAccessKey to specify how your control responds to the Enter key. (For example, ButtonBase overrides this to call OnClick. You could also handle the AccessKeyManager.AccessKeyPressed attached event, but since you are defining a custom control, overriding OnAccessKey is neater.)
Although this is an older question others may still be interested in an answer as I have been. So, here is my solution. It's based on some reverse engineering of Microsoft reference source I've found (Button and ButtonBase). I'm still newby in WPF so much code may be needless, but it works!
This code adds following features to an UserControl (they seem to be closly connected to each other):
IsDefault
IsCancel
Command
Click event
(all comments in code are made by MS)
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.ComponentModel;
using System.Windows.Automation.Peers;
using System.Security;
using System.Diagnostics;
[DefaultEvent("Click")]
public partial class MyButton : UserControl, ICommandSource {
#region "Private Variables"
// Cache valid bits
private ControlBoolFlags _ControlBoolField;
#endregion
#region "Constructors"
static MyButton()
{
EventManager.RegisterClassHandler(
typeof(MyButton),
AccessKeyManager.AccessKeyPressedEvent,
new AccessKeyPressedEventHandler(
OnAccessKeyPressed));
KeyboardNavigation.AcceptsReturnProperty.OverrideMetadata(
typeof(MyButton),
new FrameworkPropertyMetadata(
true));
// Disable IME on button.
// - key typing should not be eaten by IME.
// - when the button has a focus, IME's disabled status should
// be indicated as
// grayed buttons on the language bar.
InputMethod.IsInputMethodEnabledProperty.OverrideMetadata(
typeof(MyButton),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.Inherits));
}
#endregion
#region "AccessKey"
private static void OnAccessKeyPressed(object sender,
AccessKeyPressedEventArgs e)
{
if (!e.Handled && e.Scope == null && e.Target == null) {
e.Target = sender as MyButton;
}
}
/// <summary>
/// The Access key for this control was invoked.
/// </summary>
protected override void OnAccessKey(AccessKeyEventArgs e)
{
if (e.IsMultiple) {
base.OnAccessKey(e);
} else {
// Don't call the base b/c we don't want to take focus
OnClick();
}
}
#endregion
#region "Click"
/// <summary>
/// Event correspond to left mouse button click
/// </summary>
public static readonly RoutedEvent ClickEvent =
EventManager.RegisterRoutedEvent(
"Click",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(MyButton));
/// <summary>
/// Add / Remove ClickEvent handler
/// </summary>
[Category("Behavior")]
public event RoutedEventHandler Click
{
add {
AddHandler(ClickEvent, value);
}
remove {
RemoveHandler(ClickEvent, value);
}
}
/// <summary>
/// This virtual method is called when button is clicked and
/// it raises the Click event
/// </summary>
private void BaseOnClick()
{
RoutedEventArgs locRoutedEventArgs = new RoutedEventArgs(
MyButton.ClickEvent,
this);
this.RaiseEvent(locRoutedEventArgs);
ExecuteCommandSource(this);
}
/// <summary>
/// This method is called when button is clicked.
/// </summary>
private void OnClick()
{
if (AutomationPeer.ListenerExists(AutomationEvents.InvokePatternOnInvoked)) {
AutomationPeer locPeer =
UIElementAutomationPeer.CreatePeerForElement(this);
if (locPeer != null) {
locPeer.RaiseAutomationEvent(AutomationEvents.InvokePatternOnInvoked);
}
}
// base.OnClick should be called first. Our default command
// for Cancel Button to close dialog should happen after
// Button's click event handler has been called.
// If there Is excption And it Then 's a Cancel button and
// RoutedCommand is null,
// we will raise Window.DialogCancelCommand.
try {
BaseOnClick();
} finally {
// When the Button RoutedCommand is null, if it's a
// Cancel Button,
// Window.DialogCancelCommand will be the default command.
// Do not assign Window.DialogCancelCommand to
// Button.Command.
// If in Button click handler user nulls the Command,
// we still want to provide the default behavior.
if (Command == null && IsCancel) {
// Can't invoke Window.DialogCancelCommand directly.
// Have to raise event.
// Filed bug 936090: Commanding perf issue: can't
// directly invoke a command.
ExecuteCommand(DialogCancelCommand, null, this);
}
}
}
#endregion
#region "ClickMode"
/// <summary>
/// The DependencyProperty for the ClickMode property.
/// Flags: None
/// Default Value: ClickMode.Release
/// </summary>
public static readonly DependencyProperty ClickModeProperty =
DependencyProperty.Register(
"ClickMode",
typeof(ClickMode),
typeof(MyButton),
new FrameworkPropertyMetadata(
ClickMode.Release),
new ValidateValueCallback(
IsValidClickMode));
/// <summary>
/// ClickMode specify when the Click event should fire
/// </summary>
[Bindable(true), Category("Behavior")]
public ClickMode ClickMode
{
get {
return (ClickMode)GetValue(ClickModeProperty);
}
set {
SetValue(ClickModeProperty, value);
}
}
private static bool IsValidClickMode(object valClickMode)
{
ClickMode locClickMode = (ClickMode)valClickMode;
return locClickMode == ClickMode.Press
|| locClickMode == ClickMode.Release
|| locClickMode == ClickMode.Hover;
}
#endregion
#region "KeyDown"
/// <summary>
/// This is the method that responds to the KeyDown event.
/// </summary>
/// <param name="e">Event arguments</param>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (ClickMode == ClickMode.Hover) {
// Ignore when in hover-click mode.
return;
}
if (e.Key == Key.Space) {
// Alt+Space should bring up system menu, we shouldn't
// handle it.
if ((Keyboard.Modifiers &
(ModifierKeys.Control | ModifierKeys.Alt)) !=
ModifierKeys.Alt) {
if ((!IsMouseCaptured) &&
(object.ReferenceEquals(e.OriginalSource, this))) {
IsSpaceKeyDown = true;
CaptureMouse();
if (ClickMode == ClickMode.Press) {
OnClick();
}
e.Handled = true;
}
}
} else if (e.Key == Key.Enter
&& Convert.ToBoolean(GetValue(KeyboardNavigation.AcceptsReturnProperty))) {
if (object.ReferenceEquals(e.OriginalSource, this)) {
IsSpaceKeyDown = false;
if (IsMouseCaptured) {
ReleaseMouseCapture();
}
OnClick();
e.Handled = true;
}
} else {
// On any other key we set IsPressed to false only if
// Space key is pressed
if (IsSpaceKeyDown) {
IsSpaceKeyDown = false;
if (IsMouseCaptured) {
ReleaseMouseCapture();
}
}
}
}
private bool IsSpaceKeyDown
{
get {
return ReadControlFlag(ControlBoolFlags.IsSpaceKeyDown);
}
set {
WriteControlFlag(ControlBoolFlags.IsSpaceKeyDown, value);
}
}
#endregion
#region "Command"
/// <summary>
/// The DependencyProperty for RoutedCommand
/// </summary>
[CommonDependencyProperty()]
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(
"Command",
typeof(ICommand),
typeof(MyButton),
new FrameworkPropertyMetadata(
(ICommand)null,
new PropertyChangedCallback(
OnCommandChanged)));
/// <summary>
/// Get or set the Command property
/// </summary>
[Bindable(true), Category("Action")]
[Localizability(LocalizationCategory.NeverLocalize)]
public ICommand Command
{
get {
return (ICommand)GetValue(CommandProperty);
}
set {
SetValue(CommandProperty, value);
}
}
private static void OnCommandChanged(
DependencyObject valTarget,
DependencyPropertyChangedEventArgs e)
{
MyButton locMyButton = valTarget as MyButton;
if (locMyButton != null) {
locMyButton.OnCommandChanged(
(ICommand)e.OldValue,
(ICommand)e.NewValue);
}
}
private void OnCommandChanged(
ICommand valOldCommand,
ICommand valNewCommand)
{
if (valOldCommand != null) {
valOldCommand.CanExecuteChanged -= OnCanExecuteChanged;
}
if (valNewCommand != null) {
valNewCommand.CanExecuteChanged += OnCanExecuteChanged;
}
UpdateCanExecute();
}
#endregion
#region "CommandParameter"
/// <summary>
/// The DependencyProperty for the CommandParameter
/// </summary>
[CommonDependencyProperty()]
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(MyButton),
new FrameworkPropertyMetadata(
(object)null));
/// <summary>
/// Reflects the parameter to pass to the CommandProperty
/// upon execution.
/// </summary>
[Bindable(true), Category("Action")]
[Localizability(LocalizationCategory.NeverLocalize)]
public object CommandParameter
{
get {
return GetValue(CommandParameterProperty);
}
set {
SetValue(CommandParameterProperty, value);
}
}
#endregion
#region "CommandTarget"
/// <summary>
/// The DependencyProperty for Target property
/// Flags: None
/// Default Value: null
/// </summary>
[CommonDependencyProperty()]
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register(
"CommandTarget",
typeof(IInputElement),
typeof(MyButton),
new FrameworkPropertyMetadata(
(IInputElement)null));
/// <summary>
/// The target element on which to fire the command.
/// </summary>
[Bindable(true), Category("Action")]
public IInputElement CommandTarget
{
get {
return (IInputElement)GetValue(CommandTargetProperty);
}
set {
SetValue(CommandTargetProperty, value);
}
}
#endregion
#region "CanExecute"
private void OnCanExecuteChanged(object valTarget, EventArgs e)
{
if (valTarget != null) {
UpdateCanExecute();
}
}
private bool CanExecute
{
get {
return !ReadControlFlag(ControlBoolFlags.CommandDisabled);
}
set {
if (value != CanExecute) {
WriteControlFlag(
ControlBoolFlags.CommandDisabled,
!value);
CoerceValue(IsEnabledProperty);
}
}
}
private void UpdateCanExecute()
{
if (Command != null) {
CanExecute = CanExecuteCommandSource(this);
} else {
CanExecute = true;
}
}
#endregion
#region "IsDefault"
/// <summary>
/// The DependencyProperty for the IsDefault property.
/// Flags: None
/// Default Value: false
/// </summary>
public static readonly DependencyProperty IsDefaultProperty =
DependencyProperty.RegisterAttached(
"IsDefault",
typeof(bool),
typeof(MyButton),
new UIPropertyMetadata(
false,
new PropertyChangedCallback(
OnIsDefaultChanged)));
/// <summary>
/// Specifies whether or not this button is the default button.
/// </summary>
/// <value></value>
public bool IsDefault
{
get {
return (bool)GetValue(IsDefaultProperty);
}
set {
SetValue(IsDefaultProperty, value);
}
}
private static void OnIsDefaultChanged(
DependencyObject valTarget,
DependencyPropertyChangedEventArgs e)
{
MyButton locMyButton = valTarget as MyButton;
if (locMyButton != null) {
Window locWindow = Window.GetWindow(locMyButton);
if (locWindow == null) {
locWindow = Application.Current.MainWindow;
}
if (FocusChangedEventHandler == null) {
FocusChangedEventHandler =
new KeyboardFocusChangedEventHandler(
locMyButton.OnFocusChanged);
}
if (locWindow != null) {
if ((bool)e.NewValue) {
AccessKeyManager.Register("\x000D", locMyButton);
KeyboardNavigation.SetAcceptsReturn(
locMyButton, true);
locMyButton.UpdateIsDefaulted(
Keyboard.FocusedElement);
} else {
AccessKeyManager.Unregister("\x000D", locMyButton);
KeyboardNavigation.SetAcceptsReturn(
locMyButton, false);
locMyButton.UpdateIsDefaulted(null);
}
}
}
}
private static KeyboardFocusChangedEventHandler FocusChangedEventHandler;
private void OnFocusChanged(object valTarget, KeyboardFocusChangedEventArgs e)
{
UpdateIsDefaulted(Keyboard.FocusedElement);
}
#endregion
#region "IsDefaulted"
/// <summary>
/// The key needed set a read-only property.
/// </summary>
private static readonly DependencyPropertyKey IsDefaultedPropertyKey =
DependencyProperty.RegisterReadOnly(
"IsDefaulted",
typeof(bool),
typeof(MyButton),
new FrameworkPropertyMetadata(
false));
/// <summary>
/// The DependencyProperty for the IsDefaulted property.
/// Flags: None
/// Default Value: false
/// </summary>
public static readonly DependencyProperty IsDefaultedProperty =
IsDefaultedPropertyKey.DependencyProperty;
/// <summary>
/// Specifies whether or not this button is the button that
/// would be invoked when Enter is pressed.
/// </summary>
/// <value></value>
public bool IsDefaulted
{
get {
return (bool)GetValue(IsDefaultedProperty);
}
}
private void UpdateIsDefaulted(IInputElement valFocusElement)
{
// If it's not a default button, or nothing is focused,
// or it's disabled
// then it's not defaulted.
if (!IsDefault || valFocusElement == null || !IsEnabled) {
SetValue(IsDefaultedPropertyKey, false);
return;
}
DependencyObject locFocusDependencyObj =
valFocusElement as DependencyObject;
object locThisScope = null;
object locFocusScope = null;
// If the focused thing is not in this scope then
// IsDefaulted = false
AccessKeyPressedEventArgs locEventArgs =
default(AccessKeyPressedEventArgs);
bool locIsDefaulted = false;
try {
// Step 1: Determine the AccessKey scope from currently
// focused element
locEventArgs = new AccessKeyPressedEventArgs();
valFocusElement.RaiseEvent(locEventArgs);
locFocusScope = locEventArgs.Scope;
// Step 2: Determine the AccessKey scope from this button
locEventArgs = new AccessKeyPressedEventArgs();
this.RaiseEvent(locEventArgs);
locThisScope = locEventArgs.Scope;
// Step 3: Compare scopes
if (object.ReferenceEquals(locThisScope, locFocusScope)
&& (locFocusDependencyObj == null
|| !(bool)locFocusDependencyObj.GetValue(KeyboardNavigation.AcceptsReturnProperty))) {
locIsDefaulted = true;
}
} finally {
SetValue(IsDefaultedPropertyKey, locIsDefaulted);
}
}
#endregion
#region "IsCancel"
/// <summary>
/// The DependencyProperty for the IsCancel property.
/// Flags: None
/// Default Value: false
/// </summary>
public static readonly DependencyProperty IsCancelProperty =
DependencyProperty.Register(
"IsCancel",
typeof(bool),
typeof(MyButton),
new FrameworkPropertyMetadata(
false,
new PropertyChangedCallback(
OnIsCancelChanged)));
/// <summary>
/// Specifies whether or not this button is the cancel button.
/// </summary>
/// <value></value>
public bool IsCancel
{
get {
return (bool)GetValue(IsCancelProperty);
}
set {
SetValue(IsCancelProperty, value);
}
}
private static void OnIsCancelChanged(
DependencyObject valTarget,
DependencyPropertyChangedEventArgs e)
{
MyButton locMyButton = valTarget as MyButton;
if (locMyButton != null) {
if ((bool)e.NewValue) {
AccessKeyManager.Register("\x001B", locMyButton);
} else {
AccessKeyManager.Unregister("\x001B", locMyButton);
}
}
}
#endregion
#region "Helper Functions"
/// <summary>
/// This allows a caller to override its ICommandSource values
//// (used by Button and ScrollBar)
/// </summary>
static internal void ExecuteCommand(
ICommand command,
object parameter,
IInputElement target)
{
RoutedCommand routed = command as RoutedCommand;
if (routed != null) {
if (routed.CanExecute(parameter, target)) {
routed.Execute(parameter, target);
}
} else if (command.CanExecute(parameter)) {
command.Execute(parameter);
}
}
static internal bool CanExecuteCommandSource(
ICommandSource commandSource)
{
ICommand command = commandSource.Command;
if (command != null) {
object parameter = commandSource.CommandParameter;
IInputElement target = commandSource.CommandTarget;
RoutedCommand routed = command as RoutedCommand;
if (routed != null) {
if (target == null) {
target = commandSource as IInputElement;
}
return routed.CanExecute(parameter, target);
} else {
return command.CanExecute(parameter);
}
}
return false;
}
/// <summary>
/// Executes the command on the given command source.
/// </summary>
/// <SecurityNote>
/// Critical - calls critical function (ExecuteCommandSource).
/// TreatAsSafe - always passes in false for userInitiated,
//// which is safe
/// </SecurityNote>
[SecurityCritical(), SecuritySafeCritical()]
static internal void ExecuteCommandSource(
ICommandSource commandSource)
{
CriticalExecuteCommandSource(commandSource, false);
}
/// <summary>
/// Executes the command on the given command source.
/// </summary>
/// <SecurityNote>
/// Critical - sets the user initiated bit on a command,
/// which is used for security purposes later.
/// It is important to validate the callers of this,
/// and the implementation to make sure
/// that we only call MarkAsUserInitiated in the
/// correct cases.
/// </SecurityNote>
[SecurityCritical()]
static internal void CriticalExecuteCommandSource(
ICommandSource commandSource,
bool userInitiated)
{
ICommand command = commandSource.Command;
if (command != null) {
object parameter = commandSource.CommandParameter;
IInputElement target = commandSource.CommandTarget;
RoutedCommand routed = command as RoutedCommand;
if (routed != null) {
if (target == null) {
target = commandSource as IInputElement;
}
if (routed.CanExecute(parameter, target)) {
routed.Execute(parameter, target);
}
} else if (command.CanExecute(parameter)) {
command.Execute(parameter);
}
}
}
/// <summary>
/// DialogCancel Command. It closes window if it's dialog and return
/// false as the dialog value.
/// </summary>
/// <remarks>
/// Right now this is only used by Cancel Button to close the dialog.
static internal readonly RoutedCommand DialogCancelCommand =
new RoutedCommand(
"DialogCancel",
typeof(Window));
#endregion
#region "ControlFlags"
internal bool ReadControlFlag(ControlBoolFlags reqFlag)
{
return (_ControlBoolField & reqFlag) != 0;
}
internal void WriteControlFlag(ControlBoolFlags reqFlag, bool #set)
{
if (#set)
{
_ControlBoolField = _ControlBoolField | reqFlag;
}
else
{
_ControlBoolField = _ControlBoolField & (~reqFlag);
}
}
internal enum ControlBoolFlags : ushort
{
ContentIsNotLogical = 0x1,
// used in contentcontrol.cs
IsSpaceKeyDown = 0x2,
// used in ButtonBase.cs
HeaderIsNotLogical = 0x4,
// used in HeaderedContentControl.cs, HeaderedItemsControl.cs
CommandDisabled = 0x8,
// used in ButtonBase.cs, MenuItem.cs
ContentIsItem = 0x10,
// used in contentcontrol.cs
HeaderIsItem = 0x20,
// used in HeaderedContentControl.cs, HeaderedItemsControl.cs
ScrollHostValid = 0x40,
// used in ItemsControl.cs
ContainsSelection = 0x80,
// used in TreeViewItem.cs
VisualStateChangeSuspended = 0x100
// used in Control.cs
}
#endregion
}
/// <summary>
/// An attribute that indicates that a DependencyProperty
/// declaration is common
/// enough to be included in KnownTypes.cs.
/// </summary>
[Conditional("COMMONDPS")]
internal sealed class CommonDependencyPropertyAttribute : Attribute
{
}

Resources