WPF - Connect EventSetter to an ICommand - c#

I'm looking for a solution where I double click on a DataGridRow that calls a method in my ViewModel with an ICommand.
I have this code for my DataGrid's DataGridRow style:
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="MouseDoubleClick"
Handler="DataGridRow_MouseDoubleClick" />
</Style>
</DataGrid.Resources>
This works, but...
I need to have the method DataGridRow_MouseDoubleClick in the XAML's code-behind. Then in that method I need to call the method in my ViewModel.
I would like to bypass the code-behind and directly call the method in the ViewModel with an ICommand.
I found this code which was elegant but, calls the method whereever I (left) double click on the DataGrid.
<DataGrid>
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding MyCallback}" />
</DataGrid.InputBindings>-->
</DataGrid>
I can only allow double click on a DataGridRow.
Any suggestions?
/BR
Steffe

You could replace the event handler with an attached behaviour that executes the command:
public static class DataGridRowExtensions
{
public static readonly DependencyProperty MouseDoubleClickCommandProperty =
DependencyProperty.RegisterAttached(
"MouseDoubleClickCommand",
typeof(ICommand),
typeof(DataGridRowExtensions),
new FrameworkPropertyMetadata(default(ICommand), new PropertyChangedCallback(OnSet))
);
public static ICommand GetMouseDoubleClickCommand(DataGridRow target) =>
(ICommand)target.GetValue(MouseDoubleClickCommandProperty);
public static void SetMouseDoubleClickCommand(DataGridRow target, ICommand value) =>
target.SetValue(MouseDoubleClickCommandProperty, value);
private static void OnSet(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DataGridRow row = (DataGridRow)d;
row.MouseDoubleClick += Row_MouseDoubleClick;
}
private static void Row_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
DataGridRow row = (DataGridRow)sender;
ICommand command = GetMouseDoubleClickCommand(row);
if (command != null)
command.Execute(default);
}
}
XAML:
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="local:DataGridRowExtensions.MouseDoubleClickCommand"
Value="{Binding DataContext.MyCallback,
RelativeSource={RelativeSource AncestorType=DataGrid}}" />
</Style>

First, install the Nuget package mentioned below in your project.
Microsoft.Xaml.Behaviors.Wpf
Then add the following reference to the relevant xaml field.
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
As the next step, you can apply the double click function as follows.
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding MyCallback}"/>
</i:EventTrigger>
</i:Interaction.Triggers>

Related

Can't drag move the custom messagebox(custom widow) by MouseLeftButtonDown

Now, I'm creating a MessageBox(a Custom Control, not a User Control) inherits from Window, and provide a style in a ResourceDictionary for it.
Like this :
I hope I can drag move the messagebox by MouseLeftButtonDown and move the Blue TitleBar, but it don't work with my method .
This is my implementation:
.cs:
namespace Wpf.Controls
{
...
public MessageBoxModule()
{
...
SetupDragMoveCommand();
...
}
public static readonly DependencyProperty DragMoveCommandProperty =
DependencyProperty.Register(
"DragMoveCommand",
typeof(RoutedCommand),
typeof(MessageBoxModule));
public RoutedCommand DragMoveCommand
{
get { return (RoutedCommand)GetValue(DragMoveCommandProperty); }
set { SetValue(DragMoveCommandProperty, value); }
}
public void DragMoveCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
DragMove();
}
private void SetupDragMoveCommand()
{
DragMoveCommand = new RoutedCommand(
"DragMoveCommand",
typeof(MessageBoxModule));
CommandBindings.Add(
new CommandBinding(DragMoveCommand, DragMoveCommandExecuted));
}
...
}
.xmal:
<ResourceDictionary
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<Style TargetType="{x:Type local:MessageBoxModule}">
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MessageBoxModule}">
<!--RootLayoutPanel-->
<DockPanel x:Name="RootLayoutPanel">
<Grid x:Name="TopSiderLayout">
<Rectangle x:Name="DragRectangle" Grid.ColumnSpan="2" StrokeThickness="0" Fill="#FF499A82" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{TemplateBinding DragMoveCommand}"/
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
...
Now, I still don't know why using InvokeCommandAction to TemplateBinding Command which is a Dependency Properties defined in MessageBoxModule don't work.
But I find another method that using TemplatePart like this:
.cs:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Rectangle rect = GetTemplateChild("PART_TitleRectangle") as Rectangle;
if (rect != null)
{
rect.MouseLeftButtonDown += (sender, e) => { DragMove(); };
}
}
.xaml:
<Rectangle x:Name="PART_TitleRectangle" ... />

