简体   繁体   English

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

[英]How do I add event handlers in a loop?

I am trying to create a tray icon in C#/.NET and so far I have this code that works: 我正在尝试在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
    }

But I am trying to separate the code by having a function that returns an array of MenuItem instances, and having a loop that adds them to the ContextMenu , but I'm not sure how to add the click event handlers to the MenuItem instances in the loop: 但是我试图通过具有返回MenuItem实例数组的函数,并具有将其添加到ContextMenu的循环来分离代码,但是我不确定如何将click事件处理程序添加到ContextMenu项中的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
    }

The only thing I could think of would be to do something like this: 我唯一想到的就是要做这样的事情:

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

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

I don't think it's a bad idea to abstract your original code, but I'd suggest looking at the abstraction in a different way. 我认为抽象您的原始代码不是一个坏主意,但我建议您以另一种方式看待抽象。 I'd recommend implementing some kind of separation of the view from the model - MVC, MVP, MVVM, etc. In this way, the code that actually happens when the click occurs is abstracted away from the view, into another layer of code. 我建议实现视图与模型的某种分离-MVC,MVP,MVVM等。通过这种方式,将单击发生时实际发生的代码从视图中抽象到另一层代码中。

For example, consider something like this (writing without an IDE so please forgive typos): 例如,考虑这样的事情(在没有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();
    }

I agree with the comment that suggests that, at least for the code example you posted, there is no need to "improve" the code. 我同意这样的意见,即至少对于您发布的代码示例,不需要“改进”代码。 It's already a reasonable way to implement that particular logic. 这已经是实现该特定逻辑的合理方法。 Furthermore, it is my preference to avoid relying on naming conventions to tie specific code to specific run-time objects. 此外,我倾向于避免依赖命名约定将特定的代码绑定到特定的运行时对象。 Doing so results in a fragile (ie easily broken) implementation, and restricts your ability to change the name of the code (eg to address some unrelated aspect of naming which would otherwise provide for more readable code). 这样做会导致实现方式脆弱(即容易损坏),并限制了您更改代码名称的能力(例如,解决了一些不相关的命名方面,否则将提供更易读的代码)。

That said, if you really want to do this, you can. 就是说,如果您真的想这样做,可以。 Here is a Minimal, Complete, and Verifiable code example that illustrates how to create a delegate instance for an event handler based on the name of the object, and subscribe to the object's event: 这是一个最小,完整且可验证的代码示例 ,该示例说明如何基于对象的名称为事件处理程序创建委托实例,以及订阅对象的事件:

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);
    }
}

Personally, I prefer a more explicit approach. 就个人而言,我更喜欢一种更明确的方法。 That is, if there's really a need to encapsulate the assignment of handler to object in an abstracted way, to put this in explicit code. 也就是说,如果确实需要以抽象的方式封装处理程序对对象的分配,则可以将其放入显式代码中。 For example, provide a single event handler method to subscribe to all controls, and then have that method dispatch to the appropriate method by name: 例如,提供一个事件处理程序方法来订阅所有控件,然后按名称将该方法分派到适当的方法:

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;
    }
}

Alternatively, you can use a dictionary to represent the mapping from name to method: 另外,您可以使用字典来表示从名称到方法的映射:

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();
    }
}

In both of those examples, you don't save any typing (the switch -based approach is particularly verbose), but it does move the object-to-handler relationship into its own area of the code, allowing it to be maintained more easily without having to deal with the event subscription itself. 在这两个示例中,您都不需要保存任何类型(基于switch的方法特别冗长),但是它确实将对象与处理者的关系移到了自己的代码区域,从而使维护起来更加容易无需处理事件订阅本身。

If you really want a fully dynamic, reflection-based approach, I would opt for something more explicit and less fragile that relying on the method name. 如果您真的想要一种完全动态的,基于反射的方法,那么我会选择一种更依赖方法名称的更明确且更不易损坏的方法。 For example, you can create a custom attribute for the event handler methods, used to define what method goes with what object. 例如,您可以为事件处理程序方法创建一个自定义属性,用于定义什么方法与哪个对象一起使用。 This provides for a reasonably minimal amount of typing, but disconnects the method name from the mapping, so that you can go ahead and refactor the code to your heart's content without worrying about the event-handling aspect. 这提供了相当少的键入量,但是将方法名称与映射断开了连接,因此您可以继续将代码重构为您的内在内容,而不必担心事件处理方面。

That would look something like this: 看起来像这样:

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;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM