Подтвердить что ты не робот

События клавиатуры в приложении MVF MVPF?

Как я могу обработать событие Keyboard.KeyDown без использования кода? Мы пытаемся использовать шаблон MVVM и избегать написания обработчика события в файле с кодом.

4b9b3361

Ответ 1

Немного поздно, но здесь идет.

Недавно команда Microsoft WPF выпустила раннюю версию своего

Ответ 2

Чтобы принести обновленный ответ, framework. framework 4.0 позволяет вам сделать это красиво, позволяя привязать команду KeyBinding к команде в режиме просмотра.

Итак... Если вы хотите прослушать клавишу Enter, вы бы сделали что-то вроде этого:

<TextBox AcceptsReturn="False">
    <TextBox.InputBindings>
        <KeyBinding 
            Key="Enter" 
            Command="{Binding SearchCommand}" 
            CommandParameter="{Binding Path=Text, RelativeSource={RelativeSource AncestorType={x:Type TextBox}}}" />
    </TextBox.InputBindings>
</TextBox>

Ответ 3

WOW - там, как тысяча ответов, и здесь я собираюсь добавить еще один.

Действительно очевидная вещь в способе "почему-не-я-реализую-это-лба-шлеп" - это то, что код-сзади и ViewModel сидят в одной и той же комнате, чтобы говорить, поэтому нет причин, по которым им не разрешено разговаривать.

Если вы думаете об этом, XAML уже тесно связан с API ViewModel, поэтому вы можете просто пойти и зависеть от него позади кода.

Другие очевидные правила для подчинения или игнорирования по-прежнему применяются (интерфейсы, нулевые проверки < - особенно, если вы используете Blend...)

Я всегда делаю свойство в коде, как это:

private ViewModelClass ViewModel { get { return DataContext as ViewModelClass; } }

Это код клиента. Нулевая проверка предназначена для управления хостингом, как в blend.

void someEventHandler(object sender, KeyDownEventArgs e)
{
    if (ViewModel == null) return;
    /* ... */
    ViewModel.HandleKeyDown(e);
}

Управляйте своим событием в коде, расположенном как вы хотите (события пользовательского интерфейса являются ориентированными на пользовательский интерфейс, так что это нормально), а затем используйте метод ViewModelClass, который может ответить на это событие. Проблемы все еще разделены.

ViewModelClass
{
    public void HandleKeyDown(KeyEventArgs e) { /* ... */ }
}

Все эти другие приложенные свойства и вуду очень круты, и методы действительно полезны для некоторых других вещей, но здесь вы можете уйти с чем-то более простым...

Ответ 4

Я делаю это, используя приложенное поведение с 3 свойствами зависимостей; один - это команда для выполнения, одна - это параметр, который нужно передать команде, а другой - ключ, который вызовет выполнение команды. Здесь код:

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;
    }
}

Чтобы использовать это из xaml, вы можете сделать что-то вроде этого:

<TextBox framework:CreateKeyDownCommandBinding.Command="{Binding MyCommand}">
    <framework:CreateKeyDownCommandBinding.Key>Enter</framework:CreateKeyDownCommandBinding.Key>
</TextBox>

Изменить: CommandModelBase - это базовый класс, который я использую для всех команд. Он основан на классе CommandModel из статьи Дэна Кревира о MVVM (здесь). Здесь источник для слегка измененной версии, которую я использую с 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
    }

Комментарии и предложения по улучшению были бы очень желанными.

Ответ 5

Короткий ответ: вы не можете обрабатывать прямые события ввода с клавиатуры без кода, но вы можете обрабатывать InputBindings с помощью MVVM (я могу показать вам соответствующий пример, если это то, что вам нужно).

Можете ли вы предоставить дополнительную информацию о том, что вы хотите сделать в обработчике?

Кодовый код не следует полностью исключать с помощью MVVM. Он просто используется для задач, связанных с UI. Кардинальный пример будет иметь некоторый тип "формы ввода данных", который при загрузке должен установить фокус на первый элемент ввода (текстовое поле, поле со списком, что угодно). Обычно вам присваивается этот элемент атрибуту x: Name, а затем подключайте событие "Загружено" Window/Page/UserControl "Loaded", чтобы установить фокус на этот элемент. Это вполне нормально по шаблону, потому что задача является ориентированной на UI и не имеет ничего общего с данными, которые она представляет.

Ответ 6

Я изучил этот вопрос несколько месяцев назад, и я написал расширение разметки, которое делает трюк. Его можно использовать как регулярное связывание:

<Window.InputBindings>
    <KeyBinding Key="E" Modifiers="Control" Command="{input:CommandBinding EditCommand}"/>
</Window.InputBindings>

Полный исходный код этого расширения можно найти здесь:

http://www.thomaslevesque.com/2009/03/17/wpf-using-inputbindings-with-the-mvvm-pattern/

Помните, что это обходное решение, вероятно, не очень "чистое", потому что оно использует некоторые частные классы и поля через отражение...

Ответ 7

Я знаю, что этот вопрос очень старый, но я пришел к этому, потому что этот тип функциональности был просто проще реализован в Silverlight (5). Так что, возможно, другие тоже придут сюда.

Я написал это простое решение после того, как не смог найти то, что искал. Оказалось, это было довольно просто. Он должен работать как в Silverlight 5, так и в 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");
    }

Использование:

<TextBox KeyUp="{MarkupExtensions:KeyToCommand Command=LoginCommand, Key=Enter}"/>

Обратите внимание, что Command - это строка, а не связующая ICommand. Я знаю, что это не так гибко, но при использовании оно чище и что вам нужно 99% времени. Хотя это не должно быть проблемой для изменения.

Ответ 8

Как и у karlipoppins, но я обнаружил, что он не работает без следующих изменений/дополнений:

<TextBox Text="{Binding UploadNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <TextBox.InputBindings>
        <KeyBinding Key="Enter" Command="{Binding FindUploadCommand}" />
    </TextBox.InputBindings>
</TextBox>