我有一个旨在通过网络运行的应用程序。这意味着此应用程序的初始运行可能需要一段时间。所以我一直在制作一个启动画面来完成整个过程。
它使用线程通过静态方法显示表单。我仍然是一个线程新手,所以当我遇到错误时,我对于什么和为什么感到困惑。
当我在Visual Studio调试器外部运行时,我的代码完全没问题。但是当我从调试器内部运行它时,我得到了异常:
“跨线程操作无效:控制''从其创建的线程以外的线程访问。”
这是我的班级:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace MyApp
{
public partial class SplashScreen : Form
{
private static double OPACITY_INCREMENT = .05;
private static SplashScreen _splashForm;
private static SplashScreen SplashForm
{
get { return SplashScreen._splashForm; }
set { SplashScreen._splashForm = value; }
}
private static void ShowForm()
{
SplashScreen.SplashForm = new SplashScreen();
Application.Run(SplashScreen.SplashForm);
}
internal static void CloseForm()
{
if (SplashScreen.SplashForm != null &&
SplashScreen.SplashForm.IsDisposed == false)
{
// Make it start going away.
OPACITY_INCREMENT = -OPACITY_INCREMENT;
}
SplashScreen.SplashThread = null;
SplashScreen.SplashForm = null;
}
private static Thread _splashThread;
private static Thread SplashThread
{
get { return SplashScreen._splashThread; }
set { SplashScreen._splashThread = value; }
}
internal static void ShowSplashScreen()
{
if (SplashScreen.SplashForm != null)
{
return;
}
SplashScreen.SplashThread = new Thread(new ThreadStart(SplashScreen.ShowForm));
SplashScreen.SplashThread.IsBackground = true;
SplashScreen.SplashThread.SetApartmentState(ApartmentState.STA);
SplashScreen.SplashThread.Start();
}
public SplashScreen()
{
InitializeComponent();
this.timerFade.Start();
this.ClientSize = this.BackgroundImage.Size;
}
private void SplashScreen_Load(object sender, EventArgs e)
{
}
private void timerFade_Tick(object sender, EventArgs e)
{
if (OPACITY_INCREMENT > 0)
{
if (this.Opacity < 1)
this.Opacity += OPACITY_INCREMENT;
}
else
{
if (this.Opacity > 0)
this.Opacity += OPACITY_INCREMENT;
else
{
this.Invoke(new MethodInvoker(this.TryClose));
}
}
}
private void TryClose()
{
if (this.InvokeRequired)
{
this.BeginInvoke(new MethodInvoker(this.TryClose));
}
this.Close();
}
}
}
我正在从Program.cs Main方法中调用启动画面。
namespace CIMA
{
static class Program
{
// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
SplashScreen.ShowSplashScreen();
// Code omitted for brevity.
SplashScreen.CloseForm();
// Code omitted for brevity.
}
}
}
我希望能够从我的其他形式中调用SplashScreen.CloseForm(),但我还没有尝试过。我对调试器的用途感到困惑。
我错过了什么吗?或者如果在调试器中运行它,我是否必须禁用启动屏幕?
如果是这样,这样做的好方法是什么?我想尽可能避免使用编译符号,因为我讨厌跟踪它们。
答案 0 :(得分:4)
在.NET中,不允许从未创建控件的线程访问控件。在您的代码中,它看起来像一个单独的线程正在创建启动窗体,主线程尝试关闭启动窗体。
我建议你反过来做:在主线程中创建splash表单并让单独的线程更新它。
此外,在我的TryClose
方法中,在我调用BeginInvoke
之后,回复丢失了。
但是,您可以查看启动画面的其他实现。有一些现成的,例如http://www.codeproject.com/KB/cs/prettygoodsplashscreen.aspx
答案 1 :(得分:1)
好的 - 这是另一个解决方案。我想现在就是这样。我只是包括它以防其他人出现同样的问题。简短版本:托尔斯滕是对的。
问题在于我的启动画面的关闭功能。我使用表单内部的计时器触发了飞溅表单上的淡出。计时器会将表单的不透明度递减为零,然后在达到零时关闭启动表单。
为了解决这个问题,我做了一些事情:
完成这些更改后,“启动画面”屏幕将按预期工作。嗯......几乎。在主窗体启动之后,新的启动屏幕才会开始淡出 - 主线程需要获得足够的处理时间,计时器才能真正开始完成其工作。但这实际上是一件好事 - 我的意思是改变现状,使主要表单加载后,Splash屏幕会消失,所以结果是加分。
以下代码感兴趣。此外,我仍然有可能错误地实现了这种跨线程行为 - 我邀请批评。正如Kyle建议的那样,我想从良好的线程习惯开始,而不是糟糕的习惯。
对于任何不想要FadeOut效果的人,我在代码中注释了(注释掉了)关闭表单而不使用CloseForm()方法中的计时器。
public partial class SplashScreen : Form
{
private static double OPACITY_INCREMENT = .05;
private static SplashScreen _splashForm;
private static SplashScreen SplashForm
{
get { return SplashScreen._splashForm; }
set { SplashScreen._splashForm = value; }
}
private static void ShowForm()
{
Application.Run(SplashScreen.SplashForm);
}
internal static void CloseForm()
{
if (SplashScreen.SplashForm != null &&
SplashScreen.SplashForm.IsDisposed == false)
{
// Make it start going away.
SplashScreen.SplashForm.FadeOut();
}
//if (SplashScreen.SplashForm.InvokeRequired)
//{
// SplashScreen.SplashForm.Invoke(new MethodInvoker(SplashScreen.SplashForm.Close));
//}
//else
//{
// SplashScreen.SplashForm.DoClose();
//}
SplashScreen.SplashThread = null;
SplashScreen.SplashForm = null;
}
private void FadeOut()
{
this.timerFadeOut.Start();
}
private static Thread _splashThread;
private static Thread SplashThread
{
get { return SplashScreen._splashThread; }
set { SplashScreen._splashThread = value; }
}
internal static void ShowSplashScreen()
{
if (SplashScreen.SplashForm != null)
{
return;
}
SplashScreen.SplashForm = new SplashScreen();
SplashScreen.SplashThread = new Thread(new ThreadStart(SplashScreen.ShowForm));
SplashScreen.SplashThread.IsBackground = true;
SplashScreen.SplashThread.SetApartmentState(ApartmentState.STA);
SplashScreen.SplashThread.Start();
}
public SplashScreen()
{
InitializeComponent();
this.ClientSize = this.pictureBox1.Size;
}
private void SplashScreen_Load(object sender, EventArgs e)
{
}
private void timerFadeIn_Tick(object sender, EventArgs e)
{
if (OPACITY_INCREMENT > 0)
{
if (this.Opacity < 1)
{
this.Opacity += OPACITY_INCREMENT;
}
else
{
this.timerFadeIn.Stop();
}
}
}
private void SplashScreen_Shown(object sender, EventArgs e)
{
this.timerFadeIn.Start();
}
private void timerFadeOut_Tick(object sender, EventArgs e)
{
if (OPACITY_INCREMENT > 0)
{
if (this.Opacity > 0)
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(this.FadeOutStep));
}
else
{
this.FadeOutStep();
}
}
else
{
this.timerFadeOut.Stop();
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(this.Close));
}
else
{
this.Close();
}
}
}
}
private void FadeOutStep()
{
this.Opacity -= OPACITY_INCREMENT;
}
}
答案 2 :(得分:0)
好的 - 我找到了解决方法。
System.Diagnostics.Debugger.IsAttached
我添加了以下使用声明:
using System.Diagnostics;
...并按如下方式更改了ShowSplashScreen()方法:
internal static void ShowSplashScreen()
{
if (!Debugger.IsAttached)
{
if (SplashScreen.SplashForm != null)
{
return;
}
SplashScreen.SplashThread = new Thread(new ThreadStart(SplashScreen.ShowForm));
SplashScreen.SplashThread.IsBackground = true;
SplashScreen.SplashThread.SetApartmentState(ApartmentState.STA);
SplashScreen.SplashThread.Start();
}
}
因此,如果我从Visual Studio运行,我的程序会忽略启动画面,或者如果没有,则运行启动画面。每次测试重建时都不再更改我的代码。
我仍然不知道为什么我在调试器内运行而不是一般代码时出错,所以我希望这个网站上的一些聪明的bug可以出现并解释一下。
否则,这种解决办法对我来说已经足够了。