How to invoke ActiveX procedure outside of it's Worksheet scope? - excel

I've got an extensive project.
Amongst other things, it contains an
UserForm
Worksheet (Data) with a button called import_button
Either way, I've got one procedure inside the UserForm vba code, which acts something akin to mine.
After it is executed, I wanted to Call the import_button_Click procedure, from the Data Worksheet, however obviously, the reference is unknown, given the import_button_Click procedure is Private inside the Data Worksheet module.
Is there perhaps a way, I could invoke the import_button_Click procedure outside of the Sheet1 (Data) module?
EDIT:
Should probably note, this is the structure of the import_button_Click procedure.
Public Sub import_button_Click()
Dim prva As Range: Set prva = Sheets("Zdrojove").Range("A1")
Call import_data(get_delimeter(prva, ";", ",", ".", "-", "/"), False)
Call validate_format
End Sub

I can't recreate the issue once changing the Sub to Public. Inside CommandButton1:
Inside Module1:
Running Test:

An option would be to replace the ActiveX button on the Worksheet by a Form Control button. This would allow you to get the exact procedure, now hosted in a Module instead of in the Worksheet... Then you can get the button to execute whatever macro (or sub) you want, while being able to call this procedure from other piece of code at the same time.

There're several ways to make it work:
You declare import_button_Click Public
Public Sub import_button_Click()
'Your code
End Sub
You create another Public Sub (inside Sheet1 (Data)) to call the Private one.
Private Sub import_button_Click()
'Your code
End Sub
'Use this outside the sheet
Public Sub public_import_button_Click()
Call import_button_Click
End Sub
Hope this helps.

Related

Is there a way to tell if the Sub currently running is the parent or a called child

I have routines in VBA that can run standalone or can be called by another routine. When called as a subroutine (child) or as a primary routine (parent) there are operations that I may or may not want to execute. Is there a built-in function in VBA that I can call that will tell me if my executing code is a parent or child?
I have created global variables to test for parent/child but I would like to have a more elegant solution.
I think a good way would be to have a procedure for the action itself that has a switch (parameter) and a procedure that calls it.
Private Sub MyProcedure(Optional ByVal IsChild As Boolean = True) 'set default here
If IsChild Then
'child
Else
'parent
End If
End Sub
Now you can have a procedure to call it
Public Sub ParentCallMyProcedure()
MyProcedure IsChild:=False
End Sub
Public Sub ChildCallMyProcedure()
MyProcedure IsChild:=True
'which would be the same as
MyProcedure
End Sub
Eg if you want to call MyProcedure from a button then use
Public Sub Button1_Click()
MyProcedure IsChild:=False
End Sub
In all other procedures just use MyProcedure and IsChild is default True.
At least this is more elegant than a public/global variable.
In .Net getting info for a method, which called a method is called Reflection. It is quite straight-forward in C# - How can I find the method that called the current method?. VBA does not support it, but you could run around it, and log somewhere data about it - through a variable or logging to a worksheet or database.
There is a way to do it, if you stop the code in the middle of the called sub/function and call the Call Stack diaglog box. E.g., imagine the following seqeuence:
Sub TestMe()
Testme2
End Sub
Sub Testme2()
Stop
End Sub
If you run the code and press Ctrl+L once you are on the Stop you would get this:
If you only run TestMe2 and press Ctrl+L, you would get it correspondingly:
While the following is not really a solution it may work depending on your setup:
This is my Occam's Razor solution that I have used in the past.
Public ChildCount as Integer
Sub EveryProc()
ChildCount = ChildCount + 1
... rest of code...
ChildCount = ChildCount - 1
End Sub
This allows me to test how far I am into subroutines as ChildCount will be 1 for the parent and >1 for the children. I think the first time into the VBA, ChildCount will be zero so you need to increment and decrement the variable at the beginning and end of every sub.
I am spoiled in SAP...

Combining ActiveX Buttons from Multiple Sheets to One Button

