我有一个Windows表单应用程序,在加载主窗口之前需要加载许多东西。我认为这将需要一个进度条,所以我想使用我的主窗口的构造函数显示包含进度条控件的另一个窗体。
一切都很顺利,但是如果我尝试在介绍窗体中放置文本标签,其内容直到主窗体加载后才会显示。是否有其他方法可以避免这种情况,而不是先加载介绍窗口?
一切都很顺利,但是如果我尝试在介绍窗体中放置文本标签,其内容直到主窗体加载后才会显示。是否有其他方法可以避免这种情况,而不是先加载介绍窗口?
警告:本帖包含自我推广元素;o)
在这种情况下,我可能会使用一个闪屏窗体。我之前写过一篇博客文章(由这个SO Q&A触发),介绍了一个线程安全的闪屏窗体,可以与长时间运行的主窗体初始化一起使用。
简而言之,方法是使用ShowDialog,但在单独的线程上创建和显示窗体,以便不阻塞主线程。该窗体包含状态消息标签(当然也可以扩展为进度条)。然后有一个静态类提供线程安全的方法来显示、更新和关闭闪屏窗体。
压缩代码示例(有注释的代码示例,请查看博客文章):
using System;
using System.Windows.Forms;
public interface ISplashForm
{
IAsyncResult BeginInvoke(Delegate method);
DialogResult ShowDialog();
void Close();
void SetStatusText(string text);
}
using System.Windows.Forms;
public partial class SplashForm : Form, ISplashForm
{
public SplashForm()
{
InitializeComponent();
}
public void SetStatusText(string text)
{
_statusText.Text = text;
}
}
using System;
using System.Windows.Forms;
using System.Threading;
public static class SplashUtility<T> where T : ISplashForm
{
private static T _splash = default(T);
public static void Show()
{
ThreadPool.QueueUserWorkItem((WaitCallback)delegate
{
_splash = Activator.CreateInstance<T>();
_splash.ShowDialog();
});
}
public static void Close()
{
if (_splash != null)
{
_splash.BeginInvoke((MethodInvoker)delegate { _splash.Close(); });
}
}
public static void SetStatusText(string text)
{
if (_splash != null)
{
_splash.BeginInvoke((MethodInvoker)delegate { _splash.SetStatusText(text); });
}
}
}
使用示例:
SplashUtility<SplashForm>.Show();
SplashUtility<SplashForm>.SetStatusText("Working really hard...");
SplashUtility<SplashForm>.Close();
当然有。它被称为BackgroundWorker
。
下面是来自Figo Fei的代码片段,稍作修改以便解释:
private void button1_Click(object sender, EventArgs e)
{
progressBar1.Maximum = 100;
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// This would be the load process, where you put your Load methods into.
// You would report progress as something loads.
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
backgroundWorker1.ReportProgress(i); //run in back thread
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) //call back method
{
progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) //call back method
{
progressBar1.Value = progressBar1.Maximum;
}
你可以从主程序或MainForm构造函数中显示SplashForm,这并不重要。你所看到的是,在加载过程尚未完成时,没有处理任何消息,因此屏幕更新也不会发生。进度条是一个例外,它为此运行了自己的线程。
简单的解决方案是在更改标签后执行SplashForm.Update()
。稍微复杂一些的方法是启动一个带有消息泵(Application.Run)的单独线程。这里是一个SO问题,其中包含更多的线索。
Application.Run(new Form1());
调用Application.Run将启动消息循环,但您是否看到Form1构造函数在消息循环运行之前执行了?由于进度条逻辑在该构造函数中,因此没有运行可以分派表单绘制消息的机制。
我认为最好的方法是首先加载一个闪屏屏幕,然后启动一个工作线程(您可以使用BackgroundWorker),该线程执行耗时的工作。进度条将位于闪屏屏幕窗体上,并且您将定期更新它。完成工作后,您可以关闭闪屏屏幕并加载主窗体。