如何在C#中将窗体显示在最前面

26

各位好,

请问有人知道如何在一个本来不可见的应用程序中显示一个窗体,并让它获得焦点(即出现在其他窗口之上)吗?我使用的是C# .NET 3.5。

我怀疑我采取了“完全错误的方法”...我没有使用而是使用(new TheForm()).ShowModal()...这个窗体基本上是一个模态对话框,有几个复选框、一个文本框和OK和Cancel按钮。用户勾选复选框并输入描述(或其他内容)然后按下OK,窗体消失,进程从窗体读取用户输入,释放它并继续处理。

这个方法可以工作,除了当窗体显示时,它没有获得焦点,而是出现在“主机”应用程序的后面,直到你在任务栏(或其他位置)上单击它。这是一种非常烦人的行为,我预计将会引起很多“支持电话”,而且现有的VB6版本没有这个问题,所以我的可用性正在倒退……而且用户不会接受这样的情况(也不应该)。

因此……我开始思考整个方案……我应该像“普通应用程序”一样显示窗体,并将其余处理附加到OK按钮单击事件上。这应该可以工作。但这将需要时间,而我没有这个时间(我已经超过了时间/预算)……所以我真的需要尝试让当前方法起作用……即使是通过快速和肮脏的方法。

因此,请问有人知道如何“强制”一个.NET 3.5窗体(无论如何)获得焦点吗?我在想使用“神奇”的Windows API调用(我知道

奇怪的现象:这只出现在我的工作场所,在那里我使用的是Visual Studio 2008和Windows XP SP3……我刚刚在家里使用Visual C# 2008和Vista Ultimate失败复制这个问题……这可以正常工作。嗯?WTF?

还有一点,昨天在工作中,当我从EXE运行项目时,它显示了窗体,但是直接从IDE(我只好忍受)F5或Ctrl-F5运行时没有出现。在家里无论哪种方式都可以正常显示窗体。完全混乱!

可能相关,也可能不相关,但今天早上,在项目在调试模式下运行并编辑代码“即时”,Visual Studio崩溃并烧毁了......我认为这是一个错误信息的无限循环。错误消息大约是“无法调试此项目,因为它不是当前项目,或其他什么……所以我用进程资源管理器将其杀死了。它重新启动了,甚至提供了恢复“丢失”文件的选项,我接受了这个选项。

using System;
using System.Windows.Forms;

namespace ShowFormOnTop {
    static class Program {
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            //Application.Run(new Form1());
            Form1 frm = new Form1();
            frm.ShowDialog();
        }
    }
}

背景:我正在将一个现有的VB6实现移植到.NET…它是一个名为MapInfo的“客户端”GIS应用程序的“插件”。现有的客户端“工作不可见”,我的任务是“尽可能保持新版本与旧版本接近”,这个方法已经可以运行良好(经过多年补丁);只是它使用了一个不受支持的语言,所以我们需要移植它。

关于我:我对C#和.NET基本上一无所知,虽然我有一个初级开发者证书,但我已经是一名专业程序员有10年的经验,所以我“有点懂一些东西”。

欢迎提供任何见解...谢谢大家花时间阅读到这里。显然,简洁并不是我的强项。

谢谢,Keith。

11个回答

58

简单地说

yourForm.TopMost = true;

我会去试一试。谢谢你的建议,我很感激。 - corlettk
1
只需在 Form_Load 中设置 <code>this.TopMost = true</code> 即可完美解决问题。谢谢,谢谢,谢谢! - corlettk
我曾经遇到过类似的问题,尝试在Load事件中使用this.Focus()和this.Activate()但都没有成功。但是在Load事件中设置this.TopMost = true对我来说完美解决了问题!非常棒的帖子,感谢您的答案。+1 - Jim

6

Form.Activate() 在我的情况下起效了。


5

Form.ShowDialog()有一个重载方法,它需要一个IWin32Window对象。这个IWin32Window对象被视为该窗体的父窗口。

如果你已经有了System.Windows.Forms.Form类型的父窗口,直接传递即可。如果没有,可以通过P/Invoke调用FindWindow()方法获取HWND句柄,并创建一个虚拟的IWin32Window实现,只返回HWND句柄 (更多细节)。


应用程序中没有其他窗口......只有这个“小对话框”(允许用户输入更新的一些元数据)。 - corlettk

4

这是我在尝试了20种不同方法后写出的最终解决方案:

/* A workaround for showing a form on the foreground and with focus,
 * even if it is run by a process other than the main one
 */
public static void ShowInForeground(this Form form, bool showDialog = false)
{
    if (showDialog)
    {
        //it's an hack, thanks to https://dev59.com/5HM_5IYBdhLWcg3wPAjU#1463479
        form.WindowState = FormWindowState.Minimized;
        form.Shown += delegate(Object sender, EventArgs e) {
            ((Form)sender).WindowState = FormWindowState.Normal;
        };
        form.ShowDialog();
    }
    else
    {
        //it's an hack, thanks to https://dev59.com/pG435IYBdhLWcg3wrSBB#11941579
        form.WindowState = FormWindowState.Minimized;
        form.Show();
        form.WindowState = FormWindowState.Normal;

        //set focus on the first control
        form.SelectNextControl(form.ActiveControl, true, true, true, true);
    }
}

3

