I have a button
<Button Command="{Binding MyCommand}" />
But MyCommand is being hit when I long-press the button as well as when I just click it. Is there any way around this?
thanks
What you could do is the following:
Reference the System.Windows.Interactivity dll.
Define the namespace in your xaml code:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Then in your xaml code wire up this event trigger:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap" SourceName="btnTest">
<i:InvokeCommandAction Command="{Binding DoSomething}" />
</i:EventTrigger>
</i:Interaction.Triggers>
The EventName in this case is "Tap", the SourceName is the x:Name of the button you want to watch. Like this:
<Button Content="Click me" x:Name="btnTest"/>
Then in your ViewModel, you can wire it up to an ICommand, I typically use a RelayCommand:
private ICommand _DoSomething;
public ICommand DoSomething
{
get
{
if (_DoSomething == null)
{
_DoSomething = new RelayCommand(DoSomethingExecute);
}
return _DoSomething;
}
}
private void DoSomethingExecute()
{
Dispatcher.BeginInvoke(() =>
{
MessageBox.Show("btnTest on the tap event");
});
}
I tested, only the tap event is captured, not the long press event.
Related
So basically I'm trying to subscribe to the Navigated event on the WebBrowser control so I can get the source of the WebBrowser, but I have no idea how to subscribe to it from the ViewModel
I have tried subscribing to it from code-behind and then sending a message to the ViewModel (I'm using MVVM Light) but the ViewModel doesn't receive the message (an example of how I'm doing it)
(Code-behind)
// I don't remember the NavigatedArgs so I just put NavigatedArgs heh
WebBrowser_Navigated(object sender, NavigatedArgs)
{
// Some logic
var msg = new SendMessage() { property = param };
Messenger.Default.Send(msg)
}
(ViewModel)
Messenger.Default.Register<SendMessage>
(
this,
(action) => ReceiveMessage(action)
);
So looking around I was able to do it by binding the event to a command on the XAML file with Interaction Triggers and then using the control as a CommandParameter:
<WebBrowser x:Name="Browser" Grid.Row="1" Source="http://link.to.source">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Navigated">
<i:InvokeCommandAction Command="{Binding NavigatedCommand}" CommandParameter="{Binding RelativeSource=
{RelativeSource
Mode=FindAncestor,
AncestorType={x:Type WebBrowser}}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</WebBrowser>
and then handle it on the ViewModel:
public RelayCommand<WebBrowser> NavigatedCommand { get; set; }
public ViewModel()
{
NavigatedCommand = new RelayCommand<WebBrowser>((e) => DoStuff(e));
}
I have a simple WPF application:
View:
...
<Window.InputBindings>
<KeyBinding Key="D0" Command="{Binding MyCommand}" />
</Window.InputBindings>
<Grid>
<Button Command="{Binding MyCommand}" />
</Grid>
...
ViewModel:
public class MyViewModel : ReactiveObject
{
public ICommand MyCommand { get; }
public MyViewModel()
{
MyCommand = ReactiveCommand.Create(() => {});
}
}
Problem: When I click on the button or press D0 key I have 'The calling thread cannot access this object because a different thread owns it.' exception. But in case if I remove the button and press on D0 it's OK and command successfully completes. And there is the same result with the async command.
How should I bind this command to my button or what I do wrong?
In my ViewModel, how can I detect what key was pressed when entering text in a textBox?
In plain WPF/C# I'm doing it like this...
XAML File
<TextBox x:Name="myInputField" KeyDown="inputField_KeyDown"/>
Codebehind .xaml.cs
private void inputField_KeyDown(object sender, KeyEventArgs e)
{
if (Keyboard.IsKeyDown(Key.Enter)) {
// do something
}
}
EDIT:
FYI -What I'm trying to do is create a shortcut for the enter key.
There are a couple of ways to go about this. The first approach would be more MVVM appropriate where we just detect a change to the value of the Text that is bound to your TextBox:
In XAML:
<TextBox x:Name="myInputField",
Text="{Binding MyText, UpdateSourceTrigger=PropertyChanged}" />
In VM
private string myText;
public string MyText
{
get
{
return myText;
}
set
{
if (Set(nameof (MyText), ref myText, value))
{
// the value of the text box changed.. do something here?
}
}
}
Or, to more directly answer the question you asked, if you must rely on detecting a keypress in the textbox, you should take advantage of the EventToCommand that you can hook in with MVVMLight
In XAML:
xmlns:cmd="http://www.galasoft.ch/mvvmlight"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
...
<TextBox ....
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyDown">
<cmd:EventToCommand Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.KeyDownCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
Edit
In addition, you could also bind to the KeyBinding Command on the textbox:
<TextBox AcceptsReturn="False">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding SearchCommand}"
CommandParameter="{Binding Path=Text, RelativeSource={RelativeSource AncestorType={x:Type TextBox}}}" />
</TextBox.InputBindings>
And yet another option would be to keep handling the KeyDown event in your view, but in the codebehind call a ViewModel method:
As I understand, you actually do not want to send the Key to ViewModel. You just want to trigger something inside your ViewModel.
EventAggregator might be your solution, in your KeyDown event, you trigger event inside VM without knowing VM and you pass anything you want, there are several ways to do it.
If you are using framework like MVVMLight, Prism they might have own implementations, if you don't, there is a simple tutorial for it. (This is not the only way, you can find different implementations when you search observer pattern)
Inside your if you call Publish method which comes from EventAggregator. And all your Subscribers get that with a parameter you choose.
This way you can communicate with your ViewModel from wherever you want.
Personally I have created a Behavior as follows:
public class KeyUpToCommandBehaviour : Behavior<UIElement>
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(KeyUpToCommandBehaviour), new PropertyMetadata(default(ICommand)));
public ICommand Command
{
get { return (ICommand) GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty KeyProperty = DependencyProperty.Register(
"Key", typeof(Key), typeof(KeyUpToCommandBehaviour), new PropertyMetadata(default(Key)));
private RoutedEventHandler _routedEventHandler;
public Key Key
{
get { return (Key) GetValue(KeyProperty); }
set { SetValue(KeyProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
_routedEventHandler = AssociatedObject_KeyUp;
AssociatedObject.AddHandler(UIElement.KeyUpEvent, _routedEventHandler, true);
}
void AssociatedObject_KeyUp(object sender, RoutedEventArgs e)
{
var keyArgs = e as KeyEventArgs;
if (keyArgs == null)
return;
if(keyArgs.Key == Key)
Command?.Execute(null);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.RemoveHandler(UIElement.KeyUpEvent, _routedEventHandler);
}
}
And then use it as
<TextBox ....
<i:Interaction.Behaviors>
<attachedBehaviors:KeyUpToCommandBehaviour Key="Enter" Command="{Binding OpenFxTradeTargetingWizardCommand}"/>
</i:Interaction.Behaviors>
</TextBox>
I have a ListView/GridView setup and I want to handle a right click on the dislayed items. Is there are Databinding-way of doing this? I have seen complicated workarounds like handling the super-elements event and poking around to find its origin, but that feels awfully bloated for such basic request.
What I'd love to see is something like binding the event to an action of the item's ViewModel - is there a way to do that? Similar to this, but I can't quite wrap my head around how to adapt that to work on a single ListView item (I am not even sure that's possible, tbh).
Rough outline:
<ListView>
<ListView.View>
<GridView />
</ListView.View>
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
</Style>
</ListView.Resources>
</ListView/>
There is a way using the Interactivity assembly form the Blend SDK. It will provide an EventTrigger which executes a command when an event is raised.
<!--
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
-->
<Button Content="Click me">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding ClickCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Edit:
A possible solution for your problem could look like this:
View:
<ListView x:Name="listView">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseRightButtonUp">
<i:InvokeCommandAction Command="{Binding RightClickOnItemCommand}"
CommandParameter={Binding SelectedItem, ElementName=listView} />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
ViewModel:
public ICommand RightClickOnItemCommand { get; set; }
public void RightClickOnItem(object item)
{
}
You could try to create a style template for the list view item, and add an attached behaviour to it to handle mouse clicks.
public static readonly DependencyProperty PreviewMouseLeftButtonDownCommandProperty =
DependencyProperty.RegisterAttached("PreviewMouseLeftButtonDownCommand", typeof (ICommand),
typeof (MouseBehaviour), new FrameworkPropertyMetadata(PreviewMouseLeftButtonDownCommandChanged));
private static void PreviewMouseLeftButtonDownCommandChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
var element = (FrameworkElement) dependencyObject;
element.PreviewMouseLeftButtonDown += Element_PreviewMouseLeftButtonDown;
}
private static void Element_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs args)
{
var element = (FrameworkElement) sender;
ICommand command = GetPreviewMouseLeftButtonDownCommand(element);
if (command != null)
{
command.Execute(args);
}
}
public static void SetPreviewMouseLeftButtonDownCommand(UIElement element, ICommand value)
{
element.SetValue(PreviewMouseLeftButtonDownCommandProperty, value);
}
public static ICommand GetPreviewMouseLeftButtonDownCommand(UIElement element)
{
return (ICommand) element.GetValue(PreviewMouseLeftButtonDownCommandProperty);
}
Grid example with the trigger:
<Grid x:Name="LayoutRoot" DataContext="{Binding ProjectGrid, Source={StaticResource Locator}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding LoadedCommand, Mode=OneWay}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In my ViewModel I set the LoadedCommand like this:
public RelayCommand<RoutedEventArgs> LoadedCommand {get;private set;}
And in the ViewModel initializer I have this:
public ProjectGridViewModel()
{
LoadedCommand = new RelayCommand<RoutedEventArgs>(e =>
{
this.DoLoaded(e);
}
);
}
Then, in my DoLoaded I'm trying to do this:
Grid _projectGrid = null;
public void DoLoaded(RoutedEventArgs e)
{
_projectGrid = e.OriginalSource as Grid;
}
You can see I'm trying to get rid of my Loaded="" in my Grid in my view, and do a RelayCommand instead. The issue is the OriginalSource brings back nothing. My loaded event is running nice this way, but I need to get the Grid in via the RoutedEventArgs it seems.
I tried passing in the Grid in the EventCommand with CommandParameter="{Binding ElementName=LayoutRoot}", but this just crashes VS2010 when hitting F5 and running the project.
Any ideas? Or a better way to do this? I had the Loaded event run in the views C# then call the ViewModel in the Views code-behind, but I'd like to do a nicer binding. Talking to the ViewMode in the Views code-behind feels like a hack.
You could try to bind the CommandParameter of the EventToCommand:
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding LoadedCommand, Mode=OneWay}" CommandParameter="{Binding ElementName=LayoutRoot}" PassEventArgsToCommand="True"/>
Then, your code will be:
public RelayCommand<UIElement> LoadedCommand {get;private set;}
public ProjectGridViewModel()
{
LoadedCommand = new RelayCommand<UIElement>(e =>
{
this.DoLoaded(e);
}
);
}
Grid _projectGrid = null;
public void DoLoaded(UIElement e)
{
_projectGrid = e;
}
It should work fine :)
Bye.