我花了很多时间使用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();
}
您可以用委托来包装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进行编译。
我发布了我自己问题的答案,因为我认为这将为社区增加价值。
1) 我想"简化"我的代码,如果最重要的发现之一是:
control.InvokeRequired
真的不需要。。。这几乎是既定的。重要的是,如果您在后台(或非UI)线程中,则需要调用控件。
2) 调用在控制树上向上移动,因此如果您有:
窗体>控件>控件内部控件>etc>etc
您只需要调用"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
}
}
既然您已经在使用后台工作程序,为什么不"滥用"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;
}
}