Mouse Double Click DataGrid row

I have a DataGrid style template that I wish to add double click behaviour to. The binding should be correct but I cannot seem to get the xaml compiling / working.
All objects added to an IDictionary must have a Key attribute
or some other type of key associated with them.
What is wrong with the code below?
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="MouseDoubleClick" Handler="{Binding Connect}"/>
Update per Viktor's comment (gives exact same error):
<Style x:Key="dataGridRowStyle" TargetType="{x:Type DataGridRow}">
<EventSetter Event="PreviewMouseDoubleClick" Handler="{Binding Connect}"/>
One can use DataGrid InputBindings to achieve goal:
<DataGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding SomeCommand}" />
</DataGrid.InputBindings>
You can apply the following behavior on data grid row and follow the usage for implementation.
Double Click Behavior
public class DoubleClickBehavior
{
#region DoubleClick
public static DependencyProperty OnDoubleClickProperty = DependencyProperty.RegisterAttached(
"OnDoubleClick",
typeof(ICommand),
typeof(DoubleClickBehavior),
new UIPropertyMetadata(DoubleClickBehavior.OnDoubleClick));
public static void SetOnDoubleClick(DependencyObject target, ICommand value)
{
target.SetValue(OnDoubleClickProperty, value);
}
private static void OnDoubleClick(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var element = target as Control;
if (element == null)
{
throw new InvalidOperationException("This behavior can be attached to a Control item only.");
}
if ((e.NewValue != null) && (e.OldValue == null))
{
element.MouseDoubleClick += MouseDoubleClick;
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
element.MouseDoubleClick -= MouseDoubleClick;
}
}
private static void MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
UIElement element = (UIElement)sender;
ICommand command = (ICommand)element.GetValue(OnDoubleClickProperty);
command.Execute(null);
}
#endregion DoubleClick
}
Usage
<Style BasedOn="{StaticResource {x:Type DataGridRow}}"
TargetType="{x:Type DataGridRow}">
<Setter Property="Helpers:DoubleClickBehavior.OnDoubleClick" Value="{Binding Path=DataContext.MyCommandInVM, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ViewLayer:MyUserControl}}}" />
</Style>
Not sure if you're going the MVVM route, but I've achieved this functionality using an Attached Command Behavior to wire up the double click event to a command in my viewmodel (where "command" is a reference to my attachedCommandBehavior assembly/class):
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="command:CommandBehavior.Event" Value="MouseDoubleClick"/>
<Setter Property="command:CommandBehavior.Command" Value="{Binding SelectItemCmd}"/>
<Setter Property="command:CommandBehavior.CommandParameter" Value="{Binding }"/>
</Style>
</DataGrid.RowStyle>

How to bind mousedouble click command in mvvm

I have listview and I want to have show new window when someone double click in any position. But I have mvvm application and I don't want to have any function in code behind of xaml file, like this: How to bind a Command to double-click on a row in DataGrid and many other samples like this. I want to have method in viewmodel file and bind it like this:
<ListView ... MouseDoubleClick="{Binding myfunction}">
Thanks
This is a working example of a method to trigger a command (In the ViewModel) based on the clicked item in a list. The command in the ViewModel will get the "clicked" item as its parameter.
I'm using the Textblock.InputBindings and that might be part of the Blend SDK linked by Blachshma, but you will not need any other DLLs for this to work.
In my example the ViewModel is bound to the DataContext of the UserControl, that is why I need to use the RelativeSource FindAncestor to find the ViewModel from my TextBlock.
Edit:
Fixed the width problem by binding the Width of the TextBlock to the ActualWidth of the ListBox.
Just one problem, the double click will only work when you click inside the text in the textblock even if the list itself is much wider.
<ListView ItemsSource="{Binding Model.TablesView}" Grid.Row="1"
SelectedItem="{Binding Model.SelectedTable, Mode=TwoWay}" >
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}"
Width="{Binding Path=ActualWidth,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}" >
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding DataContext.MoveItemRightCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding .}"/>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You can use Attached Properties to bind any event you want.
For MouseDoubleClick:
namespace Behavior
{
public class MouseDoubleClick
{
public static DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(ICommand),
typeof(MouseDoubleClick),
new UIPropertyMetadata(CommandChanged));
public static DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter",
typeof(object),
typeof(MouseDoubleClick),
new UIPropertyMetadata(null));
public static void SetCommand(DependencyObject target, ICommand value)
{
target.SetValue(CommandProperty, value);
}
public static void SetCommandParameter(DependencyObject target, object value)
{
target.SetValue(CommandParameterProperty, value);
}
public static object GetCommandParameter(DependencyObject target)
{
return target.GetValue(CommandParameterProperty);
}
private static void CommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
Control control = target as Control;
if (control != null)
{
if ((e.NewValue != null) && (e.OldValue == null))
{
control.MouseDoubleClick += OnMouseDoubleClick;
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
control.MouseDoubleClick -= OnMouseDoubleClick;
}
}
}
private static void OnMouseDoubleClick(object sender, RoutedEventArgs e)
{
Control control = sender as Control;
ICommand command = (ICommand)control.GetValue(CommandProperty);
object commandParameter = control.GetValue(CommandParameterProperty);
command.Execute(commandParameter);
}
}
}
And in Xaml:
<ListBox Behavior:MouseDoubleClick.Command="{Binding ....}"
Behavior:MouseDoubleClick.CommandParameter="{Binding ....}"/>
The easiest way to do this is to use System.Windows.Interactivity and Microsoft.Expression.Interactions (both freely available through the Blend SDK)
So start by adding the following namespaces to your view
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Next, catch the DoubleClick event and pass it to the command:
<ListView ..... >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<local:EventToCommand Command="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.myfunction}" />
</i:EventTrigger
</i:Interaction.Triggers>
</ListView>
Note: The EventToCommand used is the one from the MVVM Light Toolkit and can be downloaded here.
What it does is execute the command (myFunction) as soon as the event is triggered.
This is based on the assumption that the myFunction command is in the DataContext which the ListView users. Otherwise, modify the binding of the EventToCommand to wherever the command is.

