从另一个线程启动时,我的表单无法正确显示

情况就是这样:我正在开发一个具有以下结构的简单应用程序:

  • FormMain(启动点)
  • FormNotification
  • CompleFunctions

对?

好吧,在FormMain中我有以下function:

private void DoItInNewThread(ParameterizedThreadStart pParameterizedThreadStart, object pParameters, ThreadPriority pThreadPriority) { Thread oThread = new Thread(pParameterizedThreadStart); oThread.CurrentUICulture = Settings.Instance.Language; oThread.IsBackground = true; oThread.Priority = pThreadPriority; oThread.Name = "μRemote: Background operation"; oThread.Start(pParameters); } 

因此,每次我需要调用位于ComplexFunctions上的耗时方法时,我会执行以下操作:

 // This is FormMain.cs string strSomeParameter = "lala"; DoItInNewThread(new ParameterizedThreadStart(ComplexFunctions.DoSomething), strSomeParameter, ThreadPriority.Normal); 

另一个类FormNotification,它是一个向用户显示进程信息的表单。 可以从FormMain或ComplexFunctions调用此FormNotification。 例:

 // This is ComplexFunctions.cs public void DoSomething(string pSomeParameter) { // Imagine some time consuming task FormNotification formNotif = new FormNotification(); formNotif.Notify(); } 

FormNotify有一个计时器,因此,在10秒后关闭表单。 我没有使用formNotif.ShowDialog,因为我不想把焦点放在这个Form上。 您可以查看此链接以查看我在通知中所做的事情。

好吧,这就是问题所在:当我从ComplexFunction调用FormNotify时,会FormMain中的另一个Thread 调用 …此FormNotify在几毫秒后消失。 当你做这样的事情时,效果是一样的:

 using(FormSomething formSomething = new FormSomething) { formSomething.Show(); } 

怎么能避免这个?

这些是我不想使用的可能解决方案:

  • 在FormNotify中使用Thread.Sleep(10000)
  • 使用FormNotif.ShowDialog()

这是一个简化的场景(FormNotify做了一些其他花哨的东西,只停留了10秒,但它们与看到问题无关)。

谢谢你的时间!!! 拜托,对不起我的英语。

几乎每个GUI库都被设计为仅允许在为此目的指定的单个线程(称为UI线程)中进行更改GUI的调用。 如果您在另一个线程中,则需要安排调用以更改在UI线程中创建的GUI。 在.NET中,这样做的方法是调用Invoke(同步)或BeginInvoke(异步)。 等效的Java Swing调用是invokeLater() – 几乎每个GUI库都有类似的函数。

有一种称为线程亲和力的东西。 WinForm应用程序中有两个线程,一个用于呈现,另一个用于管理用户界面。 您只处理用户界面线程。 渲染线程保持隐藏状态 – 在后台运行。 在UI线程上创建的唯一对象可以操纵UI – 即对象与UI线程具有线程关联。

因为,您尝试从与UI线程不同的线程更新UI(显示通知)。 所以在你的工作线程中定义一个委托并让FormMain听这个事件。 在事件处理程序(在FormMain中定义)中编写代码以显示FormNotify。

当您要显示通知时,从工作线程触发事件。

当控件的创建线程以外的线程尝试访问该控件的方法或属性之一时,通常会导致不可预测的结果。 常见的无效线程活动是在错误的线程上调用访问控件的Handle属性。 将CheckForIllegalCrossThreadCalls设置为true可在调试时更轻松地查找和诊断此线程活动。 请注意,在调试器外部启动应用程序时,非法的跨线程调用将始终引发exception。

注意:设置CheckForIllegalCrossThreadCalls只能在DEBUGGIN SITUATIONS中完成。 不可预知的结果将会发生,你将最终试图追逐你会发现难以发现的错误。

您不能从其他线程进行WinForms调用。 查看表单中的BeginInvoke – 您可以调用委托来显示UI线程中的表单。

编辑:从评论(不要将CheckForIllegalCrossThreadCalls设置为false)。

更多信息几乎每个GUI库都设计为仅允许在为此目的指定的单个线程(称为UI线程)中进行更改GUI的调用。 如果您在另一个线程中,则需要安排调用以更改在UI线程中创建的GUI。 在.NET中,这样做的方法是调用Invoke(同步)或BeginInvoke(异步)。 等效的Java Swing调用是invokeLater() – 几乎每个GUI库都有类似的函数。

有一种称为线程亲和力的东西。 WinForm应用程序中有两个线程,一个用于呈现,另一个用于管理用户界面。 您只处理用户界面线程。 渲染线程保持隐藏状态 – 在后台运行。 在UI线程上创建的唯一对象可以操纵UI – 即对象与UI线程具有线程关联。

因为,您尝试从与UI线程不同的线程更新UI(显示通知)。 所以在你的工作线程中定义一个委托并让FormMain听这个事件。 在事件处理程序(在FormMain中定义)中编写代码以显示FormNotify。

当您要显示通知时,从工作线程触发事件。

假设您在表单中有按钮,并希望在用户单击该按钮时打开另一个表单Form1

  private void button1_Click(object sender, EventArgs e) { Thread t = new Thread(new ThreadStart(this.ShowForm1)); t.Start(); } 

您需要做的就是检查InvokeRequired属性,如果是,则调用表单的Invoke方法传递ShowForm1委托,这将最终处于递归调用,其中InvokeRequired将为false

  delegate void Func(); private void ShowForm1() { if (this.InvokeRequired) { Func f = new Func(ShowForm1); this.Invoke(f); } else { Form1 form1 = new Form1(); form1.Show(); } } 

使用SetWindowPos API调用以确保您的通知表单是最顶层的窗口。 这篇文章解释了如何:

http://www.pinvoke.net/default.aspx/user32/SetWindowPos.html