KeyTrigger convention

Jan 30, 2011 at 7:36 PM

Hi

I noticed in a recent change set that it was now easier to extend the trigger creation in the Parser. I just whipped up one I've thought about when on another project.

I made my own KeyTrigger too since the one in Blend SDK gave me loads of problems. I had a look in Reflector and noticed it was using the RootVisual key events so it was no wonder I was getting all sorts multiple events firing! So the one I've made will subscribe to the KeyDown event of the UIElement it is attached to.

public class KeyTrigger : TriggerBase<UIElement>
{
	public static readonly DependencyProperty KeyProperty =
		DependencyProperty.Register("Key", typeof(Key), typeof(KeyTrigger), null);

	public static readonly DependencyProperty ModifiersProperty =
		DependencyProperty.Register("Modifiers", typeof(ModifierKeys), typeof(KeyTrigger), null);


	public KeyTrigger()
	{

	}

	public Key Key
	{
		get { return (Key)GetValue(KeyProperty); }
		set { SetValue(KeyProperty, value); }
	}

	public ModifierKeys Modifiers
	{
		get { return (ModifierKeys)GetValue(ModifiersProperty); }
		set { SetValue(ModifiersProperty, value); }
	}

	protected override void OnAttached()
	{
		base.OnAttached();

		this.AssociatedObject.KeyDown += AssociatedObject_KeyDown;
	}

	protected override void OnDetaching()
	{
		base.OnDetaching();

		this.AssociatedObject.KeyDown -= AssociatedObject_KeyDown;
	}

	private void AssociatedObject_KeyDown(object sender, KeyEventArgs e)
	{
		if ((e.Key == this.Key) && (Keyboard.Modifiers == GetActualModifiers(e.Key, this.Modifiers)))
		{
			base.InvokeActions(e);
		}
	}

	private static ModifierKeys GetActualModifiers(Key key, ModifierKeys modifiers)
	{
		if (key == Key.Ctrl)
		{
			modifiers |= ModifierKeys.Control;
			return modifiers;
		}
		if (key == Key.Alt)
		{
			modifiers |= ModifierKeys.Alt;
			return modifiers;
		}
		if (key == Key.Shift)
		{
			modifiers |= ModifierKeys.Shift;
		}

		return modifiers;
	}
}

I made the following changes to the parser too:

public static Func<DependencyObject, string, TriggerBase> CreateTrigger = (target, triggerText) =>
{
	if (triggerText == null)
	{
		var defaults = ConventionManager.GetElementConvention(target.GetType());
		return defaults.CreateTrigger();
	}

	//var triggerDetail = triggerText
	//    .Replace("[", string.Empty)
	//    .Replace("]", string.Empty)
	//    .Replace("Event", string.Empty)
	//    .Trim();

	var triggerDetail = triggerText
		.Replace("[", string.Empty)
		.Replace("]", string.Empty);

	var splits = triggerDetail.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);

	if (splits[0] == "Event")
	{
		return new EventTrigger { EventName = splits[1] };
	}
	else if (splits[0] == "Key")
	{
		var key = (Key)Enum.Parse(typeof(Key), splits[1], true);
		return new KeyTrigger { Key = key };
	}
	else
	{
		throw new InvalidOperationException();
	}
};

Still feels a bit rough and ready but it works so far. You can now do this in your XAML:

<Button
      Message.Attach="[Key S] = [Test($eventArgs)]">
</Button>

However, what I was wondering was if you knew a way I could get the ModifierKeys into this convention. At the moment you need to do the full syntax like this:

<Button>
	<i:Interaction.Triggers>
		<KeyTrigger
			Key="S"
			Modifiers="Control">
			<ActionMessage
				MethodName="Test">
				<Parameter
					Value="$eventArgs" />
			</ActionMessage>
		</KeyTrigger>
	</i:Interaction.Triggers>
</Button>

Sorry for the long post but wondered if you had any ideas? :)

Jan 30, 2011 at 8:08 PM

