vba Show references to other procedures - vba

Today i want to ask this question.
say i have a module and in that module i call different procedures (subs or function ) which are located in other modules, how can i list all the procedure names and their module or class in a simple way in case i wanted to copy those procedures to the current module and delete these modules.
thanks
i don't know how to achieve that but here is a dummy code
for each UserDefinedProcedure in UserProcuduresCollection
if UserDefinedProcedure.Name (is Found in this module) then
debug.print UserDefinedProcedure.Name
end if
next

DISCLAIMER: I wrote this part of the Rubberduck add-in myself, and I own and maintain the Rubberduck project.
Download and install Rubberduck. Right-click the procedure name (from its declaration, or any of its usages) and select the "Find all references" command from the code pane context menu:
You can then double-click an item in the list to navigate there.
The latest release dates a little (July 2015), but the project is well alive on GitHub, and version 2.0 should be released in 6-8 weeks. It won't let you iterate procedure/identifier references with VBA code, but a COM API to do exactly that is on the project's roadmap.
Rubberduck 2.0's "find all references" can find references to pretty much anything, from classes:
To properties:
...and library functions:
Doing this with code, even very smart code, using the VBIDE API, isn't going to be reliably possible. The reason for this is that it's perfectly legal to have this code in Module1:
Sub DoSomething()
'do something
End Sub
And then this code in Module2:
Sub DoSomething()
'do something
End Sub
To correctly resolve references to DoSomething inside Module1, you need to check whether the call is qualified (e.g. Module2.DoSomething will call the procedure inside Module2 - an unqualified call will call the procedure inside Module1).
Resolving identifier references is something I've spent pretty much an entire year refining the code for, and it's still not perfect (although it now resolves crazy ambiguous code you wouldn't even think is legal VBA) - doing that in plain VBA is suicidal at best.
Eventually we'll expose a COM API that will allow Rubberduck users to write VBA code something like this completely hypothetical code:
For Each declaration In RubberduckParserState.UserDeclarations
If declaration.DeclarationType = Procedure And declaration.ParentScope = "VBAProject.Module1" Then
For Each reference In declaration.References
Debug.Print declaration.IdentifierName " used in " & reference.ToString
Next
End If
Next

In break mode, press Ctrl+L to view the call stack. This is the best you're gonna get in the VBE.
Note that the call stack cannot be accessed via code and so what appears to be your end-goal cannot be achieved in VBA.

Related

Prevent Word macro in Normal.dotm for some templates

I have a Normal.dotm file that contains an AutoNew macro.
This macro is automatically executed each time a new document is created using any other template.
Is there any way I can prevent this automatic behavior for a specific template?
I have a Word VSTO add-in running, so I can hook into Word's events, but so far I havn't found a way to prevent this.
I do know that I can prevent macro execution when using templates programmatically, for example like this:
' Disable auto-macros before opening document
wordApplication.WordBasic.DisableAutoMacros(1)
' Open document
newWordDocument = wordApplication.Documents.Open(template.FullName, ConfirmConversions:=False, [ReadOnly]:=True, AddToRecentFiles:=False, Revert:=True)
' Re-enable auto-macros
wordApplication.WordBasic.DisableAutoMacros(0)
But this solution doesn't work when the user uses a Word template from Windows explorer or the Open-dialog in Word, since in those cases I can't execute code before it's too late already.
Or can I?
I hope someone has a trick for me :-)
-
Edit: While trying different solutions, I discovered something that might help others in similar situations, though unfortunately it doesn't help me.
It seems that if a template contains a module containing an AutoNew (or AutoOpen for that matter), that local macro is executed instead of the one in Normal.dotm.
Example:
Normal.dotm contains the following macro:
Sub AutoNew()
MsgBox "Normal.dotm"
End Sub
Test.dotm contains the following macro:
Sub AutoNew()
MsgBox "Test.dotm"
End Sub
When executing Test.dotm the message "Test.dotm" is displayed, while the message "Normal.dotm" is not displayed.
If the AutoNew macro is removed from the Test.dotm template, the message "Normal.dotm" is indeed displayed.
So it is possible to easily override the auto-macros.
The local versions of AutoNew and AutoOpen can even be empty subs that do nothing. It still works.
This is not possible in my case though, since the template I use is generated by code, and cannot contain macros (because adding macros to templates programmatically requires the user to manually activate the option "Trust access to the VBA project object model", and that's something I cannot ask my customers to do for all users. It's also a security risk.)
Based on the workaround described in the "Edit" part of the question - providing a template with "empty" Auto-macros - it's possible to use the Open XML SDK to create a template and add the VBA project to it in order to provide this functionality. This approach avoids the user needing to allow access to the VBA project on his installation. The only "macro security" that could be triggered is that for not allowing macros to run. But since the client uses macros, anyway, this should not be a major obstacle.
The simplest method is to create as much of the basic template as possible in the Word UI and use this as a starting point.
Since you're unfamiliar with the Open XML SDK, the next step would be to create one (or more) templates in the Word UI using the basic template as the starting point, saving under a different file name.
You can then use Open XML SDK Productivity Tool to see the code required to generate any one of these files, as well as, using the Compare tool, the code for converting the basic template to the derived version. This should give you a decent start with the SDK and it's object model. Once you get a feel for how the Open XML SDK works, if you're familiar with Word's object model, using that provided by the SDK is relatively straight-forward as an effort was made to make it correspond as closely as possible to the "COM" object model.
The VBA project can be added later, but you can also include it in the basic template. That would be the simplest approach.
Include this "starting point" basic template as part of your solution, installing it as part of the solution.
Within the AutoNew macro you can check the AttachedTemplate property. Only if it is a template where you want to apply the cleaning you can execute the respective macros.
Sub AutoNew()
If ActiveDocument.AttachedTemplate <> "Normal.dotm" Then
Exit Sub
End If
' rest of the macro
End Sub
If you don't control the Normal.dotm you can put an empty AutoNew macro in your own templates. As Word only executes the auto macro in the closest context, the macro in the Normal.dotm file would not be executed.
If you don't control the other templates either, you can tell your users to hold down the SHIFT key while creating a document. This prevents the execution of the auto macro.
Probably it is best, however, if you ask the owner of the other system to find another solution that does not rely on polluting the Normal.dotm file.

