implementar - wpf c# mvvm
Eventos de teclado en una aplicaciĆ³n WPF MVVM? (8)
Analicé ese problema hace unos meses y escribí una extensión de marcado que funciona. Se puede usar como un enlace regular:
<Window.InputBindings>
<KeyBinding Key="E" Modifiers="Control" Command="{input:CommandBinding EditCommand}"/>
</Window.InputBindings>
El código fuente completo para esta extensión se puede encontrar aquí:
http://www.thomaslevesque.com/2009/03/17/wpf-using-inputbindings-with-the-mvvm-pattern/
Tenga en cuenta que esta solución probablemente no es muy "limpia", ya que utiliza algunas clases privadas y campos a través de la reflexión ...
¿Cómo puedo manejar el evento Keyboard.KeyDown sin usar código subyacente? Estamos intentando usar el patrón MVVM y evitar escribir un controlador de eventos en un archivo de código subyacente.
La respuesta corta es que no puede manejar eventos de entrada de teclado sin código subyacente, pero puede manejar InputBindings con MVVM (le puedo mostrar un ejemplo relevante si esto es lo que necesita).
¿Puede proporcionar más información sobre lo que quiere hacer en el controlador?
Code-behind no se debe evitar por completo con MVVM. Simplemente debe usarse para tareas estrictamente relacionadas con la interfaz de usuario. Un ejemplo cardinal sería tener algún tipo de ''formulario de entrada de datos'' que, cuando se carga, necesita establecer el foco en el primer elemento de entrada (cuadro de texto, cuadro combinado, lo que sea). Por lo general, le asignaría un atributo x: Name, y luego conectaría el evento ''Loaded'' de Window / Page / UserControl para establecer el foco en ese elemento. Esto está perfectamente bien por el patrón porque la tarea está centrada en la interfaz de usuario y no tiene nada que ver con los datos que representa.
Lo hago utilizando un comportamiento adjunto con 3 propiedades de dependencia; uno es el comando para ejecutar, uno es el parámetro para pasar al comando y el otro es la clave que hará que el comando se ejecute. Aquí está el código:
public static class CreateKeyDownCommandBinding
{
/// <summary>
/// Command to execute.
/// </summary>
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(CommandModelBase),
typeof(CreateKeyDownCommandBinding),
new PropertyMetadata(new PropertyChangedCallback(OnCommandInvalidated)));
/// <summary>
/// Parameter to be passed to the command.
/// </summary>
public static readonly DependencyProperty ParameterProperty =
DependencyProperty.RegisterAttached("Parameter",
typeof(object),
typeof(CreateKeyDownCommandBinding),
new PropertyMetadata(new PropertyChangedCallback(OnParameterInvalidated)));
/// <summary>
/// The key to be used as a trigger to execute the command.
/// </summary>
public static readonly DependencyProperty KeyProperty =
DependencyProperty.RegisterAttached("Key",
typeof(Key),
typeof(CreateKeyDownCommandBinding));
/// <summary>
/// Get the command to execute.
/// </summary>
/// <param name="sender"></param>
/// <returns></returns>
public static CommandModelBase GetCommand(DependencyObject sender)
{
return (CommandModelBase)sender.GetValue(CommandProperty);
}
/// <summary>
/// Set the command to execute.
/// </summary>
/// <param name="sender"></param>
/// <param name="command"></param>
public static void SetCommand(DependencyObject sender, CommandModelBase command)
{
sender.SetValue(CommandProperty, command);
}
/// <summary>
/// Get the parameter to pass to the command.
/// </summary>
/// <param name="sender"></param>
/// <returns></returns>
public static object GetParameter(DependencyObject sender)
{
return sender.GetValue(ParameterProperty);
}
/// <summary>
/// Set the parameter to pass to the command.
/// </summary>
/// <param name="sender"></param>
/// <param name="parameter"></param>
public static void SetParameter(DependencyObject sender, object parameter)
{
sender.SetValue(ParameterProperty, parameter);
}
/// <summary>
/// Get the key to trigger the command.
/// </summary>
/// <param name="sender"></param>
/// <returns></returns>
public static Key GetKey(DependencyObject sender)
{
return (Key)sender.GetValue(KeyProperty);
}
/// <summary>
/// Set the key which triggers the command.
/// </summary>
/// <param name="sender"></param>
/// <param name="key"></param>
public static void SetKey(DependencyObject sender, Key key)
{
sender.SetValue(KeyProperty, key);
}
/// <summary>
/// When the command property is being set attach a listener for the
/// key down event. When the command is being unset (when the
/// UIElement is unloaded for instance) remove the listener.
/// </summary>
/// <param name="dependencyObject"></param>
/// <param name="e"></param>
static void OnCommandInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
UIElement element = (UIElement)dependencyObject;
if (e.OldValue == null && e.NewValue != null)
{
element.AddHandler(UIElement.KeyDownEvent,
new KeyEventHandler(OnKeyDown), true);
}
if (e.OldValue != null && e.NewValue == null)
{
element.RemoveHandler(UIElement.KeyDownEvent,
new KeyEventHandler(OnKeyDown));
}
}
/// <summary>
/// When the parameter property is set update the command binding to
/// include it.
/// </summary>
/// <param name="dependencyObject"></param>
/// <param name="e"></param>
static void OnParameterInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
UIElement element = (UIElement)dependencyObject;
element.CommandBindings.Clear();
// Setup the binding
CommandModelBase commandModel = e.NewValue as CommandModelBase;
if (commandModel != null)
{
element.CommandBindings.Add(new CommandBinding(commandModel.Command,
commandModel.OnExecute, commandModel.OnQueryEnabled));
}
}
/// <summary>
/// When the trigger key is pressed on the element, check whether
/// the command should execute and then execute it.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
static void OnKeyDown(object sender, KeyEventArgs e)
{
UIElement element = sender as UIElement;
Key triggerKey = (Key)element.GetValue(KeyProperty);
if (e.Key != triggerKey)
{
return;
}
CommandModelBase cmdModel = (CommandModelBase)element.GetValue(CommandProperty);
object parameter = element.GetValue(ParameterProperty);
if (cmdModel.CanExecute(parameter))
{
cmdModel.Execute(parameter);
}
e.Handled = true;
}
}
Para usar esto desde xaml puedes hacer algo como esto:
<TextBox framework:CreateKeyDownCommandBinding.Command="{Binding MyCommand}">
<framework:CreateKeyDownCommandBinding.Key>Enter</framework:CreateKeyDownCommandBinding.Key>
</TextBox>
Editar: CommandModelBase es una clase base que uso para todos los comandos. Se basa en la clase CommandModel del artículo de Dan Crevier sobre MVVM ( here ). Aquí está la fuente para la versión ligeramente modificada que uso con CreateKeyDownCommandBinding:
public abstract class CommandModelBase : ICommand
{
RoutedCommand routedCommand_;
/// <summary>
/// Expose a command that can be bound to from XAML.
/// </summary>
public RoutedCommand Command
{
get { return routedCommand_; }
}
/// <summary>
/// Initialise the command.
/// </summary>
public CommandModelBase()
{
routedCommand_ = new RoutedCommand();
}
/// <summary>
/// Default implementation always allows the command to execute.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void OnQueryEnabled(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = CanExecute(e.Parameter);
e.Handled = true;
}
/// <summary>
/// Subclasses must provide the execution logic.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void OnExecute(object sender, ExecutedRoutedEventArgs e)
{
Execute(e.Parameter);
}
#region ICommand Members
public virtual bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public abstract void Execute(object parameter);
#endregion
}
Los comentarios y sugerencias para mejoras serían muy bienvenidos.
Para brindar una respuesta actualizada, el marco .net 4.0 le permite hacer esto de forma agradable al permitirle vincular un Comando de combinación de teclas a un comando en un modelo de vista.
Entonces ... Si quisieras escuchar la tecla Enter, harías algo como esto:
<TextBox AcceptsReturn="False">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding SearchCommand}"
CommandParameter="{Binding Path=Text, RelativeSource={RelativeSource AncestorType={x:Type TextBox}}}" />
</TextBox.InputBindings>
</TextBox>
Sé que esta pregunta es muy antigua, pero lo hice porque este tipo de funcionalidad simplemente se hizo más fácil de implementar en Silverlight (5). Entonces quizás otros vendrán aquí también.
Escribí esta solución simple después de que no pude encontrar lo que estaba buscando. Resultó que era bastante simple. Debería funcionar tanto en Silverlight 5 como en WPF.
public class KeyToCommandExtension : IMarkupExtension<Delegate>
{
public string Command { get; set; }
public Key Key { get; set; }
private void KeyEvent(object sender, KeyEventArgs e)
{
if (Key != Key.None && e.Key != Key) return;
var target = (FrameworkElement)sender;
if (target.DataContext == null) return;
var property = target.DataContext.GetType().GetProperty(Command, BindingFlags.Public | BindingFlags.Instance, null, typeof(ICommand), new Type[0], null);
if (property == null) return;
var command = (ICommand)property.GetValue(target.DataContext, null);
if (command != null && command.CanExecute(Key))
command.Execute(Key);
}
public Delegate ProvideValue(IServiceProvider serviceProvider)
{
if (string.IsNullOrEmpty(Command))
throw new InvalidOperationException("Command not set");
var targetProvider = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
if (!(targetProvider.TargetObject is FrameworkElement))
throw new InvalidOperationException("Target object must be FrameworkElement");
if (!(targetProvider.TargetProperty is EventInfo))
throw new InvalidOperationException("Target property must be event");
return Delegate.CreateDelegate(typeof(KeyEventHandler), this, "KeyEvent");
}
Uso:
<TextBox KeyUp="{MarkupExtensions:KeyToCommand Command=LoginCommand, Key=Enter}"/>
Observe que Command
es una cadena y no un ICommand
vinculable. Sé que esto no es tan flexible, pero es más limpio cuando se usa y lo que necesita el 99% del tiempo. Aunque no debería ser un problema cambiar.
Similar a la respuesta de karlipoppins, pero encontré que no funcionó sin las siguientes adiciones / cambios:
<TextBox Text="{Binding UploadNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding FindUploadCommand}" />
</TextBox.InputBindings>
</TextBox>
Un poco tarde, pero aquí va.
El equipo de WPF de Microsoft lanzó recientemente una versión anterior de su WPF MVVM Toolkit . En él, encontrarás una clase llamada CommandReference que puede manejar cosas como combinaciones de teclas. Mire su plantilla WPF MVVM para ver cómo funciona.
WOW - hay como mil respuestas y aquí voy a agregar otra.
Lo realmente obvio en un tipo de ''por qué no-me di cuenta-de-esta-frente-cachetada'' es que el código subyacente y el ViewModel
sientan en la misma habitación, por así decirlo, así que no hay por lo que no se les permite tener una conversación.
Si lo piensas bien, el XAML ya está íntimamente acoplado a la API de ViewModel, por lo que puedes ir y hacer una dependencia desde el código que está detrás.
Las otras reglas obvias para obedecer o ignorar aún se aplican (interfaces, null checks <- especialmente si usas Blend ...)
Siempre hago una propiedad en el código subyacente de esta manera:
private ViewModelClass ViewModel { get { return DataContext as ViewModelClass; } }
Este es el código de cliente. La comprobación nula es para ayudar a controlar el alojamiento como en blend.
void someEventHandler(object sender, KeyDownEventArgs e)
{
if (ViewModel == null) return;
/* ... */
ViewModel.HandleKeyDown(e);
}
Maneje su evento en el código detrás como desee (los eventos de la interfaz de usuario están centrados en la interfaz de usuario así que está bien) y luego tenga un método en ViewModelClass que pueda responder a ese evento. Las preocupaciones aún están separadas.
ViewModelClass
{
public void HandleKeyDown(KeyEventArgs e) { /* ... */ }
}
Todas estas otras propiedades adjuntas y el vudú son muy buenas y las técnicas son realmente útiles para otras cosas, pero aquí puedes salir con algo más simple ...