MVP,WinForms - 如何避免臃肿的视图、Presenter和Presentation Model

4
在winforms中实现MVP模式时,我经常发现视图接口过于臃肿,具有过多的属性、setter和getter。一个简单的例子是一个视图有3个按钮和7个文本框,所有这些元素都有值、启用和可见属性在视图中公开。添加验证结果后,你很容易就会得到一个具有40多个属性的界面。使用Presentation Model,将有一个与之相同数量属性的模型。

如何轻松同步视图和Presentation Model而不需要臃肿的Presenter逻辑来来回传递所有值呢?(如果有80行的Presenter代码,那么你想象一下Presenter测试程序将会是什么样子.. 160行的代码只是为了模拟这种传输) 是否有任何框架可以处理这个问题,而不必诉诸于Winforms数据绑定?(你可能希望使用不同于Winforms视图的视图。 根据某些人的说法,这种同步应该由Presenter来完成..) 你会使用AutoMapper吗?

也许我问错了问题,但在没有一个好的解决方案的情况下,MVP似乎很容易变得臃肿。


可能是使用Windows Forms实现MVC的重复问题。 - Hans Passant
1
我不认为这是重复的。我想我已经阅读了该线程中的所有内容和参考资料。如果你能指出解决这个“膨胀”问题的答案,请告诉我。 - MatteS
2个回答

3

这只是一个想法,我知道有些人可能不喜欢它——在这里你可以做很多不同的事情。

如果你发现自己使用了很多样板代码,请封装它。

public class UiField<ContentType>
{
    public bool IsEnabled { get; set; }
    public ContentType Value { get; set; }
    public bool IsVisible { get; set; }
}

那么,你的看法是:

public interface ISampleView
{
    UiField<bool> IsStaffFullTime { get; set; }
    UiField<string> StaffName { get; set; }
    UiField<string> JobTitle { get; set; }
    UiField<int> StaffAge { get; set; }
    UiField<IList<string>> Certifications { get; set; }
}

在这里,您包装与每个字段相关的各种属性。

顺便提一下,我建议您不要手动为测试存根这些接口 - 使用模拟框架。


1
主持人模型看起来也差不多吗?您能为使用此视图和/或模型的任何操作提供演示者测试吗?我的第一个担忧是测试代码会变得更加臃肿,但在我看到它或尝试它之前,我不确定。 - MatteS

0

我开始在Jay的解决方案上进行构建,然后意识到我正在构建适配器设计模式的实现。这是我最终得出的结果,它是VB.Net而不是C#:

适配器接口定义了您只想设置一次的属性:

Public Interface IWinformsControlAdapter(Of T)

''' <summary>
''' Default value for the UI Control
''' </summary>
''' <returns></returns>
Property Value As T
Property IsEnabled As Boolean
Sub SetError(myValue As String)

