初始化主窗体时的进度条

3
我有一个Windows表单应用程序,在加载主窗口之前需要加载许多东西。我认为这将需要一个进度条,所以我想使用我的主窗口的构造函数显示包含进度条控件的另一个窗体。
一切都很顺利,但是如果我尝试在介绍窗体中放置文本标签,其内容直到主窗体加载后才会显示。是否有其他方法可以避免这种情况,而不是先加载介绍窗口?
4个回答

5

警告:本帖包含自我推广元素;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();

Fredrik,界面很好,但我想知道一个ThreadPool线程是否是最佳选择。加载可能需要几秒钟,从技术上讲,你应该使用STA线程。 - H H
非常感谢。这将来会非常有用。 - G Berdal
我使用了Fredrik的帖子中的代码,为更新进度条添加了一个接口方法,并将其编译成了.dll文件。它运行得非常好,比我看到的其他解决方案简单得多。谢谢!!! - Mark Ainsworth

2

当然有。它被称为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;
    }

希望这能对你有所帮助。

+1 对于 BackgroundWorker 的引用 - 令人惊讶的是有多少人实际上不知道这个类的存在和简单性。 - Ian Kemp
1
我非常怀疑 BGW 是正确的选择 - 它需要一个运行的 MessagePump(用于 ProgressChange),这也是问题的起因。 - H H
问题在于UI线程被阻塞了。将BGW放入其中将会打开UI线程,使ProgressBar能够运行到100%。这就是问题所在,ProgressChange事件将会触发UI线程。这就是BGW的用途。因此,我完全不同意你的看法Henk,也不同意你的反对票。 - Kyle Rosendo
问题的结构是为了展示一个不同的表单,因为长时间加载需要进度条。我的解决方案旨在消除对第二个表单的需求,并在第一个表单上使用进度条。您的解决方案旨在保留启动画面。因此,这意味着我们都是正确的,只是采用了不同的解决方案,如果这样说的话。 - Kyle Rosendo

1

你可以从主程序或MainForm构造函数中显示SplashForm,这并不重要。你所看到的是,在加载过程尚未完成时,没有处理任何消息,因此屏幕更新也不会发生。进度条是一个例外,它为此运行了自己的线程。

简单的解决方案是在更改标签后执行SplashForm.Update()。稍微复杂一些的方法是启动一个带有消息泵(Application.Run)的单独线程。这里是一个SO问题,其中包含更多的线索。


谢谢Henk,这就是我需要的。现在一切都正常了——我从未想过更新表单,因为所有值最初都在设计模式下设置了... - G Berdal

0
问题很可能是因为您在尝试显示进度条窗体时没有运行消息循环。在您的应用程序入口点中应该有一行代码,类似于以下内容。
Application.Run(new Form1());

调用Application.Run将启动消息循环,但您是否看到Form1构造函数在消息循环运行之前执行了?由于进度条逻辑在该构造函数中,因此没有运行可以分派表单绘制消息的机制。

我认为最好的方法是首先加载一个闪屏屏幕,然后启动一个工作线程(您可以使用BackgroundWorker),该线程执行耗时的工作。进度条将位于闪屏屏幕窗体上,并且您将定期更新它。完成工作后,您可以关闭闪屏屏幕并加载主窗体。


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