从UI线程强制更新GUI

81
在WinForms中,我如何从UI线程强制进行立即的UI更新?
我正在做的大概是:
label.Text = "Please Wait..."
try 
{
    SomewhatLongRunningOperation(); 
}
catch(Exception e)
{
    label.Text = "Error: " + e.Message;
    return;
}
label.Text = "Success!";

操作前标签文本未设置为“请稍等...”。

我使用另一个线程解决了这个问题,但代码变得复杂了,我希望简化代码。


5
在这里,将“SomewhatLongRunningOperation()”运行在另一个线程中是正确的答案。您不应该为任何与UI无关的事情占用UI线程。至于简化代码,您很可能可以简化对该其他线程的使用。 - cHao
13个回答

1

myControlName.Refresh()是在进行“相对长时间运行操作”之前更新控件的简单解决方案。

来源:https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.update?view=windowsdesktop-6.0 有两种重绘窗体及其内容的方式:

  1. 您可以使用Invalidate方法的一个重载与Update方法一起使用。
  2. 您可以调用Refresh方法,强制控件重新绘制自己和所有子控件。这相当于将Invalidate方法设置为true并与Update一起使用。

Invalidate方法控制何时绘制或重绘。Update方法控制何时进行绘制或重绘。如果您不使用Refresh而是将Invalidate和Update方法一起使用,则重绘的内容取决于您使用哪个Invalidate方法的重载。Update方法只是强制控件立即绘制,但是当您调用Update方法时,Invalidate方法控制要绘制什么。


0

我遇到了与属性Enabled相同的问题,我发现它引发了一个first chance exception,因为它不是线程安全的。我在这里找到了关于“如何从另一个线程更新C#中的GUI?”的解决方案https://dev59.com/wnRB5IYBdhLWcg3wUVtd#661706,并且它有效!


0
当我想要在“实时”(或基于数据更新或长时间运行的操作)中更新UI时,我使用一个辅助函数来“简化”代码(尽管这里可能看起来有些复杂,但它可以很好地扩展)。以下是我用于更新UI的代码示例:
    // a utility class that provides helper functions
    // related to Windows Forms and related elements
    public static class FormsHelperFunctions {

        // This method takes a control and an action
        // The action can simply be a method name, some form of delegate, or it could be a lambda function (see: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-expressions)
        public static void InvokeIfNeeded(this Control control, Action action)
        {

            // control.InvokeRequired checks to see if the current thread is the UI thread,
            // if the current thread is not the UI thread it returns True - as in Invoke IS required
            if(control.InvokeRequired)
            {
                // we then ask the control to Invoke the action in the UI thread
                control.Invoke(action);
            }
            // Otherwise, we don't need to Invoke
            else
            {
                // so we can just call the action by adding the parenthesis and semicolon, just like how a method would be called.
                action();
            }
        }

    }

    // An example user control
    public class ExampleUserControl : UserControl {

        /*
            //
            //*****
            // declarations of label and other class variables, etc.
            //*****
            //




            ...
        */

        // This method updates a label, 
        // executes a long-running operation, 
        // and finally updates the label with the resulting message.
        public void ExampleUpdateLabel() {

            // Update our label with the initial text
            UpdateLabelText("Please Wait...");

            // result will be what the label gets set to at the end of this method
            // we set it to Success here to initialize it, knowing that we will only need to change it if an exception actually occurs.
            string result = "Success";

            try {
                // run the long operation
                SomewhatLongRunningOperation(); 
            }
            catch(Exception e)
            {
                // if an exception was caught, we want to update result accordingly
                result = "Error: " + e.Message;
            }

            // Update our label with the result text
            UpdateLabelText(result);
        }

        // This method takes a string and sets our label's text to that value
        // (This could also be turned into a method that updates multiple labels based on variables, rather than one input string affecting one label)
        private void UpdateLabelText(string value) {

            // call our helper funtion on the current control
            // here we use a lambda function (an anonymous method) to create an Action to pass into our method
            // * The lambda function is like a Method that has no name, here our's just updates the label, but it could do anything else we needed
            this.InvokeIfNeeded(() => {

                // set the text of our label to the value
                // (this is where we could set multiple other UI elements (labels, button text, etc) at the same time if we wanted to)
                label.Text = value;
            });
        }

    }

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