编写调用控件的扩展方法的更好方法

本文关键字:方法 扩展 更好 控件 调用 | 更新日期: 2023-09-27 17:49:35

我有这个通用函数来调用WinForm控件:

public static void Invoke(this Control c, Action action)
{
    if (c.InvokeRequired)
        c.TopLevelControl.Invoke(action);
    else
        action();
}

我正在考虑通过引入更严格的约束来防止无意义的事情,从而使它变得更好,可能像:

button1.Invoke(() => list.Add(1));

也可以有冗余的输入,如:

button1.Invoke(() => button1.Hide());

因为我们已经指定了thisbutton1

所以我做到了:

public static void Invoke<T>(this T c, Action<T> action) where T : Control
{
    if (c.InvokeRequired)
        c.TopLevelControl.Invoke(action);
    else
        action(c);
}

现在我必须打电话,

button1.Invoke((c) => c.Hide());

button1.Invoke((c) => button1.Hide());

现在我觉得,即使这样,还有一些比需要的输入。如果我指定thisbutton1,那么在lambda表达式中,我不想再次指定一个虚拟变量c来告诉在哪里操作。我能再把它剪短一点吗?比如

button1.Invoke(Hide);

button1.Hide.Invoke();
c#中的

?

编写调用控件的扩展方法的更好方法

为了构建其他答案,我将把它放在一个单独的扩展类中。

public static void Invoke<T>(this T c, Action<T> action) where T : Control
    {
        if (c.InvokeRequired)
            c.Invoke(new Action<T, Action<T>>(Invoke), new object[] { c, action });
        else
            action(c);
    }

这将防止在交叉线程时抛出TargetParameterCountException

调用:

button1.Invoke(x => x.Hide());

首先让我说你可能想多了——简短的代码是一件很棒的事情,但是对于任何试图阅读代码的人来说,它开始变得困惑。

现在,你的第一个建议:

button1.Invoke(Hide);

可以工作,如果你使:

button1.Invoke(button1.Hide); 

,否则编译器无法知道在哪里查找方法Hide()。它甚至可能导致一些奇怪的行为,例如,如果所有这些代码都在某个派生类中,像这样:

class A : Control {
    public A() {
         Button button1=new Button();
         button1.Invoke(Hide);
    }
}

现在它将编译,但隐藏()方法将是整个控件的隐藏()方法,而不是按钮!实现这一点的方法很简单:

public static void Invoke(this Control c, Action action) {
    c.Invoke(action);
}

后一种方式:

button1.Hide().Invoke();

即使不添加扩展方法也可以工作,您只需要这样做:

((Action)button1.Hide).Invoke();

这当然意味着在当前线程中调用Hide()方法,这可能不是您想要的。所以改成:

((Action)button1.Hide).Invoke(button1);
public static void Invoke(this Action action, Control c) {
    c.Invoke(action);
}

可以使用SynchronizationContext。Post或SynchronizationContext。发送让框架将动作封送到UI线程,无论是Windows窗体还是WPF。静态的SynchronizationContext。当前方法将为您的应用程序类型返回适当的同步上下文。

Post异步执行,而Send阻塞,直到动作完成。

下面的代码将异步隐藏按钮:

SynchronizationContext.Current.Post(_=>button1.Hide(),null);

我会选择:

public static void Invoke<T>(this T c, Action<T> action) where T : Control
{
    if (c.InvokeRequired)
        c.TopLevelControl.Invoke(action);
    else
        action(c);
}

button.Invoke(c => c.Hide());

这是一个最干净的(你给原来指定的按钮回执行动作)和最安全的(你不需要指定button1两次…它作为lambda的参数返回给您。我相信这是一个优雅的语法

由于c#的语法限制,它肯定不能像button1.Invoke(Hide);button1.Hide.Invoke();那样做。

但是如果你愿意放弃智能感知,你可以把它缩短一点。缺点是,一些通常可以在编译时检测和修复的错误(如打字错误或参数不匹配)将成为运行时错误。有时候可以接受,有时候不行。

下面是一个用法示例:
button1.Invoke("Hide");

button1.Invoke("ResumeLayout", true);

解决方案:

internal static class ExtensionMethods
{
    internal static object Invoke<TControl>(this TControl control,
        string methodName, params object[] parameters)
        where TControl : Control
    {
        object result;
        if (control == null)
            throw new ArgumentNullException("control");
        if (string.IsNullOrEmpty(methodName))
            throw new ArgumentNullException("methodName");
        if (control.InvokeRequired)
            result = control.Invoke(new MethodInvoker(() => Invoke(control,
                methodName, parameters)));
        else
        {
            MethodInfo mi = null;
            if (parameters != null && parameters.Length > 0)
            {
                Type[] types = new Type[parameters.Length];
                for (int i = 0; i < parameters.Length; i++)
                {
                    if (parameters[i] != null)
                        types[i] = parameters[i].GetType();
                }
                mi = control.GetType().GetMethod(methodName,
                    BindingFlags.Instance | BindingFlags.Public,
                    null,  types, null);
            }
            else
                mi = control.GetType().GetMethod(methodName,
                    BindingFlags.Instance | BindingFlags.Public);
            if (mi == null)
                throw new InvalidOperationException(methodName);
            result = mi.Invoke(control, parameters);
        }
        return result;
    }