This is a perfect use-case for an Adapter: internally adapting the semantics for a set of contracts (interfaces) and exposing them as its own external API; possibly according to some other contract.
Define class modules IViewEvents:
Option Compare Database
Option Explicit
Private Const mModuleName As String = "IViewEvents"
Public Sub OnBeforeDoSomething(ByVal Data As Object, ByRef Cancel As Boolean): End Sub
Public Sub OnAfterDoSomething(ByVal Data As Object): End Sub
Private Sub Class_Initialize()
Err.Raise 5, mModuleName, AccessError(5) & "-Interface class must not be instantiated."
End Sub
IViewCommands:
Option Compare Database
Option Explicit
Private Const mModuleName As String = "IViewCommands"
Public Sub DoSomething(ByVal arg1 As String, ByVal arg2 As Long): End Sub
Private Sub Class_Initialize()
Err.Raise 5, mModuleName, AccessError(5) & "-Interface class must not be instantiated."
End Sub
ViewAdapter:
Option Compare Database
Option Explicit
Private Const mModuleName As String = "ViewAdapter"
Public Event BeforeDoSomething(ByVal Data As Object, ByRef Cancel As Boolean)
Public Event AfterDoSomething(ByVal Data As Object)
Private mView As IViewCommands
Implements IViewCommands
Implements IViewEvents
Public Function Initialize(View As IViewCommands) As ViewAdapter
Set mView = View
Set Initialize = Me
End Function
Private Sub IViewCommands_DoSomething(ByVal arg1 As String, ByVal arg2 As Long)
mView.DoSomething arg1, arg2
End Sub
Private Sub IViewEvents_OnBeforeDoSomething(ByVal Data As Object, ByRef Cancel As Boolean)
RaiseEvent BeforeDoSomething(Data, Cancel)
End Sub
Private Sub IViewEvents_OnAfterDoSomething(ByVal Data As Object)
RaiseEvent AfterDoSomething(Data)
End Sub
and Controller:
Option Compare Database
Option Explicit
Private Const mModuleName As String = "Controller"
Private WithEvents mViewAdapter As ViewAdapter
Private mData As Object
Public Function Initialize(ViewAdapter As ViewAdapter) As Controller
Set mViewAdapter = ViewAdapter
Set Initialize = Me
End Function
Private Sub mViewAdapter_AfterDoSomething(ByVal Data As Object)
' Do stuff
End Sub
Private Sub mViewAdapter_BeforeDoSomething(ByVal Data As Object, ByRef Cancel As Boolean)
Cancel = Data Is Nothing
End Sub
plus Standard Modules Constructors:
Option Compare Database
Option Explicit
Option Private Module
Private Const mModuleName As String = "Constructors"
Public Function NewViewAdapter(View As IViewCommands) As ViewAdapter
With New ViewAdapter: Set NewViewAdapter = .Initialize(View): End With
End Function
Public Function NewController(ByVal ViewAdapter As ViewAdapter) As Controller
With New Controller: Set NewController = .Initialize(ViewAdapter): End With
End Function
and MyApplication:
Option Compare Database
Option Explicit
Private Const mModuleName As String = "MyApplication"
Private mController As Controller
Public Function LaunchApp() As Long
Dim frm As IViewCommands
' Open and assign frm here as instance of a Form implementing
' IViewCommands and raising events through the callback interface
' IViewEvents. It requires an initialization method (or property
' setter) that accepts an IViewEvents argument.
Set mController = NewController(NewViewAdapter(frm))
End Function
Note how use of the Adapter Pattern combined with programming to interfaces results in a very flexible structure, where different Controller or View implementations can be substituted in at run time. Each Controller definition (in the case of different implementations being required) uses different instances of the same ViewAdapter implementation, as Dependency Injection is being used to delegate the event-source and command-sink for each instance at run time.
The same pattern can be repeated to define the relationship between the Controller/Presenter/ViewModel and the Model, though implementing MVVM in COM can get rather tedious. I have found MVP or MVC is usually better suited for COM-based applications.
A production implementation would also add proper error handling (at a minimum) to the extent supported by VBA, which I have only hinted at with the definition of the mModuleName constant in each module.