如何在循环中添加事件处理程序?

时间:2016-11-13 20:12:08

标签: c# .net event-handling

我正在尝试在C#/ .NET中创建一个托盘图标,到目前为止,我有这个代码可以运行:

        ....

        Icon i = new Icon("favicon.ico");
        ContextMenu cm = new ContextMenu();
        ni.Icon = i;            

        MenuItem delMi = new MenuItem("Delete stuff");
        MenuItem closeMi = new MenuItem("Close");
        MenuItem testMi = new MenuItem("Test");

        cm.MenuItems.Add(testMi);
        cm.MenuItems.Add(delMi);
        cm.MenuItems.Add(closeMi);

        testMi.Click += TestMi_Click;
        delMi.Click += DelMi_Click;
        closeMi.Click += CloseMi_Click;

        ni.ContextMenu = cm;
    }

    private void TestMi_Click(object sender, EventArgs e)
    {
        // Test event here
    }

    private void CloseMi_Click(object sender, EventArgs e)
    {
        // Close event here
    }

    private void DelMi_Click(object sender, EventArgs e)
    {
        // Delete event here
    }

但是我试图通过一个函数来分离代码,该函数返回一个MenuItem个实例的数组,并且有一个循环将它们添加到ContextMenu,但我不知道如何将click事件处理程序添加到循环中的MenuItem实例:

        ....
        Icon i = new Icon("favicon.ico");
        ContextMenu cm = new ContextMenu();
        ni.Icon = i;            

        MenuItem[] miArray = getArrayMI();

        foreach(MenuItem mi in miArray)
        {
            cm.MenuItems.Add(mi);

            //Not sure what to do here
            mi.Click += mi
        }

        // How do I put this section into the loop instead 
        // of adding the event handlers one by one?  
        testMi.Click += TestMi_Click;
        delMi.Click += DelMi_Click;
        closeMi.Click += CloseMi_Click;

        ni.ContextMenu = cm;
    }

    private MenuItem[] getArrayMI( )
    {
        MenuItem[] miArray = { new MenuItem("Delete stuff"), new MenuItem("Close"), new MenuItem("Test") };
        return miArray;
    }

    private void TestMi_Click(object sender, EventArgs e)
    {
        // Test event here
    }

    private void CloseMi_Click(object sender, EventArgs e)
    {
        // Close event here
    }

    private void DelMi_Click(object sender, EventArgs e)
    {
        // Delete event here
    }

我唯一能想到的就是做这样的事情:

    foreach(MenuItem mi in miArray)
    {
        cm.MenuItems.Add(mi);

        mi.Click += mi.ToString() + "_Click";
    }

2 个答案:

答案 0 :(得分:1)

我认为抽象原始代码并不是一个坏主意,但我建议以不同的方式查看抽象。我建议在模型中实现某种视图分离 - MVC,MVP,MVVM等。这样,当点击发生时实际发生的代码被从视图抽象到另一层代码中。

例如,考虑这样的事情(没有IDE写作,所以请原谅错别字):

public interface IContextAction
{
    string DisplayName { get; }
    Action Invoke { get; }
}


public class WindowViewModel
{
    public IEnumerable<IContextAction> ContextActions { get; private set; }
    /* ... */
}


    /* ... */
    ContextMenu cm = new ContextMenu();
    foreach (IContextAction action in viewModel.ContextActions)
    { 
        MenuItem item = new MenuItem(action.DisplayName);
        cm.MenuItems.Add(item);
        item.Click += (sender,args) => action.Invoke();
    }

答案 1 :(得分:0)

我同意这一评论意见,至少对于你发布的代码示例,没有必要改进&#34;代码。它已经是实现该特定逻辑的合理方式。此外,我倾向于避免依赖命名约定将特定代码绑定到特定的运行时对象。这样做会导致脆弱(即容易破坏)的实现,并限制您更改代码名称的能力(例如,为了解决一些不相关的命名方面,否则会提供更易读的代码)。

那就是说,如果你真的想这样做,你可以。这是一个Minimal, Complete, and Verifiable code example,它说明了如何根据对象的名称为事件处理程序创建委托实例,并订阅对象的事件:

