Form.Show()在控制台中调用会冻结GUI界面。

7

我正在尝试直接从控制台应用程序实例化一个表单。
出现了一些奇怪的问题,当我调用Form1.Show()时,新创建的表单并没有绘制所有的控件,并且会冻结(显示沙漏图标)。然而,当我调用ShowDialog()时,一切都很正常,除了我需要返回到控制台但我无法这样做,所以这不是一个选项...
我该如何使我的表单正确显示?我错过了什么吗?

OrderControlForm OrderControlBox = new OrderControlForm();
OrderControlBox.BuyEvent += new OrderControl.BuyDelegate(doBuy);
OrderControlBox.SellEvent += new OrderControl.SellDelegate(doSell);
OrderControlBox.Show();

上述代码是响应控制台用户输入的命令而调用的。
编辑: 以下是可工作的代码:
        Thread mThread = new Thread(delegate()
        {
            StratControlBox = new StratControl(StratIDs);
            StratControlBox.ShowDialog();
        });

        mThread.SetApartmentState(ApartmentState.STA);

        mThread.Start();

我仍然不明白为什么必须调用 ShowDialog() 而不是 Show()。 当我使用后者时,窗体只会在绘制后立即“消失”。


1
这取决于你如何调用它,请展示代码。 - HatSoft
这是一个相当简单和基础的表单调用。请查看编辑。 - Mehdi LAMRANI
你为什么不使用普通的Windows应用程序模板呢? - Reed Copsey
这是遗留代码,主要是控制台应用程序。我只想在控制台旁边打开一个窗体。 - Mehdi LAMRANI
3个回答

12
原因是因为ShowDialog执行自己的消息循环,而Show则不执行。你需要调用 Application.Run 来执行一个消息循环,而不是调用 Show。但由于它同步地循环处理窗口消息直到窗体关闭,所以与调用ShowDialog并没有什么区别。
因此,如果您想异步显示窗体,您需要从另一个线程中这样做。但是为了安全起见,请确保新线程使用公寓线程状态,即调用 newThread.SetApartmentState(ApartmentState.STA);
另外,我建议您在一个UI线程中只显示一个主窗体。如果该主窗体从其自己的线程显示其他窗体,那就可以了,但是如果您开始尝试显示多个窗体,并且每个窗体都来自其自己的线程,就会导致问题。
关于您的更新:
调用Show方法不起作用的原因有两个方面。首先,它是同步的,因此它不会返回,直到窗体关闭。这很重要,因为一旦执行离开匿名方法,线程将终止。当您调用Show时,它立即返回,然后离开您的方法,从而终止线程。
其次,即使窗体保持打开状态,它仍然无响应,就像之前一样。WinForms需要一个消息循环来不断查找新的传入窗口消息并处理它们。消息循环调用一个名为WndProc的方法。如果没有消息循环调用WndProc方法来处理传入的窗口消息,则窗体将对用户完全无响应。例如,当鼠标驱动程序通知Windows用户已按下鼠标按钮时,Windows会向您的应用程序的消息队列发送WM_MOUSEDOWN消息。如果您没有代码在某个地方不断循环查看是否有任何消息,并对它们进行操作,则永远不会收到鼠标按下事件。

如我先前所提到的,ShowDialog方法会执行自己的消息循环,因此它是有效的,但是Show则不行。Show假设它是被一个已经在运行的消息循环调用的。如果由于某些原因你不想调用ShowDialog,可以调用Application.Run(StratControlBox)代替。Run方法会为你显示窗体,然后保持在消息循环中,直到窗体关闭。因此,它是一种同步调用,就像ShowDialog一样,所以你的线程不会在窗体关闭之前终止。


这听起来不错。我尝试过这个,但是遇到了一些繁琐的跨线程错误消息。我明天黎明时再试一次,如果成功了就把它标记为答案(祈祷)。保持联系。谢谢。 - Mehdi LAMRANI
2
@MikaJacobi,跨线程错误消息是因为您试图从非UI线程访问UI对象。您需要调用Control.Invoke(阻塞)或Control.BeginInvoke(非阻塞)来在UI线程上执行代码(然后可以访问UI元素的代码)。顺便说一下,Form也是一个Control(即它也包含这些方法)。 - Tergiver
这个消息循环的技术名称是 WndProc,但窗口过程并不是消息循环。窗口过程是在消息循环内部调用的。 - Ben Voigt
@BenVoigt 理解了,感谢您的澄清。我更新了我的回答。 - Steven Doggart

4
我应该做什么才能使我的表单正确显示?我错过了什么吗?
问题在于控制台应用程序不是一个Windows应用程序,并且没有适当的“管道”来处理Windows消息。没有这个,表单无法正确处理项目,包括基本的“绘制”等消息。
通常使用Application.Run启动消息处理来处理此问题。但是,这会阻止直到窗体关闭(因此控制台将不会继续“运行”)。解决此问题的方法是将您的控制台代码移动到单独的线程中,并像正常的Windows应用程序一样使用Application.Run与您的表单一起使用。

是的,没错,确实如此。我只是想避免用另一种方式重新设计整个东西。 - Mehdi LAMRANI

-1
在.NET 4.7中,我可以简单地更改


myForm.Show()

Task.Run(() => { myForm.ShowDialog(); });

我已经在使用了

[STAThread] 
private static void Main(string[] args)
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    .
    .
    .
}

因为其他要求。


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