Solved Encapsulate Function In A Class For Command Bar (2 Viewers)

dalski

Member
Local time
Today, 22:48
Joined
Jan 5, 2025
Messages
119
I have a class for a Subform; trying to encapsulate all relevant things. The MouseUp event (assigned to the class) deletes & builds a relevant right-click context menu. But if I place a Public Function InsertPageFromABill()
within the class to handle a button in the Command Bar it is not visible to the Subform. Seems you cannot reference a class' methods in the CmdBar.OnAction property with clsName.FunctionName. I am trying not build lots of classes & trying to encapsulate the relevant things to this Subform class ideally.
If I put the function in a normal module it works fine, but this does not seem the right thing to do (should encapsulate relevant things inside the class).

What is the proper encapsulation methodology here? I could build another class for the command bar but seems too messy with unnecessary classes for the sake of creating classes. Could build another class and reference the function and gain access from there but again this seems superfluous? I would think declaring the function as Friend is the way but does not seem to work.

SubForm
Code:
'+Init & Demo:
Private Sub Form_Load()
  Dim ctrl As Control
  For Each ctrl In Me.Detail.Controls
    If ctrl.ControlType = acTextBox Then
      Set mObj = New clsBillOrPage
      mObj.fInit ctrl
      mColTenderBillsAndPages.Add mObj
    End If
  Next ctrl
End Sub

Private Sub Form_Unload(Cancel As Integer)
  For Each mObj In mColTenderBillsAndPages
    mObj.Dispose
  Next mObj
End Sub

clsBillOrPage
Code:
Public WithEvents mTb As TextBox
Private mParentFrm As Form
Public Enum eBillOrPage
  Bill = 1
  Page = 2
End Enum

Public Function fInit(ctl As Control, Optional lParentFrm As Form) As clsBillOrPage
  Set mParentFrm = lParentFrm
  Set mTb = ctl
  mTb.OnMouseUp = "[Event Procedure]"
  Set fInit = Me
End Function
Public Function Dispose()
  Set mTb = Nothing
  Set mParentFrm = Nothing
End Function
Private Sub mTb_MouseUp(Button As Integer, Shift As Integer, x As Single, Y As Single)
  Dim strBarName As String
  strBarName = "dalsBillPageRcMenu"
  Dim Result As String
  Dim lConcatID As String
  lConcatID = Screen.ActiveControl.Parent.CONCAT_ID
  If Button = vbKeyRButton Then
    'Build Empty CmdBar
    Dim lBar As CommandBar
    Set lBar = ResetCustomBar("dalsBillOrPageRcMenu")
    'Assign cmdBar to frm
    Debug.Print Screen.ActiveControl.Parent.name
    Screen.ActiveControl.Parent.ShortcutMenuBar = "dalsBillOrPageRcMenu"
 
    'Is current item Bill/ Page?
    Result = IsItBillOrPage(lConcatID)
    If Result = Bill Then
      'It's a Bill; add Bill btns
      CmdBarAddBtnsBill lBar
    ElseIf Result = Page Then
      'It's a Page; add Page btns
      MsgBox "p"
    Else
      MsgBox "Dunno"
    End If
  End If
End Sub
Public Function IsItBillOrPage(lConcatenatedID As String) As eBillOrPage
  If Left(lConcatenatedID, 4) = "Bill" Then
    IsItBillOrPage = Bill
  ElseIf Left(lConcatenatedID, 4) = "Page" Then
    IsItBillOrPage = Page
  Else
    MsgBox "Unaccounted record source type; only accounting for Bill/ Page"
  End If
End Function


'************************************************************************************************
'+ RIGHT-CLICK MENU (add btn's):
'SELECTED ITEM IS BILL:
Public Function CmdBarAddBtnsBill(theCmdBar As CommandBar)
  With theCmdBar.Controls.Add(msoControlButton)
    .Caption = "+ Page"
    .OnAction = "=InsertPageFromABill()"
  End With
  With theCmdBar.Controls.Add(msoControlButton)
    .Caption = "Edit Page"
    .OnAction = "=EditThePage()"
  End With
End Function

