Home > Software engineering >  How to read keystrokes in a custom Winforms control, regardless of focus
How to read keystrokes in a custom Winforms control, regardless of focus

Time:07-29

I have a custom component for WinForms, on which graphics are drawn. Using the Ctrl right/left mouse buttons, I can add or remove objects.

        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);
            if (e.KeyCode == Keys.ControlKey)
                this.EditorMode = true;
        }
        protected override void OnKeyUp(KeyEventArgs e)
        {
            base.OnKeyUp(e);
            if (e.KeyCode == Keys.ControlKey)
               this.EditorMode = false;
        }

        protected override void onm ouseDown(MouseEventArgs e)
        {
            if (!this.EditorMode)
            {
                base.OnMouseDown(e);
                return;
            }
            if (e.Button == MouseButtons.Left)
            {
                // adding new object
            }
            else if (e.Button == MouseButtons.Right)
            {
                // deleting object
            }
        }

Everything works fine until I add something else to the custom control. The problem is that pressing the Ctrl key will no longer be handled by the controller, but by the element on which the focus is currently set.

And I need my keyboard shortcut to work regardless of which element the focus is on... What is the best way to do this?

I tried to redefine Processcmdkey, but it does not allow me to know if the key was pressed or released

    protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            if (keyData == (Keys.ControlKey | Keys.Control))
            {
                MessageBox.Show("ctrl");
                return true;
            }
            return base.ProcessCmdKey(ref msg, keyData);
        }

what should I do to get the desired result: regardless of focusing on child controls, I can always add new objects to the drawing?

CodePudding user response:

My suggestion is to implement add and remove buttons


Adding and Removing the MessageFilter

The message filter will be added on the OnHandleCreated override and removed in the Dispose method of MainForm.

public partial class MainForm : Form, IMessageFilter
{
    public MainForm() => InitializeComponent();
    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        if(!(DesignMode ) || _isHandleInitialized)
        {
            _isHandleInitialized = true; ;
            Application.AddMessageFilter(this);
        }
    }
    bool _isHandleInitialized = false;

    // In MainForm.Designer.cs
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (components != null)
            {
                components.Dispose();
            }
            Application.RemoveMessageFilter(this);
        }
        base.Dispose(disposing);
    }
}

Handling PreFilterMessage

The MainForm will provide three event hooks for ControlLeft, ControlRight, and NoCommand. These will be fired in the PreFilterMessage method.

public partial class MainForm : Form, IMessageFilter
{
    const int WM_KEYDOWN = 0x100;
    public bool PreFilterMessage(ref Message m)
    {
        if (Form.ActiveForm == this)
        {
            switch (m.Msg)
            {
                case WM_KEYDOWN:
                    var key = (Keys)m.WParam | ModifierKeys;
                    switch (key)
                    {
                        case Keys.Control | Keys.Left:
                            ControlLeft?.Invoke(this, EventArgs.Empty);
                            Text = "Control.Left";
                            break;
                        case Keys.Control | Keys.Right:
                            ControlRight?.Invoke(this, EventArgs.Empty);
                            Text = "Control.Right";
                            break;
                        default: 
                            // Don't event if it's "just" the Control key
                            if(ModifierKeys == Keys.None)
                            {
                                Text = "Main Form";
                                NoCommand?.Invoke(this, EventArgs.Empty);
                            }
                            break;
                    }
                    break;
            }
        }
        return false;
    }

    public event EventHandler ControlLeft;
    public event EventHandler ControlRight;
    public event EventHandler NoCommand;
}

Responding to events in the UserControl

The MainForm events will be subscribed to in the OnHandleCreated override of UserControlResponder.

public partial class UserControlResponder : UserControl
{
    public UserControlResponder()
    {
        InitializeComponent();
    }
    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        if(!(DesignMode || _isHandleInitialized))
        {
            _isHandleInitialized = true;
            var main = (MainForm)Parent;
            main.ControlLeft  = onControlLeft;
            main.ControlRight  = onControlRight;
            main.NoCommand  = onNoCommand;
        }
    }
    private bool _isHandleInitialized = false;
    char _tstCount = 'A';

    private void onControlRight(object sender, EventArgs e)
    {
        BackColor = Color.LightBlue;
        BorderStyle = BorderStyle.None;
        var button = new Button
            {
                Text = $"Button {_tstCount  }",
                Size = new Size(150, 50),
            };
        button.Click  = onAnyButtonClick;
        flowLayoutPanel.Controls.Add(button);
    }
    private void onControlLeft(object sender, EventArgs e)
    {           
        BackColor = Color.LightGreen;
        BorderStyle = BorderStyle.None;
        if(flowLayoutPanel.Controls.Count != 0)
        {
            var remove = flowLayoutPanel.Controls[flowLayoutPanel.Controls.Count - 1];
            if(remove is Button button)
            {
                button.Click -= onAnyButtonClick;
            }
            flowLayoutPanel.Controls.RemoveAt(flowLayoutPanel.Controls.Count - 1);
        }
    }
    private void onNoCommand(object sender, EventArgs e)
    {
        BackColor = Color.Transparent;
        BorderStyle = BorderStyle.FixedSingle;
    }
    private void onAnyButtonClick(object sender, EventArgs e)
    {
        ((MainForm)Parent).Text = $"{((Button)sender).Text} Clicked";
    }
}
  • Related