I am trying to create a button in an excel spreadsheet that activates every other button in the workbook in a particular order. These buttons are located in 4 different sheets. I attempted to simply create a button in one sheet, and call the other buttons from this button as below:
Public Sub CommandButton1_Click()
Button2_Click
Button3_Click
Button4_Click
Button5_Click
Button6_Click
End Sub
This did not work. I was thinking that maybe I need some sort of way to reference the sheet that each button is in?
Event handlers are Private for a reason. Making them Public will work, but isn't what you should be doing.
Move the code out of these handlers and into their own public procedures; then invoke these procedures from the respective handlers - and invoke the same procedures in the appropriate order from this commandbutton handler.
For example you take this:
Private Sub Button2_Click()
'do stuff
End Sub
Turn it into this:
Private Sub Button2_Click()
DoStuff
End Sub
Public Sub DoStuff()
'do stuff
End Sub
Then you can invoke DoStuff from wherever you need to.
Event handlers should never be invoked directly, and never need to be.
Even Private event handlers within sheet modules can be started by following way.
Assuming you have a worksheet named "ActiveXButtons" (its caption) with Button2 on it and following button event code within that worksheet:
Private Sub Button2_Click()
' ...
End Sub
Then you can trigger above button's event by addressing the worksheet's codename indirectly:
Application.Run Worksheets("ActiveXButtons").CodeName & ".Button2_Click"
or shortened:
Run “Sheet2.Button2_Click“

Check Return value of private sub

In an Excel 2007 workbook I have three Excel modules, each containing one subroutine. The Driver sub (UpdateDataFromOracle) calls the subs UpdateResponse and UpdateSched. The code is working fine, but I'd like to check the "return code" of each of the called subs. I only want the Driver sub visible to the user, so I made the subs in Modules 1 and 2 Private.
Module 1 Private Sub UpdateResponse
Module 2 Private Sub UpdateSched
Module 3 Public Sub UpdateDataFromOracle
Here's code from the Driver sub
Sub UpdateDataFromOracle()
'DECLARE VARIABLES
Dim varSchedReturn as variant
'...
Call UpdateResponse
Call UpdateSched
'I Would like to insert the "return code" check here
End Sub
Here's code from the Called sub
Option Explicit
Private Sub UpdateResponse()
'DECLARE VARIABLES
'...
If Sheets(strTempSheet).UsedRange.Rows.Count > 10 Then
UpdateResponse = 0
Else UpdateResponse = 90
End If
End Sub
To call the Private subs I had to abandon the "Call" and use"
Application.Run "Module1.UpdateResponse"
But I can't figure out how to get a return code that way.
I also made UpdateResponse and UpdateSched Private Functions, but I still couldn't figure out how to get a return code back.
When I made UpdateResponse and UpdateSched Public Functions, I can use a statement at the end of the called subs like:
Else UpdateResponse = 90
The problem is that the called subroutines are visible to the user if I leave the functions Public.
My goal is to have only the Driver sub visible to the user, and be able to evaluate some sort of "Return Code" from the called subs in the Driver sub.
Thanks for looking at this.
I didn't fully read the question, but change them to Function
Private Function UpdateResponse() As Integer
'DECLARE VARIABLES
'...
If Sheets(strTempSheet).UsedRange.Rows.Count > 10 Then
UpdateResponse = 0
Else
UpdateResponse = 90
End If
End Function
Then:
Dim response ' As Variant or Integer
response = Application.Run("Module1.UpdateResponse")
Also, there are 2 better ways with Option Private Module or a public variable in Module1
3 Ways to Call a Private Sub from Another Module
The answer #DougGlancy gave worked well. It's listed as a comment to my original question, so I'm adding this Answer to indicate that his answer was correct.
One option for Windows Excel that I have used is to set values on the user's machine that can be retrieved in a later process.
https://msdn.microsoft.com/en-us/library/z46c489x(v=vs.110).aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1
Alternatives could be to set some properties of your Excel workbook or in Excel you can create names and values associated with them. (part of named ranges).
Lastly, you can add and change values in the windows registry.

How to call a function in vba which is already assigned to a button

I tried to create a ribbon in excel and I was successful. Now I have assigned a macro to a button.
Function delete_cells(control As IRibbonControl)
Now, I created another macro, from which I need to call the function delete_cells. I tried to call it as below.
Function modify_cells(control As IRibbonControl)
delete_cells
End Sub
I am getting an error saying Argument not optional. Please help me with this error.
I suggest that you create a separate subroutine that you call from the button's OnAction and anywhere else you want to call it from, e.g.:
'button macro
Sub cmdDeleteCells_OnAction(control as iRibbonControl)
DeleteCells
End Sub
'another Sub that calls the delete routine
Sub SomeOtherSub
DeleteCells
End Sub
'the one they're all talking about
Sub DeleteCells
msgbox "All your cells are toast, bwah hah hah ha!"
End Sub
EDIT: If you really want to just call the button's OnAction sub, you need to pass it an iRibbonControl object as well, so declare a fake one:
Sub CallTheButtonsCode()
Dim FakeControl As IRibbonControl
cmdDeleteCells_OnAction FakeControl
End Sub
I really don't recommend this for code maintenance reasons, but it works.
In your Function delete_cells(control As IRibbonControl) you have an REQUIRED argument ...(control As IRibbonControl). When you call the function it should look like this :
Function modify_cells(control As IRibbonControl)
delete_cells(myControl) 'myControl is your variable. Define what control you want to pass to the function.
End Sub

