2016-11-13 101 views
0

我试图创建在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,但我不知道如何将点击事件处理程序添加到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

这是过于简单化的例子。在尝试减少代码时,你会让它过于复杂。你的原始代码很好。 – Abion47

+0

我从来没有说过我试图减少我的代码。我只是好奇这将如何完成。 – glen4096

回答

1

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

这就是说,如果你真的想这样做,你可以。这里是一个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; 
    } 
} 
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(); 
    } 
相关问题