I like to use Attached Command Behaviors and Commands. Marlon Grech has a very good implementation of the Attached Command Behaviors. Using these, we could then assign a style to the ListView's ItemContainerStyle property that will set the command for each ListViewItem.
Here we set the command to be fired on the MouseDoubleClick event, and the CommandParameter, will be the data object that we click on. Here I'm traveling up the visual tree to get the command that I'm using, but you could just as easily create application wide commands.
You can use Caliburn's Action feature to map events to methods on your ViewModel. Assuming you have an ItemActivated method on your ViewModel, then corresponding XAML would look like:
Please, code behind is not a bad thing at all. Unfortunately, quite a lot people in the WPF community got this wrong.
MVVM is not a pattern to eliminate the code behind. It is to separate the view part (appearance, animations, etc.) from the logic part (workflow). Furthermore, you are able to unit test the logic part.
I know enough scenarios where you have to write code behind because data binding is not a solution to everything. In your scenario I would handle the DoubleClick event in the code behind file and delegate this call to the ViewModel.
Sample applications that use code behind and still fulfill the MVVM separation can be found here:
I realize that this discussion is a year old, but with .NET 4, are there any thoughts on this solution? I absolutely agree that the point of MVVM is NOT to eliminate a code behind file. I also feel very strongly that just because something is complicated, doesn't mean it's better. Here is what I put in the code behind:
The GridViewRowPresenter will be the visual root of all elements "inside" making up a list row element. Now we could insert a trigger there to look for MouseDoubleClick routed events and call a command via InvokeCommandAction like this:
If you have visual elements "above" the GridRowPresenter (probalby starting with a grid) you can also put the Trigger there.
Unfortunately MouseDoubleClick events are not generated from every visual element (they are from Controls, but not from FrameworkElements for example). A workaround is to derive a class from EventTrigger and look for MouseButtonEventArgs with a ClickCount of 2. This effectively filters out all non-MouseButtonEvents and all MoseButtonEvents with a ClickCount != 2.
class DoubleClickEventTrigger : EventTrigger
{
protected override void OnEvent(EventArgs eventArgs)
{
var e = eventArgs as MouseButtonEventArgs;
if (e == null)
{
return;
}
if (e.ClickCount == 2)
{
base.OnEvent(eventArgs);
}
}
}
Now we can write this ('h' is the Namespace of the helper class above):
I saw the solution from rushui with the InuptBindings but I was still unable to hit the area of the ListViewItem where there was no text - even after setting the background to transparent, so I solved it by using different templates.
This template is for when the ListViewItem has been selected and is active:
<ControlTemplate x:Key="SelectedActiveTemplate" TargetType="{x:Type ListViewItem}">
<Border Background="LightBlue" HorizontalAlignment="Stretch">
<!-- Bind the double click to a command in the parent view model -->
<Border.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ItemSelectedCommand}"
CommandParameter="{Binding}" />
</Border.InputBindings>
<TextBlock Text="{Binding TextToShow}" />
</Border>
</ControlTemplate>
This template is for when the ListViewItem has been selected and is inactive:
Here's a behavior that gets that done on both ListBox and ListView.
public class ItemDoubleClickBehavior : Behavior<ListBox>
{
#region Properties
MouseButtonEventHandler Handler;
#endregion
#region Methods
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseDoubleClick += Handler = (s, e) =>
{
e.Handled = true;
if (!(e.OriginalSource is DependencyObject source)) return;
ListBoxItem sourceItem = source is ListBoxItem ? (ListBoxItem)source :
source.FindParent<ListBoxItem>();
if (sourceItem == null) return;
foreach (var binding in AssociatedObject.InputBindings.OfType<MouseBinding>())
{
if (binding.MouseAction != MouseAction.LeftDoubleClick) continue;
ICommand command = binding.Command;
object parameter = binding.CommandParameter;
if (command.CanExecute(parameter))
command.Execute(parameter);
}
};
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewMouseDoubleClick -= Handler;
}
#endregion
}
Here's the extension class used to find the parent.
public static class UIHelper
{
public static T FindParent<T>(this DependencyObject child, bool debug = false) where T : DependencyObject
{
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
if (parentObject is T parent)
return parent;
else
return FindParent<T>(parentObject);
}
}
I succeed to make this functionality with .Net 4.7 framework by using the interactivity library, first of all make sure of declaring the namespace in the XAML file
Adapting the code above should be enough to make the double click event work on your ViewModel, however I added you the Model and View Model class from my example so you can have the full idea.
Model:
public class ApplicationModel
{
public string Name { get; set; }
public string DevelopedBy { get; set; }
}
View Model:
public class AppListVM : BaseVM
{
public AppListVM()
{
_onOpenLinkCommand = new DelegateCommand(OnOpenLink);
_appsSource = new ObservableCollection<ApplicationModel>();
_appsSource.Add(new ApplicationModel("TEST", "Luis"));
_appsSource.Add(new ApplicationModel("PROD", "Laurent"));
}
private ObservableCollection<ApplicationModel> _appsSource = null;
public ObservableCollection<ApplicationModel> AppsSource
{
get => _appsSource;
set => SetProperty(ref _appsSource, value, nameof(AppsSource));
}
private readonly DelegateCommand _onOpenLinkCommand = null;
public ICommand OnOpenLinkCommand => _onOpenLinkCommand;
private void OnOpenLink(object commandParameter)
{
ApplicationModel app = commandParameter as ApplicationModel;
if (app != null)
{
//Your code here
}
}
}
In case you need the implementation of the DelegateCommand class.