How to trigger DataTemplateSelector when property changes?

I have ContentPresenter with DataTemplateSelector:
...
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var model = item as ItemControlViewModel;
if (model.CurrentStatus == PrerequisitesStatus.Required)
{
return RequiredTemplate;
}
if (model.CurrentStatus == PrerequisitesStatus.Completed)
{
return FinishedTemplate;
}
...
return InProgressTemplate;
}
When CurrentStatus is changed, OnPropertyChanged is called.
I need somehow to trigger this DataTemplateSelector when the property is changed and change ContentPresenter DataTemplate. Any suggestions?
Threre are similar questions:
1
2, but I don't want to use any DataTriggers, because of too much states.
Tried to play with DataTriggers
<ContentPresenter
Grid.Column="1"
Height="16"
Width="16"
Margin="3">
<ContentPresenter.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="0">
<Setter Property="ContentPresenter.ContentTemplate" Value="{StaticResource ResourceKey=_requiredStatusTemplate}" />
</DataTrigger>
</ContentPresenter.Triggers>
</ContentPresenter>
But got an error:
Triggers collection members must be of type EventTrigger :(
As you requested an example with datatriggers in the comments, here you are:
A FrameworkElement can only have EventTriggers, therefore you get the error Message Triggers collection members must be of type EventTrigger
And also don't use a ContentPresenter directly, it is meant to be used inside a ControlTemplate. Better use a ContentControl when you want to have dynamic content.
See What's the difference between ContentControl and ContentPresenter?
And finally here's a suggestion to your DataTrigger issue. I have put it inside a style for reusability ....
XAML :
<Window x:Class="WpfApplication88.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="requiredTemplate">
<TextBlock Text="requiredTemplate"></TextBlock>
<!--your stuff here-->
</DataTemplate>
<DataTemplate x:Key="completedTemplate">
<TextBlock Text="CompletedTemplate"></TextBlock>
<!--your stuff here-->
</DataTemplate>
<Style x:Key="selectableContentStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="Required">
<Setter Property="ContentTemplate" Value="{StaticResource requiredTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="Completed">
<Setter Property="ContentTemplate" Value="{StaticResource completedTemplate}" />
</DataTrigger>
<!-- your other Status' here -->
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ContentControl Width="100" Height="100" Style="{StaticResource selectableContentStyle}"/>
</Grid>
</Window>
I could be wrong, but I believe the DataTemplateSelector is only used when the ItemContainerGenerator creates a container for an item added to the collection. Because a new container isn't generated when a property value changes, a new DataTemplate is never going to be applied via the selector.
As suggested in the comments, I would recommend you look at the VisualStateManager or data triggers, otherwise you're going to have to recreate the container for every item when one or more properties change value.
Just as an extra choice - if you want to stick to your templates, just use s binding with converter.
I came up with a behavior that would theoretically do this.
C#:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
public class UpdateTemplateBehavior : Behavior<ContentPresenter>
{
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(UpdateTemplateBehavior), new FrameworkPropertyMetadata(null, OnContentChanged));
public object Content
{
get => GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
static void OnContentChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is UpdateTemplateBehavior behavior)
behavior.Update();
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(object), typeof(UpdateTemplateBehavior), new FrameworkPropertyMetadata(null, OnValueChanged));
public object Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is UpdateTemplateBehavior behavior)
behavior.Update();
}
public UpdateTemplateBehavior() : base() { }
protected override void OnAttached()
{
base.OnAttached();
Update();
}
void Update()
{
if (Content != null)
{
BindingOperations.ClearBinding(AssociatedObject, ContentPresenter.ContentProperty);
AssociatedObject.Content = null;
BindingOperations.SetBinding(AssociatedObject, ContentPresenter.ContentProperty, new Binding() { Path = nameof(Content), Source = this });
}
}
}
XAML:
<ContentPresenter ContentTemplateSelector="{StaticResource MySelector}">
<i:Interaction.Behaviors>
<Behavior:UpdateTemplateBehavior Content="{Binding SomeContent}"
Value="{Binding SomeValue}"/>
</i:Interaction.Behaviors>
</ContentPresenter>
The content is "updated" (by clearing and then resetting the binding) when the content (in this example, "SomeContent") and an arbitrary value (in this example, "SomeValue") is changed, as well as when the behavior is first attached.
An update is not made unless the content is not null (my project-specific requirement). Not updating upon attaching may avoid unintentionally updating twice at once, but if the value is initially null, an update wouldn't occur until the value changes at least once.
Note: In the above example, I am not sure if the behavior has the same data context as the ContentPresenter. I use a helper class that I did not include here for brevity. Keep that in mind when testing...