Activate() 对我也有效。

BringToFront() 在这种情况下没有任何作用,我不知道为什么。


3
  1. 你说当你使用Application.Run时它能正常工作。那么,为什么你不想使用Application.Run呢?
  2. 你尝试过在OnLoad或者OnShown中调用BringToFront()吗?

我说过我没有使用Application.Run,但我并没有说它会使窗体可见。我没有“运行”这个应用程序,因为这不是一个基本的“事件驱动”方式;而是一个相对长一些的主线程,只需要显示一个带有几个控件的模态对话框... 如果需要重新构建应用程序成为“事件驱动”,那么我会这样做... 我最初没有这样做是因为它不适合该问题。首先,我希望找到另一个解决方案来解决窗体在主机GIS应用程序后面显示的问题。我将尝试在OnShow方法中使用BringToFront()(这两个都是新的对我来说;-))非常感谢您的帮助。谢谢。Keith. - corlettk
1
抱歉,我以为你说Application.Run避免了这个问题。不过,你对它的反对有点奇怪。无论你是使用Application.Run还是Form.ShowDialog,窗体都会以相同的方式接收事件。实际上,这两种方式的工作方式几乎(但并非完全)相同。它们最终都会调用ThreadContext.FromCurrent().RunMessageLoop(ThreadContext是System.Windows.Forms.dll中的一个内部类)。 - P Daddy
啊哈...我对Application.Run的反对是基于纯粹无知。我刚刚意识到Application.Run会阻塞调用线程。(请原谅我的大声喊叫,在SO评论中并没有太多强调的选项)。我非常感谢你的建议,今天早上我会去尝试一下。如果我有点暴躁,请原谅,当时已经很晚了,我非常疲惫和沮丧。 - corlettk

1
这完美地完成了工作:

formxx.WindowState = FormWindowState.Normal;
formxx.BringToFront();
formxx.Topmost=true;
formxx.Focus();

1

我从一个应用程序中进行了黑客攻击。我们有一个大型应用程序,加载一系列由不同团队编写的模块。我们编写了其中一个模块,并需要在此初始化期间打开登录对话框。它设置为“.TopMost = true”,但这并没有起作用。

它使用WindowsFormsSynchronizationContext打开对话框,然后获取对话框结果。

我很少编写GUI代码,怀疑这可能有点过度工程,但如果有人遇到问题,它可能会有所帮助。我在理解如何将状态传递给SendOrPostCallback方面遇到了问题,因为我能找到的所有示例都没有使用它。

此外,这是从一个工作中的应用程序中获取的,但我已删除了几个代码片段,并更改了一些细节。如果不能编译,请谅解。

public bool Dummy()
{

// create the login dialog
DummyDialogForm myDialog = new DummyDialogForm();

// How we show it depends on where we are. We might be in the main thread, or in a background thread
// (There may be better ways of doing this??)
if (SynchronizationContext.Current == null)
{
    // We are in the main thread. Just display the dialog
    DialogResult result = myDialog.ShowDialog();
    return result == DialogResult.OK;
}
else
{
    // Get the window handle of the main window of the calling process
    IntPtr windowHandle = Process.GetCurrentProcess().MainWindowHandle;

    if (windowHandle == IntPtr.Zero)
    {
        // No window displayed yet
        DialogResult result = myDialog.ShowDialog();
        return result == DialogResult.OK;
    }
    else
    {
        // Parent window exists on separate thread
        // We want the dialog box to appear in front of the main window in the calling program
        // We would like to be able to do 'myDialog.ShowDialog(windowHandleWrapper)', but that means doing something on the UI thread
        object resultState = null;
        WindowsFormsSynchronizationContext.Current.Send(
            new SendOrPostCallback(delegate(object state) { resultState = myDialog.ShowDialog(); }), resultState);

        if (resultState is DialogResult)
        {
            DialogResult result = (DialogResult) resultState;
            return result == DialogResult.OK;
        }
        else
            return false;

    }
}

}


0
我在项目中得到了一段代码。
private static extern bool SetForegroundWindow(
IntPtr hWnd); 
public static void ShowToFront(Form form)
{
    FormWindowState oldState = form.WindowState;
    form.WindowState = FormWindowState.Minimized;
    form.Show();

    form.Activate();
    form.TopLevel = false;
    form.TopLevel = true;
    form.SelectNextControl(form.ActiveControl, true, true, true, true);
    SetForegroundWindow(form.Handle);
    form.Focus();
    form.WindowState = oldState;
}

0

看起来这个行为是特定于XP的...因此我无法在Vista上复现它。

http://www.gamedev.net/community/forums/topic.asp?topic_id=218484

编辑: PS:已经过了我的睡觉时间(凌晨2点)。

感谢大家的回复...我可以尝试一些“小事情”...明天甚至可能去办公室尝试...是的,是的...我曾经有过生活,但我为了理发和工作而放弃了它 ;-)

祝大家好运。Keith。


如果可以的话,我会投票反对这个(我的自己的)答案...我刚刚完成了“完全验证”:form.TopMost=true; 在XP(我们所有当前和下一个SOE版本)和Vista上修复了问题。这更加证明了“我是@ Putz!”的争议...有些日子你就是运气不好;-) - corlettk
1
如果您需要,您可以删除此答案 :) - Erik Philips

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