class Program
{
    static void Main(string[] args)
    {
        Class[] classInstances =
        {
            new Class("A"),
            new Class("B"),
            new Class("C"),
        };

        foreach (Class c in classInstances)
        {
            string methodName = c.Name + "_Event";
            MethodInfo mi = typeof(Program).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static);
            EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), mi);

            c.Event += handler;
        }

        foreach (Class c in classInstances)
        {
            c.RaiseEvent();
        }
    }

    static void A_Event(object sender, EventArgs e) { Console.WriteLine("A_Event handler"); }
    static void B_Event(object sender, EventArgs e) { Console.WriteLine("B_Event handler"); }
    static void C_Event(object sender, EventArgs e) { Console.WriteLine("C_Event handler"); }
}

class Class
{
    public string Name { get; }

    public Class(string name)
    {
        Name = name;
    }

    public event EventHandler Event;

    public void RaiseEvent()
    {
        Event?.Invoke(this, EventArgs.Empty);
    }
}

就个人而言,我更喜欢更明确的方法。也就是说,如果确实需要以抽象方式将处理程序的赋值封装到对象中,则将其置于显式代码中。例如,提供单个事件处理程序方法来订阅所有控件,然后按名称将该方法分派到适当的方法:

static void Main(string[] args)
{
    Class[] classInstances =
    {
        new Class("A"),
        new Class("B"),
        new Class("C"),
    };

    foreach (Class c in classInstances)
    {
        c.Event += All_Event;
    }

    foreach (Class c in classInstances)
    {
        c.RaiseEvent();
    }
}

static void All_Event(object sender, EventArgs e)
{
    switch (((Class)sender).Name)
    {
        case "A":
            A_Event(sender, e);
            break;
        case "B":
            B_Event(sender, e);
            break;
        case "C":
            C_Event(sender, e);
            break;
    }
}

或者,您可以使用字典来表示从名称到方法的映射:

static void Main(string[] args)
{
    Class[] classInstances =
    {
        new Class("A"),
        new Class("B"),
        new Class("C"),
    };

    Dictionary<string, EventHandler> nameToHandler = new Dictionary<string, EventHandler>()
    {
        { "A", A_Event },
        { "B", B_Event },
        { "C", C_Event },
    };

    foreach (Class c in classInstances)
    {
        c.Event += nameToHandler[c.Name];
    }

    foreach (Class c in classInstances)
    {
        c.RaiseEvent();
    }
}

在这两个示例中,您都不会保存任何输入(基于switch的方法特别冗长),但它确实将对象与处理程序的关系移动到其自己的区域中。代码,允许更轻松地维护它,而无需处理事件订阅本身。

如果你真的想要一个完全动态的,基于反射的方法,我会选择更依赖于方法名称的更明确且更不易碎的东西。例如,您可以为事件处理程序方法创建自定义属性,用于定义哪个方法与哪个对象相关。这提供了相当少的键入量,但是将方法名称与映射断开连接,以便您可以继续将代码重构为您心中的内容,而无需担心事件处理方面。

这看起来像这样:

class Program
{
    static void Main(string[] args)
    {
        Class[] classInstances =
        {
            new Class("A"),
            new Class("B"),
            new Class("C"),
        };

        Dictionary<string, EventHandler> nameToHandler =
                (from mi in typeof(Program).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
                 let attribute = (Handler)mi.GetCustomAttribute(typeof(Handler))
                 where attribute != null
                 select new { attribute.Target, mi })
             .ToDictionary(x => x.Target, x => (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), x.mi));

        foreach (Class c in classInstances)
        {
            c.Event += nameToHandler[c.Name];
        }

        foreach (Class c in classInstances)
        {
            c.RaiseEvent();
        }
    }

    [Handler("A")]
    static void A_Event(object sender, EventArgs e) { Console.WriteLine("A_Event handler"); }
    [Handler("B")]
    static void B_Event(object sender, EventArgs e) { Console.WriteLine("B_Event handler"); }
    [Handler("C")]
    static void C_Event(object sender, EventArgs e) { Console.WriteLine("C_Event handler"); }
}

class Handler : Attribute
{
    public string Target { get; }

    public Handler(string target)
    {
        Target = target;
    }
}