1

我花了很多时间使用 Windows 窗体控件,但从后台工作线程开始 - 我认为这是一种很好的做法,因为您不希望在人们单击按钮时窗体被锁定。老实说,我通常在后台工作线程中执行几乎所有与 GUI 相关的操作,因此界面对用户响应很好(希望更多人会这样做!)。

所以我的问题是......每次我必须与控件交互时,我都必须“调用”它们,例如:

if (control.InvokeRequired)
{
    //
}

标准做法对吗?但是,这会导致我编写一些非常混乱的代码,因为几乎我拥有的每种控件类型,我都需要一个 MethodInvoker 委托或其他东西。它在我的保护中添加了数千行代码,而且非常耗时。

我目前有数百种“属性设置”方法,例如:

private void Safe_SetLableText(Label control, string text)
{
    if (control.InvokeRequired)
    {
        control.Invoke((MethodInvoker)delegate
        {
            control.Text = text;
        });
    }
    else
    {
        control.Text = text;
    }
}

那么,是否有其他技术或方法来做到这一点,或者某种方式能够始终改变控件的属性,无论控件是什么,无论我在哪个线程中?

类似于:(伪代码)

BackgroundWorker.RunWorkerAsync();

private void thing_to_do()
{
    // We are in a background thread now

    DoSomeDatabaseWorkThatTakesALongTime();

    InvokeAnyControls();

    // Do some stuff...
    controlX.Text = "123"
    controlY.Height = 300;
    controlZ.text = ControlA.text;

    RestoreAnyControls();
}
4

3 回答 3

3

您可以使用委托包装您的InvokeRequired代码,如下所示:

public static void Invoke2<TControl>(this TControl c, Action<TControl> code) where TControl : Control {

    if( c.InvokeRequired ) c.Invoke( delegate() { code(c); } );
    else code(c);
}

然后像这样使用它:

private void Safe_SetLableText(Label control, string text) {
    control.Invoke2( c => c.Text = text );
}

当然,您可能想要比 更好的名字Invoke2,但我希望这个想法与您同在。请注意,lambda 表达式语法是 C# 3.0 的一项功能,但Action<T>委托是 .NET 2.0 的一部分,因此只要您是 VS2008 或更高版本,它将针对 .NET Framework 2.0 进行编译。

于 2013-08-05T08:14:27.987 回答
1

我正在发布我自己问题的答案,因为我认为它会为社区增加价值。

1)我想“简化”我的代码,如果最重要的发现是:

control.InvokeRequired

真的不需要......它几乎是给定的。重要的是,如果您在后台(或非 UI)线程中,您可以依赖需要调用控件的事实。

2)调用“向上”传播控制树,所以如果你有:

窗体 > 控件 > 控件内的控件 > 等 > 等

您只需要调用“Form”(最上面),然后您就可以更改子元素的属性。

所以这是我与后台工作人员(或非 UI 线程)一起工作的干净简单的解决方案。我现在刚刚对此进行了测试,效果很好。

public partial class Form1: Form
{
    public Form1()
    {
        BackgroundWorker bgw = new BackgroundWorker();
        bgw.DoWork += new DoWorkEventHandler(this.bgDoWork);
        bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(this.bgComplete);
        bgw.RunWorkerAsync();
    }

    private void bgComplete(object sender, EventArgs e)
    {
        // You are not in the UI thread now, so you can Invoke without error
        this.Invoke((MethodInvoker)delegate
        {
            // Now you can change any property an any control within this form.
            // Remember "this" refers to Form1.
            this.label1.Text = "test123";
            this.label2.Text = "test456";
            this.label3.Text = this.label4.Text;

            // You can set progress bars too, not just label text
        }
    }

    private void bgDoWork(object sender, DoWorkEventArgs e)
    {
        // Do something that takes a long time
    }
}
于 2013-08-06T08:38:56.813 回答
0

由于您已经在使用后台工作人员,为什么不“滥用” OnProgressChanged

private void thing_to_do()
{
    // We are in a background thread now
    DoSomeDatabaseWorkThatTakesALongTime();

    BackgroundWorker.ReportProgress(1, "state");

    DoSomeMoreDatabaseWorkThatTakesALongTime();

    BackgroundWorker.ReportProgress(2, YourObjectHere);
}

void OnProgressChanged(ProgressChangedEventArgs progressArgs)
{
   switch(progressArgs.ProgressPercentage)
   {
       case 1:
          // Do some stuff...
          controlX.Text = "123"
          controlY.Height = 300;
          controlZ.text = ControlA.text;
       break;
       case 2:
          // other stuff
          YourObject obj = (YourObject) progressArgs.UserState;
          // wahtever...
       break;
       default:
       break;
   }

}
于 2013-08-05T08:41:31.950 回答