我希望在加载应用程序时显示启动画面。我有一个带有系统托盘控件的表单。我希望在加载此表单时显示启动画面,这需要一些时间,因为它正在访问Web服务API以填充一些下拉菜单。我还想在加载之前对依赖项进行一些基本测试(也就是说,Web服务可用,配置文件是可读的)。随着启动的每个阶段的进行,我想用进度更新启动屏幕。
我一直在阅读很多关于线程的内容,但是我会迷失在哪里(main()
方法?)。我也想念Application.Run()
如何工作,这是应该创建的线程吗?现在,如果带有系统托盘控件的表单是“活动”表单,那么它应该来自那里吗?在形式完成之前不会加载吗?
我不是在寻找代码讲义,而是更多的算法/方法,所以我可以一劳永逸地解决这个问题:)
答案 0 :(得分:46)
诀窍是创建单独的线程,负责显示闪屏 运行时,app .net会创建主线程并加载指定的(主)表单。为了隐藏辛勤工作,您可以隐藏主要表单,直到加载完成。
假设Form1 - 是你的主要形式而SplashForm是顶级的,那么界面很漂亮:
private void Form1_Load(object sender, EventArgs e)
{
Hide();
bool done = false;
ThreadPool.QueueUserWorkItem((x) =>
{
using (var splashForm = new SplashForm())
{
splashForm.Show();
while (!done)
Application.DoEvents();
splashForm.Close();
}
});
Thread.Sleep(3000); // Emulate hardwork
done = true;
Show();
}
答案 1 :(得分:46)
好吧,对于我过去部署的ClickOnce应用程序,我们使用Microsoft.VisualBasic
命名空间来处理启动画面线程。您可以在.NET 2.0中引用和使用C#中的Microsoft.VisualBasic
程序集,它提供了许多不错的服务。
Microsoft.VisualBasic.WindowsFormsApplicationBase
覆盖“OnCreateSplashScreen”方法,如下所示:
protected override void OnCreateSplashScreen()
{
this.SplashScreen = new SplashForm();
this.SplashScreen.TopMost = true;
}
非常简单,它会在加载过程中显示您的SplashForm(您需要创建),然后在主窗体完成加载后自动关闭它。
这真的让事情变得简单,VisualBasic.WindowsFormsApplicationBase
当然经过了微软的测试,并且有很多功能可以让你在Winforms中的生活变得更轻松,即使在100%C#的应用程序中也是如此。
在一天结束时,无论如何都是IL和bytecode
,为什么不使用呢?
答案 2 :(得分:13)
在遍布Google和SO寻找解决方案之后,这是我最喜欢的: http://bytes.com/topic/c-sharp/answers/277446-winform-startup-splash-screen
<强> FormSplash.cs:强>
public partial class FormSplash : Form
{
private static Thread _splashThread;
private static FormSplash _splashForm;
public FormSplash() {
InitializeComponent();
}
/// <summary>
/// Show the Splash Screen (Loading...)
/// </summary>
public static void ShowSplash()
{
if (_splashThread == null)
{
// show the form in a new thread
_splashThread = new Thread(new ThreadStart(DoShowSplash));
_splashThread.IsBackground = true;
_splashThread.Start();
}
}
// called by the thread
private static void DoShowSplash()
{
if (_splashForm == null)
_splashForm = new FormSplash();
// create a new message pump on this thread (started from ShowSplash)
Application.Run(_splashForm);
}
/// <summary>
/// Close the splash (Loading...) screen
/// </summary>
public static void CloseSplash()
{
// need to call on the thread that launched this splash
if (_splashForm.InvokeRequired)
_splashForm.Invoke(new MethodInvoker(CloseSplash));
else
Application.ExitThread();
}
}
<强>的Program.cs:强>
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
// splash screen, which is terminated in FormMain
FormSplash.ShowSplash();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// this is probably where your heavy lifting is:
Application.Run(new FormMain());
}
}
<强> FormMain.cs 强>
...
public FormMain()
{
InitializeComponent();
// bunch of database access, form loading, etc
// this is where you could do the heavy lifting of "loading" the app
PullDataFromDatabase();
DoLoadingWork();
// ready to go, now close the splash
FormSplash.CloseSplash();
}
我遇到了Microsoft.VisualBasic
解决方案的问题 - 在XP上工作查找,但在Windows 2003终端服务器上,主应用程序表单会在后台显示(在启动画面之后),任务栏会闪烁。并且在窗口中显示前景/焦点代码是另一种可以使用Google / SO的蠕虫。
答案 3 :(得分:9)
这是一个老问题,但在尝试为WPF找到可能包含动画的线程闪屏解决方案时,我一直遇到它。
以下是我最终拼凑的内容:
的App.xaml:
<Application Startup="ApplicationStart" …
App.XAML.cs:
void ApplicationStart(object sender, StartupEventArgs e)
{
var thread = new Thread(() =>
{
Dispatcher.CurrentDispatcher.BeginInvoke ((Action)(() => new MySplashForm().Show()));
Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
// call synchronous configuration process
// and declare/get reference to "main form"
thread.Abort();
mainForm.Show();
mainForm.Activate();
}
答案 4 :(得分:8)
我建议在aku提供的答案中的最后Activate();
之后直接致电Show();
。
引用MSDN:
激活表单会将其带到 前面,如果这是活跃的 应用程序,或它闪烁的窗口 如果这不是活动的标题 应用。表格必须可见 对于这种方法有任何影响。
如果您没有激活主表单,它可能会在后面显示任何其他打开的窗口,使它看起来有点傻。
答案 5 :(得分:6)
我认为使用aku's或Guy's之类的方法是可行的方法,但要从具体示例中删除一些内容:
基本前提是尽快在单独的帖子上展示你的情节。这就是我倾向的方式,类似于aku的说明,因为这是我最熟悉的方式。我不知道Guy提到的VB函数。而且,即使认为它是一个 VB 库,他也是对的 - 最终它都是IL。所以,即使它感觉脏也不是那么糟糕! :)我想你要确保VB为这个覆盖提供一个单独的线程,或者你自己创建一个 - 绝对研究它。
假设您创建另一个线程来显示此启动,您将需要注意跨线程UI更新。我提出这个问题是因为你提到了更新进度。基本上,为了安全起见,您需要使用委托在splash表单上调用更新函数(您创建的)。您将该委托传递到初始屏幕的表单对象上的Invoke函数。实际上,如果直接调用splash表单来更新其上的进度/ UI元素,只要您在.Net 2.0 CLR上运行,就会遇到异常。根据经验,表单上的任何UI元素都必须由创建它的线程更新 - 这就是Form.Invoke所保证的。
最后,我可能会选择在代码的main方法中创建启动(如果不使用VB重载)。对我来说,这比主表单执行对象的创建并且与它紧密绑定更好。如果您采用这种方法,我建议创建一个简单的界面,即启动画面实现 - 类似于IStartupProgressListener - 它通过成员函数接收启动进度更新。这将允许您根据需要轻松地交换/输出任一类,并很好地解耦代码。如果在启动完成时通知,启动表单也可以知道何时关闭。
答案 6 :(得分:5)
一种简单的方法是使用像main()这样的东西:
<STAThread()> Public Shared Sub Main()
splash = New frmSplash
splash.Show()
' Your startup code goes here...
UpdateSplashAndLogMessage("Startup part 1 done...")
' ... and more as needed...
splash.Hide()
Application.Run(myMainForm)
End Sub
当.NET CLR启动您的应用程序时,它会创建一个“主”线程并开始在该线程上执行main()。 Application.Run(myMainForm)最后做了两件事:
没有必要生成一个线程来处理启动窗口,事实上这是一个坏主意,因为那时你必须使用线程安全技术来更新main()中的启动内容。 / p>
如果您需要其他线程在您的应用程序中执行后台操作,您可以从main()生成它们。只需记住将Thread.IsBackground设置为True,这样它们就会在主/ GUI线程终止时死掉。否则,您必须自己安排终止所有其他线程,否则当主线程终止时,它们将使您的应用程序保持活动状态(但没有GUI)。
答案 7 :(得分:4)
我在codeproject的应用程序中发布了一篇关于启动画面合并的文章。它是多线程的,可能是您感兴趣的
答案 8 :(得分:4)
private void MainForm_Load(object sender, EventArgs e)
{
FormSplash splash = new FormSplash();
splash.Show();
splash.Update();
System.Threading.Thread.Sleep(3000);
splash.Hide();
}
我从互联网上得到了这个,但似乎无法再找到它。简单但有效。
答案 9 :(得分:3)
我非常喜欢Aku的答案,但是代码适用于C#3.0及以上,因为它使用了lambda函数。对于需要在C#2.0中使用代码的人来说,这里是使用匿名委托而不是lambda函数的代码。您需要一个名为formSplash
的最顶层winform FormBorderStyle = None
。表单的TopMost = True
参数很重要,因为启动画面看起来可能看起来如果它不是最顶端则快速消失。我也选择了StartPosition=CenterScreen
所以它看起来像专业应用程序使用启动画面。如果您想要更酷的效果,可以使用TrasparencyKey
属性制作不规则形状的闪屏。
private void formMain_Load(object sender, EventArgs e)
{
Hide();
bool done = false;
ThreadPool.QueueUserWorkItem(delegate
{
using (formSplash splashForm = new formSplash())
{
splashForm.Show();
while (!done)
Application.DoEvents();
splashForm.Close();
}
}, null);
Thread.Sleep(2000);
done = true;
Show();
}
答案 10 :(得分:2)
我不同意推荐WindowsFormsApplicationBase
的其他答案。根据我的经验,它可以减慢你的应用程序。确切地说,当它与启动画面并行运行表单的构造函数时,它会推迟表单的已显示事件。
考虑一个应用程序(没有启动画面),构造函数需要1秒,而Shown上的事件处理程序需要2秒。这个应用程序可在3秒后使用。
但假设您使用WindowsFormsApplicationBase
安装了初始屏幕。您可能认为{3}的MinimumSplashScreenDisplayTime
是明智的,并且不会减慢您的应用。但是,尝试一下,你的应用程序现在需要5秒才能加载。
class App : WindowsFormsApplicationBase
{
protected override void OnCreateSplashScreen()
{
this.MinimumSplashScreenDisplayTime = 3000; // milliseconds
this.SplashScreen = new Splash();
}
protected override void OnCreateMainForm()
{
this.MainForm = new Form1();
}
}
和
public Form1()
{
InitializeComponent();
Shown += Form1_Shown;
Thread.Sleep(TimeSpan.FromSeconds(1));
}
void Form1_Shown(object sender, EventArgs e)
{
Thread.Sleep(TimeSpan.FromSeconds(2));
Program.watch.Stop();
this.textBox1.Text = Program.watch.ElapsedMilliseconds.ToString();
}
结论:如果您的应用在Slown事件上有处理程序,请不要使用WindowsFormsApplicationBase
。您可以编写更好的代码来与构造函数和Shown事件并行运行启动。
答案 11 :(得分:0)
实际上这里不需要多线程。
每当您想要更新启动画面时,让您的业务逻辑生成一个事件。
然后让表单在连接到eventhandler的方法中相应地更新启动屏幕。
要区分更新,您可以触发不同的事件,也可以在从EventArgs继承的类中提供数据。
通过这种方式,你可以拥有漂亮的闪屏,而不会出现任何多线程问题。
实际上,你甚至可以在启动表格上支持gif图像。为了使它工作,请在处理程序中调用Application.DoEvents():
private void SomethingChanged(object sender, MyEventArgs e)
{
formSplash.Update(e);
Application.DoEvents(); //this will update any animation
}