''' <summary>
''' Select all the text in the UI Field if possible
''' </summary>
Sub [Select]()

''' <summary>
''' Gets a boolean value indicating whether all required inputs have been entered into the UI field
''' </summary>
''' <returns>true if all required input has been entered in the field</returns>
ReadOnly Property Completed As Boolean

Property DataSource As Object
Property DisplayMember As String
Property ValueMember As String
Property SelectedValue As Object
Property Checked As Boolean
End Interface

接口实现:

Public Class WinformsControlAdapter(Of T)
    Implements IWinformsControlAdapter(Of T)

    Private ReadOnly _control As Control 'The Adaptee
    Private ReadOnly _errorProvider As ErrorProvider
    Public Sub New(ByRef control As Control, ByRef errorProvider As ErrorProvider)
        _control = control
        _errorProvider = errorProvider
    End Sub

    Public Property Value As T Implements IWinformsControlAdapter(Of T).Value
        Get

            Select Case True
                Case TypeOf _control Is TextBoxBase 
                    'Typically for Textbox T = String, but not always: _control could be adapting to Integer for inputting numbers or MaskedTextbox could be adapting to Date for inputting dates, etc 
                    Return ConvertTo(Of T)(_control.Text)

                Case TypeOf _control Is RadioButton 
                    Dim myRadioButton As RadioButton = _control
                    Return ConvertTo(Of T)(myRadioButton.Checked)

                Case Else
                    Throw New NotImplementedException("Some other control.property needs to be added for .Value Property Get")

            End Select

        End Get
        Set(myValue As T)

            Select Case True
                Case TypeOf _control Is TextBoxBase 
                    _control.Text = ConvertTo(Of String)(myValue)

                Case TypeOf _control Is ListControl 
                    'Its debatable should it be the list or the selected value
                    DataSource = myValue  
                    'Dim myListControl As ListControl = _control
                    'myListControl.DataSource = ConvertTo(Of T)(myValue)

                Case TypeOf _control Is RadioButton 
                    Dim myRadioButton As RadioButton = _control
                    myRadioButton.Checked = ConvertTo(Of Boolean)(myValue)

                Case Else
                    Throw New NotImplementedException("Some other control.property needs to be added for .Value Property Set")
            End Select

        End Set
    End Property

    Public Property Checked As Boolean Implements IWinformsControlAdapter(Of T).Checked
        Get
            If TypeOf _control IsNot RadioButton Then Throw New NotImplementedException()

            Dim myRadioButton As RadioButton = _control
            Return myRadioButton.Checked
        End Get
        Set(myValue As Boolean)
            If TypeOf _control IsNot RadioButton Then Throw New NotImplementedException()

            Dim myRadioButton As RadioButton = _control
            myRadioButton.Checked = myValue
        End Set
    End Property

    Public ReadOnly Property Completed As Boolean Implements IWinformsControlAdapter(Of T).Completed
        Get
            If TypeOf _control IsNot MaskedTextBox Then Throw New NotImplementedException()

            Dim myMaskedTextBox As MaskedTextBox = _control
            Return myMaskedTextBox.MaskCompleted
        End Get
    End Property

    Public Property DataSource As Object Implements IWinformsControlAdapter(Of T).DataSource
        Get
            If TypeOf _control IsNot ListControl Then Throw New NotImplementedException()

            Dim myListControl As ListControl = _control
            Return myListControl.DataSource
        End Get
        Set(myValue As Object)
            If TypeOf _control IsNot ListControl Then Throw New NotImplementedException()

            Dim myListControl As ListControl = _control
            myListControl.DataSource = myValue
        End Set
    End Property

    Public Property DisplayMember As String Implements IWinformsControlAdapter(Of T).DisplayMember
        Get
            If TypeOf _control IsNot ListControl Then Throw New NotImplementedException()

                Dim myListControl As ListControl = _control
                Return myListControl.DisplayMember
        End Get
        Set(myValue As String)
            If TypeOf _control IsNot ListControl Then Throw New NotImplementedException()

            Dim myListControl As ListControl = _control
            myListControl.DisplayMember = myValue
        End Set
    End Property
    Public Property ValueMember As String Implements IWinformsControlAdapter(Of T).ValueMember
        Get
            If TypeOf _control IsNot ListControl Then Throw New NotImplementedException()

            Dim myListControl As ListControl = _control
            Return myListControl.ValueMember
        End Get
        Set(myValue As String)
            If TypeOf _control IsNot ListControl Then Throw New NotImplementedException()

            Dim myListControl As ListControl = _control
            myListControl.ValueMember = myValue
        End Set
    End Property
    Public Property SelectedValue As Object Implements IWinformsControlAdapter(Of T).SelectedValue
        Get
            If TypeOf _control IsNot ListControl Then Throw New NotImplementedException()

            Dim myListControl As ListControl = _control
            Return myListControl.SelectedValue
        End Get
        Set(myValue As Object)
            If TypeOf _control IsNot ListControl Then Throw New NotImplementedException()

            Dim myListControl As ListControl = _control
            myListControl.SelectedValue = myValue
        End Set
    End Property
    Public Function ConvertTo(Of T)(myValue As Object) As T
        If (TypeOf myValue Is T) Then
            Dim variable As T = myValue
            Return variable
        End If

        Try
            'Handling Nullable types i.e, int?, double?, bool? .. etc
            If Nullable.GetUnderlyingType(GetType(T)) <> Nothing Then
                Return TypeDescriptor.GetConverter(GetType(T)).ConvertFrom(myValue)
            End If

            Return Convert.ChangeType(myValue, GetType(T))

        Catch ex As Exception
            Return CType(Nothing, T) 'Return the default value for the type - same as C#: return default(T)
        End Try
    End Function
    Public Property IsEnabled As Boolean Implements IWinformsControlAdapter(Of T).IsEnabled
        Get
            Return _control.Enabled
        End Get
        Set(myValue As Boolean)
            _control.Enabled = myValue
        End Set
    End Property    

    ''' <summary>
    ''' Sets the error description string for the UI Field.
    ''' </summary>
    ''' <param name="myValue">The error message to assign to the control</param>
    Friend Sub SetError(myValue As String) Implements IWinformsControlAdapter(Of T).SetError
        _errorProvider.SetError(_control, myValue)
    End Sub

    Sub [Select]() Implements IWinformsControlAdapter(Of T).Select
        If TypeOf _control Is TextBoxBase Then
            Dim myTextBoxBase As TextBoxBase = _control
            myTextBoxBase.Select(0, _control.Text.Length)
        End If
    End Sub

End Class

在WinForm/View中,使用适配器声明属性,将视图控件公开为简单数据类型:
Property YesNo1 As IWinformsControlAdapter(Of Boolean) Implements IMyView.YesNo1
Property YesNo2 As IWinformsControlAdapter(Of Boolean) Implements IMyView.YesNo2 
Property StartDate As IWinformsControlAdapter(Of Date?) Implements IMyView.StartDate
Property EndDate As IWinformsControlAdapter(Of Date?) Implements IMyView.EndDate

在WinForm/View构造函数中,将属性与Winforms UI控件连接起来(我还使用了ErrorProvider):
公共子New() MyBase.New()
Try
    InitializeComponent()

    Yes = New WinformsControlAdapter(Of Boolean)(rdoHNSYes, ErrorProvider1)
    No = New WinformsControlAdapter(Of Boolean)(rdoHNSNo, ErrorProvider1)
    StartDate = New WinformsControlAdapter(Of Date?)(mskStartDate, ErrorProvider1)
    EndDate = New WinformsControlAdapter(Of Date?)(mskEndDate, ErrorProvider1)

在 Presenter 中: 私有只读 _view As IMyView
Public Sub New(view As IMyView)
    _view = view 'Inject dependency
End Sub

现在在您的Presenter代码中,您可以使用适配器属性:

_view.YesNo1.Value = True 
If _view.StartDate.Completed Then
etc
etc

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