VBA Excel variables value after closing the excel file - vba

I have an user form which selects a directory path. That path is stored in a variable. The problem is that every time I start the macro, I have to rechoose the path for the directory. How should be the path variable declared in order to keep it's value once the macro is closed, or the excel file is closed ?

Persisting data between calls to a macro is simpler; use a variable at module scope in the .bas module containing the macro.
Persisting data on saving is more difficult. You could write to the registry but that's tricky and you'll need to use various Windows API functions.
The simplest thing to do would be to write the data to somewhere on the workbook.

The registry is simple in VBA. It's is very limited and uses ini file concepts.
There's a few of them such as (from Object Browser [F2] in VBA editor)
Function GetAllSettings(AppName As String, Section As String)
Member of VBA.Interaction
Sub SaveSetting(AppName As String, Section As String, Key As String, Setting As String)
Member of VBA.Interaction
Sub DeleteSetting(AppName As String, [Section], [Key])
Member of VBA.Interaction
Function GetSetting(AppName As String, Section As String, Key As String, [Default]) As String
Member of VBA.Interaction
Also the newer Windows Scripting Host Shell object also makes registry access easy.
object.RegDelete(strName)
object.RegRead(strName)
object.RegWrite(strName, anyValue [,strType])
Also WMI can access registry. Unlike both above methods it can ennumerate, so you can see what is there without have to know in advance.
Dim proglist()
Set oReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\default:StdRegProv")
ret = oReg.EnumKey(&H80000002, "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", proglist)
If err.num =0 then
For each prog in proglist
msgbox prog
Next
Else
Msgbox err.num & " " & err.description & " " & err.source
err.clear
End If
It can also check security and monitor changes to keys.

I would use a Name, which can be saved with the workbook.
ThisWorkbook.Names.Add Name:="SavedPath", RefersTo:= someString
Another simple alternative is to put that value in a cell, eventually in a hidden sheet, or a hidden row/column of any suitable sheet.
A third solution is to add it to the file properties.
Registry tricks are complex, don't work if you change machine or if user has not enough rights.

I have solved the problem storing the data on a cell and then on excel file initialization giving the data to corresponding variables. Thank you very much for those who thought to a solution !

Related

Can a Variable be stored in an Excel File that can not be accessed through Excel

I just discovered that in MS Word it is possible to store a Variable in a MS Word File that can not be accessed through the regular interface when running Microsoft Word.
Sub SetMyVariable()
Dim VARNAME As String
VARNAME = "HiddenVar"
ActiveDocument.Variables.Add VARNAME, "My special info"
End Sub
This gets saved in the XML Schema under word\settings.xml
I have tried using the ThisWorkbook Object in Excel, but it doesn't seem to have a Variable object that can be added like in word.
I want to know if there is something similar in Excel to store information/varialbes that get saved with the file.
PS: the closest thing I can think of (and use in codig) is a hidden named range.
You can try with the CustomXMLParts property of the Workbook which from the link seems a generic feature of Office products and available in Excel. Given you noted that a user would have to manually inspect the XML within the unzipped xlsx files then this seems to map to the Word Variables feature. The code sample just substitutes ThisWorkbook for ActiveDocument:
Option Explicit
Sub TextXMLPart()
Dim objXMLPart As CustomXMLPart
'add
Set objXMLPart = ThisWorkbook.CustomXMLParts.Add("<foo>bar</foo>")
'inspect
For Each objXMLPart In ThisWorkbook.CustomXMLParts
Debug.Print objXMLPart.XML
Next objXMLPart
End Sub
The accepted answer to this question (which focuses on Excel and vsto) states that:
Custom XML parts For an application-level add in, this is my preferred method of storing any application data that needs to be persisted in a saved xls file without ever being visible to the user.

Activate window of another Visio instance

Currently I have a file name stored in string called filename. The file stored in the string is currently open. Issue is, this file could some times be opened in another instance of Visio.
I want to activate the file that is stored in filename string
My current method does not capture this - The code below only checks if the filename exists among the current/one instance of Visio.
For Each objDoc In objVisio.Documents
If objDoc.Name = filename Then
objDoc.activate
Exit for
End If
Next
How can I activate this file to bring it forward?
windows(filename & " - Microsoft Visio").activate
is not working either
I've tried
Dim objVisio as Visio.Application
Set objVisio = GetObject(filename).Application
which isn't working (maybe due to filename string only having the file name and not the entire file path as well)
Any other brute force methods available out there?
Any help is appreciated!
Try something like this:
objVisio.Application.Caption
Or
AppActivate "Microsoft Visio"
I guess another option is to look into this: https://msdn.microsoft.com/en-us/library/office/ff766749.aspx
I haven't worked extensively with Visio in VBA, so I am interested to see the true answer here.

VBA function error when other users try to use it

