Is there a built-in shortcut key for selecting the

2019-06-01 05:51发布

问题:

I have a main form with two child modeless forms, e.g. all of the forms can be active simultaneously:

class MainForm : Form
{
    Form child1;
    Form child2;

    public MainForm()
    {
        Text = "MainForm";
        child1 = new Form { Text = "Child1" };
        child2 = new Form { Text = "Child2" };
        child1.Show(this);
        child2.Show(this);
    }
}

I would like to allow the user to Alt+Tab into all of them, but surprisingly, I found that if any of the child forms is active, the owner form cannot be selected from the Alt+Tab menu.

All three forms show up in the list, but apparently when you select the owner window and there is an active child, the child gets selected rather than the owner. The same thing happens when selecting forms in the taskbar.

Am I missing something? I started thinking of explicitly configuring shortcut keys to allow navigating from the modeless child form to the owner window, but before doing this I wanted to confirm if there is already some built-in keyboard shortcut to do this, since I don't want to break users' expectations.

Surprisingly, I couldn't find any question mentioning this behavior, which I also found rather odd.

回答1:

Setting the Owner of a Form, causes this Form to stay on top of its Owner as a non-modal Window.
If the owned Form has its ShowInTaskbar property set to true, the standard ALT+TAB or WIN+TAB combination of keys used to iterate the opened Windows in the System, brings to front (activates) the next owned Form instead of the Owner.
Which child Form is activated, it depends on the current position of the Form in the Taskbar.

If the ShowInTaskbar property of the children is instead set to false, the Owner Form is activated.
To note that if a child Form can be minimized, some awkward behaviour can be observed: Alt or Control-tabbing, cause the child Forms to appear and disappear in an unpleasant way.

Anyway, the standard combination of CONTROL+F6 keys can be used to move the focus on the opened child Forms (and the Owner Form, in this kind of layout), also bringing them to front if necessary (if the Form is minimized).
Here, overridng ProcessCmdKey, so the combination of keys is intercepted no matter what child control has captured the cursor.

The code here activates a Form pressing both CONTROL+F6 and CONTROL+SHIFT+F6, moving the Focus to each of the opened child Forms and the Owner. It also works when a child Form is minimized (or all the them).

In the Owner Form:

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    bool isControlF6 = keyData == (Keys.Control | Keys.F6);
    bool isCtrlShiftF6 = keyData == (Keys.Control | Keys.Shift | Keys.F6);

    if (isControlF6 || isCtrlShiftF6)
    {
        Form frm = isCtrlShiftF6 
                 ? Application.OpenForms.OfType<Form>().LastOrDefault(f => f.Owner == this)
                 : Application.OpenForms.OfType<Form>().FirstOrDefault(f => f.Owner == this);
        if (frm is null) return true;
        frm.WindowState = FormWindowState.Normal;
        frm.Focus();
        return true;
    }
    return base.ProcessCmdKey(ref msg, keyData);
}

In the child Forms:

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    bool isControlF6 = keyData == (Keys.Control | Keys.F6);
    bool isCtrlShiftF6 = keyData == (Keys.Control | Keys.Shift | Keys.F6);
    if (isControlF6 || isCtrlShiftF6) {
        int frmNext = 0;
        var formsList = Application.OpenForms.OfType<Form>()
                                   .Where(f => (f.Owner == this.Owner) || (f == this.Owner)).ToList();
        for (int i = 0; i < formsList.Count; i++) {
            if (formsList[i] == this) {
                if (isCtrlShiftF6) { frmNext = i == 0 ? formsList.Count - 1 : i - 1; }
                if (isControlF6) { frmNext = i == formsList.Count - 1 ? 0 : i + 1; }
                formsList[frmNext].WindowState = FormWindowState.Normal;
                formsList[frmNext].Focus();
                return true;
            }
        }
    }
    return base.ProcessCmdKey(ref msg, keyData);
}


回答2:

Edited answer:

I don't know why but I just couldn't let this go. It seemed there should be an easy solution.

@glopes I believe this is what you are looking for based on your comment.

This code will set the focus back to the parent window just before the child window looses focus. This acts similar to clicking on the window and allows you to Alt-Tab to any window you want.

public class MainForm : Form
{
    Form child1;
    Form child2;

    public MainForm()
    {
        Text = "MainForm";
        child1 = new ChildForm { Text = "Child1", ParentPtr = Handle };
        child2 = new ChildForm { Text = "Child2", ParentPtr = Handle };
        child1.Show(this);
        child2.Show(this);
    }
}

public class ChildForm : Form
{
    [DllImport("user32.dll")]
    public static extern bool SetFocus(IntPtr hWnd);

    private const int WM_KILLFOCUS = 0x0008;

    public IntPtr ParentPtr { get; set; }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_KILLFOCUS) SetFocus(ParentPtr);

        base.WndProc(ref m);
    }
}