Question is: How to bind any event of WebBrowser to ICommand property in my View Model inside of ItemTemplate?
When i am trying to do this using the Expression blend interactivity libraries in normal for MvvmLight way, an exeption ocurs:
Error Collection property
'Microsoft.VisualStudio.DesignTools.WpfDesigner.InstanceBuilders.HwndHostInstance'.'Triggers'
is null.
WebTabItems is observable collection of items ViewModels
Here is code:
<TabControl ItemsSource="{Binding WebTabItems}" SelectedItem="{Binding SelectedWebTabItem}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<mvvm:EventToCommand Command="{Binding SelectionChangedVMCommand}" PassEventArgsToCommand="True"></mvvm:EventToCommand>
</i:EventTrigger>
</i:Interaction.Triggers>
<TabControl.ItemTemplate>
<!--header-->
<DataTemplate>
<TextBlock Text="{Binding Header}"></TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Text="{Binding NotificationRibbonText}" Visibility="{Binding NotificationRibbonVisibility}"></TextBlock>
<WebBrowser Grid.Row="1" Visibility="Visible" local:WebBrowserExtension.BindableSource="{Binding Sourse}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Navigating">
<mvvm:EventToCommand Command="{Binding NavigatingMVCommand}" PassEventArgsToCommand="True" ></mvvm:EventToCommand>
</i:EventTrigger>
</i:Interaction.Triggers>
</WebBrowser>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Event binding in tab control works great but in template does not
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<i:Interaction.Triggers>
<i:EventTrigger EventName="Navigating">
<mvvm:EventToCommand Command="{Binding NavigatingMVCommand}" PassEventArgsToCommand="True" ></mvvm:EventToCommand>
</i:EventTrigger>
</i:Interaction.Triggers>
Ps May be the problem is that WebBrowser.Navigating is not routed event, but problem is the same, How to bind to it event?
Have foud an answer how to bind event in template or when using Expression blend interactivity libraries is impossible
Attached property of ICommand type is other way through which you can achieve the same functionality.
This answer can also be used for binding to not Routed events
In my case:
XAML
<WebBrowser Grid.Row="1" Visibility="Visible"
local:WebBrowserExtension.BindableSource="{Binding NavigeteToSourse}"
local:WebBrowserExtension.NavigatingCmdExtended="{Binding NavigatingMVCommand}"
>
</WebBrowser>
separated class to add attached dependency properties to original web browser
class WebBrowserExtension
{
#region BindableSourceProperty
public static readonly DependencyProperty BindableSourceProperty =
DependencyProperty.RegisterAttached("BindableSource", typeof(string), typeof(WebBrowserExtension), new UIPropertyMetadata("", BindableSourcePropertyChanged));
public static string GetBindableSource(DependencyObject obj)
{
return (string)obj.GetValue(BindableSourceProperty);
}
public static void SetBindableSource(DependencyObject obj, string value)
{
obj.SetValue(BindableSourceProperty, value);
}
public static void BindableSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
WebBrowser browser = o as WebBrowser;
if (browser != null)
{
string uri = e.NewValue as string;
browser.Source = !String.IsNullOrEmpty(uri) ? new Uri(uri) : null;
}
}
#endregion
#region NavigatingCmdExtended
public static ICommand GetNavigatingCmdExtended(DependencyObject obj)
{
return (ICommand)obj.GetValue(NavigatingCmdExtendedProperty);
}
public static void SetNavigatingCmdExtended(DependencyObject obj, ICommand value)
{
obj.SetValue(NavigatingCmdExtendedProperty, value);
}
// Using a DependencyProperty as the backing store for CalenderOpenCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NavigatingCmdExtendedProperty =
DependencyProperty.RegisterAttached("NavigatingCmdExtended", typeof(ICommand), typeof(WebBrowserExtension), new PropertyMetadata(OnChangedNavigatingCmdExtendedProperty));
private static void OnChangedNavigatingCmdExtendedProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var webBrowser = d as WebBrowser;
if (webBrowser != null)
{
if (e.NewValue != null)
{
//attach event handler
webBrowser.Navigating += webBrowser_Navigating;
}
else
{
//detach event handler
webBrowser.Navigating -= webBrowser_Navigating;
}
}
}
///
/// Event handler for Calender Opened event.
///
///
///
static void webBrowser_Navigating(object sender, NavigatingCancelEventArgs e)
{
ICommand command = GetNavigatingCmdExtended(sender as DependencyObject);
if (command != null)
{
if (command.CanExecute(e))
{
//executes a command
command.Execute(e);
}
}
}
#endregion
and command in ViewModel
public class WebTabItemVievModel: ViewModelBase
{
public WebTabItemVievModel()
{
NavigatingMVCommand = new RelayCommand<NavigatingCancelEventArgs>(NavigatingMethod);
}
public ICommand NavigatingMVCommand { get; set; }
private void NavigatingMethod(NavigatingCancelEventArgs e)
{
Messenger.Default.Send<NotificationMessage <UriChangedMSG>>(new NotificationMessage<UriChangedMSG> (new UriChangedMSG { NewUri = e.Uri.AbsoluteUri },"test"));
CurrentUri = e.Uri.AbsoluteUri;
NotificationRibbonText = e.Uri.AbsoluteUri;
}
Refer this article for details:
http://www.codeproblem.com/articles/frameworks/wpf/87-event-to-command-binding-using-attached-properties-in-plain-wpf-without-any-extra-dependancy?showall=1&limitstart=
Related
I've been practicing MVVM pattern and come across the problem which I don't know how to solve. The problem is pretty simple and I hope the solution as well. The point is that I'm trying to use a command and binding for an element, when I'm setting up it's style, but I can't do it at the same time.
I have the following style for ListBoxItem:
<Style x:Key="OptionDieStyle" TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Width="Auto"
BorderThickness="1.5"
CornerRadius="10"
Height="30"
Background="Transparent"
Margin="5">
<TextBlock Margin="5"
Text="{Binding}"
Foreground="White"
VerticalAlignment="Center"/>
<Border.InputBindings>
<MouseBinding MouseAction="LeftClick" Command="#Omitted"
</Border.InputBindings>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This ListBox is filled with strings which are displayed in particular way because of the style.
That means that when I want to handle user's click on that element, using command, I need to set DataContext, which contains ViewModel, where command is located, for this item, but if I do it no content will be displayed in ListBox Items. Certainly, I could set event for this Border like "MouseDown" but it would be the wrong way to use MVVM.
If you have some thoughts how to solve this using commands please share them.
To make these scenarios easier, I've derived a class from CommandBindin. In which he added the ability to bind to ViewModel commands. You can set the binding to both Execute and PreviewExecute.
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
namespace CommonCore.AttachedProperties
{
public class CommandBindingHelper : CommandBinding
{
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
protected static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(CommandBindingHelper),
new PropertyMetadata(null));
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
protected static readonly DependencyProperty PreviewCommandProperty =
DependencyProperty.RegisterAttached(
"PreviewCommand",
typeof(ICommand),
typeof(CommandBindingHelper),
new PropertyMetadata(null));
public BindingBase Binding { get; set; }
public BindingBase PreviewBinding { get; set; }
public CommandBindingHelper()
{
Executed += (s, e) => PrivateExecuted(CheckSender(s), e.Parameter, CommandProperty, Binding);
CanExecute += (s, e) => e.CanExecute = PrivateCanExecute(CheckSender(s), e.Parameter, CommandProperty, Binding);
PreviewExecuted += (s, e) => PrivateExecuted(CheckSender(s), e.Parameter, PreviewCommandProperty, PreviewBinding);
PreviewCanExecute += (s, e) => e.CanExecute = PrivateCanExecute(CheckSender(s), e.Parameter, PreviewCommandProperty, PreviewBinding);
}
private static void PrivateExecuted(UIElement sender, object parameter, DependencyProperty commandProp, BindingBase commandBinding)
{
ICommand command = GetCommand(sender, commandProp, commandBinding);
if (command is not null && command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
private static bool PrivateCanExecute(UIElement sender, object parameter, DependencyProperty commandProp, BindingBase commandBinding)
{
ICommand command = GetCommand(sender, commandProp, commandBinding);
return command?.CanExecute(parameter) ?? true;
}
private static UIElement CheckSender(object sender)
{
if (sender is not UIElement element)
throw new NotImplementedException("Implemented only for UIElement.");
return element;
}
private static ICommand GetCommand(UIElement sender, DependencyProperty commandProp, BindingBase commandBinding)
{
BindingBase binding = BindingOperations.GetBindingBase(sender, commandProp);
if (binding != commandBinding)
{
if (commandBinding is null)
{
BindingOperations.ClearBinding(sender, commandProp);
}
else
{
BindingOperations.SetBinding(sender, commandProp, commandBinding);
}
}
return (ICommand)sender.GetValue(CommandProperty);
}
}
}
An example of its use:
using Simplified; // This is the space of my ViewModelBase implementation
using System.Collections.ObjectModel;
namespace Core2023.SO.ASTERY.CommandInListItem
{
public class ListItemsViewModel : ViewModelBase
{
public ObservableCollection<string> Items { get; } = new("first second third fourth fifth".Split());
public RelayCommand RemoveCommand => GetCommand<string>(item => Items.Remove(item));
}
}
<Window x:Class="Core2023.SO.ASTERY.CommandInListItem.ListItemsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Core2023.SO.ASTERY.CommandInListItem"
xmlns:ap="clr-namespace:CommonCore.AttachedProperties;assembly=CommonCore"
mc:Ignorable="d"
Title="ListItemsWindow" Height="450" Width="800"
FontSize="20">
<Window.DataContext>
<local:ListItemsViewModel/>
</Window.DataContext>
<Window.CommandBindings>
<ap:CommandBindingHelper Command="Delete" Binding="{Binding RemoveCommand}"/>
</Window.CommandBindings>
<Grid>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<UniformGrid Rows="1" Margin="5">
<TextBlock Text="{Binding}"/>
<Button Content="Remove"
Command="Delete"
CommandParameter="{Binding}"/>
</UniformGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
What do i do if I want to set different name?
The easiest way is to create a command in Window or (better) App resources.
<Application.Resources>
<RoutedUICommand x:Key="commands.Remove" Text="Delete Item" />
</Application.Resources>
<Button Content="Remove"
Command="{StaticResource commands.Remove}"
CommandParameter="{Binding}"/>
Create a static property containing the command. But it should be created at the View level, not the ViewModel.
public static class MyCommands
{
public static RoutedUICommand Remove { get; }
= new RoutedUICommand("Delete Item", "Remove", typeof(MyCommands));
public static RoutedUICommand Add { get; }
= new RoutedUICommand("Add Item", "Add", typeof(MyCommands));
}
<Button Content="Remove"
Command="{x:Static local:MyCommands.Remove}"
CommandParameter="{Binding}"/>
Adding a markup extension to the previous version to make it easier to use in XAML.
public class MyCommandsExtension : MarkupExtension
{
public string? CommandName { get; set; }
public MyCommandsExtension() { }
public MyCommandsExtension(string commandName) => CommandName = commandName;
public override object ProvideValue(IServiceProvider serviceProvider)
=> CommandName switch
{
nameof(MyCommands.Remove) => MyCommands.Remove,
nameof(MyCommands.Add) => MyCommands.Add,
_ => throw new NotImplementedException()
};
}
<Button Content="Remove"
Command="{local:MyCommands Remove}"
CommandParameter="{Binding}"/>
The approach above is working fine, but only if we're going to use commands with default ApplicationCommands' names and won't give them individual names. I was racking my brains and eventually found the proper approach.
All I had to do is just make my command static in ViewModel and change definition for my command in XAML like this:
Command="{x:Static viewModels:MyViewModel.MyCommand}
I have a usercontrol which is a dropdown displaying checkboxes. There is a checkbox click event that calls the SetText function, which sets the text based on what has been selected (which I want to keep). I would also like to add a function to the usercontrol maybe thru a command which sets a custom function. For example, when they select a checkbox I can call the function set in the viewmodel, as well as keep the SetText function.
I tried adding a Command to the checkbox. As well as a dependency property to the usecontrol for the Command. In addition a simple function to use in the viewmodel
-UserControl.xaml
<ComboBox
x:Name="CheckableCombo"
SnapsToDevicePixels="True"
OverridesDefaultStyle="True"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.CanContentScroll="True"
IsSynchronizedWithCurrentItem="True"
MinWidth="120"
MinHeight="20"
ItemsSource="{Binding ElementName=UserControl, Path=ItemsSource}"
DataContext="{Binding ElementName=UserControl, Path=DataContext}"
>
<ComboBox.ItemTemplate>
<HierarchicalDataTemplate>
<CheckBox Content="{Binding Title}"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"
Tag="{RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}"
Click="CheckBox_Click"
Command="{Binding YourCommand}"
/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding YourCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
-UserControl.xaml.cs
public ICommand YourCommand
{
get { return (ICommand)GetValue(YourCommandProperty); }
set { SetValue(YourCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for YourCommand. This enables animation, styling, binding, etc...
//public static readonly DependencyProperty YourCommandProperty =
// DependencyProperty.Register("YourCommand", typeof(ICommand), typeof(ComboWithCheckboxes), new PropertyMetadata(0));
public static readonly DependencyProperty YourCommandProperty =
DependencyProperty.Register("YourCommand", typeof(ICommand), typeof(ComboWithCheckboxes));
#endregion
public ComboWithCheckboxes()
{
InitializeComponent();
}
/// <summary>
///Whenever a CheckBox is checked, change the text displayed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
SetText();
}
/// <summary>
///Set the text property of this control (bound to the ContentPresenter of the ComboBox)
/// </summary>
private void SetText()
{
this.Text = (this.ItemsSource != null) ?
this.ItemsSource.ToString() : this.DefaultText;
// set DefaultText if nothing else selected
if (string.IsNullOrEmpty(this.Text))
{
this.Text = this.DefaultText;
}
}
}
-ViewModel.cs
public ViewModel()
{
ViewModelCommand = new DelegateCommand(MethodTest, canExecuteTest);
itemSource = new ObservableNodeList();
Node a = new Node("English");
a.IsSelected = true;
itemSource.Add(a);
Node b = new Node("Hebrew");
b.IsSelected = false;
itemSource.Add(b);
Node c = new Node("Swedish");
c.IsSelected = false;
itemSource.Add(c);
Node d = new Node("German");
d.IsSelected = false;
itemSource.Add(d);
}
private bool canExecuteTest(object obj)
{
return true;
}
private void MethodTest(object obj)
{
System.Windows.MessageBox.Show("Test Method");
}
My expected result was to be able to hit the command function when either select or deselecteding the checkboxes
I simplify your user control to be more readable and focus only on working external-command. I Modify the Binding of Command. In the list you got local data-context of item, but it is needed to bind command to external-data-context.
<ComboBox ItemsSource="{Binding ElementName=UserControl, Path=ItemsSource}">
<ComboBox.ItemTemplate>
<HierarchicalDataTemplate>
<CheckBox Content="{Binding .}"
Click="CheckBox_Click"
Command="{Binding ElementName=UserControl,Path=YourCommand}">
</CheckBox>
</HierarchicalDataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
In the UserControl1.cs I got:
public ICommand YourCommand
{
get { return (ICommand)GetValue(YourCommandProperty); }
set { SetValue(YourCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for YourCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty YourCommandProperty =
DependencyProperty.Register("YourCommand", typeof(ICommand), typeof(UserControl1), new PropertyMetadata(null));
I tested and it works for me.
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.
I've got a textbox where I have this:
<KeyBinding Command="{Binding MyCommand}" Key="Tab"/>
Problem is it swallows the Tab and doesn't tab to the next control.
How can I trap the Tab for the textbox and still preserve tabbing to the next control in the tab order?
Edit: I'm also using MVVM and MyCommand is in the ViewModel code, so that's where I need to re-throw the Tab.
It's easy to achieve, just don't use KeyBinding for this. Handle your TextBox's OnKeyDown event:
<TextBox KeyDown="UIElement_OnKeyDown" ...
Then on the code-behind, execute your command whenever Tab is pressed. Unlike KeyBinding, this won't swallow the TextInput event so it should work.
private void OnKeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Tab:
// Execute your command. Something similar to:
((YourDataContextType)DataContext).MyCommand.Execute(parameter:null);
break;
}
}
I cannot find a way to set focus to a control given your question as a purely XAML solution.
I choose to create an attacted property and then through binding set the focus to next control from the Command associated with your KeyBinding in the ViewModel.
Here is the View:
<Window x:Class="WarpTab.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:WarpTab.Commands"
xmlns:Views="clr-namespace:WarpTab.Views"
xmlns:local="clr-namespace:WarpTab.ViewModels"
Title="Main Window" Height="400" Width="800">
<Window.Resources>
<c:CommandReference x:Key="MyCommandReference" Command="{Binding MyCommand}" />
</Window.Resources>
<DockPanel>
<ScrollViewer>
<WrapPanel >
<TextBox Text="First text value" >
<TextBox.InputBindings>
<KeyBinding Command="{StaticResource MyCommandReference}" Key="Tab"/>
</TextBox.InputBindings>
</TextBox>
<TextBox Text="Next text value" local:FocusExtension.IsFocused="{Binding FocusControl}" />
<Button Content="My Button" />
</WrapPanel>
</ScrollViewer>
</DockPanel>
</Window>
Here is the ViewModel:
using System.Windows.Input;
using WarpTab.Commands;
namespace WarpTab.ViewModels
{
public class MainViewModel : ViewModelBase
{
public ICommand MyCommand { get; set; }
public MainViewModel()
{
MyCommand = new DelegateCommand<object>(OnMyCommand, CanMyCommand);
}
private void OnMyCommand(object obj)
{
FocusControl = true;
// process command here
// reset to allow tab to continue to work
FocusControl = false;
return;
}
private bool CanMyCommand(object obj)
{
return true;
}
private bool _focusControl = false;
public bool FocusControl
{
get
{
return _focusControl;
}
set
{
_focusControl = value;
OnPropertyChanged("FocusControl");
}
}
}
}
Here is the code to define the attached property that I found in the following answer.
using System.Windows;
namespace WarpTab.ViewModels
{
public static class FocusExtension
{
public static bool GetIsFocused(DependencyObject obj)
{
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached(
"IsFocused", typeof(bool), typeof(FocusExtension),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
private static void OnIsFocusedPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement)d;
if ((bool)e.NewValue)
{
uie.Focus(); // Don't care about false values.
}
}
}
}
Why don't you just use this code in your command handler?
private void MyCommandHandler(){
// Do command's work here
TraversalRequest request = new TraversalRequest(FocusNavigationDirection.Next);
request.Wrapped = true;
control.MoveFocus(request);
}
That's basically what 'Tab' does, so if you do the same, you're good to go. (Of course reverse the direction if you have a command with Shift-Tab.
I actually wrapped this into an extension method like so...
public static class NavigationHelpers{
public static void MoveFocus(this FrameworkElement control, FocusNavigationDirection direction = FocusNavigationDirection.Next, bool wrap = true) {
TraversalRequest request = new TraversalRequest(direction);
request.Wrapped = wrap;
control.MoveFocus(request);
}
}
...meaning the prior code becomes even simpler, like this...
private void MyCommandHandler(){
// Do command's work here
Control.MoveFocus();
}
...and if you don't know what the currently focused control is, you can just do this...
(Keyboard.FocusedElement as FrameworkElement).MoveFocus();
Hope this helps! If so, much appreciated if you vote me up or mark it as accepted!
Had the same problem, came across this thread and took me a while to find the best answer. Reference: Use EventTrigger on a specific key
Define this class:
using System; using System.Windows.Input; using System.Windows.Interactivity;
public class KeyDownEventTrigger : EventTrigger
{
public KeyDownEventTrigger() : base("KeyDown")
{
}
protected override void OnEvent(EventArgs eventArgs)
{
var e = eventArgs as KeyEventArgs;
if (e != null && e.Key == Key.Tab)
{
this.InvokeActions(eventArgs);
}
}
}
The xaml for your text box:
<TextBox x:Name="txtZip"
Text="{Binding Zip, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding ZipLookup.GetAddressByZipKeyCommand}" CommandParameter="{Binding ElementName=txtZip, Path=Text}" />
</TextBox.InputBindings>
<i:Interaction.Triggers>
<iCustom:KeyDownEventTrigger EventName="KeyDown">
<i:InvokeCommandAction Command="{Binding ZipLookup.GetAddressByZipKeyCommand}" CommandParameter="{Binding ElementName=txtZip, Path=Text}" />
</iCustom:KeyDownEventTrigger>
</i:Interaction.Triggers>
</TextBox>
In your window or user control root tag include these attributes:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:iCustom="clr-namespace:[NAMESPACE FOR CUSTOM KEY DOWN CLASS]"
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>