I've made this short function to find whether a name is "given name surname" or "surname, given name", however when this is run by another user (on another PC), the result function In error #NAME? :
Function FindName_Function(NameCell As String) As String
Dim FindComma As Long
Dim FindName As String
FindComma = InStr(1, NameCell, ",")
If FindComma <> 0 Then
FindName = VBA.Right(NameCell, Len(NameCell) - FindComma)
Else
FindName = VBA.Left(NameCell, InStr(1, NameCell, " ") - 1)
End If
FindName_Function = FindName
End Function
This is how the function is called:
This is the formula:
="Hello "&FindName_Function(INDEX(Table_HP_Effective_contact_list;MATCH(SiteID;Table_HP_Effective_contact_list[Site];0);4))&","
I believe you use the function as a UDF (User Defined Function) and the #NAME error indicates that the function can't be found or executed. Make sure you store the UDF on a discoverable location and has permission to run. It is not clear from your question -where- you stored the UDF and what the security settings are on the client machines.
What I did is create a new Workbook, added a new Module to the Workbook, copied the UDF in the Module, used it in a cell on the new Workbook and worked without problems. So my guess from the limited information provided is that you stored the UDF in a different location outside the Workbook, inaccessible for the other users to find.
On a side note:
- the VBA. prefix is not necessarily needed
- test if the name is empty, InStr will fail if the name is empty
If you want a better answer, please elaborate on the location of the UDF (where did you create/store the UDF) and what are the macro security settings currently in place on the machines you see the error on.
if u save the function in the same workbook and saved the workbook in *.xlsm format, then the possible cause is user did not enable macro when opening the file.
if u save the function in the same workbook and saved the workbook in *.xlsx format, then u saved it in the wrong format.
if u save the function in another workbook, then that workbook should be saved in Excel Add-In format (*.xlam) and the Add-in must be loaded in Excel.
hope this helps
+
Try use the insert function window to find the function. Select category = "User Defined".
If the function is listed, then try call it from there.
If the function is not listed, then for sure macro for that workbook is not enabled.

How to access a Word public variable in Excel VBA

I'm trying to automate some report generation where Excel VBA is doing all the work. My employer has a standardized set of templates of which all documents are supposed to be generated from. I need to populate one of these templates from Excel VBA. The Word templates utilize VBA extensively.
This is (some of) my Excel VBA code:
Sub GenerateReport() ' (Tables, InputDataObj)
' code generating the WordApp object (works!)
WordApp.Documents.Add Template:="Brev.dot"
' Getting user information from Utilities.Userinfo macro in Document
Call WordApp.Run("Autoexec") ' generating a public variable
Call WordApp.Run("Utilities.UserInfo")
' more code
End sub
In the Word VBA Autoexec module, a public variable named user is defined and declared. The Userinfo sub from the Utilities module populates user. Both these routines are run without any complaints from VBA. I would then like to be able to access the user variable in my Excel VBA, but I get the following error
Compile Error: Variable not yet created in this context.
How can I access the Word VBA variable in Excel VBA? I thought it more or less was the same?
EDIT: the user variable is a user defined Type with only String attributes. Copying the Word VBA functions that populate the user variable is absolutely doable, just more work than I though was necessary...
In a Word module:
Public Function GetUserVariable() As String '// or whatever data type
GetUserVariable = user
End Function
In an Excel module:
myUser = WordApp.Run("GetUserVariable")
Alternatively, you could be able to replicate the variables value - as it's called user I suspect it is returning some information about a user, or author, of a document. In which case one of the following might be what you're after:
'// Username assigned to the application
MsgBox WordApp.UserName
'// Username defined by the system
MsgBox Environ$("USERNAME")
'// Name of the author of the file specified
MsgBox CreateObject("Shell.Application").Namespace("C:\Users\Documents").GetDetailsOf("MyDocument.doc", 9)
Another option - if you could only add a line of code to the Utilities.UserInfo sub (after setting your public variable):
ActiveDocument.Variables("var_user") = user
Then you could access it easily afterwards in Excel:
Sub GenerateReport() ' (Tables, InputDataObj)
' code generating the WordApp object (works!)
'I am assuming your WordApp object is public, as you don't declare it.
'Capture the new document object
Dim newdoc as Object
set newdoc = WordApp.Documents.Add(Template:="Brev.dot")
' Getting user information from Utilities.Userinfo macro in Document
Call WordApp.Run("Autoexec") ' generating a public variable
Call WordApp.Run("Utilities.UserInfo")
'Get and show the value of "user"
Dim user as String
user = newdoc.Variables("var_user")
msgbox, user
End Sub
This is assuming that useris a string.
EDIT: As it is a requirement to work only on the Excel VBA, I would definely try the approach suggested by Scott and MacroMan - replicating the same functionality of the Word macros in Excel - if possible.
I assume that you've already ruled out the possibility of using an edited copy of the original template, set in a public folder...
For the sake of completness, there is another possibility: actually it is possible to inject VBA code in a Word document without the VBProject Object Model, by "brute force". If you rename a Word document as a .zip file and open it, you will notice a \word\vbaProject.bin file in it. This file contains the VBA project for the document and, in principle, one could add or change VBA code by modifying or replacing it.
I did some tests transplanting code from one document to another by simply copying the vbaProject.bin file, and the concept works. If you are interested in learning more about this file, this topic could be of use.
Notice, however, that to do what you want with such a technique would be somewhat complex (it would involve, for starters, updating zip files from your Excel VBA), and would require a lot of experimentation to mitigate the risk of accidentally corrupting your files. Definetly not recommended if you are looking for an easy and simple solution - but it is possible.