MvvmLight EventToCommand and WPFToolkit DataGrid double-click

Trying to figure out how to use EventToCommand to set a datagrid double click handler for rows. The command lives in the viewmodel for each row. Just that much out of my experience, since I haven't used interactions yet.
Thanks.
I would have used mvvmlight tag, but I don't have high enough rep yet to make new tags.
This would be the solution if the Command lives on the "GridVieModel" and not on the "RowViewModel".
<Window...
...xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:GalaSoft_MvvmLight_Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras">
<dg:DataGrid x:Name="dg">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<GalaSoft_MvvmLight_Command:EventToCommand CommandParameter="{Binding SelectedItem, ElementName=dg}" Command="{Binding Path=SelectCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</dg:DataGrid>
</Window>
You could create a rowview since the row also has its own viewmodel and use the mousedoubleclick event of a child element of the row (container) in the rowview.
Or you create a converter for your command binding:
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding SelectedItem, ElementName=dg, Mode=OneWay, Converter=...}"/>
The converter then would check if the selectedItem is of the required type to return the command (Something like ISelectCommandable with a RelayCommand Property)
In case anyone comes looking here and wonders how I ended up doing it w/o EventToCommand
public class DataGridAttachedBehaviors
{
#region DoubleClick
public static DependencyProperty OnDoubleClickProperty = DependencyProperty.RegisterAttached(
"OnDoubleClick",
typeof(ICommand),
typeof(DataGridAttachedBehaviors),
new UIPropertyMetadata(DataGridAttachedBehaviors.OnDoubleClick));
public static void SetOnDoubleClick(DependencyObject target, ICommand value)
{
target.SetValue(DataGridAttachedBehaviors.OnDoubleClickProperty, value);
}
private static void OnDoubleClick(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var element = target as Control;
if (element == null)
{
throw new InvalidOperationException("This behavior can be attached to a Control item only.");
}
if ((e.NewValue != null) && (e.OldValue == null))
{
element.MouseDoubleClick += MouseDoubleClick;
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
element.MouseDoubleClick -= MouseDoubleClick;
}
}
private static void MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
UIElement element = (UIElement)sender;
ICommand command = (ICommand)element.GetValue(DataGridAttachedBehaviors.OnDoubleClickProperty);
command.Execute(null);
}
#endregion DoubleClick
#region SelectionChanged
//removed
#endregion
}
In my xaml:
<dg:DataGrid.RowStyle>
<Style BasedOn="{StaticResource DataGridDemoRowStyle}"
TargetType="{x:Type dg:DataGridRow}">
<Setter Property="skins:DataGridAttachedBehaviors.OnDoubleClick"
Value="{Binding Recall}" />
</Style>
</dg:DataGrid.RowStyle>

Categories

Resources