KeyEventToCommand

Apr 7, 2011 at 7:36 AM
Edited Apr 7, 2011 at 7:38 AM
I'm not really sure someone in interested, but last month I struggled to map key events to my ViewModels. 
Eventually I ended up with a derived EventToCommand class that can be used like this:

<i:Interaction.Triggers> <i:EventTrigger EventName="PreviewKeyDown"> <Mvvm:KeyEventToCommand AssociatedKeys="Up" MarkEventAsHandled="True" Command="{Binding LoadPreviousInput}" /> <Mvvm:KeyEventToCommand AssociatedKeys="Down" MarkEventAsHandled="True" Command="{Binding LoadNextInput}" /> <Mvvm:KeyEventToCommand AssociatedKeys="Enter" MarkEventAsHandled="False" Command="{Binding Execute}" /> </i:EventTrigger> </i:Interaction.Triggers>
I believe its far more elegant than handling the KeyEventArgs in your ViewModel.
Anyway if someone encountered the same problem, feel free to use the class described below :)
    /// <summary>
    /// Derived class from EventToCommand to map key events to a command.
    /// </summary>
    public class KeyEventToCommand : EventToCommand
    {
        #region Variables and Properties

        // Private Fields
        private List<Key> _associatedKeys = new List<Key>();      

        /// <summary>
        /// Gets or sets the associated keys.
        /// </summary>
        /// <value>The associated keys.</value>
        public string AssociatedKeys
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                foreach (Key key in _associatedKeys)
                    sb.AppendFormat("{0} ", key);
                if (_associatedKeys.Count > 1)
                    sb.Remove(sb.Length - 1, 1);
                return sb.ToString();
            }
            set
            {
                _associatedKeys.Clear();
                foreach (string item in value.Split(' '))
                {
                    Key key;
                    if (Enum.TryParse(item, true, out key))
                        _associatedKeys.Add(key);
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the EventArgs.Handled property must set to true after exection of the command.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if EventArgs.Handled must to be set to true; otherwise, <c>false</c>.
        /// </value>
        public bool MarkEventAsHandled { get; set; }
        
        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="KeyEventToCommand"/> class.
        /// </summary>
        public KeyEventToCommand()
        {
        }
        
        #endregion

        #region Private Methods

        /// <summary>
        /// Executes the trigger.
        /// <para>To access the EventArgs of the fired event, use a RelayCommand&lt;EventArgs&gt;
        /// and leave the CommandParameter and CommandParameterValue empty!</para>
        /// </summary>
        /// <param name="parameter">The EventArgs of the fired event.</param>
        protected override void Invoke(object parameter)
        {
            KeyEventArgs args = parameter as KeyEventArgs;
            if (args == null)
                return;

            if (!_associatedKeys.Contains(args.Key))
                return;

            base.Invoke(parameter);

            if (MarkEventAsHandled)
                args.Handled = true;
        }
        
        #endregion
    }
Coordinator
May 16, 2011 at 12:25 PM

Hi,

Just curious, why didn't you use the KeyEventTrigger available in Blend?

Cheers,

Laurent

Jun 21, 2011 at 7:32 AM

@Beemers,

I like it! It sits nicely alongside my EventToCommand classes. You can refactor the AssociatedKeys getter to be a lot more simple though, no need to mess around with StringBuilder:

private const string KeySeparator = " ";

public string AssociatedKeys
{
    get
    {
        return String.Join(KeySeparator, _associatedKeys);
    }
    set
    {
        _associatedKeys.Clear();
        foreach (string item in value.Split(KeySeparator.ToCharArray()))
        {
            Key key;
            if (Enum.TryParse(item, true, out key))
            {
                _associatedKeys.Add(key);
            }
        }

    }
}

Jun 22, 2011 at 7:51 PM

@ Laurent

Sorry for the late reply. I missed your comment.
About the KeyTrigger, your probably right, I'm building wpf applications in visual studio and somehow I missed it.

@ CrtlAltDel

Nice addition, I will update my code tomorrow. Thanks :)