如何在Winforms中显示“正在加载,请稍候”消息以应对长时间加载的窗体?

52

我有一个表单,因为在表单上放置了许多控件,所以非常缓慢。

因此表单加载需要很长时间。

如何先加载表单,然后显示它,在加载过程中显示另一个带有消息"正在加载,请稍候"的表单?

13个回答

64

如果你对线程没有太多经验,那么使用一个单独的线程来显示简单的“请等待”消息就有些过度了。

一种更简单的方法是创建一个“请等待”表单,并将它作为一个无模态窗口显示在慢加载表单之前。一旦主表单加载完成,就隐藏“请等待”表单。

通过这种方式,你只使用一个主 UI 线程来首先显示“请等待”表单,然后再加载主表单。

唯一的限制是,由于线程正在忙于加载主表单,因此无法为“请等待”表单添加动画效果(如动画 GIF)。

PleaseWaitForm pleaseWait=new PleaseWaitForm ();

// Display form modelessly
pleaseWait.Show();

//  ALlow main UI thread to properly display please wait form.
Application.DoEvents();

// Show or load the main form.
mainForm.ShowDialog();

1
@Sadegh,您需要在“请稍候”窗体上使用一个控件,该控件内部创建一个线程来播放图像动画。我认为标准的PictureBox不支持此功能。但是,如果您确实需要动画效果,则可以查看David链接的一些页面。 - Ash
7
永远不要使用 Application.DoEvents(); - CodeTherapist
31
@C Sharper,你的评论可能被截断了,最后的“inappropriately”一词丢失了。 - Ash
从没想过这样做,省了我很多时间。运行得非常完美。谢谢! - CODe
1
问题是,主线程在表单加载完成之前不会渲染任何内容。因此,即使我在主表单加载的最开始调用另一个表单,它仍然只会在主表单加载后变为可见,从而打败了加载屏幕的整个目的。 - Spero
显示剩余2条评论

32
我看了大部分已发布的解决方案,但是发现了一个不同的解决方案,我更喜欢它。它很简单,不使用线程,并且对我想要的内容有效。 http://weblogs.asp.net/kennykerr/archive/2004/11/26/where-is-form-s-loaded-event.aspx 我在文章中添加了解决方案并将代码移动到所有我的表单继承的基类中。现在,只需在任何需要等待对话框的表单的frm_load()事件中调用一个函数:ShowWaitForm()。以下是代码:
public class MyFormBase : System.Windows.Forms.Form
{
    private MyWaitForm _waitForm;

    protected void ShowWaitForm(string message)
    {
        // don't display more than one wait form at a time
        if (_waitForm != null && !_waitForm.IsDisposed) 
        {
            return;
        }

        _waitForm = new MyWaitForm();
        _waitForm.SetMessage(message); // "Loading data. Please wait..."
        _waitForm.TopMost = true;
        _waitForm.StartPosition = FormStartPosition.CenterScreen;
        _waitForm.Show();
        _waitForm.Refresh();

        // force the wait window to display for at least 700ms so it doesn't just flash on the screen
        System.Threading.Thread.Sleep(700);         
        Application.Idle += OnLoaded;
    }

    private void OnLoaded(object sender, EventArgs e)
    {
        Application.Idle -= OnLoaded;
        _waitForm.Close();
    }
}

MyWaitForm是您创建的类似于等待对话框的表单名称。我添加了一个SetMessage()函数来自定义等待表单上的文本。


它说我的等待表单不存在。 - CDrosos
我发现这是最好的方法,我设计了自己的等待窗体,它运行良好。 - Bharat
在等待表单打开时,在屏幕上放置静态图像或文本是一个绝妙的主意。我改变了对ShowWaitForm的调用,并将其放置在基类中。然后,我在基类中添加了一个布尔属性,这样我就可以设置该属性,它会加载等待表单。太棒了,解决了问题! - Nathan

19

你想了解“启动画面”。

显示另一个“启动”窗体,并等待处理完成。

这里有一个示例,展示如何实现它。


嗨,David,链接显示404。 - Matas Vaitkevicius