There is a somehow 'standard' to display key shortcuts and modifiers, and it could be applied to your convention.

Consider the follwoing strings

  • Ctrl+A,B
  • Ctrl+A,Alt+B
  • Ctrl+Alt+A

The shortcuts indicate the following key actions

  • Press Ctrl and A, release both and press B
  • Press Ctrl and A, release both, press Alt and B
  • Press Ctrl, Alt and A

This means that you could add the modifiers directly to the message definition

<Button Message.Attach="[Key Ctrl+S] = [Test($eventArgs)]"/> 

since parsing such strings is more or less trivial.

Note that in the above examples I defined even multi-key gestures, that are curently not supported by your implementation.  Extending your code to allow such functionality is not hard at all (it is just a matter of maintaining a state of the multi-key gesture).

I got a multi-key gesture implementation based on the InputGesture class that includes a parser for such strings. If you are interested, I can provided it to you.

Jan 31, 2011 at 2:10 PM

BladeWise - I would be interested in seeing your implementation.  I would want to use it for Silverlight as well.

Feb 1, 2011 at 1:32 PM

Sorry for the delay, I polished the code a bit, created a sample project and did a few testing... :)

The most important classes of the project are:

  • KeySequence: it represents a set of keys plus modifiers. It is more or less like a KeyGesture, but does not require that at least a modifier is present.
  • MultiKeyBinding: represents an input binding allowing multi-key gestures. It can be used the same way as KeyBinding.
  • MultiKeyGesture: represents a multi-key gesture and contains the logic used to perform the gesture match.
  • MultiKeyGestureConverter: the type converter used to convert a string to a multi-key gesture and viceversa.

As long as the Silverlight implementation of InputGesture and InputBinding act the same way as the WPF counterpart, the provided code should work.

 

Note that gestures like

Ctrl+A,Ctrl+B

and

Ctrl+A+B

are recognized as the same gesture. It could be possible to give a different meaning to such gestures, for examples requiring that all the modifier keys are released before executing the following sequence (e.g. in Ctr+A,B you should press first Ctrl+A, then release the Ctrl key, re-press it together with B), but I preferred the simpler solution. So, consider the second syntax just a shortcut to avoid to repeat the modifier keys.

The current implementation is consistent with the Visual Studio behaviour, except for the shorter syntax.

You can download the sample with the source code here.

Jun 1, 2011 at 10:01 AM

BladeWise - Could you post your code again? Link is not alive anymore. Thanks.

Jun 1, 2011 at 10:21 AM

I think it is better if I post the code here, since it seems MediaFire is not working smoothly...

Following, just the core files (no sample). As soon as MediaFire is back in shape, you should be able to download the 'full package'.

 

KeySequence.cs

namespace Custom.Windows.Input
{
    #region Namespaces
    using System;
    using System.Text;
    using System.Windows.Input;

    #endregion

    /// <summary>
    ///   Class used to store multi-key gesture data.
    /// </summary>
    public class KeySequence
    {
        #region Fields
        /// <summary>
        ///   The sequence of keys.
        /// </summary>
        private readonly Key[] m_Keys;

        /// <summary>
        ///   The key modifiers to be applied to the sequence.
        /// </summary>
        private readonly ModifierKeys m_Modifiers;
        #endregion

        #region Properties
        /// <summary>
        ///   Gets the sequence of keys.
        /// </summary>
        /// <value>The sequence of keys.</value>
        public Key[] Keys
        {
            get { return m_Keys; }
        }

        /// <summary>
        ///   Gets the modifiers to be applied to the sequence.
        /// </summary>
        /// <value>The modifiers to be applied to the sequence.</value>
        public ModifierKeys Modifiers
        {
            get { return m_Modifiers; }
        }
        #endregion

        /// <summary>
        ///   Initializes a new instance of the <see cref = "KeySequence" /> class.
        /// </summary>
        public KeySequence(ModifierKeys modifiers, params Key[] keys)
        {
            if (keys == null)
                throw new ArgumentNullException("keys");
            if (keys.Length < 1)
                throw new ArgumentException("At least 1 key should be provided", "keys");

            m_Keys = new Key[keys.Length];
            keys.CopyTo(m_Keys, 0);
            m_Modifiers = modifiers;
        }

