Bindings are what makes Model-View-ViewModel feel a little bit like magic. Consider this example code, which creates the dynamic controls for an example UI and showcases how much of a game-changer having a MVVM framework could be for VBA code:
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
.TextBoxFor BindingPath.Create(This.ViewModel, "StringProperty"), _
Validator:=New RequiredStringValidator, _
TitleSource:="Some String:" '<~ also accepts an IBindingPath
.TextBoxFor BindingPath.Create(This.ViewModel, "CurrencyProperty"), _
FormatString:="{0:C2}", _
TitleSource:="Some Amount:" '<~ also accepts an IBindingPath
.CommandButtonFor CancelCommand.Create(Me), This.ViewModel, "Close"
End With
End Sub
This VBA code generates a dynamic UI layout with controls automatically bound to ViewModel properties, and buttons automatically bound to commands. In a project that leverages the MVVM infrastructure code, that’s the only code needed to produce this:

There’s a lot to be written about this DynamicControls
API, but that part isn’t very polished yet, and the secret sauce is that it builds (mostly anyway) on top of Property Bindings: they are what makes this sorcery possible… even with a classic drag-and-drop designer UI.
I just couldn’t resist having [at least basic, bare-bones but still extensible] support for a working .LabelFor
/ .TextBoxFor
syntax in VBA code, for the MSForms
UI library! I’ll save that for another day though, the layout stuff isn’t where it needs to be yet.
I’m missing about a million unit tests so there’s a good chance something somewhere isn’t working the way it should, but what’s there should be close enough to be worth talking about, and what matters the most is that the code is testable.
Let’s dissect how property bindings work. This time I did not push code to the examples repository, because this is an actual project in its own right, with its own need for examples. I have uploaded everything to https://github.com/rubberduck-vba/MVVM.
Property Bindings
In the context of this MVVM infrastructure code, a property binding is an object responsible for binding a source property path to a target property path; the source points to a ViewModel property, and the target to a property of a UI element (control).
ViewModel?
A ViewModel can be any object that implements the INotifyPropertyChanged
interface, as long as that class has:
Public
properties for everything the View needs to bind to.Property Let
procedures should invokeOnPropertyChanged
, but only when the property value actually changed: avoid signaling a changed property when its current value was merely overwritten with the same.Property Get
procedures are required for all binding modes;Property Let
procedures are only needed forTwoWay
andOneWayToSource
bindings.
ICommand
public properties can be exposed to avoid coupling the view with any particular specific command (other thanAcceptCommand
and/orCancelCommand
).
Note that a View could use multiple ViewModels as needed; ViewModel classes should never know anything about any View.
INotifyPropertyChanged
This interface is central in the event propagation mechanics: in design patterns terms, a class that implement it is the subject in an Observer Pattern where the registered handlers are the observers. The reason a ViewModel needs to implement this interface, is because creating a property binding registers the binding as an observer – and it handles the ViewModel telling it about a property change by applying the binding(s) for that property.
The ExampleViewModel
class illustrates how to properly implement this interface:
Public Property Get SomeProperty() As String
SomeProperty = This.SomeProperty
End Property
Public Property Let SomeProperty(ByVal RHS As String)
If This.SomeProperty <> RHS Then
This.SomeProperty = RHS
OnPropertyChanged "SomeProperty"
End If
End Property
Private Sub OnPropertyChanged(ByVal PropertyName As String)
This.Notifier.OnPropertyChanged Me, PropertyName
End Sub
Private Sub Class_Initialize()
Set This.Notifier = New PropertyChangeNotifierBase
'...
End Sub
The OnPropertyChanged
method is only invoked when the RHS
assigned value is different than the current value, and we don’t need to worry about tracking/adding observers or invoking them, because everything we need is already encapsulated in the PropertyChangeNotifierBase
class, so we implement the interface by simply passing the parameters over to this “notifier” instance:
Private Sub INotifyPropertyChanged_OnPropertyChanged(ByVal Source As Object, ByVal PropertyName As String)
This.Notifier.OnPropertyChanged Source, PropertyName
End Sub
Private Sub INotifyPropertyChanged_RegisterHandler(ByVal Handler As IHandlePropertyChanged)
This.Notifier.RegisterHandler Handler
End Sub
Now we know the interfaces involved in keeping source and target in sync, let’s look at everything else – starting with the binding paths.
IBindingPath
The documentation calls it “An object that can resolve a string property path to a value”, and that’s really all it does. The properties may need some explaining though:
Context
refers to the base object for resolving the path, i.e. your ViewModel (for the source), or a UI control (for the target).Path
refers to the property path string; usually just a ViewModel or control property name, but this string is resolved recursively so you could bind to “ViewModel.SomeObject.SomeProperty” if you needed to.Object
is the object that owns the property we’re referring to. If the path is just a property name, then this is the same reference as theContext
.PropertyName
is the resolved property name. In the example path above, that would be “SomeProperty”.
The interface also exposes Resolve
, as well as TryReadPropertyValue
, TryWritePropertyValue
, and ToString
methods; these members are invoked by the MVVM infrastructure internals.
IBindingPath
is implemented by the BindingPath
class, which exposes a Create
factory method that property-injects the Context
and Path
values and invokes the Resolve
method before returning the created object, so creating a binding path really just looks like this:
Dim Path As IBindingPath
Set Path = BindingPath.Create(ViewModel, "PropertyName")
And with that we’re ready to create an IPropertyBinding
.
PropertyBindingBase
The IPropertyBinding
interface is mostly only useful internally. There’s little of interest here that isn’t more appropriately covered by looking at the factory method for the PropertyBindingBase
class. You ready? It’s quite a mouthful…
Public Function Create(ByVal BindingManager As IBindingManager, ByVal CommandManager As ICommandManager, ByVal TargetContext As Object, ByVal SourceContext As Object, ByVal SourcePropertyPath As String, _
Optional ByVal TargetPropertyPath As String = DefaultTargetControlProperty, _
Optional ByVal Mode As BindingMode = BindingMode.TwoWayBinding, _
Optional ByVal UpdateSource As BindingUpdateSourceTrigger = BindingUpdateSourceTrigger.OnExit, _
Optional ByVal Converter As IValueConverter, _
Optional ByVal StringFormat As IStringFormatter, _
Optional ByVal Validator As IValueValidator, _
Optional ByVal ValidationAdorner As IDynamicAdorner, _
Optional ByVal CancelExitOnValidationError As Boolean = True) As IPropertyBinding
The factory method creates the IBindingPath
objects from the given context and property path strings, which makes it simpler for the calling code. Note that the target property path is Optional
, how is that possible?
If you’ve spent a bit of time with the MVVM prototype code, you probably noticed the PropertyBinding
class was pretty much out of control, and extending it to support more target types would only make it worse. So what I did is, I pulled the common code into a new PropertyBindingBase
class, then moved the control-specific code into its own specialized control-specific property binding implementation, and now there’s a strategy pattern that’s responsible for fetching the correct implementation – so that’s how binding a TextBox
target creates a TextBoxPropertyBinding
, and how binding a Label
target creates a OneWayPropertyBinding
. Each control-specific binding class can handle that control’s events and implement control-specific behavior accordingly.
IBindingManager
The binding manager is the object that knows about all the property bindings; each property binding needs a reference to the binding manager that owns it, in order to invoke data validation and trigger command bindings to evaluate whether commands can be executed. This object is automatically created when you create an AppContext
instance, but the AppContext
can be injected with any IBindingManager
implementation as needed.
ICommandManager
This “manager” guy knows about all the command bindings, which isn’t something I’ve talked about much yet. Next article about the Command Pattern will dive into more details; this object is automatically created when you create an AppContext
instance, but the AppContext
can be inject with any ICommandManager
implementation as needed.
TargetContext
Typically, that’s just a reference to the target MSForms control. Technically, it could really be any object that has any number of public properties.
SourceContext
Typically, that’s just a reference to the source ViewModel object. Technically, it could really be any object that has any number of public properties [and ideally, that implements INotifyPropertyChanged
to support 2-way bindings].
SourcePropertyPath
The last required parameter is a string representing a path (relative to the SourceContext
) to the property that holds the value we want the binding target to use; see IBindingPath
.
TargetPropertyPath
Each binding type has a default target property that is automatically inferred from the type of target (and in some cases, from the data type of the source property). For example, binding to a TextBox
control automatically wires up the control’s Text
property, such that providing a TargetPropertyPath
is only needed when binding to another target property.
Mode
This enum value determines whether the binding synchronizes the target, the source, or both. Note that binding modes OneWayBinding
and TwoWayBinding
both require the source object to implement INotifyPropertyChanged
.
UpdateSource
This enum value determines when the binding gets to update its source. When the Mode
is OneWayBinding
or OneTimeBinding
, value UpdateSourceTrigger.Never
is used automatically.
OnKeyPress
gets to validate each individual keypress. Useful for TextBox
bindings that need a key validator.
OnExit
and OnPropertyChanged
are still somewhat ambiguously defined, but OnExit
gets to keep the target control focused if there’s a validation error, and OnPropertyChanged
is [currently] firing for every keypress in a TextBox
, after the control’s Text
property udpates. Disambiguating the terms would mean breaking with MSForms’ meaning of “updated”… which may actually be a good thing: OnPropertyChanged
would basically fire on exit but without a possible navigation-blocking validation error, and then OnKeyPress
mode would still need to behave like OnPropertyChanged
as far as updating the source goes.
Converter
Each property binding can use an IValueConverter
to “convert” a value midway between the source and the target (or between the target and the source). For example we can bind a CheckBox
control to a Boolean
property, but if we need the checkbox checked when the property value is False
, we can use an InverseBooleanConverter
to convert True
to False
as the binding is applied.
StringFormat
The IAppContext.StringFormatterFactory
property (can be property-injected from AppContext.Create
) determines which formatter class type is going to be injected here. Supply a VBStringFormatterFactory
to use VB6/VBA Strings.Format
format string syntax, or supply a StringFormatterFactory
(or just leave the defaults alone) to use the much more flexible .NET String.Format
syntax.
Validator
When a binding is given an IValueValidator
, it gets to start issuing validation errors, which propagate to the ViewModel and can be used to pop a warning banner with the validation error message. Note: the AcceptCommand
class’ implementation of ICommand.CanExecute
makes it return False
when the ViewModel has validation errors.
ValidationAdorner
Having validation errors is good, letting the user know about them is even better. Supply an IDynamicAdorner
implementation by invoking ValidationErrorAdorner.Create
and use the default error formatters or supply custom ones.
Order of Operations
Several objects get involved whenever something happens in a UI control. Let’s look at what happens when we type something in a TextBox
with a standard two-way property binding to some ViewModel property.
Control Events
If the TextBox
control has a Change
event handler in the form’s code-behind (it really shouldn’t though, if we actually follow MVVM), that code probably gets to run first. The IPropertyBinding
implementation would be a TextBoxPropertyBinding
object, which handles MouseUp
and KeyPress
, but these handlers don’t trigger anything. What actually triggers the propagation of the new TextBox.Text
value to the ViewModel property, is the BeforeUpdate
and Exit
events, both of which are initially handled in the ControlEventsPunk
class, an object that leverages the Win32 IConnectionPoint
API to hook up event handlers for the MSForms.Controls
interface of our TextBox
control (we covered that in the previous post).
So the first thing to run is the ControlEventsPunk.OnBeforeUpdate
callback, which promptly iterates all registered handlers (“observers”) and invokes their HandleBeforeUpdate
method.
So the ControlEventsPunk.OnBeforeUpdate
callback propagates the event to the TextBoxPropertyBinding
, and the IHandleControlEvents_HandleBeforeUpdate
procedure runs as a result… which proceeds to forward it to the PropertyBindingBase
class with a call to OnBindingUpdateSourceOpportunity
, a method with the responsibility of conditionally invoking the ApplyToSource
method.
ApplyToSource
The method’s job is to read the value from the binding target, and then write that value to the binding source. If the binding’s Mode
is OneTimeBinding
or OneWayBinding
, we can already bail out because these modes only ever write to the binding target.
The first thing that needs to happen is a call to Resolve
against the target (an IBindingPath
). Typically the Target
path would resolve IBindingPath.Object
to a MSForms UI control, and IBindingPath.PropertyName
accordingly resolves to Text
for a TextBoxPropertyBinding
, or Value
for a CheckBoxPropertyBinding
, or Caption
for a CaptionPropertyBinding
given a Frame
or Label
target – unless a TargetPropertyPath
string was provided, in which case all bets are off and we might be binding the ForeColor
or Font.Bold
properties of a Label
-like control, or what’s stopping us from binding its Width
property (time to revisit that progress indicator, I guess).
And that’s just the tip of the iceberg, because the binding can use an IValueConverter
implementation, such that you could conceivably implement, say, a converter that takes some Enum
constant and maps each value to some Picture
icon, and then use that converter in the binding of a ViewModel property of that Enum
type to some MSForms.Image
control’s Picture
property… but I digress. Converters can also do boring things, like making sure the input value 2
becomes 0.02
before it gets written to that PercentValue
ViewModel property, and then string formats can make sure that looks like 2.0%
before it gets written to TextBox.Text
, but we’ll get to that.
So the next thing that needs to happen is a call to IBindingPath.TryReadPropertyValue
, and if we can’t do that we need to bail out, but this time ApplyResult.BindingSkipped
won’t be good enough, so we explicitly return a BindingFailed
result.
Once we know what value is currently in TextBox.Text
(when the source update trigger is OnKeyPress
, we have the KeyAscii
value for it), we need to invoke IValueConverter.ConvertBack
if a converter was specified for the binding; if the conversion fails, we return ApplyResult.BindingConversionError
.
If conversion succeeded, we’re ready to validate the converted value (or the original one, if there is no converter). If the update trigger is OnKeyPress
, then the validator operates on the single keypress – otherwise we validate the entire value. Things get interesting when there’s a validation error now: we’re returning ApplyResult.BindingValidationError
, but then if there’s a ValidationAdorner
, its Show
method gets invoked and the validation error for that property is propagated to an IValidationManager
object.
If validation passes, we try to read the source property value. If we can’t read it, we bail with a BindingFailed
result. Otherwise we compare the source value with the target value, and if they are different then we proceed to clear any validation errors for that property, and then we finally write the new value to the source property; if that final step succeeds, we return ApplyResult.BindingSuccess
result.
ApplyToTarget
When it’s a ViewModel property that changes, a similar sequence of events unfolds: the Property Let
procedure invokes INotifyPropertyChanged.NotifyPropertyChanged
, the property binding was registered as an observer, so IHandlePropertyChanged_HandlePropertyChanged
is invoked; whenever the provided Source
is the binding source and the provided PropertyName
is the source property name, ApplyToTarget
gets invoked.
When the binding mode is OneWayToSource
or OneTimeBinding
, we know we can immediately bail out, because these states don’t write to the binding target. Now, it’s entirely possible that we still need to supply a TextBox
with a Text
value even if we can’t yet resolve the binding Source
(e.g. IBindingPath.Object
resolves to Nothing
). In such cases, we attempt to get a sensible default target value depending on the name of the target property:
- “Text” and “Caption” target properties default to
vbNullString
; - “Enabled” and “Visible” target properties default to
False
; - “Value” property defaults to
False
when the target is aCheckBox
orOptionButton
.
If the source object is Nothing
and we don’t have a fallback default, we bail out. Otherwise we try to read the source (ViewModel) value, then we validate it, then we convert it, then we read the target property value, compare with the source, and overwrite it if they’re different… but just before doing that, we run it through an IStringFormatter
if we have one.
StringFormat
An MVVM application might need to use, say, a Date
value somewhere. The application needs the data type to be Date
, such that the program doesn’t need to worry about a malformed or ambiguous date string and works with the actual underlying Date
value. Such an application would define a ViewModel class with a Date
property (say, OrderDate
), and then there can be a TextBox
control showing that Date
value to the user.
If we don’t do anything, the content of that TextBox
control would be whatever VBA decides a Date
value needs to look like when represented as a String
, and that would be the (sorry, American readers) utterly nonsensical en-US format (mm-dd-yyyy). If your application’s users are happy with such a format, more power to them – but I like my date strings unambiguous and boringly standard, so I’d want the TextBox
content to say “yyyy-mm-dd” instead. By providing a FormatString
argument to the property binding, we can make it do exactly that. Or we can just as easily make it say “Thursday, October 22 2020” if we wanted to, and with a StringToDateConverter
we could round-trip that value back to a proper Date
.
Or maybe our ViewModel has a Currency
property because our application needs to get some dollar amount, and having that displayed in a TextBox
control as 1234567.89
is bare-bones good enough, but we could provide a FormatString
argument to the property binding and have our ViewModel’s Currency
property hold the numeric value 1234567.89
all while the bound TextBox
control says $1,234,567.89
.
Without MVVM property bindings doing this for us, implementing this kind of functionality is such a pain in the neck that it’s hardly ever done at all! Nobody wants to deal with parsing dates and formatted currency amounts off a TextBox
control, and for a reason: when TextBox.Text
is the value you’re working with, you are working with a String
and you do need to parse its content.
With MVVM, we’re completely elsewhere: the TextBox.Text
is just a receptacle for displaying whatever the real underlying value is (i.e. the ViewModel property), and is completely separated from it – and this tremendously simplifies everything.
The MVVM infrastructure code comes with two implementations for the IStringFormatter
interface:
StringFormatter
(default), uses .NETstring.Format
syntaxVBStringFormatter
, uses VB6/VBAStrings.Format
syntax
So in order to make a Date
ViewModel property look like YYYY-MM-DD
we could:
- Use a
VBStringFormatter
with a “YYYY-MM-DD” format string (case insensitive) - Use a
StringFormatter
with a “yyyy-MM-dd” format string (note: lowercase-“m” refers to the “minute” part of the datetime here – use uppercase-“M” for the month!)
And in order to make a Currency
ViewModel property look like money we could:
- Use a
VBStringFormatter
with a “Currency” (or a culture-invariant “$#,##0.00”) format string - Use a
StringFormatter
with a “{0:C2}” format string (if we want 2 decimals)
Creating an IStringFormatter
every time we want to use one would be annoying, so the binding manager uses the abstract factory from the IAppContext
to spawn it automatically. A nice side-effect of this, is that the string formatters for the bindings of a given context are guaranteed to all use the same syntax. So if we wanted to use VB format strings, we would create the app context like this:
Dim Context As IAppContext
Set Context = AppContext.Create(FormatterFactory:=New VBStringFormatterFactory)
Note that if you use a format string that results in a TextBox.Text
value that can’t be automatically (and correctly) parsed back into the data type of the bound ViewModel property (if that isn’t a String
), updating the binding source will likely fail with a conversion error: you will need to implement an IValueConverter
and inject it into the binding along with the format string in order to correctly convert the formatted string back to a value that is usable by the binding; a StringToDateConverter
class exists in the MVVM infrastructure code to work with Date
properties and standard format strings, but the implementation may need adjustments to handle formats that don’t automatically round-trip back to a Date
value.
Validation
Another key aspect of property bindings, is that they simplify validating user inputs. If a program needs to work with some numeric value provided by the user and the user interface doesn’t validate its inputs, there’s a type mismatch error written in the sky there, or worse. As a general rule of thumb, it’s a good idea for code to assume that a value coming from the user is anything but what the program needs to work as expected.
The best way to handle an error is always to avoid throwing that error in the first place, and validating user inputs contributes to exactly this.
If you need the digits of a phone number and present the user with a control that only accepts a certain number of digit characters and then uses a format string to prettify the value on exit, you ensure that your PhoneNumber
string value only ever contains the meaningful characters, leaving the “what a phone number looks like” concern separate from the “phone number” data itself, which means every phone number in your list/table ultimately gets to look exactly the same, as opposed to the free-form nightmares I presume we’re all well too familiar with.
The MVVM infrastructure addresses validation concerns through the IValidationManager
interface. The object that implements this interface is responsible for managing validation errors across binding sources (ViewModels) in the context of an IApplicationContext
.
ValidationManager
The role of the validation manager is to encapsulate the validation state and expose methods to add and clear validation errors; the IsValid
indexed property returns a Boolean
given a context (the ViewModel) and an optional property name: in order to know whether the entire context is valid, omit the PropertyName
argument.
OnValidationError
and ClearValidationError
respectively add and remove a validation error for a particular source property, and the validation manager keeps validation errors in a dictionary keyed with the ViewModel object (a string representation of its object pointer), such that each ViewModel can be deemed “valid” or “invalid” individually/separately.
The “manager” class isn’t responsible for doing anything with a validation error: it just holds the state, so that other components can query it and retrieve the IValidationError
for SomeViewModel.SomeProperty
.
An IValidationError
is a simple object that gives us a Message
(from the IValueValidator
that caused the binding to fail validation) and the IPropertyBinding
that couldn’t be applied.
So, that dynamic UI stuff?
It works good-enough to make a good-enough screenshot, but the IContainerLayout
stuff needs more thinking-through and more fiddling to get everything just right. See, as of this writing the layout API stacks controls horizontally or vertically, and well, that’s about it.
I want a docking panel, a layout container that can resize its children as needed and that’s a truly fascinating topic… For now there’s an IDynamicControlBuilder
interface that looks like this:
'@Folder MVVM.Infrastructure.Abstract
'@ModuleDescription "Builds dynamic MSForms UI components from a binding source."
'@Interface
'@Exposed
Option Explicit
'@Description "Creates a multiline MSForms.TextBox control for the spercified String property binding path."
Public Function TextAreaFor(ByVal SourceValue As IBindingPath, Optional ByVal Converter As IValueConverter, Optional ByVal Validator As IValueValidator, Optional ByVal ErrorAdorner As IDynamicAdorner, Optional ByVal TitleSource As Variant) As MSForms.TextBox
End Function
'@Description "Creates a MSForms.TextBox control for the specified String property binding path."
Public Function TextBoxFor(ByVal SourceValue As IBindingPath, Optional ByVal FormatString As String, Optional ByVal Converter As IValueConverter, Optional ByVal Validator As IValueValidator, Optional ByVal ErrorAdorner As IDynamicAdorner, Optional ByVal TitleSource As Variant) As MSForms.TextBox
End Function
'@Description "Creates a MSForms.Label control for the specified Caption string or String property binding path."
Public Function LabelFor(ByVal SourceCaption As Variant, Optional ByVal FormatString As String, Optional ByVal Converter As IValueConverter) As MSForms.Label
End Function
'@Description "Creates a MSForms.ComboBox control for the specified Value property binding path; SourceItems should be an array property."
Public Function ComboBoxFor(ByVal SourceValue As IBindingPath, ByVal SourceItems As IBindingPath, Optional ByVal FormatString As String, Optional ByVal Converter As IValueConverter, Optional ByVal Validator As IValueValidator, Optional ByVal ErrorAdorner As IDynamicAdorner, Optional ByVal TitleSource As Variant) As MSForms.ComboBox
End Function
'@Description "Creates a MSForms.ListBox control for the specified Value property binding path; SourceItems should be an array property."
Public Function ListBoxFor(ByVal SourceValue As IBindingPath, ByVal SourceItems As IBindingPath, Optional ByVal TitleSource As Variant) As MSForms.ListBox
End Function
'@Description "Creates a MSForms.OptionButton control for the specified Value (Boolean) property binding path; uses the specified Caption string or String property binding path for the control's Caption."
Public Function OptionButtonFor(ByVal SourceValue As IBindingPath, ByVal SourceCaption As Variant) As MSForms.OptionButton
End Function
'@Description "Creates a MSForms.CheckBoxButton control for the specified Value (Boolean) property binding path; uses the specified Caption string or String property binding path for the control's Caption."
Public Function CheckBoxFor(ByVal SourceValue As IBindingPath, ByVal SourceCaption As Variant) As MSForms.CheckBox
End Function
'@Description "Creates a MSForms.CommandButton control for the specified ICommand, using the specified ViewModel context and Caption string or String property binding path."
Public Function CommandButtonFor(ByVal Command As ICommand, ByVal BindingContext As Object, ByVal SourceCaption As Variant) As MSForms.CommandButton
End Function
…and I haven’t even tested all of it yet, and small little things that actually matter, like OptionButton
groups, aren’t being considered. I still need to think of how this API can get where it wants to be, but I really like where it’s going.
Thoughts?
To be honest, I’m having a blast with this, and writing actual working MVVM code in VBA is completely surreal, in a very awesome way.
I think it’s in itself a nice deep-dive into OOP+VBA – whether the MVVM architecture it enables ends up being the backbone of any production app or not.
What do you think?