Coder Social home page Coder Social logo

mvvm's Introduction

MVVM in VBA!

With Rubberduck, object-oriented programming in VBA is easier than ever: large projects with many small and specialized class modules can be neatly organized in a custom folder hierarchy, for one.

This project demonstrates that not only OOP but also Model-View-ViewModel can be leveraged in VBA, mainly for educational and inspirational purposes.

The wiki section should be a good place to start, right after this overview :)

Features

The 100+ modules solve many problems related to building and programming user interfaces in VBA, and provide an object model that gives an application a solid, decoupled backbone structure.

Object Model

The IAppContext interface, and its AppContext implementation, are at the top of the MVVM object model. This context object exposes IBindingManager, ICommandManager, and IValidationManager objects (among others), each holding their own piece of the application's state (property bindings, command bindings, and binding validation errors, respectively).

Property Bindings

The INotifyPropertyChanged interface allows property bindings to work both from the source (ViewModel) to the target (UI controls), and from the target to the source. Hence, by implementing this interface on ViewModel classes, UI code can bind a ViewModel property to a MSForms.TextBox control (or anything), via the IBindingManager.BindPropertyPath method - by letting the manager infer most of everything...

With Context.Bindings 'where Context is an IAppContext object reference
    ' use IBindingManager.BindPropertyPath to bind a ViewModel property to a property of a MSForms control target.
    .BindPropertyPath ViewModel, "Instructions", Me.InstructionsLabel
End With

...or by configuring every aspect of the binding explicitly.

Validation

Application code may implement the IValueValidator interface to supply a property binding with a Validator argument. Bindings that fail validation use the default dynamic error adorner (that was configured when the top-level AppContext is created) to display configurable visual indicators (border, background, font colors, but also dynamic tooltips, icons, and labels); when the binding is valid again, the visual cues are hidden and the IValidationManager holds no more IValidationError objects in its ValidationErrors collection for the ViewModel's binding context (each ViewModel gets its own "validation scope").

By default, an invalid field visually looks like this:

an invalid string property binding with the default dynamic adorner shown

Command Bindings

The ICommand interface can be implemented for anything that needs to happen in response to the user clicking a button: in MVVM you don't handle Click events anymore, instead you bind an implementation of the ICommand interface to a MSForms.CommandButton control: the MVVM infrastructure code automatically takes care to enable or disable that control (you provide the ICommand.CanExecute Boolean logic, MVVM automatically invokes it).

With Context.Commands 'where Context is an IAppContext object reference
    ' use ICommandManager.BindCommand to bind a MSForms.CommandButton to any ICommand object.
    .BindCommand ViewModel, Me.CommandButton1, ViewModel.SomeCommand
End With

Dynamic UI

This part of the API is still very much subject to breaking changes since it's very much alpha-stage, but the idea is to provide an API to make it easy to programmatically generate a user interface from VBA code, and automatically create the associated property and command bindings.

Whether your UI is dynamic or made at design-time, the recommendation would be to create the bindings in a dedicated InitializeView procedure in the form's code-behind.

This example snippet is from the ExampleDynamicView module - remember to invoke IBindingManager.Apply to bring it all to life:

Private Sub InitializeView()
    
    Dim Layout As IContainerLayout
    Set Layout = ContainerLayout.Create(Me.Controls, TopToBottom)
    
    With DynamicControls.Create(This.Context, Layout)
        
        With .LabelFor("All controls on this form are created at run-time.")
            .Font.Bold = True
        End With
        
        .LabelFor BindingPath.Create(This.ViewModel, "Instructions")
        
        .TextBoxFor BindingPath.Create(This.ViewModel, "StringProperty"), _
                    Validator:=New RequiredStringValidator, _
                    TitleSource:="Some String:"
                    
        .TextBoxFor BindingPath.Create(This.ViewModel, "CurrencyProperty"), _
                    FormatString:="{0:C2}", _
                    Validator:=New DecimalKeyValidator, _
                    TitleSource:="Some Amount:"
        
        .CommandButtonFor AcceptCommand.Create(Me, This.Context.Validation), This.ViewModel, "Close"
        
    End With
    
    This.Context.Bindings.Apply This.ViewModel
End Sub

the ExampleDynamicView at run-time

mvvm's People

Contributors

mso-dlx avatar retailcoder avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mvvm's Issues

Binding manager tests failing on initialize and on the handle property change tests

Thank you for this interesting project. I am trying to see if I can use this to implement a bit of MVVM in Excel. I saw your indication that this is super experimental.

I am getting a type mismatch error on this statement:

Set Test.ConcreteSUT = BindingManager.Create(Test.CommandManager, New StringFormatterNetFactory)

which I changed to:

Set Test.ConcreteSUT = BindingManager.Create(AppContext.Create(DebugOutput:=True), New StringFormatterNetFactory)

I am wondering if this change is insufficient and thus causing the handle property change tests to see 0 invokes.

Could you please suggest some ideas of how I could fix this?

Thanks

David

Rename the constant 'CustomError' so that it scans better when qualified with the class name

Would it be worth renaming the constant 'CommonError' to one which scans better when the constant is qualified.

e.g. rather than

CommonErrors.CommonError

to have

CommonErrors.Base

as an example

Public Enum GuardClauseErrors
        
        InvalidFromNonDefaultInstance = CustomErrors.Base + 1
        InvalidFromDefaultInstance
        ObjectAlreadyInitialized
        ObjectCannotBeNothing
        StringCannotBeEmpty
        
    End Enum

Would it be possible to add a diagram to the readme?

Hey Matt,

I've been taking a look at this - I'd really like to get to grips with how it works a little better. I've read the blog posts, had a look at the readme and codereview posts etc. and I understand the principle & motivation (I think), but the implementation with all these 100+ modules is kinda confusing me still :)

