当用户点击窗口外部时,如何关闭一个表单?

8

如果用户在窗口外单击,我想关闭 System.Windows.Forms.Form 窗体。我尝试使用 IMessageFilter,但即使如此,也没有将任何消息传递到 PreFilterMessage。如何接收窗体窗口外的点击?

8个回答

10
在你的表单的Deactivate事件中,加入“this.Close()”。当你在Windows中点击其他地方时,你的表单将立即关闭。
更新:我认为你现在拥有的是一个音量按钮,在Click事件中创建了一个VolumeSlider表单实例,并通过调用ShowDialog()使其出现,该方法会阻塞,直到用户关闭弹出的窗口。接下来的行中,你读取用户选择的音量并在程序中使用它。
这样做没问题,但是正如你已经注意到的那样,它强制用户显式关闭弹出窗口才能返回主程序。在你的弹出窗体上真正想使用的方法是Show(),但是Show()不会阻塞,这意味着在主表单上的Click事件完成后,无法知道新的音量应该是什么。
一个简单的解决方案是在你的主表单上创建一个公共方法,如下所示:
public void SetVolume(int volume)
{
    // do something with the volume - whatever you did before with it
}

接着,在音量按钮的单击事件中(也在主表单上),您可以使音量滑块出现:

VolumeSlider slider = new VolumeSlider();
slider.Show(this); // the "this" is needed for the next step

在 VolumeSlider 表单中,当用户操作滚动条(我猜是这样)时,您需要将以下代码放在滚动条的 ValueChanged 事件中(我想那大概就是这个事件):
MainForm owner = (MainForm)this.Owner;
owner.SetVolume(scrollbar.Value);

在 VolumeSlider 表单的 Deactivate 事件中,您应该像上面提到的那样添加 this.Close()。这样,您的表单将按预期运行。

我:“医生,我做这个动作很疼。” 医生:“别做那个动作。”请查看我上面的更新。 - MusiGenesis
如果这对你有用,请告诉我。我不想既咄咄逼人又错了。 :) - MusiGenesis
2
@JesonPark:当然可以:http://msdn.microsoft.com/zh-cn/library/system.windows.forms.form.deactivate.aspx - MusiGenesis
谢谢MusiGenesis,我找到了! - AminM

5

感谢p-daddy这个问题中的帮助,我找到了以下解决方案,使我能够使用ShowDialog:

protected override void OnShown(EventArgs e)
{
    base.OnShown(e);
    this.Capture = true;
}

protected override void OnCaptureChanged(EventArgs e)
{
    if (!this.Capture)
    {
        if (!this.RectangleToScreen(this.DisplayRectangle).Contains(Cursor.Position))
        {
            this.Close();
        }
        else
        {
            this.Capture = true;
        }
    }

    base.OnCaptureChanged(e);
}

1
嗯...毕竟它没有按预期工作-当表单加载时,我有一个“手”鼠标光标,并且在表单外单击会关闭它。如果我想与表单本身交互,第一次单击似乎只清除了“捕获”,并且不会在表单内注册(例如,单击复选框不会选中它),之后单击外部不会关闭窗口。 还有其他方法可以做到这一点吗? - Noam Gal
@Noam:你的表单有多简单?如果它只有一个子元素,你可以设置子元素的捕获而不是整个表单的。 - Simon
6
OnMouseCaptureChanged 不是 OnCaptureChanged。 - Ashraf Bashir
我使用了OnVisibleChanged和OnMouseCaptreChanged,它可以工作,但有时鼠标会隐藏。 - Pablo Jomer

4

使用Simon的解决方案,我遇到了Noam描述的同样问题。使用以下代码,我已经避免了“点击穿透”问题...

protected override void WndProc(ref Message m)
{    
    base.WndProc(ref m);

    // if click outside dialog -> Close Dlg
    if (m.Msg == NativeConstants.WM_NCACTIVATE) //0x86
    {
        if (this.Visible)
        {
            if (!this.RectangleToScreen(this.DisplayRectangle).Contains(Cursor.Position))
                this.Close();
        }
    }
}

3
能否提供一些背景信息,以便他人正确使用这个答案? - Andrew Barber
这是真正正确的答案。当你点击使用 ShowDialog() 打开的窗体之外时,会发送三个消息:WM_NCACTIVATEWM_ACTIVATEWM_ACTIVATEAPP。不要使用第三个,而第二个 (WM_ACTIVATE) 可能比第一个更好。还要记得设置 DialogResult - Bitterblue

1
你应该使用 Deactivate: 我有一个名为 Form2 的表单。 在 属性 窗口的 Deactivate 部分。 您声明名称为 Form2_Deactivate。 在 Form2.cs 文件中:
private void Form2_Deactivate(object sender, EventArgs e)
{
    this.Close();
}

0

简单的方法: 在Form1中使用以下代码调用Form2:

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles     Button1.Click
    Form2.Owner = Me
    Form2.Show()
End Sub

接着再次在form1中使用这段代码:

Private Sub Form1_MouseClick(sender As Object, e As MouseEventArgs) Handles Me.MouseClick
    If Form2.IsHandleCreated = True Then
        Form2.Close()
    End If
End Sub

0

如果它是在MDI应用程序中的子窗体,您可以在父窗体中捕获单击事件,否则解决方案将变得混乱。

我并不认为您建议的代表了直观的UI行为。您确定这是最佳设计吗?


这是一个音量滑块 - 这似乎是其他人实现它们的方式。 - Simon
这与下拉框并没有太大的区别 - 选择列表在你点击其他地方后就会消失。 - MusiGenesis

0

如果你正在尝试制作一个弹出窗口,它的行为有点像菜单,除了它让你与控件交互,你可以尝试在工具栏下拉菜单中托管一个用户控件。


0

这很简单:

private void button1_Click(object sender, EventArgs e)
    {
        Form f = new Form();
        f.LostFocus +=new EventHandler(f_LostFocus);
        f.Show();
    }

    void f_LostFocus(object sender, EventArgs e)
    {
        Form f = sender as Form;
        f.Close();
        f.Dispose();
    }

这是对我有效的解决方案!其他解决方案都很笨拙,特别是使用OnDeactivate()的那个,当关闭子窗体时,我的整个应用程序会将焦点转移到完全不同的应用程序中,原因是什么(??)... - Patapom

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