CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 1 of 1

Threaded View

  1. #1
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,675

    [RESOLVED] Wiring more than one shortcut key to a single menu item

    Yet another "request for comments" of mine... However, I think I may post it; the last one was about 6 weeks ago...

    My intention is to invoke a menu item's click handler in response to more than a single shortcut key (combination). (The name of the respecitve ToolStripMenuItem property ShortcutKeys is a bit misleading. They probably named it like that because a shortcut usually is a combination of a base key and modifiers.) I have written the following class for that purpose:

    Code:
    // CustomShortcutKeyDispatcher.h
    
    #pragma once
    
    namespace EriLib
    {
    
    using namespace System;
    using namespace System::Windows::Forms;
    using namespace System::Collections::Generic;
    
    ref class CustomShortcutKeyGroup;
    
    public ref class CustomShortcutKeyDispatcher
    {
    public:
      CustomShortcutKeyDispatcher(Form ^form);
    
      void RegisterGroup(Object ^objItem, array<Keys> ^akyKeys, EventHandler ^handler, bool bEnable, bool bCheckItemEnabled,
        bool bKeyTransparentIfDisabled);
      void UnRegisterGroup(Object ^objItem);
      void UnRegister(Keys keys);
      void EnableGroup(Object ^objItem, bool bEnable);
    
    private:
      static String ^CharDescription(Char ch);  // Diagnostic function
    
      void KeyDownHandler(Object ^sender, KeyEventArgs ^e);
      void KeyPressHandler(Object ^sender, KeyPressEventArgs ^e);
      void KeyUpHandler(Object ^sender, KeyEventArgs ^e);
      void RegisterGroupKeys(CustomShortcutKeyGroup ^grp);
      void Register(Keys keys, CustomShortcutKeyGroup ^group);
    
    private:
      Dictionary<Keys, CustomShortcutKeyGroup ^> ^m_dictKeyMap;
      Dictionary<Object ^, CustomShortcutKeyGroup ^> ^m_dictGroups;
    };
    
    }
    Code:
    // CustomShortcutKeyDispatcher.cpp
    
    #include "StdAfx.h"
    
    #include "CustomShortcutKeyDispatcher.h"
    
    using namespace System::ComponentModel;
    using namespace System::Diagnostics;
    
    using namespace EriLib;
    
    namespace EriLib
    {
    
    private ref class CustomShortcutKeyGroup
    {
    public:
      CustomShortcutKeyGroup(Object ^objItem, array<Keys> ^akyKeys, EventHandler ^handler, bool bEnable, bool bCheckItemEnabled,
        bool bKeyTransparentIfDisabled) :
        m_objItem(objItem), m_akyKeys(akyKeys), m_handler(handler), m_bEnabled(bEnable), m_bCheckItemEnabled(bCheckItemEnabled),
        m_bKeyTransparentIfDisabled(bKeyTransparentIfDisabled)
      {}
    
      bool HandleKey(Object ^sender, KeyEventArgs ^e)
      {
        if (m_bEnabled && (!m_bCheckItemEnabled || dynamic_cast<ToolStripItem ^>(m_objItem)->Enabled)) {
          m_handler(sender, e);
          return true;
        }
        return !m_bKeyTransparentIfDisabled;
      }
    
    public:
      Object ^m_objItem;
      array<Keys> ^m_akyKeys;
      EventHandler ^m_handler;
      bool m_bEnabled;
      bool m_bCheckItemEnabled;
      bool m_bKeyTransparentIfDisabled;
    };
    
    }
    
    // CustomShortcutKeyDispatcher members
    
    CustomShortcutKeyDispatcher::CustomShortcutKeyDispatcher(Form ^form) :
      m_dictKeyMap(gcnew Dictionary<Keys, CustomShortcutKeyGroup ^>), m_dictGroups(gcnew Dictionary<Object ^, CustomShortcutKeyGroup ^>)
    {
      form->KeyDown += gcnew KeyEventHandler(this, &CustomShortcutKeyDispatcher::KeyDownHandler);
      form->KeyPress += gcnew KeyPressEventHandler(this, &CustomShortcutKeyDispatcher::KeyPressHandler);
      form->KeyUp += gcnew KeyEventHandler(this, &CustomShortcutKeyDispatcher::KeyUpHandler);
      form->KeyPreview = true;
    }
    
    void CustomShortcutKeyDispatcher::KeyDownHandler(Object ^sender, KeyEventArgs ^e)
    {
      Debug::WriteLine(String::Format("Hit (KeyDown): {0}", e->KeyData));
      if (m_dictKeyMap->ContainsKey(e->KeyData))
        e->SuppressKeyPress = m_dictKeyMap[e->KeyData]->HandleKey(sender, e);
    }
    
    // The next two event handlers currently are for diagnostics only
    
    void CustomShortcutKeyDispatcher::KeyPressHandler(Object ^sender, KeyPressEventArgs ^e)
    {
      Debug::WriteLine(String::Format("Hit (KeyPress): {0}", CharDescription(e->KeyChar)));
    }
    
    void CustomShortcutKeyDispatcher::KeyUpHandler(Object ^sender, KeyEventArgs ^e)
    {
      Debug::WriteLine(String::Format("Released (KeyUp): {0}", e->KeyData));
    }
    
    // -----
    
    void CustomShortcutKeyDispatcher::Register(Keys keys, CustomShortcutKeyGroup ^group)
    {
      Trace::Assert(!m_dictKeyMap->ContainsKey(keys),
        String::Format("CustomShortcutKeyDispatcher: Attempted to reassign handler to shortcut key combination {0}", keys));
      m_dictKeyMap->Add(keys, group);
      Debug::WriteLine("Registered: {0}", keys);
    }
    
    void CustomShortcutKeyDispatcher::UnRegister(Keys keys)
    {
      m_dictKeyMap->Remove(keys);
    }
    
    void CustomShortcutKeyDispatcher::RegisterGroup(Object ^objItem, array<Keys> ^akyKeys, EventHandler ^handler,
      bool bEnable, bool bCheckItemEnabled, bool bKeyTransparentIfDisabled)
    {
      Trace::Assert(!m_dictGroups->ContainsKey(objItem),
        String::Format("CustomShortcutKeyDispatcher: Attempted to reassign shortcut key group to item {0}", objItem));
      CustomShortcutKeyGroup ^grp = gcnew CustomShortcutKeyGroup(objItem, akyKeys, handler, bEnable, bCheckItemEnabled,
        bKeyTransparentIfDisabled);
      m_dictGroups->Add(objItem, grp);
      if (bEnable)
        RegisterGroupKeys(grp);
    }
    
    void CustomShortcutKeyDispatcher::UnRegisterGroup(Object ^objItem)
    {
      if (m_dictGroups[objItem]->m_bEnabled)
        EnableGroup(objItem, false);
      m_dictGroups->Remove(objItem);
    }
    
    void CustomShortcutKeyDispatcher::EnableGroup(Object ^objItem, bool bEnable)
    {
      CustomShortcutKeyGroup ^grp = m_dictGroups[objItem];
      if (bEnable != grp->m_bEnabled) {
        if (bEnable)
          RegisterGroupKeys(grp);
        else
          for each (Keys key in grp->m_akyKeys)
            UnRegister(key);
        grp->m_bEnabled = bEnable;
      }
    }
    
    void CustomShortcutKeyDispatcher::RegisterGroupKeys(CustomShortcutKeyGroup ^grp)
    {
      for each (Keys key in grp->m_akyKeys)
        Register(key, grp);
    }
    
    String ^CustomShortcutKeyDispatcher::CharDescription(Char ch)
    {
      return String::Format("{0} (\\U{1:X4})",
        ch < L' ' ? String::Format("Ctrl+{0}", Char(ch + L'@')) : String::Format("'{0}'", ch),
        Int32(ch));
    }
    The thing gets set up in a form class' constructor like this:

    Code:
      public:
        Form1(void)
        {
          InitializeComponent();
          //
          //TODO: Konstruktorcode hier hinzuf&#252;gen.
          //
    
          m_cskd = gcnew CustomShortcutKeyDispatcher(this);
          m_cskd->RegisterGroup(toolStripMenuItem2, gcnew array<Keys>{Keys::Shift | Keys::Insert, Keys::Control | Keys::V},
            gcnew EventHandler(this, &Form1::toolStripMenuItem2_Click), true, true, true);
    
          chkEnableMenuItem->Checked = toolStripMenuItem2->Enabled;
        }
    The original idea behind this initiated in a form where I want to (almost) always dispatch the clipboard-related keyboard commands to the same single control, which is handled by the menu item click handlers of the edit menu.

    It seems to work fine for now, unless the menu item click handler opens a message box (or probably does anything else that causes the app to enter a modal state). In this case it's the message box that consumes the unsuppressed KeyPressed event resluting from the key press that caused my handler to be invoked from the KeyDown handler and forwards that KeyPressed event to the focused control (a text box in my test app) on the form in question, bypassing my handlers.

    This is the major known problem with this approach as of now. Of course I can strive to avoid entering a state like this from my menu item click handler, and in the real-life app this is originally designed for this can only happen under exceptional circumstances (which is to be taken literally: i.e. when an exception gets thrown). However, this is intended to be designed with the goal of reusability, and so I'd love to be able to sweep this situation wholesale.

    Of course, as always, any comments about the class design and the approach as such are welcome!
    Last edited by Eri523; July 22nd, 2011 at 11:25 AM. Reason: Minor cosmetic code fix
    I was thrown out of college for cheating on the metaphysics exam; I looked into the soul of the boy sitting next to me.

    This is a snakeskin jacket! And for me it's a symbol of my individuality, and my belief... in personal freedom.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured