为什么控制不会在进程中更新/刷新

14

我有一个Windows表单(C#.NET)和一个状态标签,但是在事件处理方法的过程中似乎无法更新。我的代码如下...

    void Process_Completed(object sender, EventArgs e)
    {

        string t = "Process is finished!";
        this.Invoke(new StatusLabelUpdator(updateStatusLabel), new object[] { t });
    }

    void Process_Started(object sender, EventArgs e)
    {
        string t = "Process has begun";
        this.Invoke(new StatusLabelUpdator(updateStatusLabel), new object[] { t });
    }

    private delegate void StatusLabelUpdator(string text);
    private void updateStatusLabel(string text)
    {
        StatusLabel1.Text = text;
        statusStrip1.Invalidate();
        statusStrip1.Refresh();
        statusStrip1.Update();
    }

当我运行代码时,一旦进程开始,将触发Process_Started方法,几秒钟后将触发Process_Completed方法。由于某种原因,我无法让状态标签显示“进程已启动”。它只会显示“进程已完成!”。正如您所看到的,我尝试了无效、刷新和更新包含状态标签的状态条,但都没有成功。我不能对状态标签本身调用update/refresh/invalidate方法,因为这些方法不可用。我做错了什么?
附加信息:
"process"是通过表单上的按钮点击来启动的,该按钮调用一个独立类中的方法,该方法如下:
public void DoSomeProcess()
{
    TriggerProcessStarted();
    System.Threading.Thread.Sleep(2000);   // For testing..
    TriggerProcessComplete();
}

在TriggerProcessxxxx方法内,我使用以下代码触发事件...

var EventListeners = EH.GetInvocationList();    //EH is the appropriate EventHandler
if (EventListeners != null)
{
    for (int index = 0; index < EventListeners.Count(); index++)
    {
        var methodToInvoke = (EventHandler)EventListeners[index];
        methodToInvoke.BeginInvoke(this, EventArgs.Empty, EndAsyncEvent, new object[] { });
    }
}

最后,我已经在updateStatusLabel方法中添加了Application.DoEvents(),但是并没有起到帮助作用。这是我的更新方法。

private void updateStatusLabel(string text)
{
    StatusLabel1.Text = text;
    statusStrip1.Refresh();
    Application.DoEvents(); 
}

所以,我猜测“处理”是在UI线程上进行的,但事件处理程序在它自己的线程上调用,然后将控件更新调回到UI线程上。这种做法很愚蠢吗?注意:包含DoSomeProcess()方法的类位于我正在引用的单独的.NET类库中。


1个回答

19
如果您在UI线程上进行处理,它将无法执行其他任何操作(例如重绘更新的标签)而处理正在运行。因此,例如,如果处理是发生的因为用户点击了一个按钮并由按钮单击处理程序触发(而不是显式地将其放置在另一个线程上),则正在UI线程上运行。即使您更新了标签的文本,直到它收到绘画消息,它才会被绘制出来,在这一点上,它可能正在繁忙地处理您的处理。
答案是在单独的线程上进行长时间运行的处理。黑客(IMHO)是使用Application.DoEvents让UI线程在处理期间执行一些UI工作。如果在更新标签之后并在开始处理之前放置其中一个,那么该标签很有可能会被重新绘制。但是,在处理期间,不能处理更多的绘画事件(导致当某人将另一个应用程序窗口移动到您的应用程序上方并返回时,窗口绘制一半等问题)。因此,我称其为黑客(即使,呃,嗯,我已知道如何做 :-) )。 编辑根据您的编辑进行更新:
Re 我假设DoSomeProcess是从UI线程触发的(例如,直接响应按钮单击或类似操作)。如果是这样的话,那么是的,你的处理肯定在UI线程上。因为TriggerProcessStarted通过BeginInvoke异步触发你的回调函数,所以你不知道它何时运行,但无论如何,你的代码立即启动处理,不会让出控制权,因此没有其他人能够获取该线程。由于这是UI线程,对委托的调用将阻塞Invoke调用来设置标签的文本,在此期间它必须等待正在繁忙处理的UI线程。(并且这是假设它被安排在不同的线程上;我不能百分之百地说服自己,因为Microsoft有两个不同的BeginInvoke——我IRC其中一个设计者已经承认这是一个非常愚蠢的想法——而且我已经有一段时间没有与这些东西打交道了。)
如果你使TriggerProcessStarted调用你的回调函数同步,那么你应该没问题。但是最好将处理(如果它不涉及UI)安排在自己的线程上。

我尝试了你的“hack”,但似乎没有帮助。我现在已经在上面的问题中添加了更多细节。 - PICyourBrain
你说得对,我把 BeginInvoke 留了下来,但是将按钮单击事件更改为在其自己的线程上开始 DoSomeWork。这解决了问题! - PICyourBrain
对于那些似乎找不到 DoEvents 的人,请尝试访问 https://dev59.com/yG855IYBdhLWcg3wKw5Y - Zukaru

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