C# 是否有一种简单的方法来声明控件并向其订阅事件处理程序?

C# 是否有一种简单的方法来声明控件并向其订阅事件处理程序?,c#,C#,通常,当我将C#与XAML或Xamarin一起使用时,我倾向于创建对象并直接指定属性,如下所示: StackLayout mainStackLayout = new StackLayout() { Padding = 15 }; mainStackLayout.Children.Add(new Button() { Text = "Clicky!", FontSize = 12 } 然后,当我意识到我想稍后为其分配一个事件处理程序时,我回来后必须执行以下操作: StackLa

通常,当我将C#与XAML或Xamarin一起使用时,我倾向于创建对象并直接指定属性,如下所示:

StackLayout mainStackLayout = new StackLayout() { Padding = 15 };

mainStackLayout.Children.Add(new Button() {
    Text = "Clicky!",
    FontSize = 12
}
然后,当我意识到我想稍后为其分配一个事件处理程序时,我回来后必须执行以下操作:

StackLayout mainStackLayout = new StackLayout() { Padding = 15 };

var btn = new Button() { Text = "Clicky!", FontSize = 12 }
btn.Clicked += (sender, args) => { DisplayAlert("Hello!"); }
mainStackLayout.Children.Add(btn)

我真的很喜欢使用lambdas来实现这一点(一般来说),但我一直想知道是否有一种方法可以将这两种方法结合起来,并向控件订阅一个事件处理程序,而不必使用临时变量?

否,不能在初始值设定项中指定
事件

我一直不明白为什么必须通过+=运算符来分配它们

这就是
事件
关键字的主要用途。事件是围绕委托的一个特殊属性,提供了超强的封装<代码>+=
-=
是唯一允许的操作

让您感到不安的是,代理/事件是多播的。允许一个简单的
foo.Click=myHandler可能会覆盖其他订阅服务器<代码>+=
表示添加到列表中。和
+=
与初始值设定项语法不兼容

因此需要一个特殊的特性,需要一个特殊的案例来允许初始值设定项中的
+=
,或者重新定义
=
来表示那里的事件
+=


但是,正如俗话所说,“特性以-100分开始…”

这有一个丑陋的解决方法-利用事实属性可以拥有setter逻辑。这仅适用于您可以控制的类型,但是:

public class Button2 : Button
{
    public EventHandler Clicked2
    {
        get { return this.Clicked; }
        set
        {
            this.Clicked += value;
        }
    }
}
这样使用:

Button2 btn = new Button2()
{
    Text = "Clicky",
    FontSize = 12,
    Clicked2 = new EventHandler( (s,e) => this.DisplayAlert("Hello!") )
};
Button btn = new Button()
{
    FontSize = 12,
    Content = "button text",
    InputBindings =
    {
        { Button.ClickEvent, (s, e) => DisplayFoo("clicked!") },
        { Button.LoadedEvent, (s, e) => DisplayFoo("loaded!") }
    }
};

我刚刚发现了另一个可能有用的黑客

在C#6中,集合初始化语法以前要求对象实现
IEnumerable
,并有一个名为
Add
(使用duck类型,它不需要实现
IList

在C#7中,
Add
方法要求已经放宽,现在可以接受扩展方法,尽管
IEnumerable
要求仍然存在

因此,我们可以使用扩展方法
Add
来“注入”我们的多语句函数(添加事件处理程序),我们只需要通过使用
IEnumerable
的成员找到一种方法

现在,虽然
System.Windows.Controls.Control
本身没有实现
IEnumerable
,但我们可以检查继承层次结构的成员,看看是否可以锁定到
UIElement.CommandBindings
-或任何其他具有所需对象内部引用的集合成员,例如,
InputBindings
(完全巧合的是,
InputBindings
可以用于设置事件处理程序,但这是一种误导)

因此,如果我们有:

static class Extensions
{
    private static readonly FieldInfo _ownerField = typeof(InputBindingCollection).GetField("_owner", BindingFlags.Instance | BindingFlags.NonPublic);

    public static void Add(this InputBindingCollection list, String eventName, Delegate handler)
    {
        Object ownerValue = _ownerField.GetValue( list );

        DependencyObject owner = (DependencyObject)ownerValue;
        // We assume it's a XAML Control instance:
        Control ownerControl = (Control)owner;

        switch( eventName )
        {
            case "click":
                ownerControl.MouseDown += (MouseButtonEventHandler)handler;
                break;
            case "keydown":
                ownerControl.KeyDown += (KeyEventHandler)handler;
            break;
        // etc...
        }
    }
}
…我们现在可以使用初始值设定项语法添加事件处理程序或调用任何多语句代码

在您的情况下,我现在可以添加如下事件处理程序:

Button btn = new Button()
{
    FontSize = 12,
    Content = "button text",
    InputBindings =
    {
        { "click", (MouseButtonEventHandler)( (s, e) => DisplayFoo("clicked!") ) }
    }
};
这是一个巨大的黑客攻击,它使用反射来获取目标
UIElement
对象-我认为可以通过使用另一种方法来获取
UIElement
-也许可以通过将
InputBinding
的一个偷偷的子类传递到
InputBindingCollection
中,通过回调来捕获它

事件的显式命名也可能得到改进

更新: 我看到大多数XAML组件(至少在WPF中)使用
RoutedEvent
对象来识别事件——这意味着我们可以使其更简单,更强类型化,而无需借助字符串破解:

static class Extensions
{
    private static readonly FieldInfo _ownerField = typeof(InputBindingCollection).GetField("_owner", BindingFlags.Instance | BindingFlags.NonPublic);

    public static void Add(this InputBindingCollection list, RoutedEvent routedEvent, RoutedEventHandler handler)
    {
        UIElement owner = (UIElement)_ownerField.GetValue( list );;
        owner.AddHandler( routedEvent, handler );
    }
}
可以按如下方式添加事件处理程序:

Button2 btn = new Button2()
{
    Text = "Clicky",
    FontSize = 12,
    Clicked2 = new EventHandler( (s,e) => this.DisplayAlert("Hello!") )
};
Button btn = new Button()
{
    FontSize = 12,
    Content = "button text",
    InputBindings =
    {
        { Button.ClickEvent, (s, e) => DisplayFoo("clicked!") },
        { Button.LoadedEvent, (s, e) => DisplayFoo("loaded!") }
    }
};
由于方法重载,我们仍然可以使用我以前的策略支持非
RoutedEvent
类型的事件**但我们也可以将其概括为接受任何
操作
回调:

static class Extensions
{
    private static readonly FieldInfo _ownerField = typeof(InputBindingCollection).GetField("_owner", BindingFlags.Instance | BindingFlags.NonPublic);

    public static void Add(this InputBindingCollection list, RoutedEvent routedEvent, RoutedEventHandler handler)
    {
        UIElement owner = (UIElement)_ownerField.GetValue( list );;
        owner.AddHandler( routedEvent, handler );
    }

    public static void Add(this InputBindingCollection list, String eventName, Delegate handler)
    {
        UIElement owner = (UIElement)_ownerField.GetValue(list);
        EventInfo eventInfo = owner.GetType().GetEvent( eventName, BindingFlags.Instance | BindingFlags.Public );
        eventInfo.AddEventHandler( owner, handler );
    }

    public static void Add<T>(this InputBindingCollection list, Action<T> callback)
        where T : UIElement
    {
        T owner = (T)_ownerField.GetValue(list);
        callback( owner );
    }
}
注意:您需要指定回调参数的类型,以便类型推断可以填写
操作的
T

你知道-我很惊讶这是多么好的工作。我甚至可以在自己的项目中使用它

更新2 我花了一些时间来研究它是否能够支持反射不可用的场景——通过避免反射
InputBindingCollection.\u owner
通过子类化
InputBinding
,但是不幸的是,inheritace上下文的
DependencyObject
并没有暴露在任何地方

我又看了一眼
BindingGroup
,这是唯一一个通过
BindingGroup.Owner
(所有
Control
类型都有)公开
InheritanceContext
的类,但不幸的是,没有办法找到一个
IEnumerable
来附加
Add
方法

更新3 我意识到可以使用另一种方法,利用对象初始值设定项表达式的“发出”值是对象本身这一事实(与
void
)相反),因此您可以这样做:

static class Extensions
{
    public static T Exec<T>(this T item, Action<T> callback)
    {
        callback( item );
        return item;
    }
}


Button btn = new Button()
{
    FontSize = 12,
    Context = "button text"
}.Exec( b => {
    b.Click + (s,e) => ... ;
    b.DoubleClick + (s,e) => ... ;
} )
静态类扩展
{
公共静态T Exec(此T项,动作回调)
{
回收(项目);
退货项目;
}
}
按钮btn=新按钮()
{
FontSize=12,
Context=“按钮文本”
}.Exec(b=>{
b、 点击+(s,e)=>;
b、 双击+(s,e)=>;
} )

您不应该在C#中创建视图级对象,而应该使用声明性XAML。为什么要使用这种方法?因为我是根据API中的数据自动生成网格的,所以我发现只使用C#Content页面更容易。你可以为IList编写一个扩展方法,替换Add(),从而返回添加的对象而不是int。Fluent样式是,对于粉丝们来说。XAML的DataGrid支持动态列和布局-所以我仍然不相信这是最好的方法。我建议您一直使用样式2编写代码,毕竟没有点击处理程序的按钮没有多大作用。事实上,我已经向MS发送了关于实现