I was wondering if there is a way to run specified lines of code in another sub , say execute only line 1 through line 10 of Sub_2 before executing the code in say, Sub_1.
I have done some background research and found up a code suggested by #Andrew below.
Private Sub Sub_1(sender As System.Object, e As System.EventArgs) Handles Button1.Click
'Call the Save Sub
Call Sub_2(sender, e)
'Proceed to execute the rest of the this Sub_1
What the above code does at the moment is execute the entire sub I have called, which is not what I want to achieve.
I have also read about the using a shared routine common to both Subs as suggested by #Henry , but the problem for me is that variables are declared in the shared routine which are required at various stages in Sub_2, but which cannot be used as the program says they are not declared (in Sub_1).
Again, what I want to achieve is execute only a few lines of code from Sub_2 in Sub_1.
I am using Visual Basic 2010 Express.
What you are trying to do is technically possible (in various really hackish ways), but would easily become a code structure nightmare. Instead, consider an alteration to the way you are building your routines.
Subs and Functions are intended to be independent 'islands' of code, which can execute (generally) without knowing anything about the rest of the program. You can pass variables into them, and get variables back, but they should operate the same time after time.
Consider this theoretical code from the scenario you are proposing:
Dim variable_1
Private Sub Sub_1()
Dim variable_2
Call Sub_2()
End Sub
Private Sub Sub_2()
'lines 1-10
'lines 11-20
End Sub
Here we have the setup for what you're looking at. Both Subs can 'see' variable_1, but Sub_2 can't see variable_2. This is called scope. Finally, there is the issue of only wanting to execute lines 1-10 inside Sub_2 when being called from Sub_1.
The first thing to do is to separate our code into logical groups. According to what you're describing, lines 1-10 of Sub_2 make sense when looked at on their own, so this automatically means that they should be grouped together. Lines 11-20 may be a different story, but since they are sometimes relevant and others not, we should separate them as well.
Private Sub Sub_2()
'lines 1-10
End Sub
Private Sub Sub_3()
Call Sub_2()
'lines 11-20
End Sub
The change looks simple, but is actually quite powerful. When we only want to execute lines 1-10, we call Sub_2. If we want to execute lines 1-20, we call Sub_3. Sub_3 will automatically call Sub_2 (lines 1-10) to execute first, and then execute lines 11-20. Easy.
Now we just have to figure out what to do about the variables. There are quite a few ways you can handle this situation, but the easiest thing to do is to expand their scope. If all the variables you need to access are declared outside of these Subs (one "level up"), then they can all access them. For example:
Dim variable_1
Dim variable_2
Private Sub Sub_1()
Dim variable_3
'can access 1, 2, 3 but NOT 4, 5
Call Sub_2()
End Sub
Private Sub Sub_2()
Dim variable_4
'can access 1, 2, 4 but NOT 3, 5
End Sub
Private Sub Sub_3()
Dim variable_5
'can access 1, 2, 5 but NOT 3, 4
End Sub
This should get you started. Once you've got this down, you can read about the various ways to pass variables into Subs and Functions (which also gives some hints about how to get them out, as well).
I very much doubt you will be able to do that. A function is a unit of code which can be called via Reflection if required, however statements within a function are not exposed via Reflection and when optimised often may not even execute in the order that they are written.
Such a process would break a lot of bad practises - what if the code changes in the future for example? An implementation can change, although one wouldn't expect the function's definition to.
I would question why you are wanting to do this and look for an alternative means of achieving your goals.
If you were hell bent on such a implementation, then you could potentially break open the CLR code and decompile and then select certain aspects to compile and run - however it would be a lot of hard work, unsustainable and very bad practise.
Related
My end goal was to generate text into a TextBox (let's call it TextBox06) based on the values inserted into a series of Textboxes -- let's name them TextBox01, TextBox02, TextBox03, TextBox04, TextBox05. These TextBoxes are in a Userform (let's call it Userform01).
These TextBoxes can have a lot of different values and combinations (over 50 values each, and depending on which order the values are placed in the textboxes there will be a different output). Moral of the story: code probably works, but gives "procedure too large" error with no real way around it (pretty sure I have shrunk the code as much as possible, but there just aren't any more generic statements that repeat, and most of it must be a bruteforce-style method of inserting most of the combinations individually).
So I went looking on the internet for a way to split the code. I tried using the "call procedure" feature. What I did was:
I created two new Modules (Module01 and Module02) in the same "project".
I split the big code half in Module01, and half in Module02. The Modules' content is something like this:
Sub Module01()
[half the code]
End Sub
I wrote the following lines of code in each sub of the textboxes involved (this is in Userform01).
Private Sub TextBox01_Change()
Application.Run ("Module01")
Application.Run ("Module02")
End Sub
[Then five more copies for Private Sub TextBox02_Change () until Private Sub TextBox06_Change () included]
Assuming this is even the correct way of doing it, I then hit a roadblock. Apparently, copy-pasting the big code in Module01 and Module02 wasn't enough. From what I understood, the program doesn't seem to understand what the TextBox01.Text (and the like) values in the code are. So I tried looking for a solution to THAT, and found several possible codes, none of which seems to have worked so far. The one that I currently have is this:
Sub Module01()
Dim Text01 As String
Set Text01 = TextBox01.Text
[repeat for Text02 to Text06 included]
[half the code]
End Sub
[repeat for Module02 with the other half of the code]
Assuming that I'm even on the right track when it comes to properly using the "call" function, what is the proper way to make this work (i.e. how do I correctly reference the TextBox01.Text content in the Module, so I can then call the Modules in the Userform01) ?
At work, I'm venturing into the world of VBA to try to make a spreadsheet template, which will run a report when a command button is clicked.
On several occasions I have encountered an "Out of Memory" run-time error, which has been easily fixed by jigging with the code using combinations of suggestions by other users on this site!
However, I am now curious about how to make codes less memory-intensive. In particular, do modules help make code less memory-intensive and, if so, how should I use them effectively in this regard? For example, should I assign a Sub to each module or would that be overkill?
I'm new to VBA so any/all help and criticism is very welcome!
Short answer: None whatsoever.
An "out of memory" error has many reasons to occur, none of which relate to how many modules your code is organized in. Because that's all modules are: an organizational tool at your disposal.
It is completely impossible to diagnose an "out of memory" error without knowing anything about what the code is doing: <meta> you would have to narrow down the problem to a particular specific piece of code before you can ask about it on this site (dumping a whole module and asking "what's wrong with this code?" isn't going to fly) </meta>
Look for very, very large arrays, clipboard operations perhaps. Maybe you're running a loop that's attempting to create and store a bajillion New large objects - could be pretty much anything... just not the number of modules.
should I assign a Sub to each module or would that be overkill?
As with many things, the answer is "it depends". Once you've familiarized yourself with the language, and start learning about Object-Oriented Programming (OOP), you'll come across design guidelines and principles that strongly advocate towards keeping public interfaces as simple as possible: adhering to the Interface Segregation Principle (the "I" of "SOLID") means that this is a perfectly complete and acceptable class module to have:
'#Interface IComparable
'#ModuleDescription("Defines a generalized type-specific comparison method that a class implements to order or sort its instances.")
Option Explicit
'#Description("Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes (-1), follows (1), or occurs in the same position in the sort order (0) as the other object.")
Public Function CompareTo(ByVal other As Object) As Integer
End Function
But that's well beyond beginner-level procedural programming concepts.
[...] which will run a report when a command button is clicked.
That command button could be an ActiveX button, or a Shape object that's attached to a macro. Assuming it's ActiveX, the worksheet's code-behind might look like this:
Option Explicit
Private Sub RunSalesReportButton_Click()
SalesReportMacro.CreateWeeklyReport
End Sub
And then you can have a SalesReportMacro standard module that might start out like this:
'#ModuleDescription("A macro that generates the weekly sales report.")
Option Explicit
Option Private Module
'#Descrition("Generates the weekly sales report.")
Public Sub CreateWeeklyReport()
If Not RefreshSalesData Then Exit Sub
CreatePivotReport
End Sub
The Public procedure at the top has a very high abstraction level: it's easy to tell at a glance what the procedure is going to be doing, because the lower-level procedures have meaningful names that tell us exactly what's going on. These lower-level procedures would be right underneath:
Private Function RefreshSalesData() As Boolean
Dim reportWeek As String
reportWeek = PromptForReportWeek
If Not IsNumeric(reportWeek) Then Exit Function
On Error GoTo CleanFail
AdjustDataConnectionCommand reportWeek
RefreshSalesData = True
CleanExit:
Exit Function
CleanFail:
MsgBox "Could not refresh the data for week " & reportWeek & ".", vbExclamation
Resume CleanExit
End Function
And the deeper you go, the lower the abstraction level becomes:
Private Function PromptForReportWeek() As String
Dim currentWeek As Integer
currentWeek = GetCurrentWeekNumber
PromptForReportWeek = InputBox("Please specify week#", "Generate Report", currentWeek)
End Function
Private Function GetCurrentWeekNumber()
GetCurrentWeekNumber = 42 'todo
End Function
Private Sub AdjustDataConnectionCommand(ByVal weekNumber As String)
With ThisWorkbook.Connections(1).OLEDBConnection
.CommandText = "dbo.WeeklySalesReport " & weekNumber
.Refresh
End With
End Sub
Private Sub CreatePivotReport()
'todo
End Sub
Procedures (Sub, Function, and others you'll discover in due time) should have one purpose, and be kept as short and focused on that one task as possible. By doing that, you make it easier to later refactor your code - say in 2 months you need to add another ActiveX button for a new CreateWeeklyInventoryReport macro: there's a good chance that this new report will also need to PromptForReportWeek - if that functionality is already well-abstracted into its own scope, you can more easily just remove/cut it from that module, add a new CalendarParameterPrompt module (which... might end up only having that procedure for a while.. until you need a good place to put a similar PromptForReportMonth function), add/paste it in there, make it Public, and invoke it from any other macro/module.
Doing this instead of massive omnipotent procedures that know everything and do everything (and eventually stop compiling because they got so large VBA refuses to compile them), will spare you lots of headaches in the near future.
I learned from internet that our vba macros (subs and functions) should be more or less the size of the screen.
But all my code is written exactly in the opposite: a have a huuuge main sub that calls functions and small subs.
My question is: if I decide to convert my main huge sub into small subs (sub1, sub2, sub3, etc), then what should be the best way to keep the value of a variable that was assigned in sub1 and use it in sub2, that was assigned in sub2 and use it in sub3, and so on? If I have no answer to this question, I am going to save those values in a sheet, but maybe this way is going to cause me trouble that I cannot see by now. Thanks for any help.
A Sub or a Function should have a single and clear purpose, told by its name. If you break down your code in such chunks, you will know what information those methods will need and deliver. At the same time they will become smaller. If you have to choose between size and clarity, go for clarity.
Do NOT put your values in a sheet. That is just a different and less efficient way of using global variables, which is something you should avoid (not at all cost, but almost).
Both methods below would work. In my personal opinion, method 1 keeps code more simple (you avoid to pass big bunches of parameters to each macro) but depending on your need you might choose one or the other.
Global variables
Your code would look something like this:
'Global variables declaration
Dim a As Integer
Dim b As Integer
'
Public Sub mainRoutine()
setA '<-- call first macro
setB '<-- call second macro
MsgBox a + b '<-- show A + B (2 + 3 = 5)
End Sub
'code of sub-routines
Private Sub setA()
a = 2 '<-- since a is global, it will keep the value
End Sub
Private Sub setB()
b = 3 '<-- since b is global, it will keep the value
End Sub
Please note: global means that the value of this variable will live for all execution of your program. Memory will be released once the program ends.
Pass variables as ByRef parameters
Public Sub mainRoutine()
Dim a As Integer '<-- a is local
a = 2 '<-- setting a to 2
setA a '<-- passing a as ByRef parameter
MsgBox a '<-- a is now 3
End Sub
Private Sub setA(ByRef a As Integer)
a = 3 '<-- setting a as 3.
End Sub
Of course, the above method would only allow to keep a = 2/3 during the execution of mainRoutine. But if at some point you execute another macro called by another stack (for example another button in the spreadsheet), you wouldn't be able to access the value of a as you would in method 1.
Important: no, variables on the spreadsheet is not a good idea, unless you don’t need to keep the value after closing and reopening the spreadsheet (in that case you would be using the spreadsheet as a sort of database).
VBA can be considered as an object oriented programming language (OOP).It has 3 of the 4 pillars of OOP:
Abstraction
Encapsulation
Polymorphism
This is discussed in Is VBA an OOP language, and does it support polymorphism?
OOP means understanding the SOLID principles.
Single Responsibility Principle
Open Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
Best practice is to adhere to these principles and the first is the Single Responsibility Principle.
This states that your functions/subs should have one responsibility and focus on doing one thing.
Advantages (amongst others):
The reduced code complexity and dependencies mean:
1) Easier to debug as your track down which sub/function the error is raised in
2) Units tests can be more easily be built around code that is based on single responsibility.
3) Can be more easily slotted into a wider code base
To use variables between subs/functions consider:
1) Either public variables, and/or
2) Passing variables as arguments
Other info:
OOP VBA pt.1: Debunking Stuff
This is probably the dumbest question I've ever asked here, but it's hard to find answers to things like this.
I have a program with a bunch of modules/subs that each calculate a different variable. They're pretty complex, so I like to keep them separate. Now I want an earlier module to skip to another module based on user input. I thought I could use the call (sub name) method for this, but then the program returns to where the call line was and continues on that module from where it left off.
Example:
Module 1:
Sub NewPracticeSub()
Call otherpracticesub
MsgBox ("We've gone back to this sub... :(")
End Sub
Module 2:
Sub otherpracticesub()
MsgBox ("We're in the other practice sub!")
End Sub
I don't want it to return to Module 1. What can I do to have it switch control to Module 2 without it then returning to complete Module 1 upon completion of Module 2?
I feel like I just used the most confusing language possible to explain all of this, but thank you for your help anyways!!
Edit: I know I used the words module and sub interchangeably, and I know they're different. I like to keep each sub (which are each very large in my program) in their own modules because it's easier to keep track of them, and easier to explain/demonstrate the application flow to other people.
I think all you're looking for is the command Exit Sub which will make the program leave the subroutine without continuing any further, But the way you usually want to do this is, rather than calling a Sub, rather call a Function that returns a boolean value.
So, for example:
Public Function MyFunc() as Boolean
....
If [good] MyFunc = True
Else MyFunc = False
End Function
Then you could do something along the lines of:
Sub MyCallingSub()
...
If MyFunc = True then Exit Sub
Else ...
End Sub
It just adds in A LOT more felxibility and ability to choose whether you want to continue further in your sub or not.
Hope that makes sense.
Other than using the ugly End statement which I will describe below (and strongly recommend you to avoid), I'm not aware of any way to circumvent the call stack. Even John's response necessarily returns to the calling procedure, and evaluates another statement to determine whether to proceed or end.
This may yield undesirable outcomes, which is why I hesitate to recommend it, in favor of properly structuring your code, loops, etc., with respect to the call stack.
In any case, here is how you can use the End statement within your child subroutines, without needing any sort of public/global variables. This still allows you the flexibility to decide when & where to invoke the End statement, so it need not always be invoked.
Sub NewPracticeSub()
Call otherpracticesub, True
MsgBox ("We've gone back to this sub... :(")
End Sub
Sub otherpracticesub(Optional endAll as Boolean=False)
MsgBox ("We're in the other practice sub!")
If endAll then End '## Only invoke End when True is passed to this subroutine
End Sub
Why I say this method should be avoided, via MSDN:
"Note The End statement stops code execution abruptly, without
invoking the Unload, QueryUnload, or Terminate event, or any other
Visual Basic code. Code you have placed in the Unload, QueryUnload,
and Terminate events of forms and class modules is not executed.
Objects created from class modules are destroyed, files opened using
the Open statement are closed, and memory used by your program is
freed. Object references held by other programs are invalidated.
The End statement provides a way to force your program to halt. For
normal termination of a Visual Basic program, you should unload all
forms. Your program closes as soon as there are no other programs
holding references to objects created from your public class modules
and no code executing."
It will always return but that doesn't mean its a problem. I suggest you use Exit Sub as follows:
Sub NewPracticeSub()
Call otherpracticesub
**Exit Sub**
'Nothing more can execute here so its no longer a worry
End Sub
Module 2:
Sub otherpracticesub()
MsgBox ("We're in the other practice sub!")
End Sub
I have some legacy code that uses VBA to parse a word document and build some XML output;
Needless to say it runs like a dog but I was interested in profiling it to see where it's breaking down and maybe if there are some options to make it faster.
I don't want to try anything until I can start measuring my results so profiling is a must - I've done a little searching around but can't find anything that would do this job easily. There was one tool by brentwood? that requires modifying your code but it didn't work and I ran outa time.
Anyone know anything simple that works?
Update: The code base is about 20 or so files, each with at least 100 methods - manually adding in start/end calls for each method just isn't appropriate - especially removing them all afterwards - I was actually thinking about doing some form of REGEX to solve this issue and another to remove them all after but its just a little too intrusive but may be the only solution. I've found some nice timing code on here earlier so the timing part of it isn't an issue.
Using a class and #if would make that "adding code to each method" a little easier...
Profiler Class Module::
#If PROFILE = 1 Then
Private m_locationName As String
Private Sub Class_Initialize()
m_locationName = "unknown"
End Sub
Public Sub Start(locationName As String)
m_locationName = locationName
MsgBox m_locationName
End Sub
Private Sub Class_Terminate()
MsgBox m_locationName & " end"
End Sub
#Else
Public Sub Start(locationName As String)
'no op
End Sub
#End If
some other code module:
' helper "factory" since VBA classes don't have ctor params (or do they?)
Private Function start_profile(location As String) As Profiler
Set start_profile = New Profiler
start_profile.Start location
End Function
Private Sub test()
Set p = start_profile("test")
MsgBox "do work"
subroutine
End Sub
Private Sub subroutine()
Set p = start_profile("subroutine")
End Sub
In Project Properties set Conditional Compilation Arguments to:
PROFILE = 1
Remove the line for normal, non-profiled versions.
Adding the lines is a pain, I don't know of any way to automatically get the current method name which would make adding the profiling line to each function easy. You could use the VBE object model to inject the code for you - but I wonder is doing this manually would be ultimately faster.
It may be possible to use a template to add a line to each procedure:
http://msdn.microsoft.com/en-us/library/aa191135(office.10).aspx
Error handler templates usually include an ExitHere label of some description.. The first line after the label could be the timer print.
It is also possible to modify code through code: "Example: Add some lines required for DAO" is an Access example, but something similar could be done with Word.
This would, hopefully, narrow down the area to search for problems. The line could then be commented out, or you could revert to back-ups.
Insert a bunch of
Debug.Print "before/after foo", Now
before and after snippets that you think might run for long terms, then just compare them and voila there you are.
My suggestion would be to divide and conquer, by inserting some timing lines in a few key places to try to isolate the problem, and then drill down on that area.
If the problem is more diffused and not obvious, I'd suggest simplifying by progressively disabling whole chunks of code one at a time, as far as is possible without breaking the process. This is the analogy of finding speed bumps in an Excel workbook by progressively hard coding sheets or parts of sheets until the speed problem disappears.
About that "Now" function (above, svinto) ...
I've used the "Timer" function (in Excel VBA), which returns a Single.
It seems to work just fine. Larry