编写调用控件的扩展方法的更好方法
本文关键字:方法 扩展 更好 控件 调用 | 更新日期: 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());
因为我们已经指定了this
是button1
。
所以我做到了:
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());
现在我觉得,即使这样,还有一些比需要的输入。如果我指定this
是button1
,那么在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;
}