我的 Windows 窗体应用程序有一个主窗体(派生自基本 Form
)。其他可以在那里打开的模式窗体派生 self 的类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
}
所以在代码的某些地方,这些事件可以像这样触发:
NotifierService.NotifyExclamation("Fail!");
在主窗体中有用于通知目的的 StatusStrip
控件,并且由于主窗体订阅了这些事件——它们的消息将显示在状态条中。
但是!,正如我之前所说,用户可以打开其他表单,这些表单可以生成其他表单等等......(它们派生自一个类 ManagedForm
,该类将订阅 NotifierService
一旦被创建)。
在这些表单中,还有另一种逻辑如何通知用户——他们需要显示带有消息的 MessageBox
es。如您所见,我在事件访问器中添加了一些魔法,只允许任何类型的一个订阅者,因为没有这个,所有打开的表单都会生成它们自己的 MessageBox
es。但是,当一个子级 ManagedForm
生成了另一个子级并且第二个子级已关闭时,将不会显示 MessageBox
es。
我应该实现什么魔法来允许仅从第一个 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;
}
}
然后我打开了一个模态子窗体并创建了一个事件已被 NotifierService
触发的情况。已生成并显示了一个 MessageBox
(没关系)。之后,我从一开始就打开了另一个模式窗体,并创建了另一种情况,在这种情况下,另一个事件被触发了。已生成并显示一个 MessageBox
(这也可以)。现在我正在关闭第二个表单并创建一个需要触发事件的情况。没有显示 MessageBox
es(但在主窗体的状态条中事件消息已正确显示,因此与我的第一个实现相比没有任何改变)。
我应该更改 remove
子句中的内容吗?我不需要只有一个订阅者应该是,我需要每个订阅者应该是不同的类型。对不起,如果英语不好。
最佳答案
您尝试解决问题的方式在设计上根本就是错误的。您的服务类定义了一个在某些情况下将被触发的事件。一些客户订阅该事件,以这种方式请求在事件发生时得到通知。这只是实现 Observer pattern 的 .NET 方式。 ,因此您的服务(作为主题或可观察对象)不应在订阅或通知部分应用任何逻辑,从而破坏该模式的全部目的。 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));
}
}
假设只有事件表单应该处理通知,MainForm
和 ManagedForm
中的现有处理程序将在它们的方法主体中使用类似这样的东西
if (this != ActiveForm) return;
// do the processing
你甚至可以像这样创建一个基本表单
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
}
并让您的 MainForm
、ManagedForm
(以及任何其他需要的)继承自它并覆盖 OnNotifyXXX
方法并应用它们的逻辑.
总而言之,这种方法将使您的服务保持抽象,并将决定权留给服务的客户。
(B) 如果您的服务的唯一目的是充当专门针对您的表单的通知协调员,那么您可以删除事件以及订阅/取消订阅部分(因为 Application. OpenForms
和 Form.ActiveForm
已经提供了所需的足够信息)并处理您服务中的逻辑。为此,您需要某种基本接口(interface)或表单,最简单的方法是通过创建这样的基本表单类来使用与选项 (A) 中可选内容类似的方法
class NotifiedForm : Form
{
public virtual void OnNotifyOK(object sender, NotifierServiceEventArgs e) { }
// similar for other notifications
}
并让您的 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
}
如果逻辑是只通知事件表单。
或者
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
}
如果需要一些其他逻辑(我对此表示怀疑)。
希望对您有所帮助。无论如何,选项 (A) 更灵活,允许更多的使用场景,但如果使用场景是设计固定的,那么选项 (B) 更好,因为它对客户端的要求更少(因此更不容易出错)并在一个地方提供集中的应用程序逻辑。
关于c# - 如何禁用从一种类型的多个实例订阅事件并只允许一个?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31600922/