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
}
Related
I have read a few articles on here, that describe how to listen to notifications raised. However: I am still having trouble to apply those to my application.
I currently have an application with several "pages".
One of the pages contains a WPF Treeview control in it along with several ViewModels and data models.
public class FoldersSearchViewModel
{
private ReadOnlyCollection<DriveTreeViewItemViewModel> _drives;
public FoldersSearchViewModel(string[] logicalDrives)
{
_drives = new ReadOnlyCollection<DriveTreeViewItemViewModel>(
Environment.GetLogicalDrives()
.Select(s => new DriveInfo(s))
.Where(di => di.IsReady)
.Select(di => new DriveTreeViewItemViewModel(di))
.ToList()
);
}
public ReadOnlyCollection<DriveTreeViewItemViewModel> Drives
{
get { return _drives; }
}
}
This ViewModel contains DriveTreeViewItemViewModels and is bound via DataContext to the UserControl ("page").
The Drive- and DirectoryTreeViewItemViewModel classes contain a few attributes, but are otherwise based on TreeViewItemViewModel, which you can see here:
public class TreeViewItemViewModel : INotifyPropertyChanged
{
#region Data
static readonly protected TreeViewItemViewModel DummyChild = new TreeViewItemViewModel();
readonly ObservableCollection<TreeViewItemViewModel> _children;
readonly TreeViewItemViewModel _parent;
bool _isExpanded;
bool _isSelected;
#endregion // Data
#region Constructors
protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren)
{
_parent = parent;
_children = new ObservableCollection<TreeViewItemViewModel>();
if (lazyLoadChildren)
_children.Add(DummyChild);
}
// This is used to create the DummyChild instance.
private TreeViewItemViewModel()
{
}
#endregion // Constructors
#region Presentation Members
#region Children
/// <summary>
/// Returns the logical child items of this object.
/// </summary>
public ObservableCollection<TreeViewItemViewModel> Children
{
get { return _children; }
}
#endregion // Children
#region HasLoadedChildren
/// <summary>
/// Returns true if this object's Children have not yet been populated.
/// </summary>
public bool HasDummyChild
{
get { return this.Children.Count == 1 && this.Children[0] == DummyChild; }
}
#endregion // HasLoadedChildren
#region IsExpanded
/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is expanded.
/// </summary>
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
this.OnPropertyChanged("IsExpanded");
}
// Expand all the way up to the root.
if (_isExpanded && _parent != null)
_parent.IsExpanded = true;
// Lazy load the child items, if necessary.
if (this.HasDummyChild)
{
this.Children.Remove(DummyChild);
this.LoadChildren();
}
}
}
#endregion // IsExpanded
#region IsSelected
/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is selected.
/// </summary>
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
#endregion // IsSelected
#region LoadChildren
/// <summary>
/// Invoked when the child items need to be loaded on demand.
/// Subclasses can override this to populate the Children collection.
/// </summary>
protected virtual void LoadChildren()
{
}
#endregion // LoadChildren
#region Parent
public TreeViewItemViewModel Parent
{
get { return _parent; }
}
#endregion // Parent
#endregion // Presentation Members
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion // INotifyPropertyChanged Members
}
I have followed the tutorial and ideas described in http://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode and everything works great so far.
My problem is: I would like to add a string "selected" as an attribute to FoldersSearchViewModel, which would contain the path of the selected child ViewModel. The DriveTreeViewItemViewModel and the DirectoryTreeViewItemViewModel each have a "Path" property, that contains the full path to the child.
So: once OnPropertyChanged("IsSelected") is called, I would like to notify FoldersSearchViewModel about it and have the method copy the Path-property from the selected TreeViewItemViewModel into the new "selected"(string) attribute.
I could achieve this by passing the FoldersSearchViewModel-object to the children and children's children etc. in the constructor - but is there no better way of doing this? I suppose I should hook the FoldersSearchViewModel to the PropertyChanged-event of every node and sub-node, but I would like to know what someone with experience in MVVM would do in such a case.
By the way: I could use the WPF Treeview.SelectedItem to get the currently selected TreeViewItemViewModel, but that does not sound right since I want to keep the view, the models and the viewmodels separate.
P.s.: I tried reading and making use of MVVM in WPF - How to alert ViewModel of changes in Model... or should I?, but sadly it does not seem to solve my problem.
Any help is greatly appreciate!
The way mine works is, the Messenger service is a singleton as discussed. I also use DI, so a VM that needs to use it gets the IMessengerService instance injected into it.
IMessengerService looks like:
public interface IMessengerService : IServiceBase
{
Message<T> GetMessage<T>() where T : IMessageBase;
}
Message "param" classes are available application wide, so you might have something like:
public class FolderOpened : IMessageBase
{
}
So, the FolderOpened class is available throughout the application, its defined at compile time obviously.
Any client that would care about this message will subscribe to the message in its VM constructor:
_messenger.GetMessage().Handler += ...
It doesn't matter if the sender has "registered" it yet, the messenger is just based on the message class type.
At any time, anybody can send the message:
_messenger.GetMessage().SendMessage(...);
YMMV, but my messenger will automatically disconnect disposed / non existant subscribers, but really, the correct way would be for a VM to unsubscribe in its finalizer or dispose method.
Does that clear it up?
The MVVM way to do it would be to use a messenger / event aggregator pattern and broadcast an event.
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.
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've got a WPF application with a Treeview control.
When the user clicks a node on the tree, other TextBox, ComboBox, etc. controls on the page are populated with appropriate values.
The user can then make changes to those values and save his or her changes by clicking a Save button.
However, if the user selects a different Treeview node without saving his or her changes, I want to display a warning and an opportunity to cancel that selection.
MessageBox: Continue and discard your unsaved changes? OK/Cancel http://img522.imageshack.us/img522/2897/discardsj3.gif
XAML...
<TreeView Name="TreeViewThings"
...
TreeViewItem.Unselected="TreeViewThings_Unselected"
TreeViewItem.Selected="TreeViewThings_Selected" >
Visual Basic...
Sub TreeViewThings_Unselected(ByVal sender As System.Object, _
ByVal e As System.Windows.RoutedEventArgs)
Dim OldThing As Thing = DirectCast(e.OriginalSource.DataContext, Thing)
If CancelDueToUnsavedChanges(OldThing) Then
'put canceling code here
End If
End Sub
Sub TreeViewThings_Selected(ByVal sender As System.Object, _
ByVal e As System.Windows.RoutedEventArgs)
Dim NewThing As Thing = DirectCast(e.OriginalSource.DataContext, Thing)
PopulateControlsFromThing(NewThing)
End Sub
How can I cancel those unselect/select events?
Update: I've asked a follow-up question...
How do I properly handle a PreviewMouseDown event with a MessageBox confirmation?
UPDATE
Realized I could put the logic in SelectedItemChanged instead. A little cleaner solution.
Xaml
<TreeView Name="c_treeView"
SelectedItemChanged="c_treeView_SelectedItemChanged">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Code behind. I have some classes that is my ItemsSource of the TreeView so I made an interface (MyInterface) that exposes the IsSelected property for all of them.
private MyInterface m_selectedTreeViewItem = null;
private void c_treeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (m_selectedTreeViewItem != null)
{
if (e.NewValue == m_selectedTreeViewItem)
{
// Will only end up here when reversing item
// Without this line childs can't be selected
// twice if "No" was pressed in the question..
c_treeView.Focus();
}
else
{
if (MessageBox.Show("Change TreeViewItem?",
"Really change",
MessageBoxButton.YesNo,
MessageBoxImage.Question) != MessageBoxResult.Yes)
{
EventHandler eventHandler = null;
eventHandler = new EventHandler(delegate
{
c_treeView.LayoutUpdated -= eventHandler;
m_selectedTreeViewItem.IsSelected = true;
});
// Will be fired after SelectedItemChanged, to early to change back here
c_treeView.LayoutUpdated += eventHandler;
}
else
{
m_selectedTreeViewItem = e.NewValue as MyInterface;
}
}
}
else
{
m_selectedTreeViewItem = e.NewValue as MyInterface;
}
}
I haven't found any situation where it doesn't revert back to the previous item upon pressing "No".
I had to solve the same problem, but in multiple treeviews in my application. I derived TreeView and added event handlers, partly using Meleak's solution and partly using the extension methods from this forum: http://forums.silverlight.net/t/65277.aspx/1/10
I thought I'd share my solution with you, so here is my complete reusable TreeView that handles "cancel node change":
public class MyTreeView : TreeView
{
public static RoutedEvent PreviewSelectedItemChangedEvent;
public static RoutedEvent SelectionCancelledEvent;
static MyTreeView()
{
PreviewSelectedItemChangedEvent = EventManager.RegisterRoutedEvent("PreviewSelectedItemChanged", RoutingStrategy.Bubble,
typeof(RoutedPropertyChangedEventHandler<object>), typeof(MyTreeView));
SelectionCancelledEvent = EventManager.RegisterRoutedEvent("SelectionCancelled", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(MyTreeView));
}
public event RoutedPropertyChangedEventHandler<object> PreviewSelectedItemChanged
{
add { AddHandler(PreviewSelectedItemChangedEvent, value); }
remove { RemoveHandler(PreviewSelectedItemChangedEvent, value); }
}
public event RoutedEventHandler SelectionCancelled
{
add { AddHandler(SelectionCancelledEvent, value); }
remove { RemoveHandler(SelectionCancelledEvent, value); }
}
private object selectedItem = null;
protected override void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e)
{
if (e.NewValue == selectedItem)
{
this.Focus();
var args = new RoutedEventArgs(SelectionCancelledEvent);
RaiseEvent(args);
}
else
{
var args = new RoutedPropertyChangedEventArgs<object>(e.OldValue, e.NewValue, PreviewSelectedItemChangedEvent);
RaiseEvent(args);
if (args.Handled)
{
EventHandler eventHandler = null;
eventHandler = delegate
{
this.LayoutUpdated -= eventHandler;
var treeViewItem = this.ContainerFromItem(selectedItem);
if (treeViewItem != null)
treeViewItem.IsSelected = true;
};
this.LayoutUpdated += eventHandler;
}
else
{
selectedItem = this.SelectedItem;
base.OnSelectedItemChanged(e);
}
}
}
}
public static class TreeViewExtensions
{
public static TreeViewItem ContainerFromItem(this TreeView treeView, object item)
{
if (item == null) return null;
var containerThatMightContainItem = (TreeViewItem)treeView.ItemContainerGenerator.ContainerFromItem(item);
return containerThatMightContainItem ?? ContainerFromItem(treeView.ItemContainerGenerator, treeView.Items, item);
}
private static TreeViewItem ContainerFromItem(ItemContainerGenerator parentItemContainerGenerator, ItemCollection itemCollection, object item)
{
foreach (var child in itemCollection)
{
var parentContainer = (TreeViewItem)parentItemContainerGenerator.ContainerFromItem(child);
var containerThatMightContainItem = (TreeViewItem)parentContainer.ItemContainerGenerator.ContainerFromItem(item);
if (containerThatMightContainItem != null)
return containerThatMightContainItem;
var recursionResult = ContainerFromItem(parentContainer.ItemContainerGenerator, parentContainer.Items, item);
if (recursionResult != null)
return recursionResult;
}
return null;
}
}
Here is an example of usage (codebehind for window containing a MyTreeView):
private void theTreeView_PreviewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (e.OldValue != null)
e.Handled = true;
}
private void theTreeView_SelectionCancelled(object sender, RoutedEventArgs e)
{
MessageBox.Show("Cancelled");
}
After choosing the first node in the treeview, all other node changes are cancelled and a message box is displayed.
You can't actually put your logic into the OnSelectedItemChanged Method, if the logic is there the Selected Item has actually already changed.
As suggested by another poster, the PreviewMouseDown handler is a better spot to implement the logic, however, a fair amount of leg work still needs to be done.
Below is my 2 cents:
First the TreeView that I have implemented:
public class MyTreeView : TreeView
{
static MyTreeView( )
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(MyTreeView),
new FrameworkPropertyMetadata(typeof(TreeView)));
}
// Register a routed event, note this event uses RoutingStrategy.Tunnel. per msdn docs
// all "Preview" events should use tunneling.
// http://msdn.microsoft.com/en-us/library/system.windows.routedevent.routingstrategy.aspx
public static RoutedEvent PreviewSelectedItemChangedEvent = EventManager.RegisterRoutedEvent(
"PreviewSelectedItemChanged",
RoutingStrategy.Tunnel,
typeof(CancelEventHandler),
typeof(MyTreeView));
// give CLR access to routed event
public event CancelEventHandler PreviewSelectedItemChanged
{
add
{
AddHandler(PreviewSelectedItemChangedEvent, value);
}
remove
{
RemoveHandler(PreviewSelectedItemChangedEvent, value);
}
}
// override PreviewMouseDown
protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{
// determine which item is going to be selected based on the current mouse position
object itemToBeSelected = this.GetObjectAtPoint<TreeViewItem>(e.GetPosition(this));
// selection doesn't change if the target point is null (beyond the end of the list)
// or if the item to be selected is already selected.
if (itemToBeSelected != null && itemToBeSelected != SelectedItem)
{
bool shouldCancel;
// call our new event
OnPreviewSelectedItemChanged(out shouldCancel);
if (shouldCancel)
{
// if we are canceling the selection, mark this event has handled and don't
// propogate the event.
e.Handled = true;
return;
}
}
// otherwise we want to continue normally
base.OnPreviewMouseDown(e);
}
protected virtual void OnPreviewSelectedItemChanged(out bool shouldCancel)
{
CancelEventArgs e = new CancelEventArgs( );
if (PreviewSelectedItemChangedEvent != null)
{
// Raise our event with our custom CancelRoutedEventArgs
RaiseEvent(new CancelRoutedEventArgs(PreviewSelectedItemChangedEvent, e));
}
shouldCancel = e.Cancel;
}
}
some extension methods to support the TreeView finding the object under the mouse.
public static class ItemContainerExtensions
{
// get the object that exists in the container at the specified point.
public static object GetObjectAtPoint<ItemContainer>(this ItemsControl control, Point p)
where ItemContainer : DependencyObject
{
// ItemContainer - can be ListViewItem, or TreeViewItem and so on(depends on control)
ItemContainer obj = GetContainerAtPoint<ItemContainer>(control, p);
if (obj == null)
return null;
// it is worth noting that the passed _control_ may not be the direct parent of the
// container that exists at this point. This can be the case in a TreeView, where the
// parent of a TreeViewItem may be either the TreeView or a intermediate TreeViewItem
ItemsControl parentGenerator = obj.GetParentItemsControl( );
// hopefully this isn't possible?
if (parentGenerator == null)
return null;
return parentGenerator.ItemContainerGenerator.ItemFromContainer(obj);
}
// use the VisualTreeHelper to find the container at the specified point.
public static ItemContainer GetContainerAtPoint<ItemContainer>(this ItemsControl control, Point p)
where ItemContainer : DependencyObject
{
HitTestResult result = VisualTreeHelper.HitTest(control, p);
DependencyObject obj = result.VisualHit;
while (VisualTreeHelper.GetParent(obj) != null && !(obj is ItemContainer))
{
obj = VisualTreeHelper.GetParent(obj);
}
// Will return null if not found
return obj as ItemContainer;
}
// walk up the visual tree looking for the nearest ItemsControl parent of the specified
// depObject, returns null if one isn't found.
public static ItemsControl GetParentItemsControl(this DependencyObject depObject)
{
DependencyObject obj = VisualTreeHelper.GetParent(depObject);
while (VisualTreeHelper.GetParent(obj) != null && !(obj is ItemsControl))
{
obj = VisualTreeHelper.GetParent(obj);
}
// will return null if not found
return obj as ItemsControl;
}
}
and last, but not least the custom EventArgs that leverage the RoutedEvent subsystem.
public class CancelRoutedEventArgs : RoutedEventArgs
{
private readonly CancelEventArgs _CancelArgs;
public CancelRoutedEventArgs(RoutedEvent #event, CancelEventArgs cancelArgs)
: base(#event)
{
_CancelArgs = cancelArgs;
}
// override the InvokeEventHandler because we are going to pass it CancelEventArgs
// not the normal RoutedEventArgs
protected override void InvokeEventHandler(Delegate genericHandler, object genericTarget)
{
CancelEventHandler handler = (CancelEventHandler)genericHandler;
handler(genericTarget, _CancelArgs);
}
// the result
public bool Cancel
{
get
{
return _CancelArgs.Cancel;
}
}
}
Instead of selecting for Selected/Unselected, a better route might be to hook into PreviewMouseDown. The preblem with handling a Selected and Unselected event is that the event has already occurred when you receive the notification. There is nothing to cancel because it's already happened.
On the other hand, Preview events are cancelable. It's not the exact event you want but it does give you the oppuritunity to prevent the user from selecting a different node.
You can't cancel the event like you can, for example, a Closing event. But you can undo it if you cache the last selected value. The secret is you have to change the selection without re-firing the SelectionChanged event. Here's an example:
private object _LastSelection = null;
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (IsUpdated)
{
MessageBoxResult result = MessageBox.Show("The current record has been modified. Are you sure you want to navigate away? Click Cancel to continue editing. If you click OK all changes will be lost.", "Warning", MessageBoxButton.OKCancel, MessageBoxImage.Hand);
switch (result)
{
case MessageBoxResult.Cancel:
e.Handled = true;
// disable event so this doesn't go into an infinite loop when the selection is changed to the cached value
PersonListView.SelectionChanged -= new SelectionChangedEventHandler(OnSelectionChanged);
PersonListView.SelectedItem = _LastSelection;
PersonListView.SelectionChanged += new SelectionChangedEventHandler(OnSelectionChanged);
return;
case MessageBoxResult.OK:
// revert the object to the original state
LocalDataContext.Persons.GetOriginalEntityState(_LastSelection).CopyTo(_LastSelection);
IsUpdated = false;
Refresh();
break;
default:
throw new ApplicationException("Invalid response.");
}
}
// cache the selected item for undo
_LastSelection = PersonListView.SelectedItem;
}
CAMS_ARIES:
XAML:
code :
private bool ManejarSeleccionNodoArbol(Object origen)
{
return true; // with true, the selected nodo don't change
return false // with false, the selected nodo change
}
private void Arbol_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.Source is TreeViewItem)
{
e.Handled = ManejarSeleccionNodoArbol(e.Source);
}
}
private void Arbol_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Source is TreeViewItem)
{
e.Handled=ManejarSeleccionNodoArbol(e.Source);
}
}
Since the SelectedItemChanged event is triggered after the SelectedItem has already changed, you can't really cancel the event at this point.
What you can do is listen for mouse-clicks and cancel them before the SelectedItem gets changed.
You could create your custom control that derives from TreeView and then override the OnSelectedItemChanged method.
Before calling the base, you could first fire a custom event with a CancelEventArgs parameter. If the parameter.Cancel become true, then don't call the base, but select the old item instead (be careful that the OnSelectedItemChanged will be called again).
Not the best solution, but at least this keeps the logic inside the tree control, and there is not chance that the selection change event fires more than it's needed. Also, you don't need to care if the user clicked the tree, used the keyboard or maybe the selection changed programatically.
I solved this problem for 1 tree view and display of 1 document at a time. This solution is based on an attachable behavior that can be attached to a normal treeview:
<TreeView Grid.Column="0"
ItemsSource="{Binding TreeViewItems}"
behav:TreeViewSelectionChangedBehavior.ChangedCommand="{Binding SelectItemChangedCommand}"
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"
ToolTipService.ShowOnDisabled="True"
VerticalAlignment="Center" Margin="3" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
and the code for the behavior is this:
/// <summary>
/// Source:
/// http://stackoverflow.com/questions/1034374/drag-and-drop-in-mvvm-with-scatterview
/// http://social.msdn.microsoft.com/Forums/de-DE/wpf/thread/21bed380-c485-44fb-8741-f9245524d0ae
///
/// Attached behaviour to implement the SelectionChanged command/event via delegate command binding or routed commands.
/// </summary>
public static class TreeViewSelectionChangedBehavior
{
#region fields
/// <summary>
/// Field of attached ICommand property
/// </summary>
private static readonly DependencyProperty ChangedCommandProperty = DependencyProperty.RegisterAttached(
"ChangedCommand",
typeof(ICommand),
typeof(TreeViewSelectionChangedBehavior),
new PropertyMetadata(null, OnSelectionChangedCommandChange));
/// <summary>
/// Implement backing store for UndoSelection dependency proeprty to indicate whether selection should be
/// cancelled via MessageBox query or not.
/// </summary>
public static readonly DependencyProperty UndoSelectionProperty =
DependencyProperty.RegisterAttached("UndoSelection",
typeof(bool),
typeof(TreeViewSelectionChangedBehavior),
new PropertyMetadata(false, OnUndoSelectionChanged));
#endregion fields
#region methods
#region ICommand changed methods
/// <summary>
/// Setter method of the attached ChangedCommand <seealso cref="ICommand"/> property
/// </summary>
/// <param name="source"></param>
/// <param name="value"></param>
public static void SetChangedCommand(DependencyObject source, ICommand value)
{
source.SetValue(ChangedCommandProperty, value);
}
/// <summary>
/// Getter method of the attached ChangedCommand <seealso cref="ICommand"/> property
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static ICommand GetChangedCommand(DependencyObject source)
{
return (ICommand)source.GetValue(ChangedCommandProperty);
}
#endregion ICommand changed methods
#region UndoSelection methods
public static bool GetUndoSelection(DependencyObject obj)
{
return (bool)obj.GetValue(UndoSelectionProperty);
}
public static void SetUndoSelection(DependencyObject obj, bool value)
{
obj.SetValue(UndoSelectionProperty, value);
}
#endregion UndoSelection methods
/// <summary>
/// This method is hooked in the definition of the <seealso cref="ChangedCommandProperty"/>.
/// It is called whenever the attached property changes - in our case the event of binding
/// and unbinding the property to a sink is what we are looking for.
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnSelectionChangedCommandChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TreeView uiElement = d as TreeView; // Remove the handler if it exist to avoid memory leaks
if (uiElement != null)
{
uiElement.SelectedItemChanged -= Selection_Changed;
var command = e.NewValue as ICommand;
if (command != null)
{
// the property is attached so we attach the Drop event handler
uiElement.SelectedItemChanged += Selection_Changed;
}
}
}
/// <summary>
/// This method is called when the selection changed event occurs. The sender should be the control
/// on which this behaviour is attached - so we convert the sender into a <seealso cref="UIElement"/>
/// and receive the Command through the <seealso cref="GetChangedCommand"/> getter listed above.
///
/// The <paramref name="e"/> parameter contains the standard EventArgs data,
/// which is unpacked and reales upon the bound command.
///
/// This implementation supports binding of delegate commands and routed commands.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void Selection_Changed(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var uiElement = sender as TreeView;
// Sanity check just in case this was somehow send by something else
if (uiElement == null)
return;
ICommand changedCommand = TreeViewSelectionChangedBehavior.GetChangedCommand(uiElement);
// There may not be a command bound to this after all
if (changedCommand == null)
return;
// Check whether this attached behaviour is bound to a RoutedCommand
if (changedCommand is RoutedCommand)
{
// Execute the routed command
(changedCommand as RoutedCommand).Execute(e.NewValue, uiElement);
}
else
{
// Execute the Command as bound delegate
changedCommand.Execute(e.NewValue);
}
}
/// <summary>
/// Executes when the bound boolean property indicates that a user should be asked
/// about changing a treeviewitem selection instead of just performing it.
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnUndoSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TreeView uiElement = d as TreeView; // Remove the handler if it exist to avoid memory leaks
if (uiElement != null)
{
uiElement.PreviewMouseDown -= uiElement_PreviewMouseDown;
var command = (bool)e.NewValue;
if (command == true)
{
// the property is attached so we attach the Drop event handler
uiElement.PreviewMouseDown += uiElement_PreviewMouseDown;
}
}
}
/// <summary>
/// Based on the solution proposed here:
/// Source: http://stackoverflow.com/questions/20244916/wpf-treeview-selection-change
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void uiElement_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
// first did the user click on a tree node?
var source = e.OriginalSource as DependencyObject;
while (source != null && !(source is TreeViewItem))
source = VisualTreeHelper.GetParent(source);
var itemSource = source as TreeViewItem;
if (itemSource == null)
return;
var treeView = sender as TreeView;
if (treeView == null)
return;
bool undoSelection = TreeViewSelectionChangedBehavior.GetUndoSelection(treeView);
if (undoSelection == false)
return;
// Cancel the attempt to select an item.
var result = MessageBox.Show("The current document has unsaved data. Do you want to continue without saving data?", "Are you really sure?",
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
if (result == MessageBoxResult.No)
{
// Cancel the attempt to select a differnet item.
e.Handled = true;
}
else
{
// Lets disable this for a moment, otherwise, we'll get into an event "recursion"
treeView.PreviewMouseDown -= uiElement_PreviewMouseDown;
// Select the new item - make sure a SelectedItemChanged event is fired in any case
// Even if this means that we have to deselect/select the one and the same item
if (itemSource.IsSelected == true )
itemSource.IsSelected = false;
itemSource.IsSelected = true;
// Lets enable this to get back to business for next selection
treeView.PreviewMouseDown += uiElement_PreviewMouseDown;
}
}
#endregion methods
}
In this example I am showing a blocking message box in order to block the PreviewMouseDown event when it occurs. The event is then handled to signal that selection is cancelled or it is not handled to let the treeview itself handle the event by selecting the item that is about to be selected.
The behavior then invokes a bound command in the viewmodel if the user decides to continue anyway (PreviewMouseDown event is not handled by attached behavior and bound command is invoked.
I guess the message box showing could be done in other ways but I think its essential here to block the event when it happens since its otherwise not possible to cancel it(?). So, the only improve I could possible think off about this code is to bind some strings to make the displayed message configurable.
I have written an article that contains a downloadable sample since this is otherwise a difficult area to explain (one has to make many assumptions about missing parts that and the may not always be shared by all readers)
Here is an article that contains my results:
http://www.codeproject.com/Articles/995629/Cancelable-TreeView-Navigation-for-Documents-in-WP
Please comment on this solution and let me know if you see room for improvement.