Application.OnTime in Add-In Workbook_Open

I've built a custom Excel add-in and I'm currently trying to figure out a way to prompt users via VBA when new versions of the add-in are available.
I tried just using the workbook_open event to check for the latest version and then prompt the user with a userform, but I discovered that when Excel loads an add-in that trigger a userform, Excel stops loading the workbook the user actually tried to open and reloads the add-in. So while the userform works like I wanted, the user gets a blank (read no sheets) Excel shell with a loaded add-in.
So I considered using Application.OnTime to postpone the VBA until after the add-in and target file were both open. I got the impression both here and here that this is possible, but I am only able to make it work in an .xlsm file and not a .xlam file.
Here's the code I'm testing with:
Sub Workbook_Open()
Application.OnTime Now() + TimeValue("00:00:05"), "Test_Addin.xlam!Versioning.Notify_User"
End Sub
And in a regular code module:
Sub Notify_User()
MsgBox "Hello"
End Sub
So, my question: Am I doing something wrong here?
I'm wondering if there's something about how an add-in is loaded/designed that keeps it from allowing this type of action to be performed.
Alternatively, is there a different way to do this that you can think of?
I read an interesting blog post (and comments) by Dick Kusleika on this topic, but it sounded like some people put a version check in each sub procedure... I have a lot of procedures, so this doesn't sound like a good alternative, although I may have to resort to it.
Well, time is of the essence so I resorted to the least desirable option: a check at the beginning of each procedure.
To the interested parties, here's how I did it:
Somewhere towards the beginning of each sub procedure I put this line of code:
If Version_Check Then Call Notify_User
And in my Versioning module I put this function and procedure:
Function Version_Check() As Boolean
Installed = FileDateTime(ThisWorkbook.FullName)
Available = FileDateTime("\\NetworkDrive\Test_AddIn.xlam")
If Installed < Available Then Version_Check = True
End Function
Sub Notify_User()
Update_Check.Show
End Sub
So each time a procedure is run this code checks for a version on our corporate network with a modified datetime greater than the datetime of the installed add-in.
I'm still very open to alternative ways of checking for new versions so feel free to post an answer.