        /// <summary>
        ///   Returns a <see cref = "string" /> that represents the current <see cref = "object" />.
        /// </summary>
        /// <returns>
        ///   A <see cref = "string" /> that represents the current <see cref = "object" />.
        /// </returns>
        public override string ToString()
        {
            var builder = new StringBuilder();

            if (m_Modifiers != ModifierKeys.None)
            {
                if ((m_Modifiers & ModifierKeys.Control) != ModifierKeys.None)
                    builder.Append("Ctrl+");
                if ((m_Modifiers & ModifierKeys.Alt) != ModifierKeys.None)
                    builder.Append("Alt+");
                if ((m_Modifiers & ModifierKeys.Shift) != ModifierKeys.None)
                    builder.Append("Shift+");
                if ((m_Modifiers & ModifierKeys.Windows) != ModifierKeys.None)
                    builder.Append("Windows+");
            }

            builder.Append(m_Keys[0]);
            for (var i = 1; i < m_Keys.Length; i++)
                builder.Append("+" + m_Keys[i]);

            return builder.ToString();
        }
    }
}

 

MultiKeyBinding.cs

namespace Custom.Windows.Input
{
    #region Namespaces
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Windows.Input;

    #endregion

    /// <summary>
    ///   Class used to define a
    /// </summary>
    public class MultiKeyBinding : InputBinding
    {
        #region Properties
        /// <summary>
        ///   Gets the multi-key gesture.
        /// </summary>
        /// <value>The multi-key gesture.</value>
        private MultiKeyGesture MultiKeyGesture
        {
            get { return base.Gesture as MultiKeyGesture; }
        }

        /// <summary>
        ///   Gets or sets the key sequences.
        /// </summary>
        /// <value>The key sequences.</value>
        public IEnumerable<KeySequence> KeySequences
        {
            get { return MultiKeyGesture.KeySequences; }
            set { base.Gesture = new MultiKeyGesture(value.ToArray()); }
        }

        /// <summary>
        ///   Gets or sets the <see cref = "System.Windows.Input.InputGesture" /> associated with this input binding.
        /// </summary>
        /// <value></value>
        /// <returns>
        ///   The associated gesture. The default is null.
        /// </returns>
        [TypeConverter(typeof(MultiKeyGestureConverter))]
        public override InputGesture Gesture
        {
            get { return base.Gesture; }
            set
            {
                if (!(value is MultiKeyGesture))
                    throw new ArgumentException("The type " + value.GetType() + " is not a valid " + typeof(MultiKeyGesture) + " type", "value");

                base.Gesture = value;
            }
        }
        #endregion

        /// <summary>
        ///   Initializes a new instance of the <see cref = "MultiKeyBinding" /> class.
        /// </summary>
        public MultiKeyBinding()
        {
            base.Gesture = new MultiKeyGesture(new KeySequence(ModifierKeys.None, Key.None));
        }

        /// <summary>
        ///   Initializes a new instance of the <see cref = "MultiKeyBinding" /> class.
        /// </summary>
        /// <param name = "command">The command.</param>
        /// <param name = "gesture">The gesture.</param>
        public MultiKeyBinding(ICommand command, MultiKeyGesture gesture)
                : base(command, gesture)
        {
            base.Gesture = new MultiKeyGesture(new KeySequence(ModifierKeys.None, Key.None));
        }

        /// <summary>
        ///   Initializes a new instance of the <see cref = "MultiKeyBinding" /> class.
        /// </summary>
        /// <param name = "command">The command.</param>
        /// <param name = "sequences">The key sequences.</param>
        public MultiKeyBinding(ICommand command, params KeySequence[] sequences)
                : base(command, new MultiKeyGesture(sequences))
        {
            base.Gesture = new MultiKeyGesture(new KeySequence(ModifierKeys.None, Key.None));
        }
    }
}

 

