【问题标题】:Is there a built-in shortcut key for selecting the owner of the currently active form?是否有用于选择当前活动表单所有者的内置快捷键?
【发布时间】:2019-02-24 17:02:54
【问题描述】:

我有一个带有两个子无模式表单的主表单,例如所有表单都可以同时激活:

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);
    }
}

我想允许用户Alt+Tab 进入所有这些表单,但令人惊讶的是,我发现如果任何子表单处于活动状态,则无法从Alt+Tab 菜单中选择所有者表单。

所有三个表单都显示在列表中,但显然当您选择所有者窗口并且有一个活动的子窗口时,选择的是子窗口而不是所有者。在任务栏中选择表单时也会发生同样的情况。

我错过了什么吗?我开始考虑显式配置快捷键以允许从无模式子窗体导航到所有者窗口,但在此之前我想确认是否已经有一些内置键盘快捷键可以执行此操作,因为我不想打破用户的期望。

令人惊讶的是,我找不到任何提到这种行为的问题,我也觉得这很奇怪。

【问题讨论】:

  • 您是否设置了所有者 (.Show(this);) 以便新表单保持在其顶部?如果这是所需的行为,那么您将得到您所要求的。如果您不想要这种行为,请不要设置所有者。
  • 我相信我在文本中包含的代码 sn-p 解决了您的第一个问题。关于“你得到你想要的”的评论,你能详细说明一下吗?既然 modeless 对话框都可以用鼠标独立激活,为什么不能用键盘独立激活呢?在我看来,这是一个明显的可访问性问题(即无法访问键盘或不可见的用户无法访问表单)。
  • 您可以随时覆盖ProcessCmdKey,检查用户是否按下了CTRL + F6,并将焦点传递给所有者:this.Owner.Focus()。您当然可以使用任何组合或单个键,这只是默认设置。当你 ALT + TAB 时,孩子在它的所有者之上,所以它是接收焦点的窗口。
  • 是的,我怀疑我必须为此设置自己的快捷键机制。请问为什么CTRL+F6?这是从子窗口移动到其所有者的 Windows 标准/建议吗?我之所以这么问,是因为重要的是要知道有经验的 Windows 用户是否会期待这一点,或者这是否是必须明确教授的内容。
  • 好吧,在 Visual Studio 中,打开一些文件并按Ctrl+F6,你会看到。其他程序也一样。这是将焦点移动到不同子窗口(或所有者的)的默认快捷方式。

标签: c# winforms keyboard-shortcuts alt-tab modeless-dialog


【解决方案1】:

设置窗体的所有者,使该窗体作为非模式窗口保持在其所有者之上。
如果 owned 窗体将其 ShowInTaskbar 属性设置为 true,则用于迭代系统中打开的 Windows 的标准 ALT+TABWIN+TAB 组合键,将显示在前面(激活) 下一个拥有的表单而不是所有者。
哪个child Form被激活,这取决于Form在Taskbar中的当前位置。

如果子项的ShowInTaskbar 属性改为false,则会激活所有者表单。
需要注意的是,如果可以最小化子窗体,可以观察到一些尴尬的行为:Alt 或 Control-tabbing,导致子窗体以不愉快的方式出现和消失。

无论如何,CONTROL+F6 键的标准组合可用于将焦点移动到打开的子窗体(以及所有者窗体,在这种布局中),如果需要也将它们带到前面(如果窗体是最小化)。
在这里,覆盖ProcessCmdKey,所以键的组合被拦截,无论哪个子控件捕获了光标。

这里的代码激活一个表单,同时按下 CONTROL+F6CONTROL+SHIFT+F6,将焦点移动到每个打开的子表单和所有者。当子表单最小化(或所有子表单)时,它也可以工作。

在所有者表单中:

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);
}

在子窗体中:

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】:

    编辑答案:

    我不知道为什么,但我就是放不下。似乎应该有一个简单的解决方案。

    @glopes 根据您的评论,我相信这就是您正在寻找的内容。

    此代码将在子窗口失去焦点之前将焦点设置回父窗口。这类似于单击窗口,并允许您使用 Alt-Tab 键切换到您想要的任何窗口。

    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);
        }
    }
    

    【讨论】:

    • 不幸的是,这对我不起作用,因为我需要子窗口始终位于父窗口的顶部,尽管它们可以独立激活,即 modeless 而不是比 modal 对话框。
    • 更新后的答案非常接近。不幸的是,它现在阻止使用鼠标进行独立激活。如果您选择child1,然后选择child2(使用鼠标),由于焦点消息的处理顺序,您最终会选择主窗体。
    • 我明白你的意思,如果你点击标题栏,它会按预期工作。当您单击窗口区域时,它不会。
    猜你喜欢
    • 1970-01-01
    • 2022-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-13
    • 1970-01-01
    • 1970-01-01
    • 2012-08-03
    相关资源
    最近更新 更多