如何在.NET中为MVP模式公开用户控件的属性

3
我正在实现一个简单的UserControl,它实际上是一个高级的TextBox。其中之一的功能是您可以设置格式规范,这些格式规范会自动应用于其内容。例如,如果您将格式规范设置为“000”,并且内容为“42”,那么“042”将出现。
我按照MVP模式来实现这个UserControl。该实现类似于此:如何在winforms mvp模式中实现usercontrol?。另请查看此问题:被动视图和显示逻辑 方法1:
我的View中的Title属性如下:
private string title;
public string Title {
    get { return title; }
    set { title = value; titleTextBox.Text = presenter.Format(title); }
}

我认为这个实现增加了不必要的耦合。例如,如果我更改“Presenter”的“Format”方法,那么我将不得不查看所有的“View”并相应地更改调用语句。

方法二

我的“View”中的“Title”属性如下:

public string Title {
    get { return presenter.Title; }
    set { presenter.Title = value; }
}

我的PresenterTitle属性的代码如下:

private string title;
public string Title {
    get { return title; }
    set { _view.SetTitle(this.Format(value); }
}

现在我需要在View接口和View实现中添加SetTitle方法:

public void SetTitle(string title) {
    titleTextBox.Text = title;
}

因此,采用这种方法我得到了一个类似Java的丑陋的SetTitle方法。

第三种方法

不要调用SetTitle,而是在View中创建一个新的属性RealTitle并设置它。这个Real前缀使得它仍然很丑陋。

你的方法

你能想到更好的方法吗?

使用场景

UserControl应该像这样使用:

var c = new FancyTextBox();
c.Format = "000";
c.Text = "42";
Controls.Add(c);

这段代码应该在UserControl中显示"042"

总体情况

Form            FancyTextBoxView             FancyTextBoxPresenter
  |                     |                              |
  |  ftb.Text = "42"    |                              |
  |-------------------->|                              |
  |                     |                              |
  |                     |              A               |
  |                     |----------------------------->|
  |                     |                              |
  |                     |              B               |
  |                     |<-----------------------------|

A和B是什么操作?我希望格式化的文本出现在UI中。格式化代码位于Presenter中。View有一个titleTextBox,将在UI中显示文本。

2个回答

2
为什么不这样定义您视图的标题属性呢?
public string Title {
    get { return titleTextBox.Text; }
    set { titleTextBox.Text = value; }
}

完全没有必要定义额外的SetTitle方法。另外,在MVP中,您的视图不应该知道您的Presenter。

然后,每当触发Format函数时,您可以从中设置您的视图标题,例如:

void OnFormatCalled()
{
   _view.Title = FormatTitle(_view.Title);
}

我在问题中澄清了用例。如果我执行上述操作,格式化将如何应用?“View”不应该知道“Presenter”的存在,这是真的吗?那么我应该如何将事件委托给“Presenter”?或者调用帮助方法,例如我们示例中的“Format”? - prekageo
通过让你的视图公开事件,例如按钮点击事件,让你的 Presenter 订阅这些事件。 - Edwin de Koning
你说得没错!但是UserControl没有公开Format方法。这是Presenter的一个方法。用户只能设置TitleFormat。当用户更改其中一个时,应该发生什么? - prekageo
@prekageo:这取决于你想要什么,它应该在更改时触发重新格式化,还是用户首先需要按下一个按钮? - Edwin de Koning
好的,你可以在视图中重写文本框的OnTextChanged事件处理程序,并引发自己的自定义事件(即FancyTextBoxChanged)。然后在你的Presenter中捕获此事件,格式化文本(从视图中读取所需的值),并将值设置回视图。明白了吗?还是需要一个例子? - Edwin de Koning
显示剩余2条评论

1

通常我会将View接口(和Model接口)注入到Presenter中。这样,您只需要考虑依赖项和测试的一个地方。

当标题设置完毕时,让您的View触发一个事件,让Presenter订阅它。然后,Presenter可以格式化文本并在View上设置标题——Presenter负责视图的呈现,视图只是渲染内容。通过这种方式,视图变得非常简单,几乎没有任何逻辑。这使得它可以在单元测试期间被模拟,并使得Presenter易于测试。

此外,请注意,格式化逻辑也通过代理属性注入到Presenter中,从而将格式化与Presenter解耦,使其可更改和可测试。这只是一种做法...我喜欢这种方法。

public class Presenter
{
   private IView _view;
   private IModel _model;

   public Func<string, string> TitleFormatter { get; set; }

   public Presenter(IView view, IModel model)
   {
      _model = model;
      _view = view;

      _view.OnSetTitle += (s, e) => {
          _view.Title = TitleFormatter(e.Text);
       };
   }
}

public View : IView
{
    public event EventHandler<TitleChangedEventArgs> TitleChanged;

    public SomeUserActionEvent(object sender, SomeUserInterfaceEventArgs e)
    {
       TitleChanged(e.Text);
    }
}

不要从属性中触发事件。在接口中将事件公开为单独的内容,并在视图中实现它。 - Peter Kelly
好的。所以你描述了这样一种情况:
在视图中:public string Title { set { /* 触发事件供 presenter 捕获 */ } }
在 presenter 的事件处理程序中:_view.RealTitle = Format(title)
在视图中:public string RealTitle { set { titleTextBox.Text = value } }
看起来像第三种方法,对吗?
- prekageo
谁应该调用SomeUserActionEvent方法?请查看问题。我已经添加了一个用例和一个图表。 - prekageo
当用户在框中输入时,事件被触发并出现格式化。我认为你误解了MVP的用途。它是UI和业务逻辑的关注点分离。听起来你只是想子类化一个用户控件并忘记MVP部分。 - Peter Kelly
看,你的图示中有一条消息从表单(Form)传递到了你的FancyView。这是启动整个过程并触发事件传递到Presenter的消息。 - Peter Kelly
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接