MultiKeyGesture.cs

//#define DEBUG_MESSAGES

namespace Custom.Windows.Input
{
    #region Namespaces
    using System;
    using System.ComponentModel;
    using System.Text;
    using System.Windows.Input;

    #endregion

    /// <summary>
    ///   Class used to define a multi-key gesture.
    /// </summary>
    [TypeConverter(typeof(MultiKeyGestureConverter))]
    public class MultiKeyGesture : InputGesture
    {
        #region Static Fields
        /// <summary>
        ///   The maximum delay between key presses.
        /// </summary>
        private static readonly TimeSpan m_MaximumDelay = TimeSpan.FromSeconds(1);
        #endregion

        #region Static Members
        /// <summary>
        ///   Determines whether the keyis define.
        /// </summary>
        /// <param name = "key">The key to check.</param>
        /// <returns>
        ///   <c>True</c> if the key is defined as a gesture key; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsDefinedKey(Key key)
        {
            return ((key >= Key.None) && (key <= Key.OemClear));
        }

        /// <summary>
        ///   Gets the key sequences string.
        /// </summary>
        /// <param name = "sequences">The key sequences.</param>
        /// <returns>The string representing the key sequences.</returns>
        private static string GetKeySequencesString(params KeySequence[] sequences)
        {
            if (sequences == null)
                throw new ArgumentNullException("sequences");
            if (sequences.Length == 0)
                throw new ArgumentException("At least one sequence must be specified.", "sequences");

            var builder = new StringBuilder();

            builder.Append(sequences[0].ToString());

            for (var i = 1; i < sequences.Length; i++)
                builder.Append(", " + sequences[i]);

            return builder.ToString();
        }

        /// <summary>
        ///   Determines whether the specified key is a modifier key.
        /// </summary>
        /// <param name = "key">The key.</param>
        /// <returns>
        ///   <c>True</c> if the specified key is a modifier key; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsModifierKey(Key key)
        {
            return key == Key.LeftCtrl || key == Key.RightCtrl || key == Key.LeftShift || key == Key.RightShift || key == Key.LeftAlt || key == Key.RightAlt || key == Key.LWin || key == Key.RWin;
        }
        #endregion

        #region Fields
        /// <summary>
        ///   The display string.
        /// </summary>
        private readonly string m_DisplayString;

        /// <summary>
        ///   The key sequences composing the gesture.
        /// </summary>
        private readonly KeySequence[] m_KeySequences;

        /// <summary>
        ///   The index of the current gesture key.
        /// </summary>
        private int m_CurrentKeyIndex;

        /// <summary>
        ///   The current sequence index.
        /// </summary>
        private int m_CurrentSequenceIndex;

        /// <summary>
        ///   The last time a gesture key was pressed.
        /// </summary>
        private DateTime m_LastKeyPress;
        #endregion

        #region Properties
        /// <summary>
        ///   Gets the key sequences composing the gesture.
        /// </summary>
        /// <value>The key sequences composing the gesture.</value>
        public KeySequence[] KeySequences
        {
            get { return m_KeySequences; }
        }

        /// <summary>
        ///   Gets the display string.
        /// </summary>
        /// <value>The display string.</value>
        public string DisplayString
        {
            get { return m_DisplayString; }
        }
        #endregion

        /// <summary>
        ///   Initializes a new instance of the <see cref = "MultiKeyGesture" /> class.
        /// </summary>
        /// <param name = "sequences">The key sequences.</param>
        public MultiKeyGesture(params KeySequence[] sequences)
                : this(GetKeySequencesString(sequences), sequences)
        {
        }

