I'm writing a macro to save VBA modules as 64 bit strings in another self-extracting module. The self-extracting module is designed to hold several long strings (could be any length, up to the max 2GB strings I suppose), and a few short snippets of code to decompress the strings and import the modules they represent.
Anyway, when my macro builds the self extracting module it needs to save the really long strings (I'm saving as hardcoded Consts). But if they are too long (>1024 ish) to fit on a single line in the VBA editor, I get errors.
How should I format these hardcoded strings so that I can save them either as Consts or in another way in my self-extracting module? So far I've been saving each string as several Const declarations in 1000 character chunks, but it would be preferable to have one string per item only.
As suggested in the comment, you can use custom XML part to store information inside the workbook.
Here’s the code:
Option Explicit
Public Sub AddCustomPart()
Dim oXmlPart As CustomXMLPart
Dim strTest As String
strTest = "<Test_ID>123456</Test_ID>"
Set oXmlPart = ReadCustomPart("Test_ID")
'/ Check if there is already an elemnt available with same name.
'/ VBA or Excel Object Model, doesn't perevnt duplicate entries.
If oXmlPart Is Nothing Then
Set oXmlPart = ThisWorkbook.CustomXMLParts.Add(strTest)
Else
MsgBox oXmlPart.DocumentElement.Text
End If
End Sub
Function ReadCustomPart(strTest As String) As CustomXMLPart
Dim oXmlPart As CustomXMLPart
For Each oXmlPart In ThisWorkbook.CustomXMLParts
If Not oXmlPart.DocumentElement Is Nothing Then
If oXmlPart.SelectSingleNode("/*").BaseName = strTest Then
Set ReadCustomPart = oXmlPart
Exit Function
End If
End If
Next
Set ReadCustomPart = Nothing
End Function
Related
I want to get a list of routines from a VBA project, then run the macros selected by the user.
The image below shows the native "Macros" box. I want to extend this functionality to multiple macros across multiple documents.
I found this link which solves the first part of the problem. Now that I have my list, how do I run a selected routine by name?
Hello and welcome to SO
Below is code sample how to execute VBA macro using code. You need to add some form to select documents and macros for execute. This depends on your implementation.
Sub RunMacroUsingCode()
Dim vbaProjectName As String
vbaProjectName = "InventorVBA"
Dim vbaModuleName As String
vbaModuleName = "m_Tests"
Dim vbaMacroName As String
vbaMacroName = "RunMultipleMacrosTestCall"
Dim vbaProject As InventorVBAProject
For Each vbaProject In ThisApplication.VBAProjects
If vbaProject.name = vbaProjectName Then Exit For
Next
Dim vbaModule As InventorVBAComponent
For Each vbaModule In vbaProject.InventorVBAComponents
If vbaModule.name = vbaModuleName Then Exit For
Next
'Using result is optional
Dim result As Variant
Call vbaModule.InventorVBAMembers(vbaMacroName).Execute(result)
End Sub
Function RunMultipleMacrosTestCall()
Call MsgBox("TEST")
RunMultipleMacrosTestCall = True
End Function
I have an Excel function that populates a dictionary with information from a SQL pull. To help visualize the answer set, I had it currently dumping into a new workbook - and while I don't need to visualize it anymore, I still find it helpful to populate.
The answer set doesn't change unless I myself have done something in the database populating it, so I don't need the function to perform the query every time. Therefore, once the dictionary is populated, I am bypassing the query unless I force it to initialize the dictionary with a refresh parameter.
The module is structured as follows:
Option Explicit
Option Compare Text
Private dProducts As Scripting.Dictionary
------
Function ProdLookup(sValue As Variant, sReturn As Variant, sLookupType As
Variant, _Optional iVendor As Integer, Optional bRefresh As Boolean) As
Variant
If sValue = "" Then
ProdLookup = ""
Exit Function
End If
If sLookupType = "SKU" Then
If (dProducts Is Nothing) Or (bRefresh = True) Then
Call Create_dProdsBySKU
End If
ProdLookup = dProducts(CStr(sValue.Value))(CStr(sReturn.Value))
Exit Function
End If
End Function
------
Sub Create_dProdsBySKU()
Dim newBook As Workbook
Set newBook = Workbooks.Add
'Rest of code to create query, run it, retrieve results, dump onto
'newBook, and populate into dProducts
newBook.Close SaveChanges:=False
End Sub
If I simply run Create_dProdsBySKU from within the Editor, the dictionary populates onto a new workbook, and closes. If I use the ProdLookup function within Excel, however, it never creates a new workbook - and if I put a watch on newBook, it shows it's got a value of ThisWorkbook.
Attempting to see the properties of newBook in the Watch window hangs Excel and I need to End Task from the Task Manager.
What am I missing?
If I use the ProdLookup function within Excel
If you are using the function as a UDF, it will not be permitted to create a new workbook. UDFs are limited to only returning a value to the cell containing the function call.
What is the best way to store a large text file (max 10Mb) in an Excel file?
I have a couple of requirements:
It has to be embedded so that the excel file can be moved and sent to a different computer and all the text files will follow.
It needs to be done from a macro.
And a macro needs to be able to read the file contents after it has been embedded.
I already tried to store it by breaking the text into several chunks enough small to fit into a cell (~32 000 chars), but it didn't work. After my macro had inserted the first 150 000 characters it gave me an "Out of Memory" error.
I remember seeing one web page with a couple of options for this I but cannot find it anymore. Any suggestions are most welcome. I will try them out if you are not sure if it works or not.
It would likely be best to simply save the .txt file alongside the Excel file, and have the macro pull the text as needed from that folder. To read more on importing files see this:
http://answers.microsoft.com/en-us/office/forum/office_2010-customize/vba-code-to-import-multiple-text-files-from/525bd388-0f7d-4b4a-89f9-310c67227458
Keeping the .txt within the Excel file itself is not necessary and will likely make it harder to transfer files in the long run. For example, if you cannot e-mail a file larger than 10MB, then you can simply break your .txt file in half and e-mail separately - using a macro which loads the text into Excel locally.
Very simple CustomXMLPart example:
Sub CustomTextTester()
Dim cxp1 As CustomXMLPart, cxp2 As CustomXMLPart
Dim txt As String
'read file content
txt = CreateObject("scripting.filesystemobject").opentextfile( _
"C:\_Stuff\test.txt").readall()
'Add a custom XML part with that content
Set cxp1 = ThisWorkbook.CustomXMLParts.Add("<myXMLPart><content><![CDATA[" & txt _
& "]]></content></myXMLPart>")
Debug.Print cxp1.SelectSingleNode("myXMLPart/content").FirstChild.NodeValue
End Sub
Consider the method shown below. It uses Caption property of Label object located on a worksheet for data storage. So you can create a number of such containers with different names.
Sub Test()
Dim sText
' create special hidden sheet for data storage
If Not IsSheetExists("storage") Then
With ThisWorkbook.Worksheets.Add()
.Name = "storage"
.Visible = xlVeryHidden
End With
End If
' create new OLE object TypeForms.Label type as container
AddContainer "test_container_"
' read text from file
sText = ReadTextFile("C:\Users\DELL\Desktop\tmp\tmp.txt", 0)
' put text into container
PutContent "test_container_", sText
' retrieve text from container
sText = GetContent("test_container_")
' show length
MsgBox Len(sText)
' remove container
RemoveContainer "test_container_"
End Sub
Function IsSheetExists(sSheetName)
Dim oSheet
For Each oSheet In ThisWorkbook.Sheets
If oSheet.Name = sSheetName Then
IsSheetExists = True
Exit Function
End If
Next
IsSheetExists = False
End Function
Sub AddContainer(sName)
With ThisWorkbook.Sheets("storage").OLEObjects.Add(ClassType:="Forms.Label.1")
.Visible = False
.Name = sName
End With
End Sub
Sub RemoveContainer(sName)
ThisWorkbook.Sheets("storage").OLEObjects.Item(sName).Delete
End Sub
Sub PutContent(sName, sContent)
ThisWorkbook.Sheets("storage").OLEObjects.Item(sName).Object.Caption = sContent
End Sub
Function GetContent(sName)
GetContent = ThisWorkbook.Sheets("storage").OLEObjects.Item(sName).Object.Caption
End Function
Function ReadTextFile(sPath, iFormat)
With CreateObject("Scripting.FileSystemObject").OpenTextFile(sPath, 1, False, iFormat)
ReadTextFile = ""
If Not .AtEndOfStream Then ReadTextFile = .ReadAll
.Close
End With
End Function
I have an Excel .xlam file that adds a button in the ribbon to do the following:
Scan the ActiveSheet for some pre-set parameters
Take my source text (a string value, hard coded directly in a VBA Module) and replace designated areas with the parameters retrieved from step 1
Generate a file containing the calculated text
I save the source text this way because it can be password protected and I don't need to drag another file around everywhere that the .xlam file goes. The source text is saved in a separate module called "Source" that looks something like this (Thanks VBA for not having Heredocs):
'Source Module
Public Function GetSource() As String
Dim s As String
s = ""
s = s & "This is the first line of my source text" & vbCrLf
s = s & "This is a parameter {par1}" & vbCrLf
s = s & "This is another line" & vbCrLf
GetSource = s
End Function
The function works fine. My problem is if I want to update the source text, I now have to manually do that in the .xlam file. What I would like to do is build something like a Sub ImportSource() in another module that will parse some file, rebuild the "Source" Module programatically, then replace that Module with my calculated source code. What I don't know is if/how to replace the source code of a module with some value in a string variable.
It's like metaprogramming at its very worst and philosophically I'm against doing this down to my very core. Practically, however, I would like to know if and how to do it.
I realize now that what you really want to do is store some values in your document in a way that is accessible to your VBA, but that is not readable to a user of the spreadsheet. Following Charles Williams's suggestion to store the value in a named range in a worksheet, and addressing your concern that you don't want the user to have access to the values, you would have to encrypt the string...
The "proper way" to do this is described in this article - but it's quite a bit of work.
A much shorter routine is found here. It just uses simple XOR encryption with a hard coded key - but it should be enough for "most purposes". The key would be "hidden" in your macro, and therefore not accessible to prying eyes (well, not easily).
Now you can use this function, let's call it encrypt(string), to convert your string to a value in the spreadsheet:
range("mySecretCell").value = encrypt("The lazy dog jumped over the fox")
and when you need to use it, you use
Public Function GetSource()
GetSource = decrypt(Range("mySecretCell").value)
End Function
If you use the XOR version (second link), encrypt and decrypt would be the same function...
Does that meet your needs better?
As #brettdj already pointed out with his link to cpearson.com/excel/vbe.aspx , you can programmatically change to code of a VBA module using the VBA Extensibility library! To use it, select the library in the VBA editor Tools->References. Note that you need to also change the options in your Trust center and select: Excel Options->Trust Center->Trust Center Settings->Macro Settings->Trust access to the VBA project object model
Then something like the following code should do the job:
Private mCodeMod As VBIDE.CodeModule
Sub UpdateModule()
Const cStrModuleName As String = "Source"
Dim VBProj As VBIDE.VBProject
Dim VBComp As VBIDE.VBComponent
Set VBProj = Workbooks("___YourWorkbook__").VBProject
'Delete the module
VBProj.VBComponents.Remove VBProj.VBComponents(cStrModuleName)
'Add module
Set VBComp = VBProj.VBComponents.Add(vbext_ct_StdModule)
VBComp.Name = cStrModuleName
Set mCodeMod = VBComp.CodeModule
'Add procedure header and start
InsertLine "Public Function GetSource() As String"
InsertLine "Dim s As String", 1
InsertLine ""
'Add text
InsertText ThisWorkbook.Worksheets("Sourcetext") _
.Range("___YourRange___")
'Finalize procedure
InsertLine "GetSource = s", 1
InsertLine "End Function"
End Sub
Private Sub InsertLine(strLine As String, _
Optional IndentationLevel As Integer = 0)
mCodeMod.InsertLines _
mCodeMod.CountOfLines + 1, _
Space(IndentationLevel * 4) & strLine
End Sub
Private Sub InsertText(rngSource As Range)
Dim rng As Range
Dim strCell As String, strText As String
Dim i As Integer
Const cLineLength = 60
For Each rng In rngSource.Cells
strCell = rng.Value
For i = 0 To Len(strCell) \ cLineLength
strText = Mid(strCell, i * cLineLength, cLineLength)
strText = Replace(strText, """", """""")
InsertLine "s = s & """ & strText & """", 1
Next i
Next rng
End Sub
You can "export" and "import" .bas files programmatically. To do what you are asking, that would have to be the approach. I don't believe it's possible to modify the code in memory. See this article
This is really stumping me. I put a question up yesterday regarding collections being passed between modules (see here), but it doesn't seem like I am getting anymore explanations on that one, so i am attempting to restate the problem more clearly in a generic way.
I have a module (module1) and a userform (userform1). I create a collection (or array) in userform1 and add worksheet objects to this array. I then pass control to module1, which calls a sub in userform1 called addNewFile, which is supposed to add the newly created workbook to the collection. However, each time module1 calls addNewFile i get one of two scenarios: 1) the collection has been erased and all worksheets that had been added are now gone (for a collection), 2) i get an error saying that i have a type mismatch (for an array). I don't know why this is happening, so here is the code below to illustrate better. Any help would be appreciated, even if it is just to tell me that it is not possible to store worksheet objects in arrays.
UserForm1
Dim workBooksCollection as New Collection 'can also define as an array
Private Sub CommandButton1_click()
Dim mainWorkBook as workbook
Set mainWorkBook = ActiveWorkbook
Dim testwb As Workbook
workBooksCollection.Add Item:=mainWorkBook, key:="main" 'Adds successfully
workBooksCollection.Add Item:=testwb, key:="test" 'Adds successfully
MsgBox "the size of the array is: " & usedWorkBooks.Count 'Prints off as size 2
Module1.initialize
'After running initialize, prints off as size 0, meaning collection has been erased
MsgBox "the size of the array is: " & usedWorkBooks.Count 'Prints off as size 0
End Sub
Public Sub addNewFile(filepath As String, sheetKey As String)
Dim newWorkBook As Workbook
Set newWorkBook = Workbooks.Open(filepath)
MsgBox "The name of the workbook is: " & newWorkBook.name 'Prints off name of workbook successfully
workBooksCollection.Add Item:=newWorkBook, key:=sheetKey
MsgBox "the size of the array is: " & workBooksCollection.Count 'Prints off as size 1
End Sub
Module1
Public Sub intialize()
Dim filepath as string
'The filepath is set to any path of a workbook
'This will print out that the array size is 1
UserForm1.addNewFile filePath, "secondBook"
End Sub
Sorry if i seem to be beating a dead horse here, but I really don't have any idea what is going on here. I am used to the idea of collections and lists being global and not changing when being referenced by another module. Any help on what is going on here would be great.
I would like to comment, but I can't since I had to redo my account because I got locked out of my original.
If my answer isn't helpful, I will delete in a bit, but what if you replace -
Dim workBooksCollection as collection 'can also define as an array
from the UserForm module, into Module 1 as:
Public workBooksCollection as collection 'can also define as an array
Does it help?