[英]MVP, WinForms - how to avoid bloated view, presenter and presentation model
When implementing MVP pattern in winforms I often find bloated view interfaces with too many properties, setters and getters.在 winforms 中实现 MVP 模式时,我经常发现臃肿的视图接口有太多的属性、setter 和 getter。 An easy example with be a view with 3 buttons and 7 textboxes, all having value, enabled and visible properties exposed from the view.
一个简单的例子是一个带有 3 个按钮和 7 个文本框的视图,它们都具有从视图中公开的值、启用和可见属性。 Adding validation results for this, and you could easily end up with an interface with 40ish properties.
为此添加验证结果,您可以轻松地获得具有 40 项属性的界面。 Using the Presentation Model, there'll be a model with the same number of properties aswell.
使用 Presentation Model,也会有一个具有相同数量属性的模型。
How do you easily sync the view and the presentation model without having bloated presenter logic that pass all the values back and forth?您如何轻松地同步视图和演示模型,而无需来回传递所有值的臃肿演示器逻辑? (With that 80ish line presenter code, imagine with the presenter test that mocks the model and view will look like..160ish lines of code just to mock that transfer.) Is there any framework to handle this without resorting to winforms databinding?
(使用 80 行代码演示者代码,想象一下模拟模型和视图的演示者测试看起来像 ..160 行代码只是为了模拟该传输。)是否有任何框架可以在不求助于 winforms 数据绑定的情况下处理这个问题? (you might want to use different views than a winforms view. According to some, this sync should be the presenters job..) Would you use AutoMapper?
(您可能希望使用与 winforms 视图不同的视图。根据某些人的说法,这种同步应该是演示者的工作..)您会使用 AutoMapper 吗?
Maybe im asking the wrong questions, but it seems to me MVP easily gets bloated without some good solution here..也许我问了错误的问题,但在我看来 MVP 很容易变得臃肿而没有一些好的解决方案。
This is just one idea, and I know where some people might not like it -- there are lots of different things you can do here.这只是一个想法,我知道有些人可能不喜欢它——你可以在这里做很多不同的事情。
If you find yourself using a lot of boilerplate code, encapsulate it.如果您发现自己使用了大量样板代码,请将其封装起来。
public class UiField<ContentType>
{
public bool IsEnabled { get; set; }
public ContentType Value { get; set; }
public bool IsVisible { get; set; }
}
In your view, then:在你看来,那么:
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; }
}
Here you wrap up the various properties associated with each field.在这里您总结了与每个字段相关的各种属性。
Incidentally, I suggest that you not stub these interfaces by hand for testing -- use a mocking framework.顺便说一句,我建议您不要手工存根这些接口进行测试——使用模拟框架。
I started building on Jay's solution then realised that what I was building was implementation of the Adapter Design Pattern.我开始构建 Jay 的解决方案,然后意识到我构建的是适配器设计模式的实现。 Here is what I ended up with, its VB.Net rather than C#:
这是我最终得到的,它是 VB.Net 而不是 C#:
The adapter Interface which defines the properties you only want to set up once:定义您只想设置一次的属性的适配器接口:
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
The Interface Implementation:接口实现:
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
In the WinForm/View, declare Properties that will expose view controls as simple data types using the Adapter:在 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
In the WinForm/View constructor, wire up the properties to the Winforms UI controls (I am also using ErrorProvider):在 WinForm/View 构造函数中,将属性连接到 Winforms UI 控件(我也在使用 ErrorProvider):
Public Sub New() MyBase.New() Public Sub 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)
In the Presenter: Private ReadOnly _view As IMyView在演示者中:私有只读 _view As IMyView
Public Sub New(view As IMyView)
_view = view 'Inject dependency
End Sub
Now in your presenter code, you can use the adapter properties:现在在您的演示者代码中,您可以使用适配器属性:
_view.YesNo1.Value = True
If _view.StartDate.Completed Then
etc
etc
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.