        /// <summary>
        ///   Initializes a new instance of the <see cref = "MultiKeyGesture" /> class.
        /// </summary>
        /// <param name = "displayString">The display string.</param>
        /// <param name = "sequences">The key sequences.</param>
        public MultiKeyGesture(string displayString, params KeySequence[] sequences)
        {
            if (sequences == null)
                throw new ArgumentNullException("sequences");
            if (sequences.Length == 0)
                throw new ArgumentException("At least one sequence must be specified.", "sequences");

            m_DisplayString = displayString;
            m_KeySequences = new KeySequence[sequences.Length];
            sequences.CopyTo(m_KeySequences, 0);
        }

        /// <summary>
        ///   Determines whether this <see cref = "System.Windows.Input.KeyGesture" /> matches the input associated with the specified <see cref = "System.Windows.Input.InputEventArgs" /> object.
        /// </summary>
        /// <param name = "targetElement">The target.</param>
        /// <param name = "inputEventArgs">The input event data to compare this gesture to.</param>
        /// <returns>
        ///   true if the event data matches this <see cref = "System.Windows.Input.KeyGesture" />; otherwise, false.
        /// </returns>
        public override bool Matches(object targetElement, InputEventArgs inputEventArgs)
        {
            var args = inputEventArgs as KeyEventArgs;

            if (args == null || args.IsRepeat)
                return false;

            var key = args.Key != Key.System ? args.Key : args.SystemKey;
            //Check if the key identifies a gesture key...
            if (!IsDefinedKey(key))
                return false;

            var currentSequence = m_KeySequences[m_CurrentSequenceIndex];
            var currentKey = currentSequence.Keys[m_CurrentKeyIndex];

            //Check if the key is a modifier...
            if (IsModifierKey(key))
            {
                //If the pressed key is a modifier, ignore it for now, since it is tested afterwards...
                return false;
            }

            //Check if the current key press happened too late...
            if (m_CurrentSequenceIndex != 0 && ((DateTime.Now - m_LastKeyPress) > m_MaximumDelay))
            {
                //The delay has expired, abort the match...
                ResetState();
#if DEBUG_MESSAGES
                System.Diagnostics.Debug.WriteLine("Maximum delay has elapsed", "[" + MultiKeyGestureConverter.Default.ConvertToString(this) + "]");
#endif
                return false;
            }

            //Check if current modifiers match required ones...
            if (currentSequence.Modifiers != args.KeyboardDevice.Modifiers)
            {
                //The modifiers are not the expected ones, abort the match...
                ResetState();
#if DEBUG_MESSAGES
                System.Diagnostics.Debug.WriteLine("Incorrect modifier " + args.KeyboardDevice.Modifiers + ", expecting " + currentSequence.Modifiers, "[" + MultiKeyGestureConverter.Default.ConvertToString(this) + "]");
#endif
                return false;
            }

            //Check if the current key is not correct...
            if (currentKey != key)
            {
                //The current key is not correct, abort the match...
                ResetState();
#if DEBUG_MESSAGES
                System.Diagnostics.Debug.WriteLine("Incorrect key " + key + ", expecting " + currentKey, "[" + MultiKeyGestureConverter.Default.ConvertToString(this) + "]");
#endif
                return false;
            }

            //Move on the index, pointing to the next key...
            m_CurrentKeyIndex++;

            //Check if the key is the last of the current sequence...
            if (m_CurrentKeyIndex == m_KeySequences[m_CurrentSequenceIndex].Keys.Length)
            {
                //The key is the last of the current sequence, go to the next sequence...
                m_CurrentSequenceIndex++;
                m_CurrentKeyIndex = 0;
            }

            //Check if the sequence is the last one of the gesture...
            if (m_CurrentSequenceIndex != m_KeySequences.Length)
            {
                //If the key is not the last one, get the current date time, handle the match event but do nothing...
                m_LastKeyPress = DateTime.Now;
                inputEventArgs.Handled = true;
#if DEBUG_MESSAGES
                System.Diagnostics.Debug.WriteLine("Waiting for " + (m_KeySequences.Length - m_CurrentSequenceIndex) + " sequences", "[" + MultiKeyGestureConverter.Default.ConvertToString(this) + "]");
#endif
                return false;
            }

            //The gesture has finished and was correct, complete the match operation...
            ResetState();
            inputEventArgs.Handled = true;
#if DEBUG_MESSAGES
            System.Diagnostics.Debug.WriteLine("Gesture completed " + MultiKeyGestureConverter.Default.ConvertToString(this), "[" + MultiKeyGestureConverter.Default.ConvertToString(this) + "]");
#endif
            return true;
        }

