Double Click on a WPF ListView - how to fire a command and not use an event handler - wpf

I am a little confused on how to implement an event as a command in my particular situation. I want to honour MVVM, but don't get how in this case.
I have a WPF 'view' - viewCustomerSearch. This has some text boxes on it, and when the user clicks 'Search' the results are populated in ListView. viewCustomerSearch is bound to viewmodelCustomerSearch, and it works great.
viewCustomerSearch is hosted on viewCustomer.
I want to know have viewCustomerSearch expose a custom command - CustomerSelectedCommand - that is 'fired' whenever the ListView in viesCustomerSearch is double clicked, and then handled by the viewmodel behind viewCustomer (which is viewmodelCustomer). This seems the theoretical MVVM pattern implemented correctly.
I have broken down the main problem into three smaller problems, but hopefully you can see they are all components of the same challenge.
FIRST PROBLEM: in order to have viewCustomerSearch expose a custom command I seem to have to put this code in viewCustomerSearch - which seems to 'break' MVVM (no code in the view code behind).
public readonly DependencyProperty CustomerSelectedCommandProperty = DependencyProperty.Register("CustomerSelectedCommand", typeof(ICommand), typeof(viewCustomerSearch));
public ICommand CustomerSelectedCommand
{
get { return (ICommand)GetValue(CustomerSelectedCommandProperty); }
set { SetValue(CustomerSelectedCommandProperty, value); }
}
SECOND PROBLEM (and this is the one that is really getting to me): Best explained by showing what I would do which breaks MVVM. I would have an event handler in the view:
private void lstResults_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (CustomerSelectedCommand != null) CustomerSelectedCommand.Execute(((ViewModels.viewmodelCustomerSearchResult)this.lstResults.SelectedItem).CustomerId);
}
Well ... I know that you shouldn't put this event handler here; rather it should have a Command to handle it in the viewmodelCustomerSearch. The two problems here are
because the 'CustomerSelectedCommand' ICommand is implemented in
viewCustomerSearch, viewmodelCustomerSearch can't see it to fire it.
I cannot see how to bind the MouseDoubleClick event to a command, instead of an event handler in the view code behind. I am reading about Attached Properties, but cannot see how they are to be applied here.
(Please note: I am using the common 'RelayCommand' elsewhere in the application; does this come into play here??)
THIRD PROBLEM: When I do use the non-MVVM way of firing the command in the code behind event handler, you can see that I am passing in the Selected Customer Id as an arguement into the command. How do I see that argument in the Command handler in viewCustomer? I create a new RelayCommand to handle it, but it seems the Execute method does not take arguments?
Given all of the above, I have to say that I do NOT personally subscribe to the 'MVVM means NO CODE IN THE VIEW'. That seems crazy to me; code that is entirely to do with the view, and the view only, should not - IMHO - go in the viewmodel. That said, though, this does seem like logic-y stuff (not view stuff).
Many thanks for some insight. Sorry for the long post; trying to balance enough information for you to help me with 'War and Peace'.
DS

In your view you can add a "Command" property in xaml and bind it to your ViewModel's command
Command="{Binding CustomerSelectedCommand}"
Parameters can be passed in multiple ways. Most of the time, I just have other items bound to my ViewModel and I can just use them directly. However there is also a property called CommandParameter, here's an example of specifying it in XAML.
CommandParameter="{Binding ElementName=txtPassword}"
then in my ViewModel the definition of my Command looks like this
private void UserLogonCommandExecute(object parameter)
{
...
var password_box = parameter as PasswordBox;
...
}
It sounds like you already know how to set up a RelayCommand in your ViewModel so I won't go into that. I found How Do I: Build Data-driven WPF Application using the MVVM pattern helpful when I was getting started.
Per Comment Request Command Property Example
I'm just going to grab some working code, here's how you add a Command property to a button in XAML.
<Button Command="{Binding ConnectCommand}">
//Your button content and closing </Button> here
This assume you have set your DataContext to a ViewModel that has a Command called ConnectCommand. Here's an example for ConnectCommand. You'll need to replace the contents of ConnectCommandCanExecute and ConnectCommandExecute with whatever work you want done.
public ICommand ConnectCommand
{
get
{
if (_connectCommand == null)
{
_connectCommand = new RelayCommand(param => ConnectCommandExecute(),
param => ConnectCommandCanExecute);
}
return _connectCommand;
}
}
private bool ConnectCommandCanExecute
{
get { return !_instrumentModel.IsConnected; }
}
private void ConnectCommandExecute()
{
if (TcpSettingsChanged()) SaveTcpSettings();
_instrumentModel.Connect(_tcpData);
}
RelayClass
One part of making this simple is the RelayClass I have in one of my core library .dlls. I probably got this from one of the videos I watched. This can be cut and pasted in it's entirety, there is nothing here you need to customize, except you'll probably want to change the namespace this is in.
using System;
using System.Diagnostics;
using System.Windows.Input;
namespace Syncor.MvvmLib
{
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public RelayCommand(Action<object> execute)
: this(execute, (Predicate<object>) null)
{
this._execute = execute;
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
this._execute = execute;
this._canExecute = canExecute;
}
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
if (this._canExecute != null)
return this._canExecute(parameter);
else
return true;
}
public void Execute(object parameter)
{
this._execute(parameter);
}
}
}