How do I add a User Defined Function to Excel?

I have a simple stored procedure that returns a Description and a Name when you give it a ID. I need to enable this inline in multiple Excel Sheets. Something like =ItemLookup('12345') that would then return the aforementioned info.
I have not done a lot with Excel programming and am simply wondering what my options are for tackling this. Is this a VBA thing or should this be an external DLL that I COM register? Both felt like overkill but then I realized I had no idea if they were. I really wanted to use VSTO for this but it sounds like that is not possible for Cell level UDF's without having to modifiy each Workbook with some VBA.
The best way to add UDF functions to Excel is with Excel-DNA (which is a free, open-source library I develop), and any of the .NET languages - VB.NET, C# and F# are all fine.
To get started you make a new 'Class Library' project in Visual Studio (any edition), install the 'Excel-DNA' package from the NuGet package manager, and add your code:
Public Module MyDataAccessFunctions
<ExcelFunction(Description:="Gets the Item from the database")>
Public Function ItemLookup(code As String) As String
' Here you have to do some work to get the data
Return "Hello " & code
End Function
End Module
Pressing F5 builds and starts Excel, and you're done - try putting =ItemLookup("Paladin") into a cell.
The resulting add-in is a single .xll file, which you can copy and use on any machine that has .NET without any installation or admin permissions. It works with old Excel versions too.
The best place for support (including absolute beginners' questions) is the Excel-DNA Google group.
You can use Excel to create a VBA UDF pretty easily, just hit alt+f8, right click your project in the project hierarchy on the left of the screen, and click add module.
Here is a quick Hello World function you can just paste into the module, then click play (or alt+f8 from worksheets)
sub test()
msgbox "helloworld"
end sub
If it was me, I would probably just create a list of the file paths that need to be searched. Then create a VBA macro that opens them in excel, searches them for the key, and returns other information from the row the key was found on.
You can open files with the 'Application.Open' method, simply pass in
the file path as an argument. 'Application.Open' returns a workbook
object.
Each workbook will have several worksheets, you can access them
through the workbook's 'Worksheet' property
Getting each used cell in a workbook can be done via looping through the 'UsedRange'
property in each worksheet
Get the value of a cell for comparison from the cell's 'value' property
Cells also have a 'row' property so you can find other items on the same row
If you're used to VBA you could get this running in less than an hour. But since you're just starting out it'll probably take a 3+ hours since you'll have more research/debugging

MS Access RunCode Macro cannot find my procedure

I'm only posting this since I wasn't able to find a solution anywhere. I finally figured it out. Kind of silly really.
When using the RunCode property within an Access Macro, I was trying to run a Sub from my global module. I was getting the error "The expression you entered has a function name that database can't find." I couldn't figure out what the issue was. I followed the advice of everyone that posted on this issue, which was mostly the following:
Use () at the end of the procedure name
DO NOT use the "=" before the procedure name
Still didn't work!
THEN I read the error message carefully. It mentions that it could not find the FUNCTION name. Apparently, the RunCode property specifically requires a "Function" not a Sub. So, I simply changed my Sub to Function and it worked fine!
Hope this helps.
Another solution that worked for me:
The module name can NOT have the same name as the procedure(s) in the module(s).
I had a similar issue with the error message. My VBA code had the following declaration:
private function MyFunction()
....
end function
I removed private declaration to get the Macro Runcode to execute the MyFunction()
For example:
Function MyFunction()
End Function
My mistake was putting the function in a Class Module instead of a regular module.
Access 2013: A function called with MyFunction() from RunCode where MyFunction does not exist gives me error 2425. None of the above work for me, however, and I was still getting Error Number 2001 when the function exists and is public. Database is in a Trusted Location. No compile errors, but something in MyFunction did not work, namely
DoCmd.ShowAllRecords
after GoToControl worked to select my subform. Real problem was my code to remove a filter with VBA. Manual for ShowAllRecords seems to indicate that this should work, but replacing DoCmd.ShowAllRecords with
DoCmd.RunCommand acCmdRemoveFilterSort
fixed my problem.
The database seems to need to have objects in it other than the VBA function being called as well. Without other objects (specifically a table in my case), the function is unable to be found from within the calling environment (eg Excel VBA).
Access Office 365: My subroutine works when called from within VBA and used to work in Macros. Moved function text to a separate module, saved it by itself. Gave the module (not class) a unique name.
I was borrowing a "template" vbasic text from online as a shell. And while I renamed the function in the code section, MSAccess/Vbasic did not show the name change in the function heading of the Module box so when I ran the macro which called this function it said it couldn't find it. Tried repeatedly to fix. Then I noticed the name was different in the code section of vbasic versus the heading of the function dialog box. So I changed the name manually in the heading box and it prompted me to save which I did and now it works. Maybe this is why snake software is so popular. :)
My VSTO C# calls to app.Run("MyMacro") stopped working suddenly and the error message started printing. ("Access cannot find the procedure MyMacro.")
The problem on my machine was that my code (which connected to a running instance of Access) was connecting to a zombie database instance that did not have that VBA macro in it. Go figure. The zombie instance did not show in the Taskbar, in the Alt-TAB display, or in a list of windows that I printed out using a console program.
The way I discovered it was to have my VSTO code print out the name of the database that it was connecting to. The Access database was one that I had been working with earlier in the day, without errors. I had closed the Access app holding it successfully hours before I tried running MyMacro.
I can't think of anything that I did that was unusual, other than upgrading VStudio to the latest version. Probably a reboot will fix things. What a weird problem.

VB.NET - Ctrl + Break suspends code on "Partial Class frmMain"

This is an IDE question for Visual Basic 2008 Express Edition. It might be a bug in the IDE, or maybe it's my fault somehow?
My main form is named frmMain and in my application's properties I have set frmMain as my startup object. All of that seems like what a lot of software engineers do.
But while debugging I hit Ctrl + Break, as I have done for years, and I get an behavior in the IDE that I wasn't expecting. Upon doing so, I get the green background text and the green arrow indicating in a tooltip:
This is the next statement to execute when this thread returns from the current function.
Even if I didn't have the designer document open, it automatically opens frmMain.Designer.vb in the editor and hihglights line in green. The line is of course: Partial Class frmMain which is line 2 of the file. (Yes, it's highlighting the second line of the designer-generated code.)
frmMain seems to have fully loaded and it's my startup object. As far as I know, there shouldn't be a "next statement to execute" at all because code should be idle. I don't want to see the Designer.vb document... I want to edit my own code.
What's causing this? Even though my form is behaving just fine, could there somehow be an unfinished aspect of loading the form such that it is "not returning" from a function?
The Visual Basic compiler will add an entry point in your form. The entry point is the standard main function or "shared sub main", which in turn contains the code "application.run(new form)". Since this is compiler generated code there is no source location, so the Visual Basic editor highlights the class definition.
The clue to this is in the call stack. Notice the Main().
Shared Sub Main()
Application.Run(new frmMain)
End Sub
I found the answer on my own:
I checked the "Enable application framework" checkbox in the solution's properties, and all is well!
I believe this issue arose when I was working on experimental code in which I had desired to make Sub Main my startup object. I had cleared the checkbox because doing so is necessary to use Sub Main.
When the experiment didn't pan out I reset the startup object back to frmMain, and my app worked fine. However I had not re-checked the box. I hadn't noticed the change in the IDE behavior for several days (when I needed the more standard behavior) so I had not observed any correlation.
Although checking that box is definitely the solution, it's still not exactly clear to me is what the heck this box actually does, other than cause me to spend a lot of time on StackOverflow.com! ;-)
To others who encounter a similar situation, I'm now quite confident that my settings were not corrupt, and if you're using the Express edition, please don't be mislead by MS documentation which may lead you to believe it's the "Just My Code" option. This option cannot be changed in the Express versions. (But it's not because you can't turn it on -- It's because you can't turn it OFF in Express!)
Thanks to everyone for your efforts.
I'm going to guess that it is trying to show you the equivalent of:
Application.Run(new frmMain)
This is the code that gets generated to startup your form. But in VB.NET this code gets burried. Create a Main() function and change your startup type to that with this line in it and repeat the process. You'll see it highlight Application.Run. That is the method that contains your Windows message pump loop.