Winforms:如何显示“加载”窗体?

5

我有一个网格,当双击一行时会加载一个表单。然而需要加载大量数据,因此我希望显示一个简单的表单,上面写着“正在加载,请稍候...”。当所有加载完成后,表单必须消失。

这是我现在拥有的,但它不起作用:

调用具有大量数据的表单的代码:

FormWithLotData form = new FormWithLotData();
form.ShowDialog(this);

FormWithLotData的构造函数:

// Show load form
FormIsLoading frm = new FormIsLoading();
_CloseLoadForm closeForm = new _CloseLoadForm(frm.Close);
System.Threading.Thread thread = new System.Threading.Thread(frm.Show);

thread.Start();

InitializeComponent();

this.Visible = false;

LoadAllData();

this.Visible = true;

// Close load form
Invoke(closeForm);

希望你能帮我解决问题。
编辑: 我想在加载表单上显示一个动画gif。
解决方案: 我创建了一个后台工作者。DoWork事件处理所有的加载,并使用invoke()方法将节点添加到树视图中。现在,GUI不会挂起,用户也不会觉得应用程序挂起。
5个回答

6
您需要反转您的代码。
FormWithLotData的构造函数在UI线程中运行。这是必须显示您的FormIsLoading表单的线程。因此,不要尝试使用新线程显示此表单,而是使用它进行数据加载。
其他人建议的DoEvents方式最容易实现并且(可能?我自己从未做过)可能效果很好。
更好的模式是在工作线程上执行数据加载。在显示FormWithLotData之前,在后台线程上开始加载数据并显示您的Loading对话框。加载数据的方法应该有一个回调方法进入Loading对话框,以信号何时关闭它。一旦关闭,您就可以构造一个新的FWLD,将已经加载的数据传递给它,并显示它。
尝试在已调用表单之后加载数据会将您的UI与数据操作混合在一起,迫使您的表单不仅负责UI,还负责数据检索。对于KISS和Single Responsibility来说都不好,个人认为。
在更新后,似乎DoEvents将是您问题的唯一真正答案,但带有一些警告。
您将无法在构建树时同时以MODAL方式显示另一个表单。您仍然必须在表单的构造函数中执行繁重的工作。您仍然必须隐藏主表单并Show()(而不是ShowDialog)您的加载表单。您还必须在构建树的每个可能时刻调用DoEvents。这不是一个非常优雅的解决方案,但这可能是您目前最好的选择。

+1。比我的想法更好,而且应该解决了 GIF 不动画的问题... 这要复杂一些,但并不多,而且我认为值得努力。 - David
我的情况中,重载的部分是构建一个大型树形视图。如果我将此方法放入线程中,则无法访问树形视图,因为树形视图在另一个线程中。 - Martijn
@Martijn:你可以在那个线程上调用该对象的事件。如果你想要更新GUI对象,这就是你需要做的。 - galford13x
@Martijn,这很不幸。在WPF中,这相对容易。你可能仍然可以做到这一点。你需要做的是构建一个真正的Thread(即不使用ThreadPool),并将其公寓状态设置为STA。然后在工作线程中构造它,在完成时将其传回。然后,您需要将此预构建树添加到UI中,希望它不会因线程亲和性问题而爆炸。这似乎比您在应用程序中所需的要复杂得多。 - user1228
@Martijn更新了答案。在您的LoadAllData方法的每隔一行中添加一个DoEvents调用,看看是否适用于您。 - user1228

5

关于......怎么样?

FormIsLoading frm = new FormIsLoading();
frm.Show();
Application.DoEvents();

// ... load data ...

frm.Close();

1
谢谢,这个可行,但是……我的gif静止不动了。有解决这个问题的方法吗? - Martijn
@Martijn DoEvents(我认为)只会将消息队列推送一次,因此您必须在LoadAllData中的每个机会调用它。 - user1228

2

在 Form_Load 事件中添加以下内容:

this.Visible = true;
Application.DoEvents();

在任何其他处理发生之前。Application.DoEvents会导致UI以当前状态显示窗体,而通常情况下,当进行其他处理时,UI线程会被锁定。


我创建了一个加载事件,并将那段代码放入该事件中。现在我遇到了这个异常:Invoke或BeginInvoke不能在控件的窗口句柄被创建之前调用。 - Martijn

0
在你的表单内,你可以使用一个计时器控件来模拟加载过程,当进度条达到100%时,它会卸载表单或任何其他动画。 对于进度条代码,只需从工具箱中添加控件,然后编写以下代码即可。
ProgressBar1.Value = ProgressBar1.Value+1;
if(ProgressBar1.Value == 100)
{ 
  timer1.Enabled = false; this.Hide();
  MessageBox.Show("Complete Loading...");
}

0

不要直接在UI线程中执行LoadAllData(),而是启动一个后台线程来执行它。然后,在Form_Loaded事件处理程序中,使用一个AutoResetEvent等待后台数据检索线程发出信号。一旦收到信号,您就可以继续执行与数据相关的任何操作,例如将其绑定到UI界面。

出于各种原因,这种方法可能有些笨拙,但它可以帮助您入门。

编辑:我在上面的回答中有些懒惰...更好的选择是将一个委托(回调)传递给后台线程,当委托被调用(在数据检索完成后)时,它会自动切换回UI线程,并开始对数据进行必要的处理。


其实,我本来就想建议这个。但是...当你在等待ARE时阻塞UI线程时,你正在阻塞UI线程。所以整个后台工作程序都变得毫无意义。你需要的是一个回调函数,LoadAllData方法可以调用它来向UI线程发出数据已加载的信号。 - user1228
请注意我的评论,它有些“笨重”。尽管如此,它仍然有效,因为后台线程没有被阻塞,并且它是唤醒ARE的线程,这意味着UI线程可以继续执行。无论如何,我已经进行了编辑并提出了更好的建议。 - slugster
如果他的后台线程工作了十分钟,你的UI线程将被阻塞十分钟。用户会认为应用程序已经锁定,这正是@Mart试图避免的。你的更新是正确的。 - user1228

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