I was wondering if you'd consider adding some kind of high level block diagrams to the readme? I'd understand if it's not your top priority right now, and IIRC I read in one of your blog posts that you don't like diagrams that much, but personally I find visual aids valuable in providing an overview of how the code fits together. I'm still not fluent enough in polymorphism to just see some code and piece it together in my head.

In the past I've done things like:

jMLJB 1 ...or this (please ignore the erroneous hungarian notation):
hl2Sl 1

... and I've found them really useful coming back to the code months later, I hope reviewers have found them useful too getting to grips with the code, even if they are quite informal (well you could easily confirm/deny the reviewer perspective;).

So IDK how you'd feel about adding this/ whether you would have the time at all, but I think different people learn differently and although the blog posts are excellent for explaining concepts precisely and with clear examples, for large projects where you might want a zoomed out view, I find supplementing the code with visual aids is really helpful, although that's just my opinion.


P.s. I would consider adding this myself if you like the sound of it, and will at some point in the future if you don't do it yourself, but I thought I'd ask in advance as you're probably much better placed to make it and of course this way I could benefit too.

ValueRangeValidator not detecting valid values

Hi there,

Thanks for putting together this project, Mat. I thought your inititial MVVM code looked great, and this iteration brings some nice additions. I'm looking forward to seeing how far you take it.

Following is a minor issue I came across while testing:

I tried to create a textbox that accepts only integer values between 0 and 100. In my test, any value I enter triggers the invalid value error message.

image

There wasn't an example of this particular validator, so I immitated how some of the other binding parameters were setup. Most likely, I'm using the wrong syntax. Here are the steps I took:

  • In ExampleView Designer, add a textbox and name it "textbox2"

  • In ExampleView Code-behind, add a property binding:

        .BindPropertyPath ViewModel, "SomeNumber", Me.TextBox2, _
            UpdateTrigger:=OnKeyPress, _
            Validator:=ValueRangeValidator.Create(Min:=0, max:=100), _
            ValidationAdorner:=ValidationErrorAdorner.Create( _
                Target:=Me.TextBox2, _
                TargetFormatter:=ValidationErrorFormatter.WithErrorBorderColor.WithErrorBackgroundColor)
  • In the ExampleViewModel, add the property "SomeNumber" to the custom type:
Private Type TViewModel
    Notifier As INotifyPropertyChanged
    
    SomeCommand As ICommand
    SomeFilePath As String
    SomeAmount As Currency
    SomeDate As Date
    SomeProperty As String
    SomeOption As Boolean
    SomeOtherOption As Boolean
    SomeItems As Variant
    SelectedItemText As String
    SelectedItemIndex As Long
    BooleanProperty As Boolean
    ByteProperty As Byte
    CurrencyProperty As Currency
    DateProperty As Date
    DoubleProperty As Double
    LongProperty As Long
    StringProperty As String
    
    SomeNumber As Long
End Type
  • In the ExampleViewModel, add the property getter and setter:
Public Property Get SomeNumber() As Long
    SomeNumber = this.SomeNumber
End Property

Public Property Let SomeNumber(ByVal RHS As Long)
    If this.SomeNumber <> RHS Then
        this.SomeNumber = RHS
        OnPropertyChanged "SomeNumber"
    End If
End Property
  • In Example module, execute the "run()" sub. Now enter a valid a number between 0 and 100. When you press "tab" to move to the next control, the invalid value error message will display.

That's it. Thanks for any help on this.
-Jay

dynamic userform hangs upon first validation error in alphanumeric textbox

  1. try to leave upper textbox in the dynamic userform without entry -> causes validation error correctly, unable to leave this textbox
  2. enter text -> textbox stays invalid, unable to leave this textbox though the validation error should be removed and the user should be allowed to proceed

eventually must force end to stop this macro

Why ConnectToConnectionPoint API?

I can see the benefit of using ConnectToConnectionPoint but is it really necessary? Is there any event that cannot be captured using WithEvents? I tend to use a general purpose class that captures the events I need. Example here. The downside is that events need to be repeated for each control type but the upside is that there is no need for an extra dependancy and it works on MAC. Am I missing anything?

Run-time error '13': Type mismatch

error

Hello, If I want to implement a validation to ComboBox control
and have the following error: Run-time error '13': Type mismatch on this line when it evaluates to TRUE:

If Not Validator Is Nothing Then
    BindingBase.AsINotifyValidationError.RegisterHandler ValidationManager
End If

BindPropertyPath:

Context.Bindings.BindPropertyPath ViewModel, "ComboBoxItemIndex", Me.ComboBox, _
            Mode:=TwoWayBinding, _
            UpdateTrigger:=OnPropertyChanged, _
            Validator:=New RequiredSelectionValidator, _
            ValidationAdorner:=ValidationErrorAdorner.Create(Target:=Me.ComboBox, _
            TargetFormatter:=ValidationErrorFormatter.WithErrorBorderColor.WithErrorBackgroundColor)

RequiredSelectionValidator Class:

'@ModuleDescription "A validator that requires a ComboBox Item to be selected."
Option Explicit

Implements IValueValidator

Private Function IValueValidator_IsValid(ByVal value As Variant, ByVal Source As IBindingPath, ByVal Target As IBindingPath) As Boolean
    IValueValidator_IsValid = value > -1
End Function

Private Property Get IValueValidator_Message() As String
    IValueValidator_Message = "Value cannot be empty."
End Property

Private Property Get IValueValidator_Trigger() As BindingUpdateSourceTrigger
    IValueValidator_Trigger = OnExit
End Property

Any suggestions?

MVVM project in VB6 ide

Hi, just found this very interesting project , ¿could this project be integrated in the vb6 classic ide or does only works in excel?. thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.