65.9K
CodeProject 正在变化。 阅读更多。
Home

使用松散耦合方法演进 Windows Forms 事件处理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (9投票s)

2011年6月12日

CPOL

3分钟阅读

viewsIcon

32940

downloadIcon

536

通过对子对象或同级对象进行方法接口化,减少 OwnerObject 成员或资源的暴露。

引言

我将提出一种替代标准事件处理、订阅和取消订阅的方法,即通过接口暴露方法。我还将提出一个 GenericEventArgs 类来加速事件参数的编码。

最近,"我如何让 FormB 的控件影响 FormA" 这个问题被问了几次。我开始考虑一个场景:一个 OwnerForm 会打开一个 ChildFormChildForm 中的某些事件可能需要修改 OwnerForm 上的数据。这个场景的一些解决方案建议将 OwnerForm 引用传递给 ChildForm。虽然这样做可行,但我认为这很脆弱。如果 OwnerForm 有任何更改,无疑需要至少打开 ChildForm 的代码,以确保没有任何地方出错。

通过对事件进行接口化,我们只暴露 Action 的引用。这可以被认为是某种事件发生时的默认操作。对于 OwnerObject 的每个实现,其独特性倾向于很高,但系统将是可识别的,并且增加的灵活性将是有益的。松散耦合的事件也将更容易避免我称之为 functionCreep(this) 的情况。

  • functionCreep(this) - 执行与 OwnerObject 密切相关的函数或操作资源
  • functionCreep(this) 会导致 OwnerObject 的资源暴露

背景

对事件委托和自定义 EventArgs 有基本了解将很有帮助。

Using the Code

源文件下载包含编译和执行项目所需的所有必要组件,包括主窗体和设置窗体。有两个类——特别是 IEventPublisherGenericEventArgs——对于使此过程有用且工厂化至关重要。

下载源代码并在 Visual Studio 中打开,编译并运行。

Form1Startup.PNG

点击打开设置将打开“设置窗体”。

SettingsWindow.PNG

在文本框中输入“Presto”,然后点击设置标题按钮。OwnerForm 的标题会改变。

接下来,从组合框中选择一个人。选择一个人后,OwnerFormPropertyGrid 将显示该人的属性。

虽然这些是非常简单的例子,但该项目演示了在不暴露 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 );
}

除了为 changeTitleDelegatedisplayPropsDelegate 赋值之外,这看起来是一个非常标准的构造函数。IEventPublisher 是暴露两个 Action getters 的接口。

IEventPublisher:

public interface IEventPublisher
{
    Action<object, EventArgs> ChangeFormTitle { get; }
    Action<object, EventArgs> DisplayProperties { get; }
}

uxSetTitle_ClickuxPersonSelector_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 被实例化时,ChangeFormTitleDisplayProperties 会被赋值。当 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( ) 方法中的注释。
© . All rights reserved.