12
一个简单的解决方案:
using (Form2 f2 = new Form2())
{
    f2.Show();
    f2.Update();

    System.Threading.Thread.Sleep(2500);
} // f2 is closed and disposed here

然后将您的加载替换为睡眠。
这是有意阻塞UI线程。


我喜欢这个简单的解决方案。是的,它不能用于动画gif,但我只是在对话框上贴了一个沙漏图像,就可以继续了。 - Ocean Airdrop

10

让"加载屏幕"只在特定时间显示的另一种方法是,在事件之前放置它,并在事件完成其工作后将其关闭。

例如:您想为将结果保存为MS Excel文件的事件显示加载表单,并在处理完成后将其关闭,可以按照以下步骤操作:

LoadingWindow loadingWindow = new LoadingWindow();

try
{
    loadingWindow.Show();                
    this.exportToExcelfile();
    loadingWindow.Close();
}
catch (Exception ex)
{
    MessageBox.Show("Exception EXPORT: " + ex.Message);
}

或者你可以将loadingWindow.Close()放在finally块中。


1
这几乎永远不会起作用。前台线程继续执行 exportToExcelFile,而在显示 loadingWindow 之前就已经忙碌了。 - rune711

5

我在一个名为FormWait的表单中放置了一些动画gif,然后我这样调用它:

// show the form
new Thread(() => new FormWait().ShowDialog()).Start();

// do the heavy stuff here

// get the form reference back and close it
FormWait f = new FormWait();
f = (FormWait)Application.OpenForms["FormWait"];
f.Close();

3
我尝试使用这个,但是遇到了一个IO非法线程交叉异常。我通过使它“线程安全”进行了修改。虽然这不是最好的做法,但对于一个简单的等待表单来说是可以接受的:将f.Close()替换为 f.Invoke(new ThreadStart(delegate {f.Close();})); - Schrodo_Baggins
我在这里添加了一个经过测试的解决方案:https://dev59.com/VHDYa4cB1Zd3GeqPAG17#45406217 - user2288580

5

我通常会这样做。

        NormalWaitDialog/*your wait form*/ _frmWaitDialog = null;


        //Btn Load Click Event
        _frmWaitDialog = new NormalWaitDialog();
        _frmWaitDialog.Shown += async (s, ee) =>
        {
            await Task.Run(() =>
           {
               // DO YOUR STUFF HERE 
               // And if you want to access the form controls you can do it like this
               this.Invoke(new Action(() =>
               {
                   //here you can access any control of form you want to access from cross thread! example
                   TextBox1.Text = "Any thing!";
               }));

               //Made long running loop to imitate lengthy process
               int x = 0;
               for (int i = 0; i < int.MaxValue; i++)
               {
                   x += i;
               }

           }).ConfigureAwait(true);
            _frmWaitDialog.Close();
        };
        _frmWaitDialog.ShowDialog(this);

我尝试过类似这样的操作,但当我从资源管理器中打开与链接文件类型相关联的应用程序时,它会导致我的应用程序失去焦点... - Nyerguds
1
.ConfigureAwait(true) 是默认值。 - Panagiotis Kanavos

5

您应该创建一个后台线程来创建和填充表单。这将允许您的前台线程显示加载消息。


4

3

当您同时拥有一个动画图像时,最好的方法是:

1- 您必须创建一个“WaitForm”,该表单接收将在后台执行的方法。 就像这个:

public partial class WaitForm : Form
{
    private readonly MethodInvoker method;

    public WaitForm(MethodInvoker action)
    {
        InitializeComponent();
        method = action;
    }

    private void WaitForm_Load(object sender, EventArgs e)
    {
        new Thread(() =>
        {
            method.Invoke();
            InvokeAction(this, Dispose);
        }).Start();
    }

    public static void InvokeAction(Control control, MethodInvoker action)
    {
        if (control.InvokeRequired)
        {
            control.BeginInvoke(action);
        }
        else
        {
            action();
        }
    }
}

2 - 您可以像这样使用Waitform

private void btnShowWait_Click(object sender, EventArgs e)
{
    new WaitForm(() => /*Simulate long task*/ Thread.Sleep(2000)).ShowDialog();
}

非常好用,谢谢!+1 - JeffS

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