问题
I am relatively new to WPF Binding, and having a difficulty in binding a ICommand
implemented Class which is to detect user keyboard input in the window? At first I think I need to bind it to the Window on XAML, but, it appears that Window doesn't have Command
.
This is my ViewModel Class
public class NoteBoxViewModel
{
public MusicalNotation MusicalNotation { get; set; }
public ICommand KeyboardHotkeyCommand { get; private set; }
public bool IsSelected { get; set; }
public NoteBoxViewModel()
{
MusicalNotation = new MusicalNotation();
KeyboardHotkeyCommand = new KeyboardHotkeyCommand(this);
IsSelected = true;
}
}
This is my KeyboardHotkeyCommand Class
public class KeyboardHotkeyCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private NoteBoxViewModel _vm;
public KeyboardHotkeyCommand(NoteBoxViewModel vm)
{
_vm = vm;
}
public bool CanExecute(object parameter)
{
return _vm.IsSelected;
}
public void Execute(object parameter)
{
if (Keyboard.IsKeyDown(Key.OemPeriod))
{
_vm.MusicalNotation.Note = Notes.Blank;
}
else if (Keyboard.IsKeyDown(Key.D0))
{
_vm.MusicalNotation.Note = Notes.Rest;
}
else if (Keyboard.IsKeyDown(Key.D1))
{
_vm.MusicalNotation.Note = Notes.N1;
}
else if (Keyboard.IsKeyDown(Key.D2))
{
_vm.MusicalNotation.Note = Notes.N2;
}
else if (Keyboard.IsKeyDown(Key.D3))
{
_vm.MusicalNotation.Note = Notes.N3;
}
else if (Keyboard.IsKeyDown(Key.D4))
{
_vm.MusicalNotation.Note = Notes.N4;
}
else if (Keyboard.IsKeyDown(Key.D5))
{
_vm.MusicalNotation.Note = Notes.N5;
}
else if (Keyboard.IsKeyDown(Key.D6))
{
_vm.MusicalNotation.Note = Notes.N6;
}
else if (Keyboard.IsKeyDown(Key.D7))
{
_vm.MusicalNotation.Note = Notes.N7;
}
else if (Keyboard.Modifiers == ModifierKeys.Control && Keyboard.IsKeyDown(Key.OemPlus))
{
_vm.MusicalNotation.Octave++;
}
else if (Keyboard.Modifiers == ModifierKeys.Control && Keyboard.IsKeyDown(Key.OemMinus))
{
_vm.MusicalNotation.Octave--;
}
}
}
This is my Notes Enumeration
public enum Notes
{
N1,
N2,
N3,
N4,
N5,
N6,
N7,
Rest,
Blank
}
Questions :
- How to bind
KeyboardHotkeyCommand
to my XAML Class so I can detect user input (input does not going toTextbox
or any sort of text editor first)? I also tried to bind it toWindow_IsKeyDown
(it's a bad practice, I know) but it failed afterall. Is it possible to achieve it withoutevent
? - In my
KeyboardHotkeyCommand
, there's an event namedCanExecuteChanged
. I filled it with exact direction with what is told here : http://blog.lab49.com/archives/2650. But I have no idea why it's filled that way. Can anyone explain to me? - I also studied the tutorial here : http://reedcopsey.com/series/windows-forms-to-mvvm/ and it seems that he can directly instantiate the
ICommand
withActionCommand
, but I can't find any in my Visual Studio. Anyone knows why?
Note :
- Even though I'm new to MVVM (just knew it yesterday), but I want to use as much as MVVM pattern as possible, so, I would like to avoid to use events (if that's possible, of course), since I read that it's not a good MVVM practice.
- I think, whatever my
MusicalNotation
class look like, it'll not affect the solution of this problem at all, since it's just a 'Model'. But if it's needed, I am 100% willing to show you as well.
回答1:
Using the MVVM pattern, you can bind keystrokes to your VM commands by using the InputBindings
on the Main Window. A sample Xaml
snippet looks like this...
<Window.InputBindings>
<KeyBinding Key="Right" Command="{Binding NavigateCommand}" CommandParameter="f"/>
<KeyBinding Key="Left" Command="{Binding NavigateCommand}" CommandParameter="b"/>
<KeyBinding Key="Delete" Command="{Binding NavigateCommand}" CommandParameter="delete"/>
<KeyBinding Key="F5" Command="{Binding GetAllCommand}"/>
</Window.InputBindings>
The KeyBinding
class takes several parameters: the key itself, which is an enumeration, along with any modifiers like CTRL, ALT, and SHIFT.
The two parameters of immediate interest are the Command
parameter, which gets binded to the VM, and the CommandParameter
, which appears as the argument in your command delegates.
In this example, the Xaml
is binding three different keystrokes to the NavigateCommand and using the CommandParameter
to let the VM know which key was pressed.
The docs for InputBindings are here http://msdn.microsoft.com/en-us/library/system.windows.input.inputbinding(v=vs.110).aspx with more samples of usage.
Note: the usage assumes that the DataContext
of the Window has been set to the VM instance that implements these commands, otherwise there will be a data binding error.
Your implementation would look something like this...
<Window.InputBindings>
<KeyBinding Key="A" Command="{Binding KeyBoardHotKeyCommand}" CommandParameter="N1"/>
</Window.InputBindings>
With the ICommand delegates like...
private void ExecuteKeyboardHotKeyCommand(object obj)
{
Notes note;
if (obj!=null && Enum.TryParse(obj.ToString(), out note))
{
Console.WriteLine(@"User pressed {0}", note);
}
}
private bool CanExecuteKeyboardHotKeyCommand(object obj)
{
return true;
}
Your 'Notes' enumeration is fine. Your ICommand
delegates are along the right track for MVVM, but need to be abstracted away from Keyboard.IsKeyDown
because these references cannot be easily tested. Well written View Models are hardware agnostic, and do not really care if the event occurred on the keyboard or some other device (like a touch screen).
For your last question, I use the Josh Smith's RelayCommand
. It looks like this...
public class RelayCommand : ICommand
{ //http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
}
CanExecuteChanged
is an event that the WPF binding engine (or some other exogenous class) can subscribe to. An implementation looks like this...
public ICommand KeyBoardHotKeyCommand { get; set; }
public ViewModel()
{
KeyBoardHotKeyCommand = new RelayCommand(ExecuteKeyboardHotKeyCommand, CanExecuteKeyboardHotKeyCommand);
}
private void ExecuteKeyboardHotKeyCommand(object obj)
{
Notes note;
if (obj!=null && Enum.TryParse(obj.ToString(), out note))
{
Console.WriteLine(@"User pressed {0}", note);
}
}
private bool CanExecuteKeyboardHotKeyCommand(object obj)
{
return true;
}
Lastly, for your question about Reed's implementation, Reed will probably be along when members in the US wake up. But for the moment, his ActionCommand
(now part of Expression Studio http://msdn.microsoft.com/en-us/library/microsoft.expression.interactivity.core.actioncommand(v=expression.40).aspx
) is fundamentally the same as Josh Smith's RelayCommand
. They are all using the same principles to implement the ICommand
interface.
来源:https://stackoverflow.com/questions/24304969/how-to-bind-keyboard-input-command-to-main-window