"Include" in Excel VBA?

I have a user-form which is made up of many subs, this is assigned as a macro to a button on the worksheet. When the user is finished with this user-form they can press a button on it which causes its visibility to become false and when entered again everything appears how it was left resulting in a save like feature.
I now need to apply this to multiple buttons on the worksheet and each user form needs to have the exact same code and same buttons but be a separate form as each individual button requires it's own save like feature. The way I was planning on doing this was to copy the existing user form and paste it many times with different names however, if a modification is required it will take a long time to carry out therefore, is there a method such as "include" which could use a base module from which all the code is accessed so that if I ever need to change anything I just do it on that one module and everything else updates via the include?
EDIT:
I now have a public function called costing() and am getting an error when I used:
Private Sub material_Change()
Call costing
End Sub
You can have multiple instances of the same form. You can use this to retain multiple sets of form values
Try this:
Create your form, as usual. Let's call it MyForm
Create several buttons on your sheet. My example uses ActiveX buttons, but Form Control buttons can be used too. Let's call them CommandButton1 and CommandButton2
In your form module, include a Terminate Sub, which includes this code
Private Sub UserForm_Terminate()
' any other code you may need...
Unload Me
End Sub
The Form buton to save/Hide the form needs to be
Private Sub btnSaveAndHide_Click()
Me.Hide
End Sub
The Sheet Button code is as follows
The code is identical for each button (and calls a common Sub), and each button has its own Static form variable.)
The Error handler is needed to deal with the case a form is not properly closed. In this case the instance no longer exists, but the local Static variable is also not Nothing
Example shows form shown as Modeless, you can change this to Modal if you want.
Private Sub CommandButton1_Click()
Static frm As MyForm
ShowMyForm frm
End Sub
Private Sub CommandButton2_Click()
Static frm As MyForm
ShowMyForm frm
End Sub
Private Sub ShowMyForm(frm As MyForm)
If frm Is Nothing Then Set frm = New MyForm
On Error GoTo EH
frm.Show vbModeless
Exit Sub
EH:
If Err.Number = -2147418105 Then
On Error GoTo 0
Set frm = Nothing
Set frm = New MyForm
frm.Show
End If
On Error GoTo 0
End Sub
End result: multiple copies of the same form, each with their own values
In responce to comment How would I access the variables inside of each user form externally
In the example above the Form instances are only accessable in the Command Button Click Handler routines, or within the Form module itself. If you can write your code in the form module, then no change is needed.
To make the Form instances available elsewhere, consider moving their declaration to Module Scope of a standard Module. You could declare them as, eg individual variables, an array (either static or dynamic), a Collection, a Dictionary. Which structure is best will depend on how you want to manage and access your form instances.
For example, a Static Array: Code in a standard Module
Option Explicit
Global MyForms(1 To 2) As MyForm
Update the CommandButton code to
Private Sub CommandButton1_Click()
ShowMyForm Module1.MyForms(1)
End Sub
Private Sub CommandButton2_Click()
ShowMyForm Module1.MyForms(2)
End Sub
Private Sub ShowMyForm(frm As MyForm) no change, same as before
The code works the same as before, but you can now access the Global variable in a standard Module
Sub Demo()
Dim i As Long
For i = LBound(MyForms) To UBound(MyForms)
If Not MyForms(i) Is Nothing Then
MsgBox "Form " & i & " Value = " & MyForms(i).TextBox1.Value
End If
Next
End Sub
You don't need an "Include" (none exists in VBA); all you need to do is create a module and make the common methods public.
For example, if you create a module and have a function like this:
Public Function Add(first As Integer, second As Integer) As Integer
Add = first + second
End Function
Then you can access it like this from another module/form/class module:
Sub test()
MsgBox Add(3, 6)
End Sub

Resources