В предыдущем посте я представил свою библиотеку хранилища. Здесь я хотел бы сосредоточиться на интеграции библиотеки с компонентом MVP.
Отображение функциональных классов
Прежде всего, на рисунке показан новый член библиотеки хранилища, DataCompositeManager диспетчер данных, не обсуждаемый в предыдущем посте. Поскольку в пользовательской форме отображается одна запись, загруженная из DataRecordModel и данные загружаются из базы данных в DataTabelModelдополнительный код необходим для передачи данных между двумя классами модели. Для этого DataCompositeManager был введен менеджер, отвечающий за межмодельные переводы.
DataCompositeManager
'@Folder "ContactEditor.Storage.Manager"
'@ModuleDescription "Composite class incorporating one Table and one Record model with backends. Record submodel is used to represent a row from the Table."
'@PredeclaredId
'@IgnoreModule ProcedureNotUsed
'@Exposed
Option Explicit
Private Type TDataCompositeManager
RecordModel As DataRecordModel
RecordStorage As IDataRecordStorage
TableModel As DataTableModel
TableStorage As IDataTableStorage
End Type
Private this As TDataCompositeManager
Private Sub Class_Initialize()
Set this.RecordModel = New DataRecordModel
Set this.TableModel = New DataTableModel
End Sub
Private Sub Class_Terminate()
Set this.RecordModel = Nothing
Set this.TableModel = Nothing
End Sub
Public Property Get Record() As Scripting.Dictionary
Set Record = this.RecordModel.Record
End Property
Public Property Get RecordModel() As DataRecordModel
Set RecordModel = this.RecordModel
End Property
Public Property Get TableModel() As DataTableModel
Set TableModel = this.TableModel
End Property
Public Property Get FieldNames() As Variant
FieldNames = this.TableModel.FieldNames
End Property
Public Property Get Values() As Variant
Values = this.TableModel.Values
End Property
Public Property Get IDs() As Variant
IDs = this.TableStorage.GetIds
End Property
Public Sub InitRecord(ByVal ClassName As String, ByVal ConnectionString As String, ByVal TableName As String)
Set this.RecordStorage = DataRecordFactory.CreateInstance(ClassName, this.RecordModel, ConnectionString, TableName)
End Sub
Public Sub InitTable(ByVal ClassName As String, ByVal ConnectionString As String, ByVal TableName As String)
Set this.TableStorage = DataTableFactory.CreateInstance(ClassName, this.TableModel, ConnectionString, TableName)
End Sub
Public Sub LoadDataIntoModel()
this.TableStorage.LoadDataIntoModel
this.RecordStorage.LoadDataIntoModel
End Sub
Public Sub SaveDataFromModel()
this.RecordStorage.SaveDataFromModel
this.TableStorage.SaveDataFromModel
End Sub
Public Sub SaveRecordDataToRecordStorage()
this.RecordStorage.SaveDataFromModel
End Sub
Public Sub LoadRecordFromTable(ByVal RecordId As String)
this.TableModel.CopyRecordToDictionary this.RecordModel.Record, RecordId
this.RecordModel.RecordIndex = this.TableModel.RecordIndexFromId(RecordId)
this.RecordModel.IsNotDirty
End Sub
Public Sub UpdateRecordToTable()
this.TableModel.UpdateRecordFromDictionary this.RecordModel.Record
End Sub
На рисунке также показаны компоненты MVP: КонтактыEditorModel (Модель), КонтактыEditorForm (Просмотр) и Связаться с редактором (Ведущий). КонтактыEditorModel инкапсулирует экземпляр DataCompositeManager и выставляет напоказ.
КонтактыEditorModel
'@Folder "ContactEditor.Forms.Contact Editor"
'@IgnoreModule ProcedureNotUsed
'@Exposed
Option Explicit
Public Enum DataPersistenceMode
DataPersistenceDisabled
DataPersistenceOnApply
DataPersistenceOnExit
End Enum
Private Type TContactEditorModel
RecordTableManager As DataCompositeManager
PersistenceMode As DataPersistenceMode
SuppressEvents As Boolean
End Type
Private this As TContactEditorModel
Private Sub Class_Initialize()
Set this.RecordTableManager = New DataCompositeManager
this.SuppressEvents = False
End Sub
Private Sub Class_Terminate()
Set this.RecordTableManager = Nothing
End Sub
Public Property Get RecordTableManager() As DataCompositeManager
Set RecordTableManager = this.RecordTableManager
End Property
Public Property Get PersistenceMode() As DataPersistenceMode
PersistenceMode = this.PersistenceMode
End Property
Public Property Let PersistenceMode(ByVal Mode As DataPersistenceMode)
this.PersistenceMode = Mode
End Property
Public Property Get SuppressEvents() As Boolean
SuppressEvents = this.SuppressEvents
End Property
Public Property Let SuppressEvents(ByVal Mode As Boolean)
this.SuppressEvents = Mode
End Property
КонтактыEditorForm является Немодальный, и он определяет несколько настраиваемых событий, обрабатываемых Presenter.
КонтактыEditorForm
'@Folder "ContactEditor.Forms.Contact Editor"
Option Explicit
'''' To avoid issues, populate ComboBox.List with array of strings,
'''' cast if necessary (ComboBox.List column elements used for
'''' ComboBox.Value must have the same type as ComboBox.Value,
'''' otherwise expect runtime errors and glitches.
Implements IDialogView
Public Event FormLoaded()
Public Event LoadRecord(ByVal RecordId As String)
Public Event ApplyChanges()
Public Event FormConfirmed()
Public Event FormCancelled(ByRef Cancel As Boolean)
Private Type TView
Model As ContactEditorModel
IsCancelled As Boolean
End Type
Private this As TView
Private Function OnCancel() As Boolean
Dim cancelCancellation As Boolean: cancelCancellation = False
RaiseEvent FormCancelled(cancelCancellation)
If Not cancelCancellation Then Me.Hide
OnCancel = cancelCancellation
End Function
Private Sub id_Change()
If this.Model.SuppressEvents Then Exit Sub
RaiseEvent LoadRecord(id.Value)
End Sub
Private Sub OkButton_Click()
Me.Hide
RaiseEvent FormConfirmed
End Sub
Private Sub CancelButton_Click()
'@Ignore FunctionReturnValueDiscarded
OnCancel
End Sub
Private Sub ApplyButton_Click()
RaiseEvent ApplyChanges
End Sub
Private Sub UpdateDisabledRadio_Click()
this.Model.PersistenceMode = DataPersistenceMode.DataPersistenceDisabled
End Sub
Private Sub UpdateOnApplyRadio_Click()
this.Model.PersistenceMode = DataPersistenceMode.DataPersistenceOnApply
End Sub
Private Sub UpdateOnExitRadio_Click()
this.Model.PersistenceMode = DataPersistenceMode.DataPersistenceOnExit
End Sub
Private Sub IDialogView_ShowDialog(ByVal viewModel As Object)
Set this.Model = viewModel
Me.Show vbModeless
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = Not OnCancel
End If
End Sub
Private Sub UserForm_Activate()
InitializeId
InitializeAge
InitializeGender
InitializeTableUpdating
RaiseEvent FormLoaded
End Sub
Private Sub InitializeGender()
Dim listValues() As Variant
listValues = Array("male", "female")
Gender.Clear
Gender.List = listValues
End Sub
Private Sub InitializeAge()
Dim listValues(18 To 80) As Variant
Dim AgeValue As Long
For AgeValue = 18 To 80
listValues(AgeValue) = CStr(AgeValue)
Next AgeValue
Age.Clear
Age.List = listValues
End Sub
Private Sub InitializeId()
id.Clear
id.List = this.Model.RecordTableManager.IDs
End Sub
Private Sub InitializeTableUpdating()
UpdateDisabledRadio.Value = True
End Sub
Private Sub FirstName_Change()
If this.Model.SuppressEvents Then Exit Sub
this.Model.RecordTableManager.RecordModel.SetField "FirstName", FirstName.Value
End Sub
Private Sub LastName_Change()
If this.Model.SuppressEvents Then Exit Sub
this.Model.RecordTableManager.RecordModel.SetField "LastName", LastName.Value
End Sub
Private Sub Age_Change()
If this.Model.SuppressEvents Then Exit Sub
this.Model.RecordTableManager.RecordModel.SetField "Age", Age.Value
End Sub
Private Sub Gender_Change()
If this.Model.SuppressEvents Then Exit Sub
this.Model.RecordTableManager.RecordModel.SetField "Gender", Gender.Value
End Sub
Private Sub Email_Change()
If this.Model.SuppressEvents Then Exit Sub
this.Model.RecordTableManager.RecordModel.SetField "Email", Email.Value
End Sub
Private Sub Country_Change()
If this.Model.SuppressEvents Then Exit Sub
this.Model.RecordTableManager.RecordModel.SetField "Country", Country.Value
End Sub
Private Sub Domain_Change()
If this.Model.SuppressEvents Then Exit Sub
this.Model.RecordTableManager.RecordModel.SetField "Domain", Domain.Value
End Sub
Связаться с редактором инициализирует DataCompositeManager член КонтактыEditorModel и инициирует последующие операции в обработчиках событий.
Связаться с редактором
'@Folder "ContactEditor.Forms.Contact Editor"
Option Explicit
'@MemberAttribute VB_VarHelpID, -1
Private WithEvents view As ContactEditorForm
Private Type TPresenter
Model As ContactEditorModel
Dialog As IDialogView
End Type
Private this As TPresenter
Public Sub Show(ByVal TableBackEnd As String)
Set view = New ContactEditorForm
Set this.Dialog = view
InitializeModel TableBackEnd
'''' Loads data from the backends into the Model
this.Model.RecordTableManager.LoadDataIntoModel
this.Dialog.ShowDialog this.Model
End Sub
Private Sub ApplyChanges()
this.Model.RecordTableManager.SaveRecordDataToRecordStorage
Select Case this.Model.PersistenceMode
Case DataPersistenceMode.DataPersistenceOnApply
this.Model.RecordTableManager.UpdateRecordToTable
this.Model.RecordTableManager.SaveDataFromModel
Case DataPersistenceMode.DataPersistenceOnExit
this.Model.RecordTableManager.UpdateRecordToTable
Case DataPersistenceMode.DataPersistenceDisabled
Exit Sub
End Select
End Sub
Private Sub view_ApplyChanges()
ApplyChanges
End Sub
Private Sub view_FormLoaded()
LoadFormFromModel
End Sub
Private Sub LoadFormFromModel()
this.Model.SuppressEvents = True
Dim FieldName As Variant
Dim FieldIndex As Long
Dim FieldNames As Variant: FieldNames = this.Model.RecordTableManager.FieldNames
For FieldIndex = LBound(FieldNames) To UBound(FieldNames)
FieldName = FieldNames(FieldIndex)
view.Controls(FieldName).Value = CStr(this.Model.RecordTableManager.RecordModel.GetField(FieldName))
Next FieldIndex
this.Model.SuppressEvents = False
End Sub
Private Sub view_LoadRecord(ByVal RecordId As String)
If this.Model.RecordTableManager.RecordModel.IsDirty Then
Dim SaveChanges As Boolean
SaveChanges = MsgBox("Apply unsaved changes?", vbYesNo + vbExclamation + vbDefaultButton2)
If SaveChanges Then ApplyChanges
End If
this.Model.RecordTableManager.LoadRecordFromTable RecordId
LoadFormFromModel
End Sub
Private Sub view_FormCancelled(ByRef Cancel As Boolean)
'setting Cancel to True will leave the form open
Cancel = MsgBox("Cancel this operation?", vbYesNo + vbExclamation) = vbNo
If Not Cancel Then
' modeless form was cancelled and is now hidden.
' ...
Set view = Nothing
End If
End Sub
Private Sub view_FormConfirmed()
'form was okayed and is now hidden.
'...
If this.Model.PersistenceMode <> DataPersistenceDisabled Then
this.Model.RecordTableManager.UpdateRecordToTable
this.Model.RecordTableManager.SaveDataFromModel
Else
this.Model.RecordTableManager.SaveRecordDataToRecordStorage
End If
Set view = Nothing
End Sub
'@Description "Instantiates model and binds it to the desired backends."
Private Sub InitializeModel(ByVal TableBackEnd As String)
Set this.Model = New ContactEditorModel
Dim ClassName As String
Dim TableName As String
Dim ConnectionString As String
'''' Binds TableModel to its backend
Select Case TableBackEnd
Case "ADODB"
ClassName = "ADODB"
TableName = "Contacts"
ConnectionString = "sqlite:"
Case "Worksheet"
ClassName = "Worksheet"
TableName = "Contacts"
ConnectionString = ThisWorkbook.Name & "!" & Contacts.Name
Case "CSV"
ClassName = "CSV"
TableName = "Contacts.xsv!sep=,"
ConnectionString = ThisWorkbook.Path
End Select
this.Model.RecordTableManager.InitTable ClassName, ConnectionString, TableName
'''' Binds RecordModel to its backend
ClassName = "Worksheet"
TableName = vbNullString
ConnectionString = ThisWorkbook.Name & "!" & ContactBrowser.Name
this.Model.RecordTableManager.InitRecord ClassName, ConnectionString, TableName
End Sub
1 ответ
Я понял, что было бы предпочтительнее разместить все операции, связанные с DataCompositeManager экземпляр в одном модуле (Presenter). Напоминаю, что причина, по которой я также держал такие операции в пределах CONTROL_Change обработчики в форме code-behind: я думал, что для каждого экземпляра элемента управления в Presenter потребуется атрибут уровня модуля WithEvents. Я просто понял, что могу сохранить CONTROL_Change обработчики в форме code-behind и передают пары control_name / new_value в Presenter для дальнейшей обработки.
В КонтактыEditorForm, Я должен был определить событие настраиваемой формы:
Public Event FieldChanged(ByVal FieldName As String, ByVal NewValue As Variant)
и изменили обработчики
Private Sub FIELDNAME_Change()
If this.Model.SuppressEvents Then Exit Sub
this.Model.RecordTableManager.RecordModel.SetField "FIELDNAME", FIELDNAME.Value
End Sub
в (см. обработчик id_Change)
Private Sub FIELDNAME_Change()
If this.Model.SuppressEvents Then Exit Sub
RaiseEvent FieldChanged("FIELDNAME", FIELDNAME.Value)
End Sub
Тогда в пределах Связаться с редактором, Я мог бы сделать
Private Sub view_FieldChanged(ByVal FieldName As String, ByVal NewValue As Variant)
this.Model.RecordTableManager.RecordModel.SetField FieldName, NewValue
End Sub