9 .net c# events delegates winforms
我有一个主窗体的Windows窗体应用程序(从基础派生Form).可以在那里打开的其他模态形式来自我的类ManagedForm,它也来自Form.
此外,我有一个静态通知程序服务,它会触发这样的事件:
public static class NotifierService
{
public delegate void NotifierServiceEventHandler(object sender, NotifierServiceEventArgs e);
private static readonly object Locker = new object();
private static NotifierServiceEventHandler _notifierServiceEventHandler;
#region Events
public static event NotifierServiceEventHandler OnOk
{
add
{
lock (Locker)
{
_notifierServiceEventHandler += value;
if (
_notifierServiceEventHandler.GetInvocationList()
.Count(
_ =>
_.Method.DeclaringType != null &&
value.Method.DeclaringType != null &&
_.Method.DeclaringType == value.Method.DeclaringType) <= 1)
return;
_notifierServiceEventHandler -= value;
}
}
remove
{
lock (Locker)
{
_notifierServiceEventHandler -= value;
}
}
}
// and many more events similar to previous...
#endregion
#region Event firing methods
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
NotifierServiceEventHandler handler;
lock (Locker)
{
handler = _notifierServiceEventHandler;
}
if (handler == null) return;
handler(typeof (NotifierService),
new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
}
#endregion
}
Run Code Online (Sandbox Code Playgroud)
因此,在某些代码处,这些事件可能被触发:
NotifierService.NotifyExclamation("Fail!");
Run Code Online (Sandbox Code Playgroud)
在主窗体中,有StatusStrip用于通知目的的控件,并且由于主窗体已订阅这些事件 - 它们的消息将显示在状态条中.
但是,正如我之前所说,用户可能会打开其他表单,这些表单可能会生成其他表单等等...(它们来自一个类ManagedForm,它将NotifierService在创建后立即订阅).
在这些表单中,还有另一个逻辑如何通知用户 - 他们需要显示MessageBox带有消息的es.正如您所看到的,我在事件访问器中添加了一些魔法,只允许任何类型的一个订阅者,因为没有这个所有打开的表单将生成他们自己的MessageBoxes.但是当一个孩子ManagedForm生产了另一个孩子而第二个孩子已经关闭时 - MessageBox将不会显示任何一个孩子.
我应该实现什么样的魔法来允许首先订阅ManagedForm?非常感谢任何想法.
编辑:建议的想法并没有解决这个问题.我试图将事件更改为:
private static readonly object Locker = new object();
private static EventHandler<NotifierServiceEventArgs> _myEvent;
public static event EventHandler<NotifierServiceEventArgs> OnOk
{
add
{
if (_myEvent == null || _myEvent.GetInvocationList().All(_ => _.Method.DeclaringType != value.Method.DeclaringType))
{
_myEvent += value;
}
}
remove
{
_myEvent -= value;
}
}
Run Code Online (Sandbox Code Playgroud)
然后我打开一个模态子表单并创建一个事件被触发的情境NotifierService.一个MessageBox已生成并显示(这是确定).之后我从第一个开始另一个模态形式,并创建另一个事件已被触发的情况.一个MessageBox已生成并显示(这也行).现在我正在关闭第二种形式,并制定一个触发事件所需的情况.没有显示MessageBoxes(但是在事件的主要表单消息的状态条中已经正确显示,因此我的第一个实现没有任何改变).
我应该改变remove条款吗?我不需要只有一个订阅者,我需要每个订阅者都应该是不同的类型.抱歉如果英语不好.
您试图解决问题的方式在设计上是根本错误的.您的服务类定义了在某些情况下将触发的事件.有些客户订阅了该事件,这样就要求在事件发生时收到通知.这只是实现Observer模式的.NET方式,因此您的服务(作为主题或可观察的)不应该在subscribe和notify部分都不应用任何逻辑,从而破坏了模式的整个目的.Hans Passant已经指出了你的设计中的一些缺陷,但即使他的解决方案并不完美,因为查看事件签名,完全不清楚只有表单实例方法应该注册 - 可以尝试使用静态方法,匿名lambda /方法,一些类方法等
所以,IMO以下是你有一些可行的选择.
(A)保留您的NotificationService活动,但从订阅和通知部分中删除任何"魔法"(不久,使用常规方式定义和触发事件)并将订阅者所需的逻辑放入:
public static class NotifierService
{
public delegate void NotifierServiceEventHandler(object sender, NotifierServiceEventArgs e);
public static event NotifierServiceEventHandler OnOk;
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
var handler = OnOk;
if (handler != null)
handler(typeof(NotifierService), new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
}
}
Run Code Online (Sandbox Code Playgroud)
假设只有活动表单应该处理通知,你MainForm和ManagedForm他们的现有处理程序将在他们的方法体内使用这样的东西
if (this != ActiveForm) return;
// do the processing
Run Code Online (Sandbox Code Playgroud)
您甚至可以创建这样的基本表单
class NotifiedForm : Form
{
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
NotifierService.OnOk += OnNotifyOK;
// similar for other events
}
protected override void OnDeactivate(EventArgs e)
{
base.OnDeactivate(e);
NotifierService.OnOk -= OnNotifyOK;
// similar for other events
}
protected virtual void OnNotifyOK(object sender, NotifierServiceEventArgs e) { }
// similar for other events
}
Run Code Online (Sandbox Code Playgroud)
让你的MainForm,ManagedForm(以及任何其他需要)从继承,只是覆盖OnNotifyXXX的方法和应用他们的逻辑.
总而言之,这种方法可以使您的服务保持抽象,并将决策留给服务的客户.
(B)如果您的服务的唯一目的是作为专门针对您的表单的通知协调员,那么您可以删除事件以及订阅/取消订阅部分(因为Application.OpenForms并且Form.ActiveForm已经提供了足够的信息)并处理您服务中的逻辑.为了做到这一点,你需要某种基本接口或表单,最简单的方法是通过创建一个像这样的基本表单类,在选项(A)中使用类似于可选的方法
class NotifiedForm : Form
{
public virtual void OnNotifyOK(object sender, NotifierServiceEventArgs e) { }
// similar for other notifications
}
Run Code Online (Sandbox Code Playgroud)
让你的MainForm,ManagedForm和其他需要继承它.请注意,这里没有逻辑(检查ActiveForm等),因为现在这是调用者的责任.那么服务可能是这样的:
public static class NotifierService
{
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
var target = Form.ActiveForm as NotifiedForm;
if (target != null)
target.OnNotifyOK(typeof(NotifierService), new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
}
// similar for other notifications
}
Run Code Online (Sandbox Code Playgroud)
如果逻辑是仅通知活动表单.
要么
public static class NotifierService
{
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
// Could also be a forward for, forach etc.
for (int i = Application.OpenForms.Count - 1; i >= 0; i--)
{
var target = Application.OpenForms[i] as NotifiedForm;
if (target != null /* && someOtherCritaria(target) */)
{
target.OnNotifyOK(typeof(NotifierService), new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
// Could also continue
break;
}
}
}
// similar for other notifications
}
Run Code Online (Sandbox Code Playgroud)
如果需要一些其他逻辑(我怀疑).
希望有所帮助.在任何情况下,选项(A)都更灵活,允许更多的使用场景,但如果使用场景是通过设计修复的,则选项(B)更好,因为它对客户端的要求较少(因此不易出错)并在一个地方提供集中的应用程序逻辑.
我希望您按如下方式进行:
消除事件访问器方法的魔力,让所有订阅者订阅该事件。现在您的主表单和所有其他表单都订阅了该事件。
现在将魔法放入您的事件调用方法中。例如,在您的 NotifyOK 方法中,首先获取 deligate 的调用列表,现在仅当您尚未调用特定 DeclaringType 时,才使用调用列表中每个 deligate 的 DynamicInvoke 或 Invoke 方法一一调用每个 deligate。请参阅下面的算法:
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
NotifierServiceEventHandler handler;
lock (Locker)
{
handler = _notifierServiceEventHandler;
}
if (handler == null) return;
// Get invocation list of handler as you have done in event accessor
//initialise a new List<T> to hold the declaring types
// loop through each member (delegate) of invocation list
// if the current member declaration type is not in List<t>
// Invoke or DynamicInvoke current delegate
// add the declaration type of current delegate to List<t>
}
Run Code Online (Sandbox Code Playgroud)| 归档时间: |
|
| 查看次数: |
589 次 |
| 最近记录: |