        /// <summary>
        ///   Resets the state of the gesture.
        /// </summary>
        private void ResetState()
        {
            m_CurrentSequenceIndex = 0;
            m_CurrentKeyIndex = 0;
        }
    }
}

 

MultiKeyGestureConverter.cs

namespace Custom.Windows.Input
{
    #region Namespaces
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Globalization;
    using System.Text;
    using System.Windows.Input;

    #endregion

    /// <summary>
    ///   Class used to define a converter for the <see cref = "MultiKeyGesture" /> class.
    /// </summary>
    /// <remarks>
    ///   At the moment it is able to convert strings like <c>Alt+K,R</c> in proper multi-key gestures.
    /// </remarks>
    public class MultiKeyGestureConverter : TypeConverter
    {
        #region Static Fields
        /// <summary>
        ///   The default instance of the converter.
        /// </summary>
        private static MultiKeyGestureConverter m_Default;

        /// <summary>
        ///   The inner key converter.
        /// </summary>
        private static readonly KeyConverter m_KeyConverter = new KeyConverter();

        /// <summary>
        ///   The inner modifier key converter.
        /// </summary>
        private static readonly ModifierKeysConverter m_ModifierKeysConverter = new ModifierKeysConverter();
        #endregion

        #region Static Properties
        /// <summary>
        ///   Gets the default instance of the converter.
        /// </summary>
        /// <value>The default instance of the converter.</value>
        public static MultiKeyGestureConverter Default
        {
            get { return m_Default ?? (m_Default = new MultiKeyGestureConverter()); }
        }
        #endregion

        #region Static Members
        /// <summary>
        ///   Tries to get the modifier equivalent to the specified string.
        /// </summary>
        /// <param name = "str">The string.</param>
        /// <param name = "modifier">The modifier.</param>
        /// <returns><c>True</c> if a valid modifier was found; otherwise, <c>false</c>.</returns>
        private static bool TryGetModifierKeys(string str, out ModifierKeys modifier)
        {
            switch (str.ToUpper())
            {
                case "CONTROL":
                case "CTRL":
                    modifier = ModifierKeys.Control;
                    return true;
                case "SHIFT":
                    modifier = ModifierKeys.Shift;
                    return true;
                case "ALT":
                    modifier = ModifierKeys.Alt;
                    return true;
                case "WINDOWS":
                case "WIN":
                    modifier = ModifierKeys.Windows;
                    return true;
                default:
                    modifier = ModifierKeys.None;
                    return false;
            }
        }
        #endregion

        /// <summary>
        ///   Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context.
        /// </summary>
        /// <param name = "context">An <see cref = "System.ComponentModel.ITypeDescriptorContext" /> that provides a format context.</param>
        /// <param name = "sourceType">A <see cref = "System.Type" /> that represents the type you want to convert from.</param>
        /// <returns>
        ///   true if this converter can perform the conversion; otherwise, false.
        /// </returns>
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType == typeof(string);
        }

        /// <summary>
        ///   Converts the given object to the type of this converter, using the specified context and culture information.
        /// </summary>
        /// <param name = "context">An <see cref = "System.ComponentModel.ITypeDescriptorContext" /> that provides a format context.</param>
        /// <param name = "culture">The <see cref = "System.Globalization.CultureInfo" /> to use as the current culture.</param>
        /// <param name = "value">The <see cref = "object" /> to convert.</param>
        /// <returns>
        ///   An <see cref = "object" /> that represents the converted value.
        /// </returns>
        /// <exception cref = "System.NotSupportedException">
        ///   The conversion cannot be performed.
        /// </exception>
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            var str = (value as string);

