2

Is there a way to distinguish whether the Enter event on a control was raised by keyboard (Tab, Shift+Tab) or by direct mouse click?

I need to perform an action only when the user is moving to the control using Tab, but not when the user directly clicks on the control. I have tried to intercept the mouse click directly, but it seems the Enter event is raised before Click.

glopes
  • 4,038
  • 3
  • 26
  • 29

3 Answers3

2

Instead of tracking the Tab key, you can use the WM_MOUSEACTIVATE message to detect activation of the control with the mouse. You could either sub-class each control type you use and override the WndProc method or use a NativeWindow listener class like the one presented below. Depending on how many types of controls you use, it may be less work and clutter to just sub-class those controls to provide a property that indicates that the control was selected using the mouse. It is your decision to make, but the pattern will be the same.

This code is a slight modification of the example shown in the MS documentation.

public class MouseActivateListener : NativeWindow
{
    private Control parent;

    public MouseActivateListener(Control parent)
    {
        parent.HandleCreated += this.OnHandleCreated;
        parent.HandleDestroyed += this.OnHandleDestroyed;
        parent.Leave += Parent_Leave;
        this.parent = parent;
        if (parent.IsHandleCreated)
        {
            AssignHandle(parent.Handle);
        }
    }

    private void Parent_Leave(object sender, EventArgs e)
    {
        MouseActivated = false;
    }

    private void OnHandleCreated(object sender, EventArgs e)
    {
        AssignHandle(((Form)sender).Handle);
    }

    private void OnHandleDestroyed(object sender, EventArgs e)
    {
        ReleaseHandle();
    }

    public bool MouseActivated { get; set; }


    [System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
    protected override void WndProc(ref Message m)
    {
        const Int32 WM_MouseActivate = 0x21;

        base.WndProc(ref m);

        if (m.Msg == WM_MouseActivate && m.Result.ToInt32() < 3)
        {
            MouseActivated = true;
        }
    }
}

Example Usage:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private MouseActivateListener textBox1Listener;
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        textBox1Listener = new MouseActivateListener(textBox1);
    }

    private void textBox1_Enter(object sender, EventArgs e)
    {
        if (textBox1Listener.MouseActivated)
        {
            MessageBox.Show("Mouse Enter");
        }
        else
        {
            MessageBox.Show("Tab Enter");
        }
    }
}
TnTinMn
  • 11,522
  • 3
  • 18
  • 39
0

You can use the Form.KeyPreview event and store the last key press in a variable. Then in your control's Enter event, check the value of the key that was pressed last. If this is a tab, do whatever you need to:

private Keys lastKeyCode;
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
    this.lastKeyCode = e.KeyCode;
}

Then in the Enter event, check it:

if (lastKeyCode == Keys.Tab)
{
    // Whatever...
}
CodingYoshi
  • 25,467
  • 4
  • 62
  • 64
  • Unfortunately, the `PreviewKeyDown` event doesn't seem to be raised before the `Enter` event. Also `ProcessCmdKey` doesn't really work because you only know when the Tab key was pressed, but not when it was released. – glopes Apr 08 '18 at 21:58
  • @glopes Why do you have to perform this action in `Enter` event. Any specific reason? – CodingYoshi Apr 08 '18 at 22:03
  • UI design requirement, I need to trigger different actions depending if the control is entered using a key press or a mouse click, not much I can do about that. – glopes Apr 08 '18 at 22:08
  • @glopes See my edited answer please. A different approach. – CodingYoshi Apr 08 '18 at 22:26
  • This will not work because the user may press the Tab key, and then click the control before pressing any other key. – glopes Apr 08 '18 at 22:27
  • @glopes Ok but now the user has done both so which direction will you take? Anyhow, even that case can be handled if you also capture if the mouse was clicked as shown [here](https://stackoverflow.com/questions/247946/handling-a-click-for-all-controls-on-a-form) – CodingYoshi Apr 08 '18 at 22:35
  • This seems like excessive bookkeeping, tracking the current state of the Tab key is enough. See my answer below, in the end I had to use a message filter instead of the form events because Tab is so hard to capture correctly. – glopes Apr 08 '18 at 22:40
0

Intercepting WM_KEYUP and WM_KEYDOWN directly with a message filter to retrieve the state of the Tab key worked. This seems excessive for such a seemingly straightforward task, but apparently the Tab key is suppressed from most windows forms events.

Would be happy to take a cleaner answer, but for now, this is it:

class TabMessageFilter : IMessageFilter
{
    public bool TabState { get; set; }

    public bool PreFilterMessage(ref Message m)
    {
        const int WM_KEYUP = 0x101;
        const int WM_KEYDOWN = 0x100;

        switch (m.Msg)
        {
            case WM_KEYDOWN:
                if ((Keys)m.WParam == Keys.Tab) TabState = true;
                break;
            case WM_KEYUP:
                if ((Keys)m.WParam == Keys.Tab) TabState = false;
                break;
        }

        return false;
    }
}

class MainForm : Form
{
    TabMessageFilter tabFilter;

    public MainForm()
    {
         tabFilter = new TabMessageFilter();
         Application.AddMessageFilter(tabFilter);
    }

    protected override void OnFormClosed(FormClosedEventArgs e)
    {
         Application.RemoveMessageFilter(tabFilter);
         base.OnFormClosed(e);
    }

    void control_Enter(object sender, EventArgs e)
    {
         if (tabFilter.TabState) // do something
         else // do domething else
    }
}
glopes
  • 4,038
  • 3
  • 26
  • 29
  • But even this approach is limited to what you commented towards my answer: "This will not work because the user may press the Tab key, and then click the control before pressing any other key." Am I missing something? – CodingYoshi Apr 08 '18 at 22:50
  • @CodingYoshi the problem is that I have other controls in my form. The user can press tab in another control and then move the mouse and click to select this control. The `Enter` event is raised between `WM_KEYDOWN` and `WM_KEYUP` so in this case I can be absolutely sure that the event was caused by the Tab key. – glopes Apr 08 '18 at 23:26
  • I agree it is convoluted, I was surprised myself that I could only find this way to do it. – glopes Apr 08 '18 at 23:27
  • I have not verified, but this technique may have issues with a TextBox that accepts Tabs. Also, I would recommend overriding the form's OnActivated and OnDeactivate methods and attach/detach the filter in those methods. Do you need this function for all controls on Forn or is a limited number. If it is limited, you could attach a NativeWindow listener and listen for WM_MouseActivate. This message is sent directly to the window and is not picked-up by the message pre-filter. – TnTinMn Apr 09 '18 at 00:58
  • @TnTinMn can you elaborate on possible issues? I am using it with a TextBox that accepts tabs and it seems to work. My understanding is that the pre-filter runs before any controls in the form, and since I am not doing anything to the message (just logging the tab state), I am not interfering with the normal functioning of other controls. – glopes Apr 09 '18 at 01:21
  • @TnTinMn also, I am curious about the WM_MouseActivate approach. It might be worth it to post a solution, as there don't seem to be many examples lying around. – glopes Apr 09 '18 at 01:22
  • There is no issue with interference. What I was concerned about was possible false positives; however after looking at the code again, I see that that is unlikely. The basic listener example is in the the documentation for [NativeWindow](https://msdn.microsoft.com/en-us/library/system.windows.forms.nativewindow(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1); but they always make it confusing, so I will post one shortly. – TnTinMn Apr 09 '18 at 01:43