我已经痛苦地意识到,在事件驱动的GUI代码中,人们需要多么频繁地编写以下代码模式

private void DoGUISwitch() {
    // cruisin for a bruisin' through exception city
    object1.Visible = true;
    object2.Visible = false;
}

就变成:

private void DoGUISwitch() {
    if (object1.InvokeRequired) {
        object1.Invoke(new MethodInvoker(() => { DoGUISwitch(); }));
    } else {
        object1.Visible = true;
        object2.Visible = false;
    }
}

这在c#中是一种尴尬的模式,无论是记忆还是输入。有没有人想出某种捷径或构造来在一定程度上自动化这个?如果可以像object1.InvokeIfNecessary一样,将函数附加到对象上,无需执行所有这些额外的工作就可以进行检查,那就太棒了。可见= true类型快捷方式。

前面的回答讨论了每次只调用Invoke()的不可行性,即使这样,Invoke()语法也是低效的,而且处理起来仍然很尴尬。

有人找到什么捷径了吗?


当前回答

这是我在所有代码中一直使用的表单。

private void DoGUISwitch()
{ 
    Invoke( ( MethodInvoker ) delegate {
        object1.Visible = true;
        object2.Visible = false;
    });
} 

我是根据这篇博客文章得出的结论。这种方法没有让我失败过,所以我认为没有理由用检查InvokeRequired属性来使我的代码复杂化。

希望这能有所帮助。

其他回答

我宁愿使用方法Delegate的单个实例,而不是每次都创建一个新实例。 在我的情况下,我用来显示进度和(信息/错误)消息从一个后台工作者复制和铸造大数据从一个sql实例。在大约70000个进度和消息调用之后,我的表单停止工作并显示新消息。 这没有发生时,我开始使用一个单一的全局实例委托。

delegate void ShowMessageCallback(string message);

private void Form1_Load(object sender, EventArgs e)
{
    ShowMessageCallback showMessageDelegate = new ShowMessageCallback(ShowMessage);
}

private void ShowMessage(string message)
{
    if (this.InvokeRequired)
        this.Invoke(showMessageDelegate, message);
    else
        labelMessage.Text = message;           
}

void Message_OnMessage(object sender, Utilities.Message.MessageEventArgs e)
{
    ShowMessage(e.Message);
}

用法:

control.InvokeIfRequired(c => c.Visible = false);

return control.InvokeIfRequired(c => {
    c.Visible = value

    return c.Visible;
});

代码:

using System;
using System.ComponentModel;

namespace Extensions
{
    public static class SynchronizeInvokeExtensions
    {
        public static void InvokeIfRequired<T>(this T obj, Action<T> action)
            where T : ISynchronizeInvoke
        {
            if (obj.InvokeRequired)
            {
                obj.Invoke(action, new object[] { obj });
            }
            else
            {
                action(obj);
            }
        }

        public static TOut InvokeIfRequired<TIn, TOut>(this TIn obj, Func<TIn, TOut> func) 
            where TIn : ISynchronizeInvoke
        {
            return obj.InvokeRequired
                ? (TOut)obj.Invoke(func, new object[] { obj })
                : func(obj);
        }
    }
}

李的方法可以进一步简化

public static void InvokeIfRequired(this Control control, MethodInvoker action)
{
    // See Update 2 for edits Mike de Klerk suggests to insert here.

    if (control.InvokeRequired) {
        control.Invoke(action);
    } else {
        action();
    }
}

可以这样命名

richEditControl1.InvokeIfRequired(() =>
{
    // Do anything you want with the control here
    richEditControl1.RtfText = value;
    RtfHelpers.AddMissingStyles(richEditControl1);
});

不需要将控件作为参数传递给委托。c#自动创建闭包。

如果你必须返回一个值,你可以使用这个实现:

private static T InvokeIfRequiredReturn<T>(this Control control, Func<T> function)
{
    if (control.InvokeRequired) {
        return (T)control.Invoke(function);
    } else {
        return function();
    }
}

更新:

根据其他一些海报,Control可以概括为ISynchronizeInvoke:

public static void InvokeIfRequired(this ISynchronizeInvoke obj,
                                         MethodInvoker action)
{
    if (obj.InvokeRequired) {
        var args = new object[0];
        obj.Invoke(action, args);
    } else {
        action();
    }
}

DonBoitnott指出,与Control不同,ISynchronizeInvoke接口需要Invoke方法的对象数组作为动作的参数列表。


更新2

Mike de Klerk建议的编辑(参见插入点第一个代码片段中的注释):

// When the form, thus the control, isn't visible yet, InvokeRequired  returns false,
// resulting still in a cross-thread exception.
while (!control.Visible)
{
    System.Threading.Thread.Sleep(50);
}

请参阅下面ToolmakerSteve和nawfal对这个建议的评论。

你可以写一个扩展方法:

public static void InvokeIfRequired(this Control c, Action<Control> action)
{
    if(c.InvokeRequired)
    {
        c.Invoke(new Action(() => action(c)));
    }
    else
    {
        action(c);
    }
}

像这样使用它:

object1.InvokeIfRequired(c => { c.Visible = true; });

编辑:正如Simpzon在评论中指出的那样,你也可以将签名更改为:

public static void InvokeIfRequired<T>(this T c, Action<T> action) 
    where T : Control

以下是李、奥利弗和斯蒂芬回答的改进版/综合版。

public delegate void InvokeIfRequiredDelegate<T>(T obj)
    where T : ISynchronizeInvoke;

public static void InvokeIfRequired<T>(this T obj, InvokeIfRequiredDelegate<T> action)
    where T : ISynchronizeInvoke
{
    if (obj.InvokeRequired)
    {
        obj.Invoke(action, new object[] { obj });
    }
    else
    {
        action(obj);
    }
} 

模板允许灵活和无强制转换的代码,这是更可读的,而专用委托提供了效率。

progressBar1.InvokeIfRequired(o => 
{
    o.Style = ProgressBarStyle.Marquee;
    o.MarqueeAnimationSpeed = 40;
});