Is it possible to specify a location in a user's home directory for a shared library in VBA (in Office for Mac)?

I'm currently using the VBA code similar to the following to specify a location of a shared library for use in communicating (passing a string) from an Office application to a desktop application. The VBA code/macros will need to exist in an add-in (.ppa).
Private Declare Sub sharedLibPassString CDecl Lib "/Users/myUserName/Library/Application Support/myCompanyName/MySharedLib.dylib" Alias "PassString" (ByVal aString As String)
In code from a VBA Macro, I then can do the following.
Call sharedLibPassString(myString)
I've got the communication working, but I'd like to replace the /Users/myUserName/ part with the current user's home directory. Normally on a Mac, you'd specify ~/Library/Application Support/..., but the ~/ syntax doesn't work, producing a "File not found" runtime error.
I discovered that using the following Environment Variable method gets me the ~/ location that I need:
Environ("HOME")
However, I don't see a way to make this part of the CDecl Lib statement, since, as far as I can tell, Environ is evaluated at runtime.
Is there any way to specify a location of a shared library in the user's home directory (~/) in VBA?
Here are a few notes about my environment/approach:
I'm using a Mac, though I believe if there is a solution it would be similar on a PC.
I don't believe it shouldn't matter, but the Office application I'm using is PowerPoint (2011).
The reason I'm trying to access an area inside of the Application Support directory, instead of the default location for shared libraries is because I'd like the Desktop application to place the shared library in a location without an installer, and without requiring a user's or administrator's privileges. If there is a better solution or location to accomplish the same task, this would be very helpful as well.
Sorry for giving a long response, I just wanted to make sure I explained this fairly well.
From this page (Anatomy of a Declare Statement) we read that
The Lib keyword specifies which DLL contains the function. Note that
the name of the DLL is contained in a string within the Declare
statement.
(emphasis added)
From experimentation, the VBE scolds me if I try to give anything but a string constant.
The only work around that I'm aware of requires rewriting the string constant at runtime.
Here is an example of how this could be done: Let's say your delaration statement is in Module1 in your current project, and that you deliberately wrote the declaration in this format at the top of your module:
Private Declare Sub sharedLibPassString CDecl Lib _
"/Users/myUserName/Library/Application Support/myCompanyName/MySharedLib.dylib" _
Alias "PassString" (ByVal aString As String)
You can access that module via code through this (requires permissions to VBA in trust Center listed under Developer Macro Settings):
Dim myModule
set myModule = ActivePresentation.VBProject.VBComponents("Module1").CodeModule
Once you've gained the CodeModule, you can replace the 2nd line directly:
myModule.ReplaceLine 2, Environ("HOME") & " _"
Mission accomplished!
If you do this, you will need to update the path prior to attempting to call your declared sub. There must be a break in execution that allows VBA to recognize the change. Also, you will not be able to modify the code while in break mode.
Now if I were doing this, I'd want to make sure I replace the right line, to not break code. You can check the contents of the 2nd line by calling this myModule.Lines(2,1) which will return the string value of the line.
However, here is a more robust solution that will find the correct line and then replace it (assumes myModule has already been defined as listed above):
Dim SL As Long, EL As Long, SC As Long, EC As Long
Dim Found As Boolean
SL = 1 ' Start on line 1
SC = 1 ' Start on Column 1
EL = 99999 ' Search until the 99999th line
EC = 999 ' Search until the 999th column
With myModule
'If found, the correct line will be be placed in the variable SL
'Broke search string into two pieces so that I won't accidentally find it.
Found = .Find("/Users/myUserName/Library/Application Support/myCompanyName/" & _
"MySharedLib.dylib", SL, SC, EL, EC, True, False, False)
If Found = True Then
'Replace the line with the line you want, second paramater should be a string of the value.
.ReplaceLine SL, Environ("HOME") & " _"
End If
End With
I don't have a Mac, so this is an incomplete answer and I don't know if it will help, but it's a couple of ideas.
I know on Windows, VB doesn't load an external library until you first try to call a function declared with it, and if you specify only the filename in the declare statement, it will use the system path to look for it. Once I did the same thing you are doing, loading a library from a dynamic path, by specifying only a filename, then making a system API call to set the current working directory to the directory of the library before loading it. Changing the PATH environment variable would probably also work.
Second idea: you could hard-code the path to a filename in the /tmp directory; then automatically copy the desired library to that location before loading it. Watch out for the file being in use by another process, but that's only an error if it is a different version of the file to the one that you want.