我正在尝试在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";
}
答案 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;
}
}