RightClickStuffModule - Though should be in the class I think this may be a minor exception where no point in writing this generic procedure all over the place it will be used all over the place with the exception to the Function InsertPageFromABill (this is the function I want to encapsulate as it is not generic and specific to the class.
Code:
'If menu exists; del & build new one (no exists fn)
Public Function ResetCustomBar(BarName As String) As Office.CommandBar
  On Error Resume Next
  On Error GoTo Line1
  CommandBars(BarName).Delete
Line1:
  Set ResetCustomBar = CommandBars.Add(BarName, msoBarPopup, False, True)
End Function

'If menu exists; del & build new one (no exists fn)
Public Function ResetCustomBar(BarName As String) As Office.CommandBar
  On Error Resume Next
  On Error GoTo Line1
  CommandBars(BarName).Delete
Line1:
  Set ResetCustomBar = CommandBars.Add(BarName, msoBarPopup, False, True)
End Function

Public Function InsertPageFromABill()
  Dim lBillID As String
  lBillID = Screen.ActiveControl.Parent.CONCAT_ID
  DoCmd.OpenForm "TenderPageEditF", , , , acFormAdd
End Function
 
Last edited:
Your desired syntax "clsName.FunctionName" only works with static classes, which have
Attribute VB_PredeclaredId = True
in the header. See also this.
Public methods then are available throughout the rest of the code.
 
I will give a little more context

In other languages you have what is known as Static properties and Procedures. This allows you to build a class and encapsulate and protect the functionality, but utilize the procedures without having to instantiate an instance.
Example in VB is the Math class which has all the math functions

You do not have to do something like

dim MyMath as New Math
x = myMath.log(100)

You simply do
x = Math.log(100)
notice "ClassName.ClassMethod"

In some other languages everything is a class and there is no thing like a standard module, which is kind of like the static properties.

You may ask why make a class when you can use a standard module.
1. It is more protected. You will never have a name conflict because any name of a property and method is protected in the class
2. With a class you have to access it through the class name or instance. This may provide some protection if you have standard module functions in two modules that have similar names. You are less likely to call the wrong function.
3. You have a pseudo constructor in the initialize method
4. You can actually build properties in a standard module. I have never done that, because if I am creating properties I already want the other benefits of a class over a standard module.

So you can fake static approach by predeclaring your class. This basically creates a global instance of your class immediately. See discussion on how you predeclare you class, and some easier ways if you have MZ-Tools.
 
Hey @dalski, if you are working with a popup menu or commandbar, and you don't want to handle the click using the OnAction property callback, check out this thread: How to handle a CommandBarButton click event.
• This is actually the same pattern as the TextBox right click handler you were working on in a different thread.
• The cPopup class wraps a CommandBar, holds a collection of cPopupButton instances, and exposes a method (ButtonClicked) that cPopupButton can call if a click occurs.
• Each cPopupButton class wraps a single Office.CommandBarButton, holds a reference to cPopup, handles the CommandBarButton.Click event, and calls back to cPopup.ButtonClicked.
• cPopup then raises a PopupClick event, passing the CommandBarButton back up to the object hosting cPopup, usually a Form.
This enables a form to create, show, and handle events from a CommandBar directly, without having to use CommandBarButton.OnAction to define a callback.
 
Wish I would have seen this earlier. I have a few pretty large command bars. Here is one example
It started small but I kept adding buttons. The code really became a pain to manage. With the on action you have to be precise in your naming so that the action mirrors the caption. Towards the end I figured I just needed to refactor the code because it was such a pain, but never got to it.
This would have saved a lot of time.
 
This would have saved a lot of time.
Yeah, when I developed that pattern I was pretty happy, because the pattern I was using before was...
• Save and expose a temporary public reference to the owner/creator of cPopup
• Execute the .OnAction callback
• Get the .OnAction method to re-callback to the temp/public cPopup owner with results
• Destroy the temp/public cPopup owner reference
It was an ugly hack.

But what I do now is even cooler. The owner/creator of cPopup passes an ICommand to cPopupButton, whose .Click handler--rather than bubble the event back up to the owner thru cPopup--just executes the ICommand.
 

Users who are viewing this thread

  • Back
    Top Bottom