2016-11-13 63 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(); 
    } 
相關問題