如何在Winforms中制作“弹出”(提示,下拉,弹出)窗口?

6

如何在WinForms中制作我称之为“弹出窗口”的东西?


既然我使用了自己编造的词语“popup”,那么让我举些所谓的“popup”窗口的例子:

  • 一个工具提示窗口(可以超出其父窗体的边界,不会出现在任务栏中,不是模态的,也不会夺取焦点):

    enter image description here

  • 一个弹出菜单窗口(可以超出其父窗体的边界,不会出现在任务栏中,不是模态的,也不会夺取焦点):

    enter image description here

  • 一个下拉窗口(可以超出其父窗体的边界,不会出现在任务栏中,不是模态的,也不会夺取焦点):

    enter image description here

  • 一个主菜单窗口(可以超出其父窗体的边界,不会出现在任务栏中,不是模态的,也不会夺取焦点):

    enter image description here

  • 更新一个弹出窗口,当使用鼠标或键盘进行交互时不会使其成为"活动"窗口("所有者"窗口仍然是活动窗口):

enter image description here

我正在寻找的神秘“弹出窗口”的属性包括:
  • 可以超出其父窗体的边界(即不是子窗口
  • 不会出现在任务栏中(即Windows确定哪些窗口应该出现的启发式方法不起作用,也没有WS_EX_APPWINDOW扩展窗口样式)
  • 不是模态的(即不会禁用其“所有者”
  • 不会窃取焦点
  • 始终位于其“所有者”的顶部
  • 与之交互时不会成为“活动”窗口(所有者仍然处于活动状态)
Windows应用程序已经成功创建了这样的窗口。如何在WinForms应用程序中实现?
  • 如何在本地代码中实现以上所有内容?
  • 如何在Delphi中创建弹出窗口?
  • 我有这个本地代码来显示一个“弹出”窗口 - 需要哪些P / Invokes才能在.NET中执行相同的操作?
  • 我在.NET中有一组P / Invoke - 我可以重用常规的WinForm,覆盖某些方法,以达到相同的效果吗?
  • 我有一个WinForm,通过覆盖某些方法显示为“弹出” - 是否有内置的Control可以作为我的弹出窗口?
  • 如何在WinForms中模拟下拉窗口?

尝试#1

我尝试了Show(onwer) + ShowWithoutActivation方法:

PopupForm dd = new PopupForm ();
dd.Show(this);

使用PopupForm:
public class PopupForm: Form
{
    public PopupForm()
    {
        InitilizeComponent();
    }

    private void InitilizeComponent()
    {
        this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
        this.WindowState = FormWindowState.Normal;
        this.ShowInTaskbar = false;
    }

    protected override bool ShowWithoutActivation
    { get { return true; } }
}

几乎解决了问题,但是我被提醒了“弹出”窗口的另一个属性:当使用鼠标或键盘与其交互时,它们不会从其“所有者”表单中获得焦点。

您想将所有内容放在一个“弹出窗口”中,还是使用多个“弹出窗口”方法? - Moonlight
@Moonlight 不管内容是什么,概念应该是相同的。一旦我有了一个“弹出表单”,我可以放置任何我想要的内容。 - Ian Boyd
我很难看出这个问题和昨天的问题之间的区别。我在那里的答案对于你现在尝试做的事情也适用。 - LarsTech
@LarsTech 通过询问关于“下拉菜单”的问题,我限制了我的答案范围在“下拉菜单”上。我想要一个更广泛适用的问题(和答案)。这个问题的答案解决了“下拉菜单”的问题,但不一定反之亦然。最后,我想要一个关于“弹出窗口”的问题,这样任何搜索“弹出窗口”的人都能找到这个问题,而不仅仅局限于“下拉菜单”。 - Ian Boyd
3个回答

3

我对于组合框下拉菜单和菜单的工作原理很感兴趣,因此进行了更多的研究。

有两种基本方法。

将弹出窗口创建为主窗口所拥有的重叠窗口

如果弹出窗口具有嵌入式控件,或者您希望弹出窗口作为模式对话框来使用,则需要使用此方法。

如果用户要与弹出窗口中的子控件交互,则其必须接收激活。 (因此,处理WM_MOUSEACTIVATE等阻止激活的各种技术是误导。)当它接收到激活时,Windows将停用主窗口。 解决此问题的方法是向父级发送WM_NCACTIVATE消息,以在不更改其激活状态的情况下更新其视觉外观。 .Net ToolStrip采用了这种方法,我的其他答案使用代码演示了这种方法。

将弹出窗口创建为桌面窗口的子窗口

组合框下拉菜单和(我猜)菜单使用此方法,但您无法嵌入子控件,因此其适用性不广泛。

弹出窗口是子窗口,因此不会干扰激活。 它总是在顶部。 使用鼠标捕获来检测单击弹出窗口之外的区域并关闭它。

但是,这并不容易实现。 激活仍然保留在主应用程序中,因此它保持焦点并接收按键。 这似乎排除了在弹出窗口中嵌入控件,因为它们无法接收焦点。 组合框通过将按键消息转发到其下拉列表来处理此问题。(请注意,菜单、组合框下拉菜单等都完全是所有者绘制的,因为它们没有嵌入的窗口。)


在我的“祖先”问题中,Lars建议您提到的内容——ToolStrip。在我看来,您的研究使得ToolStrip成为此问题最佳可能的答案之一。 - Ian Boyd

3

您想要一个拥有的窗口。在您的主窗体中:

private void showPopup_Click(object sender, EventArgs e)
{
    PopupForm popupForm = new PopupForm();
    // Make "this" the owner of form2
    popupForm.Show(this);
}

弹出表单应该长这样:

public partial class PopupForm : Form
{
    private bool _activating = false;

    public PopupForm()
    {
        InitializeComponent();
    }

    // Ensure the popup isn't activated when it is first shown
    protected override bool ShowWithoutActivation
    {
        get
        {
            return true;
        }
    }

    private const int WM_NCACTIVATE = 0x86;

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

    protected override void WndProc(ref Message m)
    {
        // The popup needs to be activated for the user to interact with it,
        // but we want to keep the owner window's appearance the same.
        if ((m.Msg == WM_NCACTIVATE) && !_activating && (m.WParam != IntPtr.Zero))
        {
            // The popup is being activated, ensure parent keeps activated appearance
            _activating = true;
            SendMessage(this.Owner.Handle, WM_NCACTIVATE, (IntPtr) 1, IntPtr.Zero);
            _activating = false;
            // Call base.WndProc here if you want the appearance of the popup to change
        }
        else
        {
            base.WndProc(ref m);
        }
    }
}

确保PopupForm.ShowInTaskbar = false


有用。接近了。但是当用户单击它时,表单会获得焦点(与不获得焦点的弹出窗口相反,例如Windows资源管理器)。 - Ian Boyd
@IanBoyd:你能给出一个更具体的例子吗?一个可以与之交互,但不会被激活的窗口?如果你想让弹出窗口完全忽略鼠标点击,请在显示form2后设置“form2.Enabled = False”。 - arx
Windows资源管理器 - 过滤和分组列(附有问题的截图) - Ian Boyd
@IanBoyd:我觉得这个新版本可以做到。虽然它是一种粗略的技术,但它似乎相当普遍。 - arx
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Taylor Clark

0

将您的“弹出”窗口创建为桌面的子窗口,然后在不激活它的情况下显示它。

hWnd = CreateWindowEx(..., WS_CHILDWINDOW | WS_VISIBLE | WS_BORDER | WS_CLIPSIBLINGS, ..., GetDesktopWindow(), ...);
SetWindowPos(hWnd, HWND_TOPMOST, ..., SWP_NOACTIVATE);

完成此操作后,即使单击“弹出”窗口,您的原始窗口仍保持激活状态。 “弹出”窗口可以拥有自己的子控件。 您可以在其上单击按钮。 但是,如果它是编辑控件,则无法编辑它,我不知道为什么。 可能是因为您的原始窗口已经有一个闪烁的光标。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接