Why don't you name it "DoubleClickCommand" that way you don't put business logic in your control. And then bind this command to your viewmodel, Like Tod explained.
Regarding your code behind, there is a pure xaml solution, to be more precise it involves attached behaviors, but does not need to override a WPF class(which i like to avoid), search for "fire command on event" for example this.
One final thing: Code Behind does NOT break MVVM in any way, i wonder where this myth came from. Code behind is perfectly fine! MVVM is to separate view and logic, not telling you where to put your code. Design principles should help, not hinder you.

Related

How does NotifyOnValidationError end up calling CanExecute (and why doesn't it work with MVVM Light RelayCommand)

Maybe the longest question title of all time! Because this is a two part question.
(1) I do not understand how setting NotifyOnValidationError="True" can trigger updates to my CanExecute. There is a bit of magic involved here that I need to understand. Someone(thing) subscribes to the CanExecuteChanged event of my ICommand but the call stack points to External code, so I can not figure out what is going on.
(2) Maybe the most important follow up questions is: Why does it not work in MVVM Light RelayCommand! The CanExecute is only called once at initialization and then never again. Looking at the source code for RelayCommand in MVVM Light does not reveal any chocking differences compared to my own implementation. I should mention that Prism's DelegateCommand does not seem to work either.
(Bonus) Maybee I am approaching this problem the wrong way? I just basically want to enable/disable buttons based on Validation failiures.
XAML (snippet):
<TextBox Grid.Column="1" Grid.Row="0">
<Binding Path="X" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
<Binding.ValidationRules>
<ExceptionValidationRule></ExceptionValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox>
<Button Grid.Column="1" Grid.Row="3" Command="{Binding CalculateCommand}">
Calculate
</Button>
RelayCommand:
public class MyRelayCommand : ICommand
{
readonly Action<object> Execute_;
readonly Predicate<object> CanExecute_;
public MyRelayCommand(Action<object> Execute, Predicate<object> CanExecute)
{
if (Execute == null)
throw new ArgumentNullException("No action to execute for this command.");
Execute_ = Execute;
CanExecute_ = CanExecute;
}
public bool CanExecute(object parameter)
{
return (CanExecute_ == null) ? true : CanExecute_(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
Execute_(parameter);
}
}
ViewModel:
private DelegateCommand _calculateCommmand;
public DelegateCommand CalculateCommand
{
get
{
return _calculateCommmand ?? (_calculateCommmand = new DelegateCommand(
() =>
{
Sum = X + X;
},
() =>
{
try
{
Convert.ChangeType(X, TypeCode.Byte);
return true;
}
catch
{
return false;
}
}));
}
}
PS: If you wanna buy my X + X program when it is done email me at sales#xplusx.com
(2) I figured this one out myself. You can choose to include RelayCommand from two different namespaces, make sure you use
using GalaSoft.MvvmLight.CommandWpf;
I am still looking for a good answer to (1), how the plumbing works that raises CanExecutetChanged based on validation error.
I don't think it depends on the ICommand implementation. In yours, I see a public event EventHandler CanExecuteChanged, where you tell the CommandManager to handle the invocation of your command's CanExecute() method. Without the CommandManager, you would have to handle this yourself, e.g. by providing your ICommand implementation with a public void RaiseCanExecuteChanged() method that your ViewModel has to call for every command it considers to be needed to recalculated, e.g. inside the ViewModel's OnPropertyChanged. Example: https://codereview.stackexchange.com/questions/124361/mvvm-am-i-doing-it-right
So the CommandManager does the magic for you. As soon as you invoke your ViewModel's PropertyChanged event, the "external code" handles the affected commands and asks them for a fresh CanExecute() value.
(1) I think it goes like this.
When we bind a RelayCommand : ICommand to Button.Command, the binding process will also attach an eventhandler to the ICommand.CanExecuteChanged. This is default behavior.
The eventhandler passed to CanExecutedChanged will be passed along and attached to the static event CommandManager.RequerySuggested.
When a validation error occurs and the NotifyOnValidationError is set an external force, or a jedi, will raise the RequerySuggested event which will broadcast to ALL active commands.
The Button recieves the event and consequently calls CanExecute to know if it shold disable/enable the button.
I would like to know more about the third bullet point above, so I will keep the question open for a little longer to give the experts a chance to chime in.

WPF Is there a simple way to update GUI from main thread [duplicate]

I'm a web and backend programmer by nature. Normally I try to avaoid making windows programs. Now I have to make a WPF client.
I have a background task that raises an event every often time. (It is working like a poller and when the criteria are met an event is raised). Noob as I am I wrote this code that was attached to the event to update the UI.
private void IsDisconnectedEvent()
{
UserWindow.Visibility = Visibility.Hidden;
DisconnectWindow.Visibility = Visibility.Visible;
}
This gives an exception because I am not on the same thread. After some googling I found that I should change the code with:
private void IsDisconnectedEvent()
{
Dispatcher.Invoke(() =>
{
UserWindow.Visibility = Visibility.Hidden;
DisconnectWindow.Visibility = Visibility.Visible;
});
}
This works, but this is not the only event and thus makes my code horrible ugly. Are there better ways to do this?
Regarding this:
This works, but this is not the only event and thus makes my code
horrible ugly
Yes, your WPF-based code will definitely be extremely horrible unless you understand and embrace The WPF Mentality.
Basically, all interactions between your custom logic (AKA Business logic or Application Logic) and the WPF UI should manifest in the form of Declarative DataBinding as opposed to the traditional imperative approach.
This means that there should be nothing like this:
UserWindow.Visibility = Visibility.Hidden;
anywhere in your code, simply because introducing things like that makes your code dependent on the UI and thus only executable on the UI thread.
Instead, the WPF approach to that would be to declaratively DataBind the Visibility propety of the UI element (IN XAML) to a relevant bool property that you can operate from the outside, like this:
<UserWindow Visibility="{Binding ShowUserWindow, Converter={my:BoolToVisibilityConverter}}">
<!-- ... -->
</UserWindow>
Then, you would need to create a relevant class that contains the properties the UI is expecting to bind to. This is called a ViewModel.
Notice that in order to properly support Two-Way WPF DataBinding, your ViewModels must Implement the INotifyPropertyChanged interface.
When doing so, it is also convenient to have the PropertyChanged event from that interface marshalled to the UI thread, so that you no longer have to worry about setting the ViewModel's properties by using the Dispatcher.
Therefore our first step is to have all our ViewModels inherit from a class like this:
(taken from this answer):
public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
//Raise the PropertyChanged event on the UI Thread, with the relevant propertyName parameter:
Application.Current.Dispatcher.BeginInvoke((Action) (() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}
Once we have our Property Change Notification Dispatch to the UI Thread in place, we can proceed to create a relevant ViewModel that suits, in this case, the UserWindow and it's DataBinding expectations:
public class UserViewModel: PropertyChangedBase
{
private bool _showUserWindow;
public bool ShowUserWindow
{
get {return _showUserWindow; }
set
{
_showUserWindow = value;
OnPropertyChanged("ShowUserWindow"); //This is important!!!
}
}
}
Finally, you would need to set the Window's DataContext to an instance of it's corresponding ViewModel. One simple way to do that is in the Window's constructor:
public UserWindow() //Window's Constructor
{
InitializeComponent(); //this is required.
DataContext = new UserViewModel(); //here we set the DataContext
}
As you can see in this example, there is literally no need to manipulate the UI element's properties in procedural code. This is good not only because it resolves the Thread Affinity issues (because now you can set the ShowUserWindow property from any thread), but also because it makes your ViewModels and logic completely decoupled from the UI and thus testable and more scalable.
This same concept applies to EVERYTHING in WPF.
One detail that I need to mention is that I'm making use of a technique of Combining MarkupExtension and IValueConverter in order to reduce the the XAML boilerplate involved in using Converters.
You can read more about that in the link and also the MSDN DataBinding page linked above.
Let me know if you need further details.

WPF shortcut in whole window

I'm developing MVVM application and I'd like to create global shortcuts for my application. It's necessary to navigatate application between modules. I have main window with code:
<UserControl.InputBindings>
<KeyBinding Command="{Binding ChangeModuleComand}"
Key="M"
Modifiers="Control"/>
</UserControl.InputBindings>
It works when I have focus on my window,
But there is a problem when i have focus on TextBox in that UserControl (Command is not fired).
Is that possible to catch that key pressure without lots of modifications in whole application?
A.
It sounds like your event is being handled before it bubbles up that far. Next I am wondering if you could move your input binding up to your window rather than a user control. My final option would be to remove your input bindings and create a global check in code.
Since you're already working with input bindings, I have added the code option below:
//In App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
EventManager.RegisterClassHandler(typeof(UIElement), UIElement.KeyDownEvent, new RoutedEventHandler(GlobalClick));
}
private void GlobalClick(object sender, RoutedEventArgs e)
{
var args = (KeyEventArgs) e;
if (args.KeyboardDevice.IsKeyDown(Key.M) &&
args.KeyboardDevice.IsKeyDown(Key.LeftCtrl) || args.KeyboardDevice.IsKeyDown(Key.RightCtrl))
MessageBox.Show("Works");
}
Documenting this answer for others, as there is a much simpler way to do this that is rarely referenced, and doesn't require touching the XAML at all.
To link a keyboard shortcut at the Window level, in the Window constructor simply add a new KeyBinding to the InputBindings collection. As the command, pass in your arbitrary command class that implements ICommand. For the execute method, simply implement whatever logic you need. In my example below, my WindowCommand class takes a delegate that it will execute whenever invoked. When I construct the new WindowCommand to pass in with my binding, I simply indicate in my initializer, the method that I want the WindowCommand to execute.
You can use this pattern to come up with your own quick keyboard shortcuts.
public YourWindow() //inside any WPF Window constructor
{
...
//add this one statement to bind a new keyboard command shortcut
InputBindings.Add(new KeyBinding( //add a new key-binding, and pass in your command object instance which contains the Execute method which WPF will execute
new WindowCommand(this)
{
ExecuteDelegate = TogglePause //REPLACE TogglePause with your method delegate
}, new KeyGesture(Key.P, ModifierKeys.Control)));
...
}
Create a simple WindowCommand class which takes an execution delegate to fire off any method set on it.
public class WindowCommand : ICommand
{
private MainWindow _window;
//Set this delegate when you initialize a new object. This is the method the command will execute. You can also change this delegate type if you need to.
public Action ExecuteDelegate { get; set; }
//You don't have to add a parameter that takes a constructor. I've just added one in case I need access to the window directly.
public WindowCommand(MainWindow window)
{
_window = window;
}
//always called before executing the command, mine just always returns true
public bool CanExecute(object parameter)
{
return true; //mine always returns true, yours can use a new CanExecute delegate, or add custom logic to this method instead.
}
public event EventHandler CanExecuteChanged; //i'm not using this, but it's required by the interface
//the important method that executes the actual command logic
public void Execute(object parameter)
{
if (ExecuteDelegate != null) //let's make sure the delegate was set
{
ExecuteDelegate();
}
else
{
throw new InvalidOperationException("ExecuteDelegate has not been set. There is no method to execute for this command.");
}
}
}
I'm sure this can be used for other controls as well, but haven't tried it.

MVVM pattern violation: MediaElement.Play()

I understand that ViewModel shouldn't have any knowledge of View, but how can I call MediaElement.Play() method from ViewModel, other than having a reference to View (or directly to MediaElement) in ViewModel?
Other (linked) question: how can I manage View's controls visibility from ViewModel without violating MVVM pattern?
1) Do not call Play() from the view model. Raise an event in the view model instead (for instance PlayRequested) and listen to this event in the view:
view model:
public event EventHandler PlayRequested;
...
if (this.PlayRequested != null)
{
this.PlayRequested(this, EventArgs.Empty);
}
view:
ViewModel vm = new ViewModel();
this.DataContext = vm;
vm.PlayRequested += (sender, e) =>
{
this.myMediaElement.Play();
};
2) You can expose in the view model a public boolean property, and bind the Visibility property of your controls to this property. As Visibility is of type Visibility and not bool, you'll have to use a converter.
You can find a basic implementation of such a converter here.
This related question might help you too.
For all the late-comers,
There are many ways to achieve the same result and it really depends on how you would like to implement yours, as long as your code is not difficult to maintain, I do believe it's ok to break the MVVM pattern under certain cases.
But having said that, I also believe there is always way to do this within the pattern, and the following is one of them just in case if anyone would like to know what other alternatives are available.
The Tasks:
we don't want to have direct reference from the ViewModel to any UI elements, i.e. the the MediaElement and the View itself.
we want to use Command to do the magic here
The Solution:
In short, we are going to introduce an interface between the View and the ViewModel to break the dependecy, and the View will be implementing the interface and be responsible for the direct controlling of the MediaElement while leaving the ViewModel talking only to the interface, which can be swapped with other implementation for testing purposes if needed, and here comes the long version:
Introduce an interface called IMediaService as below:
public interface IMediaService
{
void Play();
void Pause();
void Stop();
void Rewind();
void FastForward();
}
Implement the IMediaService in the View:
public partial class DemoView : UserControl, IMediaService
{
public DemoView()
{
InitializeComponent();
}
void IMediaService.FastForward()
{
this.MediaPlayer.Position += TimeSpan.FromSeconds(10);
}
void IMediaService.Pause()
{
this.MediaPlayer.Pause();
}
void IMediaService.Play()
{
this.MediaPlayer.Play();
}
void IMediaService.Rewind()
{
this.MediaPlayer.Position -= TimeSpan.FromSeconds(10);
}
void IMediaService.Stop()
{
this.MediaPlayer.Stop();
}
}
we then do few things in the DemoView.XAML:
Give the MediaElement a name so the code behind can access it like above:
<MediaElement Source="{Binding CurrentMedia}" x:Name="MediaPlayer"/>
Give the view a name so we can pass it as a parameter, and
import the interactivity namespace for later use (some default namespaces are omitted for simplicity reason):
<UserControl x:Class="Test.DemoView"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ia="http://schemas.microsoft.com/expression/2010/interactivity"
x:Name="MediaService">
Hookup the Loaded event through Trigger to pass the view itself to the view model through a Command
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Loaded">
<ia:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=MediaService}"></ia:InvokeCommandAction>
</ia:EventTrigger>
</ia:Interaction.Triggers>
last but not least, we need to hookup the media controls through Commands:
<Button Command="{Binding PlayCommand}" Content="Play"></Button>
<Button Command="{Binding PauseCommand}" Content="Pause"></Button>
<Button Command="{Binding StopCommand}" Content="Stop"></Button>
<Button Command="{Binding RewindCommand}" Content="Rewind"></Button>
<Button Command="{Binding FastForwardCommand}" Content="FastForward"></Button>
We now can catch everything in the ViewModel (I'm using prism's DelegateCommand here):
public class AboutUsViewModel : SkinTalkViewModelBase, IConfirmNavigationRequest
{
public IMediaService {get; private set;}
private DelegateCommand<IMediaService> loadedCommand;
public DelegateCommand<IMediaService> LoadedCommand
{
get
{
if (this.loadedCommand == null)
{
this.loadedCommand = new DelegateCommand<IMediaService>((mediaService) =>
{
this.MediaService = mediaService;
});
}
return loadedCommand;
}
}
private DelegateCommand playCommand;
public DelegateCommand PlayCommand
{
get
{
if (this.playCommand == null)
{
this.playCommand = new DelegateCommand(() =>
{
this.MediaService.Play();
});
}
return playCommand;
}
}
.
. // other commands are not listed, but you get the idea
.
}
Side note: I use Prism's Auto Wiring feature to link up the View and ViewModel. So at the View's code behind file there is no DataContext assignment code, and I prefer to keep it that way, and hence I chose to use purely Commands to achieve this result.
I use media element to play sounds in UI whenever an event occurs in the application. The view model handling this, was created with a Source property of type Uri (with notify property changed, but you already know you need that to notify UI).
All you have to do whenever source changes (and this is up to you), is to set the source property to null (this is why Source property should be Uri and not string, MediaElement will naturally throw exception, NotSupportedException I think), then set it to whatever URI you want.
Probably, the most important aspect of this tip is that you have to set MediaElement's property LoadedBehaviour to Play in XAML of your view. Hopefully no code behind is needed for what you want to achieve.
The trick is extremely simple so I won't post a complete example. The view model's play function should look like this:
private void PlaySomething(string fileUri)
{
if (string.IsNullOrWhiteSpace(fileUri))
return;
// HACK for MediaElement: to force it to play a new source, set source to null then put the real source URI.
this.Source = null;
this.Source = new Uri(fileUri);
}
Here is the Source property, nothing special about it:
#region Source property
/// <summary>
/// Stores Source value.
/// </summary>
private Uri _Source = null;
/// <summary>
/// Gets or sets file URI to play.
/// </summary>
public Uri Source
{
get { return this._Source; }
private set
{
if (this._Source != value)
{
this._Source = value;
this.RaisePropertyChanged("Source");
}
}
}
#endregion Source property
As for Visibility, and stuff like this, you can use converters (e.g. from bool to visibility, which you can find on CodePlex for WPF, SL, WP7,8) and bind your control's property to that of the view model's (e.g. IsVisible). This way, you control parts of you view's aspect. Or you can just have Visibility property typed System.Windows.Visibility on your view model (I don't see any pattern breach here). Really, it's not that uncommon.
Good luck,
Andrei
P.S. I have to mention that .NET 4.5 is the version where I tested this, but I think it should work on other versions as well.

