I am attempting to have a key binding cause an event within the view model. I have been search for a while and have not come across any solutions that have worked thus far, unfortunately.
This is what I am basically attempting to implement:
<i:Interaction.Triggers>
<i:EventTrigger EventName="createNew">
<cal:ActionMessage MethodName="newCustomer" />
</i:EventTrigger>
</i:Interaction.Triggers>
I am wanting a way to provide a "hotkey" to allow the user to implement a newCustomer event within the view model. So far it won't even reach into the view model. If I attach the EventName="KeyDown" it works wonderfully if any key is pressed, but I am attempting to target a single key.
I will add that the code behind in the view model looks like this.
public void createNew(object sender, KeyEventArgs e)
{ if (e.Key == Key.F9)
{
addCustomer();
} }
Not too sure what control you are using, but for example purposes I'll use a text-box...
Not entirely sure if this will help even, but I hope it does:
The View:
<TextBox x:Name="MyTextBox" TextAlignment="Left" FontSize="10" Width="240" cal:Message.Attach="[Event KeyUp]=[Action ExecuteAction($executionContext)]"/>
The ViewModel:
* For example purposes I'll use "Enter" as the single key on which you want the actions performed.
public void ExecuteAction(ActionExecutionContext context)
{
var eventArgs = (KeyEventArgs)context.EventArgs;
if (eventArgs.Key != Key.Enter) return;
//Execute Actions...
}
This ought to fire code on each key-press on the control you want, if the key is not equal to whatever you specify, it won't do anything...
If I'm waaay off, maybe I just don't understand the issue correctly :D
Related
I'm developing complex data entry forms with various pop-up lookups, etc. Because of different things - focus of certain controls get lost and I need some way to set focus in MVVM. So far I came up with attached property which I coded like this(actual dependency property declaration skipped):
private static void SetFocus(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBox = d as TextBox;
if (textBox != null)
{
textBox.Focus();
}
}
So, it's pretty simple. When property changes - focus get's set.
My view:
<TextBox Text="{Binding CurrentItem.SerialNumber, Mode=TwoWay, NotifyOnValidationError=True}"
behaviors:TextBoxBehaviors.IsFocused="{Binding SecondaryControlFocus}"
Grid.Column="1" Grid.Row="2" Margin="1" Grid.ColumnSpan="2" TabIndex="2" />
As you see - I attach that behavior and Bind to "SecondaryControlFocus" property.
ViewModel:
public bool SecondaryControlFocus
{
get
{
return this.secondaryControlFocus;
}
set
{
this.secondaryControlFocus = value;
this.RaisePropertyChanged(() => this.SecondaryControlFocus);
}
}
And code how I set focus:
this.SecondaryControlFocus = !this.SecondaryControlFocus;
To me this code smells because I have to toggle property force and back in order to trigger property..
Is there nicer way to accomplish what I do? There is nothing more irritating when power user can't use TAB keys... And I need to get control over focusing in MVVM, this is important for proper data entry flow. But I want code to be somewhat "nice"
It does smell, but I don't think there's anything we can do about it
Usually I do the same thing you have with the AttachedProperty, and keep a single IsFocused bool somewhere in the View (since this is a View-Specific problem, and should not be mixed in with the business logic). I'll then have the View listen to some kind of Event System such as (PRISM's EventAggregator or MVVM Light's Messenger) for ResetFocus events, and I'll raise the ResetFocus event whenever something causes focus to change between my windows/pages, or after a dialog box.
It's not pretty, but it works.
I need to provide access key shortcut for several functions such as Save. To do this I have started by handling the KeyUp event of my root object which is a Grid called LayoutRoot (typically created as default in a Silverlight UserControl or Page).
I am using an MVVM pattern but for this I have added code in the code behind as such (this is UI interaction so it seems OK):
private void LayoutRoot_KeyUp(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.S:
if ((Keyboard.Modifiers & ModifierKeys.Windows) == ModifierKeys.Windows)
{
e.Handled = true;
// save
}
break;
case Key.C:
if ((Keyboard.Modifiers & ModifierKeys.Windows) == ModifierKeys.Windows)
{
e.Handled = true;
// clear fields
}
break;
}
}
I have used the Windows key because there are no shorcuts in the browser that use it as far as I know.
I implement the Save functionality on the Save button using a Command, therefore maintaining the MVVM pattern. E.g.
public RelayCommand CommandSavePtr { get; private set; }
CommandSavePtr = new RelayCommand(OnSavePtr);
private void OnSavePtr()
{
....
In XAML: -
<Button x:Name="SavePtrButton"
Command="{Binding CommandSavePtr}"
Style="{StaticResource StandardButtonStyle}"
IsEnabled="{Binding Ptr.HasErrors, Converter={StaticResource NotOperatorValueConverter}}">
<StackPanel Orientation="Horizontal">
<Image Source="/G4SPrisonerEscorting_ResourceDictionaries;component/images/accept.png" Style="{StaticResource SubPanelIconStyle}"/>
<TextBlock Text="Save"/>
</StackPanel>
</Button>
My problem now is that I don't know how to communicate to the ViewModel from my above KeyUp event to perform the same Save function that is perfomed when clicking the Save button.
Could anyone point me in the right direction.
BTW I am using GalaSoft's MVVM Light to do the Commanding.
This is an old question, but I thought I would answer it anyway if someone else stumbles upon the same problem.
It should be possible to fire the command for the button in the code-behind in the following manner:
SavePtrButton.Command.Execute(SavePtrButton.CommandParameter);
i'm just starting with the mvvm model in Silverlight.
In step 1 i got a listbox bound to my viewmodel, but now i want to propagate a click in a button and a selecteditemchanged of the listbox back to the viewmodel.
I guess i have to bind the click event of the button and the selecteditemchanged of the listbox to 2 methods in my viewmodel somehow?
For the selecteditemchanged of the listbox i think there must also be a 'return call' possible when the viewmodel tries to set the selecteditem to another value?
i come from a asp.net (mvc) background, but can't figure out how to do it in silverlight.
Roboblob provides excellent step-by-step solution for Silverlight 4. It strictly follows MVVM paradigm.
I would not bind or tie the VM in any way directly to the events of controls within the View. Instead, have a separate event that is raised by the View in response to the button click.
[disclaimer: this code is all done straight from my head, not copy & pasted from VS - treat it as an example!!]
So in pseudo code, the View will look like this:
private void MyView_Loaded(...)
{
MyButton.Click += new EventHandler(MyButton_Click);
}
private void MyButton_Click(...)
{
//Raise my event:
OnUserPressedGo();
}
private void OnUserPressedGo()
{
if (UserPressedTheGoButton != null)
this.UserPressedTheGoButton(this, EventArgs.Empty);
}
public EventHandler UserPressedTheGoButton;
and the VM would have a line like this:
MyView.UserPressedTheGoButton += new EventHandler(myHandler);
this may seem a little long-winded, why not do it a bit more directly? The main reason for this is you do not want to tie your VM too tightly (if at all) to the contents of the View, otherwise it becomes difficult to change the View. Having one UI agnostic event like this means the button can change at any time without affecting the VM - you could change it from a button to a hyperlink or that kool kat designer you hire may change it to something totally weird and funky, it doesn't matter.
Now, let's talk about the SelectedItemChanged event of the listbox. Chances are you want to intercept an event for this so that you can modify the data bound to another control in the View. If this is a correct assumption, then read on - if i'm wrong then stop reading and reuse the example from above :)
The odds are that you may be able to get away with not needing a handler for that event. If you bind the SelectedItem of the listbox to a property in the VM:
<ListBox ItemSource={Binding SomeList} SelectedItem={Binding MyListSelectedItem} />
and then in the MyListSelectedItem property of the VM:
public object MyListSelectedItem
{
get { return _myListSelectedItem; }
set
{
bool changed = _myListSelectedItem != value;
if (changed)
{
_myListSelectedItem = value;
OnPropertyChanged("MyListSelectedItem");
}
}
}
private void OnPropertyChanged(string propertyName)
{
if (this.NotifyPropertyChanged != null)
this.NotifyPropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
To get that NotifyPropertyChanged event, just implement the INotifyPropertyChanged interface on your VM (which you should have done already). That is the basic stuff out of the way... what you then follow this up with is a NotifyPropertyChanged event handler on the VM itself:
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "MyListSelectedItem":
//at this point i know the value of MyListSelectedItem has changed, so
//i can now retrieve its value and use it to modify a secondary
//piece of data:
MySecondaryList = AllAvailableItemsForSecondaryList.Select(p => p.Id == MyListSelectedItem.Id);
break;
}
}
All you need now is for MySecondaryList to also notify that its value has changed:
public List<someObject> MySecondaryList
{
get { return _mySecondaryList; }
set
{
bool changed = .......;
if (changed)
{
... etc ...
OnNotifyPropertyChanged("MySecondaryList");
}
}
}
and anything bound to it will automatically be updated. Once again, it may seem that this is the long way to do things, but it means you have avoided having any handlers for UI events from the View, you have kept the abstraction between the View and the ViewModel.
I hope this has made some sense to you. With my code, i try to have the ViewModel knowing absolutely zero about the View, and the View only knowing the bare minimum about the ViewModel (the View recieves the ViewModel as an interface, so it can only know what the interface has specified).
Regarding binding the button click event I can recommend Laurent Bugnion's MVVM Light Toolkit (http://www.galasoft.ch/mvvm/getstarted/) as a way of dealing with this, I'll provide a little example, but Laurent's documentation is most likely a better way of understanding his framework.
Reference a couple of assemblies in your xaml page
xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
add a blend behaviour to the button
<Button Content="Press Me">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<command:EventToCommand Command="{Binding ViewModelEventName}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
and create the event within your viewmodel which will be called when the button is clicked
public RelayCommand ViewModelEventName { get; protected set; }
...
public PageViewModel()
{
ViewModelEventName = new RelayCommand(
() => DoWork()
);
}
This supports passing parameters, checking whether execution is allowed etc also.
Although I haven't used it myself, I think the Prism framework also allows you to do something similar.
The easiest way is to implement ButtonClick event handler and invoke Window.Close() method, but how doing this through a Command binding?
All it takes is a bit of XAML...
<Window x:Class="WCSamples.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close"
Executed="CloseCommandHandler"/>
</Window.CommandBindings>
<StackPanel Name="MainStackPanel">
<Button Command="ApplicationCommands.Close"
Content="Close Window" />
</StackPanel>
</Window>
And a bit of C#...
private void CloseCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
this.Close();
}
(adapted from this MSDN article)
Actually, it is possible without C# code. The key is to use interactions:
<Button Content="Close">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction TargetObject="{Binding ElementName=window}" MethodName="Close"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
In order for this to work, just set the x:Name of your window to "window", and add these two namespaces:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
This requires that you add the Expression Blend SDK DLL to your project, specifically Microsoft.Expression.Interactions.
In case you don't have Blend, the SDK can be downloaded here.
I think that in real world scenarios a simple click handler is probably better than over-complicated command-based systems but you can do something like that:
using RelayCommand from this article http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
public class MyCommands
{
public static readonly ICommand CloseCommand =
new RelayCommand( o => ((Window)o).Close() );
}
<Button Content="Close Window"
Command="{X:Static local:MyCommands.CloseCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}"/>
If the window was shown with Window.ShowDialog():
The simplest solution that I know of is to set the IsCancel property to true of the close Button:
<Button Content="Close" IsCancel="True" />
No bindings needed, WPF will do that for you automatically!
This properties provide an easy way of saying these are the "OK" and "Cancel" buttons on a dialog. It also binds the ESC key to the button.
Reference: MSDN Button.IsCancel property.
For .NET 4.5 SystemCommands class will do the trick (.NET 4.0 users can use WPF Shell Extension google - Microsoft.Windows.Shell or Nicholas Solution).
<Window.CommandBindings>
<CommandBinding Command="{x:Static SystemCommands.CloseWindowCommand}"
CanExecute="CloseWindow_CanExec"
Executed="CloseWindow_Exec" />
</Window.CommandBindings>
<!-- Binding Close Command to the button control -->
<Button ToolTip="Close Window" Content="Close" Command="{x:Static SystemCommands.CloseWindowCommand}"/>
In the Code Behind you can implement the handlers like this:
private void CloseWindow_CanExec(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void CloseWindow_Exec(object sender, ExecutedRoutedEventArgs e)
{
SystemCommands.CloseWindow(this);
}
In the beginning I was also having a bit of trouble figuring out how this works so I wanted to post a better explanation of what is actually going on.
According to my research the best way to handle things like this is using the Command Bindings. What happens is a "Message" is broadcast to everything in the program. So what you have to do is use the CommandBinding. What this essentially does is say "When you hear this Message do this".
So in the Question the User is trying to Close the Window. The first thing we need to do is setup our Functions that will be called when the SystemCommand.CloseWindowCommand is broadcast. Optionally you can assign a Function that determines if the Command should be executed. An example would be closing a Form and checking if the User has saved.
MainWindow.xaml.cs (Or other Code-Behind)
void CloseApp( object target, ExecutedRoutedEventArgs e ) {
/*** Code to check for State before Closing ***/
this.Close();
}
void CloseAppCanExecute( object sender, CanExecuteRoutedEventArgs e ) {
/*** Logic to Determine if it is safe to Close the Window ***/
e.CanExecute = true;
}
Now we need to setup the "Connection" between the SystemCommands.CloseWindowCommand and the CloseApp and CloseAppCanExecute
MainWindow.xaml (Or anything that implements CommandBindings)
<Window.CommandBindings>
<CommandBinding Command="SystemCommands.CloseWindowCommand"
Executed="CloseApp"
CanExecute="CloseAppCanExecute"/>
</Window.CommandBindings>
You can omit the CanExecute if you know that the Command should be able to always be executed Save might be a good example depending on the Application. Here is a Example:
<Window.CommandBindings>
<CommandBinding Command="SystemCommands.CloseWindowCommand"
Executed="CloseApp"/>
</Window.CommandBindings>
Finally you need to tell the UIElement to send out the CloseWindowCommand.
<Button Command="SystemCommands.CloseWindowCommand">
Its actually a very simple thing to do, just setup the link between the Command and the actual Function to Execute then tell the Control to send out the Command to the rest of your program saying "Ok everyone run your Functions for the Command CloseWindowCommand".
This is actually a very nice way of handing this because, you can reuse the Executed Function all over without having a wrapper like you would with say WinForms (using a ClickEvent and calling a function within the Event Function) like:
protected override void OnClick(EventArgs e){
/*** Function to Execute ***/
}
In WPF you attach the Function to a Command and tell the UIElement to execute the Function attached to the Command instead.
I hope this clears things up...
One option that I've found to work is to set this function up as a Behavior.
The Behavior:
public class WindowCloseBehavior : Behavior<Window>
{
public bool Close
{
get { return (bool) GetValue(CloseTriggerProperty); }
set { SetValue(CloseTriggerProperty, value); }
}
public static readonly DependencyProperty CloseTriggerProperty =
DependencyProperty.Register("Close", typeof(bool), typeof(WindowCloseBehavior),
new PropertyMetadata(false, OnCloseTriggerChanged));
private static void OnCloseTriggerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = d as WindowCloseBehavior;
if (behavior != null)
{
behavior.OnCloseTriggerChanged();
}
}
private void OnCloseTriggerChanged()
{
// when closetrigger is true, close the window
if (this.Close)
{
this.AssociatedObject.Close();
}
}
}
On the XAML Window, you set up a reference to it and bind the Behavior's Close property to a Boolean "Close" property on your ViewModel:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<i:Interaction.Behaviors>
<behavior:WindowCloseBehavior Close="{Binding Close}" />
</i:Interaction.Behaviors>
So, from the View assign an ICommand to change the Close property on the ViewModel which is bound to the Behavior's Close property. When the PropertyChanged event is fired the Behavior fires the OnCloseTriggerChanged event and closes the AssociatedObject... which is the Window.
I am just getting started with MVVM and im having problems figuring out how I can bind a key press inside a textbox to an ICommand inside the view model. I know I can do it in the code-behind but im trying to avoid that as much as possible.
Update: The solutions so far are all well and good if you have the blend sdk or your not having problems with the interaction dll which is what i'm having. Is there any other more generic solutions than having to use the blend sdk?
First of all, if you want to bind a RoutedUICommand it is easy - just add to the UIElement.InputBindings collection:
<TextBox ...>
<TextBox.InputBindings>
<KeyBinding
Key="Q"
Modifiers="Control"
Command="my:ModelAirplaneViewModel.AddGlueCommand" />
Your trouble starts when you try to set Command="{Binding AddGlueCommand}" to get the ICommand from the ViewModel. Since Command is not a DependencyProperty you can't set a Binding on it.
Your next attempt would probably be to create an attached property BindableCommand that has a PropertyChangedCallback that updates Command. This does allow you to access the binding but there is no way to use FindAncestor to find your ViewModel since the InputBindings collection doesn't set an InheritanceContext.
Obviously you could create an attached property that you could apply to the TextBox that would run through all the InputBindings calling BindingOperations.GetBinding on each to find Command bindings and updating those Bindings with an explicit source, allowing you to do this:
<TextBox my:BindingHelper.SetDataContextOnInputBindings="true">
<TextBox.InputBindings>
<KeyBinding
Key="Q"
Modifiers="Control"
my:BindingHelper.BindableCommand="{Binding ModelGlueCommand}" />
This attached property would be easy to implement: On PropertyChangedCallback it would schedule a "refresh" at DispatcherPriority.Input and set up an event so the "refresh" is rescheduled on every DataContext change. Then in the "refresh" code just, just set DataContext on each InputBinding:
...
public static readonly SetDataContextOnInputBindingsProperty = DependencyProperty.Register(... , new UIPropetyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
var element = obj as FrameworkElement;
ScheduleUpdate(element);
element.DataContextChanged += (obj2, e2) =>
{
ScheduleUpdate(element);
};
}
});
private void ScheduleUpdate(FrameworkElement element)
{
Dispatcher.BeginInvoke(DispatcherPriority.Input, new Action(() =>
{
UpdateDataContexts(element);
})
}
private void UpdateDataContexts(FrameworkElement target)
{
var context = target.DataContext;
foreach(var inputBinding in target.InputBindings)
inputBinding.SetValue(FrameworkElement.DataContextProperty, context);
}
An alternative to the two attached properties would be to create a CommandBinding subclass that receives a routed command and activates a bound command:
<Window.CommandBindings>
<my:CommandMapper Command="my:RoutedCommands.AddGlue" MapToCommand="{Binding AddGlue}" />
...
in this case, the InputBindings in each object would reference the routed command, not the binding. This command would then be routed up the the view and mapped.
The code for CommandMapper is relatively trivial:
public class CommandMapper : CommandBinding
{
... // declaration of DependencyProperty 'MapToCommand'
public CommandMapper() : base(Executed, CanExecute)
{
}
private void Executed(object sender, ExecutedRoutedEventArgs e)
{
if(MapToCommand!=null)
MapToCommand.Execute(e.Parameter);
}
private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute =
MapToCommand==null ? null :
MapToCommand.CanExecute(e.Parameter);
}
}
For my taste, I would prefer to go with the attached properties solution, since it is not much code and keeps me from having to declare each command twice (as a RoutedCommand and as a property of my ViewModel). The supporting code only occurs once and can be used in all of your projects.
On the other hand if you're only doing a one-off project and don't expect to reuse anything, maybe even the CommandMapper is overkill. As you mentioned, it is possible to simply handle the events manually.
The excellent WPF framework Caliburn solves this problem beautifully.
<TextBox cm:Message.Attach="[Gesture Key: Enter] = [Action Search]" />
The syntax [Action Search] binds to a method in the view model. No need for ICommands at all.
Perhaps the easiest transition from code-behind event handling to MVVM commands would be Triggers and Actions from Expression Blend Samples.
Here's a snippet of code that demonstrates how you can handle key down event inside of the text box with the command:
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyDown">
<si:InvokeDataCommand Command="{Binding MyCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
The best option would probably be to use an Attached Property to do this. If you have the Blend SDK, the Behavior<T> class makes this much simpler.
For example, it would be very easy to modify this TextBox Behavior to fire an ICommand on every key press instead of clicking a button on Enter.