使用松散耦合方法演进 Windows Forms 事件处理
通过对子对象或同级对象进行方法接口化,减少 OwnerObject 成员或资源的暴露。
引言
我将提出一种替代标准事件处理、订阅和取消订阅的方法,即通过接口暴露方法。我还将提出一个 GenericEventArgs
类来加速事件参数的编码。
最近,"我如何让 FormB 的控件影响 FormA" 这个问题被问了几次。我开始考虑一个场景:一个 OwnerForm
会打开一个 ChildForm
;ChildForm
中的某些事件可能需要修改 OwnerForm
上的数据。这个场景的一些解决方案建议将 OwnerForm
引用传递给 ChildForm
。虽然这样做可行,但我认为这很脆弱。如果 OwnerForm
有任何更改,无疑需要至少打开 ChildForm
的代码,以确保没有任何地方出错。
通过对事件进行接口化,我们只暴露 Action 的引用。这可以被认为是某种事件发生时的默认操作。对于 OwnerObject
的每个实现,其独特性倾向于很高,但系统将是可识别的,并且增加的灵活性将是有益的。松散耦合的事件也将更容易避免我称之为 functionCreep(this)
的情况。
functionCreep(this)
- 执行与OwnerObject
密切相关的函数或操作资源functionCreep(this)
会导致OwnerObject
的资源暴露
背景
对事件委托和自定义 EventArgs 有基本了解将很有帮助。
Using the Code
源文件下载包含编译和执行项目所需的所有必要组件,包括主窗体和设置窗体。有两个类——特别是 IEventPublisher
和 GenericEventArgs
——对于使此过程有用且工厂化至关重要。
下载源代码并在 Visual Studio 中打开,编译并运行。
点击打开设置将打开“设置窗体”。
在文本框中输入“Presto”,然后点击设置标题按钮。OwnerForm
的标题会改变。
接下来,从组合框中选择一个人。选择一个人后,OwnerForm
的 PropertyGrid
将显示该人的属性。
虽然这些是非常简单的例子,但该项目演示了在不暴露 IEventPublisher
接口允许的任何控件或成员的情况下,将方法暴露给 ChildForm
的能力。为了实现这一点,我们必须修改 ChildForm
的构造函数以接受 IEventPublisher
并对其进行一些操作。
这是 FormSettings
窗口的构造函数
// fields
Action<object, EventArgs> changeTitleDelegate;
Action<object, EventArgs> displayPropsDelegate;
public FormSettings ( IEventPublisher EventOwner )
{
// couple to events
changeTitleDelegate = EventOwner.ChangeFormTitle;
displayPropsDelegate = EventOwner.DisplayProperties;
InitializeComponent ( );
// fills uxPersonSelector (ComboBox) with Person records
fillPersonSelector ( );
// subscribe
uxSetTitle.Click += new EventHandler ( uxSetTitle_Click );
uxCloseSettings.Click += delegate { this.Close ( ); };
uxPersonSelector.SelectedValueChanged +=
new EventHandler ( uxPersonSelector_SelectedValueChanged );
}
除了为 changeTitleDelegate
和 displayPropsDelegate
赋值之外,这看起来是一个非常标准的构造函数。IEventPublisher
是暴露两个 Action getters 的接口。
IEventPublisher
:
public interface IEventPublisher
{
Action<object, EventArgs> ChangeFormTitle { get; }
Action<object, EventArgs> DisplayProperties { get; }
}
uxSetTitle_Click
和 uxPersonSelector_SelectedValueChanged
这两个函数提供了灵活性,决定何时执行,或者是否执行 OwnerForm
的“默认”方法。
这是 FormSettings
窗口的构造函数
void uxSetTitle_Click ( object sender, EventArgs e )
{
// pre-process if necessary
// invoke delegate
this.changeTitleDelegate ( this,
new GenericEventArgs<string> ( uxFormTitle.Text ) );
}
void uxPersonSelector_SelectedValueChanged ( object sender, EventArgs e )
{
// invoke delegate
this.displayPropsDelegate ( this,
new GenericEventArgs<Person> ( (Person)uxPersonSelector.SelectedValue ) );
}
注意:Person
类包含在项目源代码中。
查看我们的 OwnerForm
;在 uxOpenSettings_Click2
函数中,我们实例化并显示 SettingsForm
作为对话框。当 SettingsForm
被实例化时,ChangeFormTitle
和 DisplayProperties
会被赋值。当 SettingsForm
的事件触发时,OwnerForm
中的函数会被执行。
IEventPublisher
:
// constructor
public Form1 ( )
{
InitializeComponent ( );
uxOpenSettings.Click += new EventHandler ( this.uxOpenSettings_Click2 );
}
// functions
private void uxOpenSettings_Click2 ( object sender, EventArgs e )
{
using (FormSettings settings = new FormSettings ( this ))
{
// show settings in Dialog mode
settings.ShowDialog ( );
}
}
void ChangeFormTitle ( object sender, EventArgs e )
{
// note the use of GenericEventArgs<T>
var args = (GenericEventArgs<string>)e;
// change Form1 title
Text = args.Value;
}
void DisplayProperties ( object sender, EventArgs e )
{
// note the use of GenericEventArgs<T>
var args = (GenericEventArgs<Person>)e;
uxPersonProperties.SelectedObject = args.Value;
}
#region IEventPublisher Members
Action<object, EventArgs> IEventPublisher.ChangeFormTitle
{
get { return ChangeFormTitle; }
}
Action<object, EventArgs> IEventPublisher.DisplayProperties
{
get { return DisplayProperties; }
}
#endregion
GenericEventArgs
类包含在项目源代码中。它是 EventArgs
子类的一个非常简单的形式,它传递一个强类型参数。它的使用和实现应该是显而易见的。
关注点
- 子窗口没有可用的所有者资源。
- 没有向子窗口暴露所有者控件。
- 没有可以调用的静态方法。这往往会迫使类设计考虑静态变量。
- 无需担心在窗体外部订阅或取消订阅事件。
GenericEventArgs T
对象是一个额外的福利。
这是一项实验,旨在找到一种逻辑方法,允许跨类执行方法,而不会过度暴露 OwnerObject
。我不喜欢将控件引用传递给子对象或同级对象。这很容易导致内存泄漏或开放事件订阅。我相信我的方法既巧妙又发人深省。这是一次非常有意义的学习经历,我欢迎所有想法、评论和担忧。
历史
- [2011/6/12] 初次发布,拼写和语法编辑。
- [2011/6/14] 重新提交了 [源文件] zip 文件。感谢您告知我存在问题。
- [2011/6/15] 再次提交了新的 [源文件]。编辑了 FormSettings.Designer.cs
Dispose( )
方法中的注释。