            if (!string.IsNullOrEmpty(str))
            {
                var sequences = str.Split(',');
                string[] keyStrings;

                var keySequences = new List<KeySequence>();


                foreach (var sequence in sequences)
                {
                    var modifier = ModifierKeys.None;
                    var keys = new List<Key>();
                    keyStrings = sequence.Split('+');
                    var modifiersCount = 0;

                    ModifierKeys currentModifier;
                    string temp;
                    while ((temp = keyStrings[modifiersCount]) != null && TryGetModifierKeys(temp.Trim(), out currentModifier))
                    {
                        modifiersCount++;
                        modifier |= currentModifier;
                    }

                    for (var i = modifiersCount; i < keyStrings.Length; i++)
                    {
                        var keyString = keyStrings[i];
                        if (keyString != null)
                        {
                            var key = (Key)m_KeyConverter.ConvertFrom(keyString.Trim());
                        keys.Add(key);
                    }
                    }

                    keySequences.Add(new KeySequence(modifier, keys.ToArray()));
                }

                return new MultiKeyGesture(str, keySequences.ToArray());
            }

            throw GetConvertFromException(value);
        }

        /// <summary>
        ///   Converts the given value object to the specified type, using the specified context and culture information.
        /// </summary>
        /// <param name = "context">An <see cref = "System.ComponentModel.ITypeDescriptorContext" /> that provides a format context.</param>
        /// <param name = "culture">A <see cref = "System.Globalization.CultureInfo" />. If null is passed, the current culture is assumed.</param>
        /// <param name = "value">The <see cref = "object" /> to convert.</param>
        /// <param name = "destinationType">The <see cref = "System.Type" /> to convert the <paramref name = "value" /> parameter to.</param>
        /// <returns>
        ///   An <see cref = "object" /> that represents the converted value.
        /// </returns>
        /// <exception cref = "System.ArgumentNullException">
        ///   The <paramref name = "destinationType" /> parameter is null.
        /// </exception>
        /// <exception cref = "System.NotSupportedException">
        ///   The conversion cannot be performed.
        /// </exception>
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string))
            {
                var gesture = value as MultiKeyGesture;

                if (gesture != null)
                {
                    var builder = new StringBuilder();

                    KeySequence sequence;

                    for (var i = 0; i < gesture.KeySequences.Length; i++)
                    {
                        if (i > 0)
                            builder.Append(", ");

                        sequence = gesture.KeySequences[i];
                        if (sequence.Modifiers != ModifierKeys.None)
                        {
                            builder.Append((string)m_ModifierKeysConverter.ConvertTo(context, culture, sequence.Modifiers, destinationType));
                            builder.Append("+");
                        }

                        builder.Append((string)m_KeyConverter.ConvertTo(context, culture, sequence.Keys[0], destinationType));

                        for (var j = 1; j < sequence.Keys.Length; j++)
                        {
                            builder.Append("+");
                            builder.Append((string)m_KeyConverter.ConvertTo(context, culture, sequence.Keys[0], destinationType));
                        }
                    }

                    return builder.ToString();
                }
            }

            throw GetConvertToException(value, destinationType);
        }
    }
}

Nov 29, 2011 at 7:48 AM

I was looking for something like this. Since the link to your sample isn't working, do you mind telling what exactly needs to be changed in the Parser to be able to use this. I haven't been able to get it working.

Nov 29, 2011 at 8:01 AM

Have you checked it? The link is working fine for me.

Nov 29, 2011 at 8:30 AM

Yes, it seems it was the internal firewall that messed something up :). I did check the code. It's pretty clear now to me. However, the sample is without using the power of CM and using routedUICommands to get it done. If i would want to implement it directly with CM like cshepherd did with his example, what would be the best approach? Modifying the CM source code and modifying the Parser.cs class (if so, what do i exactly need to alter?). The second option would be not modifying the CM src code and leave it as is, and implement your code in my own project. What would be the best way? thanks again for the quick reply!