Drag and Drop in MVVM with ScatterView

I'm trying to implement drag and drop functionality in a Surface Application that is built using the MVVM pattern. I'm struggling to come up with a means to implement this while adhering to the MVVM pattern. Though I'm trying to do this within a Surface Application I think the solution is general enough to apply to WPF as well.
I'm trying to produce the following functionality:
User contacts a FrameworkElement within a ScatterViewItem to begin a drag operation (a specific part of the ScatterViewItem initiates the drag/drop functionality)
When the drag operation begins a copy of that ScatterViewItem is created and imposed upon the original ScatterViewItem, the copy is what the user will drag and ultimately drop
The user can drop the item onto another ScatterViewItem (placed in a separate ScatterView)
The overall interaction is quite similar to the ShoppingCart application provided in the Surface SDK, except that the source objects are contained within a ScatterView rather than a ListBox.
I'm unsure how to proceeded in order to enable the proper communication between my ViewModels in order to provide this functionality. The main issue I've encountered is replicating the ScatterViewItem when the user contacts the FrameworkElement.
You could use an attached property. Create an attached property and in the setproperty method bind to the droped event :
public static void SetDropCommand(ListView source, ICommand command)
{
source.Drop += (sender, args) =>
{
var data = args.Data.GetData("FileDrop");
command.Execute(data);
};
}
Then you can bind a command in your view model to the relevant control on the view. Obviously you may want to make your attached property apply to your specific control type rather than a listview.
Hope that helps.
I had a go at getting Steve Psaltis's idea working. It took a while - custom dependency properties are easy to get wrong. It looks to me like the SetXXX is the wrong place to put your side-effects - WPF doesn't have to go though there, it can go directly to DependencyObject.SetValue, but the PropertyChangedCallback will always be called.
So, here's complete and working the code for the custom attached property:
using System.Windows;
using System.Windows.Input;
namespace WpfApplication1
{
public static class PropertyHelper
{
public static readonly DependencyProperty DropCommandProperty = DependencyProperty.RegisterAttached(
"DropCommand",
typeof(ICommand),
typeof(PropertyHelper),
new PropertyMetadata(null, OnDropCommandChange));
public static void SetDropCommand(DependencyObject source, ICommand value)
{
source.SetValue(DropCommandProperty, value);
}
public static ICommand GetDropCommand(DependencyObject source)
{
return (ICommand)source.GetValue(DropCommandProperty);
}
private static void OnDropCommandChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ICommand command = e.NewValue as ICommand;
UIElement uiElement = d as UIElement;
if (command != null && uiElement != null)
{
uiElement.Drop += (sender, args) => command.Execute(args.Data);
}
// todo: if e.OldValue is not null, detatch the handler that references it
}
}
}
In the XAML markup where you want to use this, you can do e.g.
xmlns:local="clr-namespace:WpfApplication1"
...
<Button Content="Drop here" Padding="12" AllowDrop="True"
local:PropertyHelper.DropCommand="{Binding DropCommand}" />
.. And the rest is just making sure that your ViewModel, bindings and command is right.
This version passes an IDataObject through to the command which seems better to me - you can query it for files or whatever in the command. But that's just a current preference, not an essential feature of the answer.

Resources