Excel VBA add code userform programmatically - excel

I got a tricky issue with my VBA-code. The situation is that I have a manual created userform. I add controls to the userform with a macro and it works fine for me. But now I also need to add event-code to the userform. Following code I want to add with .CodeModule.InsertLines. The important part is that the textboxes, which I want to call should work variably, but it doesn't work, any ideas how to fix this? (Textboxes are named like this: textbox_0, textbox_1 and following)
Dim iMaxColumns As Integer
Dim iCount As Integer
iMaxColumns = Tabelle4.Cells(8, 2).Value
Dim vArray(0 To iMaxColumns - 1) As String
For iCount = 0 To iMaxColumns - 1
vArray(iCount) = textbox_ & iCount &.Value
Next
'do sth. with the arrray
I assume the problem is that I can't add variables to my textbox object. I also could work with the complete path to my textbox calling it with .Designer.Controls("textbox_" & iCount & "") but that is a bunch of code and I hope to avoid that.

I figured out a quite easy way to solve my problem. I wrote all the necessary code in a seperate module. There I can address all the variables and information I need. After that when creating the UserForm I just copy all the code into the UserForms code block.
Public Function edit_userform(strUserForm As String, _
strUserFormEvents As String)
Dim VBProj As VBIDE.VBProject
Dim VBComp As VBIDE.VBComponent
Dim VBComp_Event As VBIDE.VBComponent
Dim strCode As String
Set VBProj = ActiveWorkbook.VBProject
Set VBComp = VBProj.VBComponents(strUserForm)
Set VBComp_Event = VBProj.VBComponents(strUserFormEvents)
With VBComp_Event.CodeModule
strCode = .Lines(1, .CountOfLines)
End With
VBComp.CodeModule.AddFromString strCode
End Function

Related

Debug doesn't work with library references - Errormsg "Cant enter break mode at this time"

I wrote a macro in Excel VBA to make users send their Excel-File via E-Mail automatically back to me.
To use this macro every user must install the Outlook Library. For this I created the function add_outlook. If I try to run the function it works.
The only problem occurring is that VBA doesn't let me debug. When stepping through the code I get the Errormsg "Cant enter break mode at this time"
Is there a workaround or fix?
Thanks a lot!
Option Explicit
Public Function add_outlook()
'DEBUGGING DOESNT WORK
'late binding
Dim vbProj As Object
Set vbProj = ThisWorkbook.VBProject
Dim vbRefs As Object
Set vbRefs = vbProj.References
Dim vbRef As Object
'Libary GUID and Data
Dim libname As String
libname = "Outlook"
Dim guid As String
Dim major As Long
Dim minor As Long
Dim exists As Boolean
guid = "{00062FFF-0000-0000-C000-000000000046}"
major = 9
minor = 6
'Reference cleanup function
For Each vbRef In vbRefs
If vbRef.Name = libname Then
'problem occurs here
vbRefs.Remove Reference:=vbRef
End If
Next
'add Ref
vbRefs.AddFromGuid guid:=guid, major:=major, minor:=minor
End Function
Solution:
Sub Workbook_Open()
Call add_outlook
End Sub
Cant step through code
Get reference when Workbook_open event is triggered

.net Excel Range Address limitation

I want to define multiple areas in one single Excel.Range object. The purpose is to colorize multiple different areas by setting one single Range. This should save time using the Excel interop, which is very slow in such operations. The problem is, that I get an error (HRESULT: 0x800A03EC) when I try to put a "big" address line into the Range. Could somebody tell me if there is a limitation using Excel interop and does anybody have a solution for colorizing lots of areas at once / in a fast manner?
The "big" address line in the example is just to show you where the problem is. I know it does not make a lot of sense to put A1:A2 multiple times into the address.
Dim objExcelApp As New Excel.Application
objExcelApp.Visible = True
Dim objExcelWorkbooks As Excel.Workbooks = objExcelApp.Workbooks
Dim objExcelWB As Excel.Workbook = objExcelWorkbooks.Add
Dim objExcelWS As Excel.Worksheet = objExcelWB.Worksheets(1)
Dim rng As Excel.Range
rng = objExcelWS.Range("A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2;A1:A2")
The most likely cause of your issue is that you are trying to create an array of values from a complex multi-area range.
(Edit: I have removed the extra information based on VBA, not VB.Net, which is not relevant to the Question).
The following StackOverflow Q&A also addresses other causes and solutions: HRESULT: 0x800A03EC on Worksheet.range
I found a really fast possibilty in filling a multi-area range with more content in using the Union function in combination with the approach of splitting the address into chunks of 255 char strings. This function does the job very well. This code is with semicolon, because its a country specific seperator it seems (comma is not working in my case, maybe you can modify it). Thanks #K.Dᴀᴠɪs for the hint:
Private Function CombineAddressToRange(ByVal Address As String, ByVal objExcelWorksheet As Excel.Worksheet, ByVal objExcelApp As Excel.Application) As Excel.Range
Dim SplitAddress As String()
Dim TempAddress As String = ""
Dim FinalRange As Excel.Range
SplitAddress = Address.Split(";")
'Initialize Range
FinalRange = objExcelWorksheet.Range(SplitAddress(0))
If UBound(SplitAddress) >= 1 Then
For i = 1 To UBound(SplitAddress)
If Len(TempAddress) + 1 + Len(SplitAddress(i)) > 255 Then
FinalRange = objExcelApp.Union(FinalRange, objExcelWorksheet.Range(TempAddress))
TempAddress = SplitAddress(i)
Else
If TempAddress = "" Then
TempAddress = SplitAddress(i)
Else
TempAddress = TempAddress & ";" & SplitAddress(i)
End If
End If
Next
If TempAddress <> "" Then
FinalRange = objExcelApp.Union(FinalRange, objExcelWorksheet.Range(TempAddress))
End If
End If
Return FinalRange
End Function

VBA: Chart.export produces corrupt image - until scrolled over or chart.activate

I have a form that manipulates a chart and then exports it, like so:
Workbooks(sWB).Sheets("Output").Unprotect sPW
Workbooks(sWB).Sheets("Output").ChartObjects(1).Chart.Export strDocName
However, occasionally this doesn't work properly anymore and a corrupt image is exported each time. Going to the sheet with the chart and scrolling over it (without clicking) solves the issue for a while. For now I fixed it like this:
Workbooks(sWB).Sheets("Output").Unprotect sPW
Workbooks(sWB).Sheets("Output").ChartObjects(1).Activate 'chart sometimes falls asleep somehow, maybe this will fix
Workbooks(sWB).Sheets("Output").ChartObjects(1).Chart.Export strDocName
This seems to have solved the issue which I dubbed "sleeping charts" for now.
However, I would like to understand how this works, and to fix it without using "Activate" as that can impact the user experience of my users (who use multiple Excel sheets along this VBA-excel form).
Anyone who understands what happens here?
Full function code chain
Private Sub C171CmdLe1Dr1Graph_Click()
Call ShowGraph(1, 1, True)
End Sub
Private Sub ShowGraph(ByVal intLeNr As Integer, ByVal intDrNr As Integer, ByVal Show As Boolean) 'Delete
Dim strDocName As String
Dim strLeNr As String
Dim oChart As Frm_DCT_ShowGraph
Call dle(intLeNr - 1).DrawChart(intLeNr, intDrNr)
'export the chart
strDocName = strMyDocsPath & "\DRT_Chart" & Right(strLeNr, 1) & ".gif"
Workbooks(sWB).Sheets("Output").Unprotect sPW
Workbooks(sWB).Sheets("Output").ChartObjects(1).Activate 'chart sometimes falls asleep somehow, maybe this will fix
Workbooks(sWB).Sheets("Output").ChartObjects(1).Chart.Export strDocName
If Show Then
'create new chart, load it and show it
Set oChart = New Frm_DCT_ShowGraph
oChart.DocName = strDocName
oChart.Show vbModeless
End If
Workbooks(sWB).Sheets("Output").Protect sPW
DoEvents
End Sub
Public Sub DrawChart(ByVal intLeNr As Integer, ByVal intDrNr As Integer)
Dim lngN As Long
Dim sngPlotArr() As Single
Dim strIRange As String
Dim strURange As String
...
'create plot data
Call cDriver(intDrNr - 1).Plot(sngPlotArr)
'unlock worksheet
Workbooks(sWB).Sheets("Output").Unprotect sPW
'clear range first
Workbooks(sWB).Sheets("Output").Range(strIRange).Clear
Workbooks(sWB).Sheets("Output").Range(strURange).Clear
'fill in data
For lngN = 0 To UBound(sngPlotArr, 1)
Workbooks(sWB).Sheets("Output").Range(strIRange).Columns(lngN + 1).Value2 = sngPlotArr(lngN, 0)
Workbooks(sWB).Sheets("Output").Range(strURange).Columns(lngN + 1).Value2 = sngPlotArr(lngN, 1)
'give OS some time
If lngN Mod 100 = 0 Then
DoEvents
End If
Next
...
'relock
Workbooks(sWB).Sheets("Output").Protect sPW
End Sub

Print a variable's name

I might be bad at googling as I couldn't find the answer to below question:
Sub TEST()
Dim sthstring as string
sthstring = "Hello World"
end sub
While we all know it's easy to use msgbox to print "Hello world", is it possible to print out the variable's name (which in this case, "sthstring") and how?
EDIT: please do not provide with answer such as:
Dim someotherstring as string
someotherstring = "sthstring"
as I meant to find a way to print the 'name' of the variable, thanks
After reading comments I think you may find this answer useful.
VBA doesn't support reflection just like #Monty Wild has mentioned already but adding references to Microsoft Visual Basic for Applications Extensibility 5.3 grants you the access the VBE object. You can then iterate object modules in your VBA Project and retrieve the entire code for a module in a String format.
Consider the following (Stick it in a new workbook's Module1)
Sub Main()
Dim code As String
code = GetCodeModule("Module1")
Debug.Print code
End Sub
Private Function GetCodeModule(ByVal codeModuleName As String) As String
Dim VBProj As VBIDE.VBProject
Dim VBComp As VBIDE.VBComponent
Dim CodeMod As VBIDE.CodeModule
Set VBProj = ThisWorkbook.VBProject
Set VBComp = VBProj.VBComponents(codeModuleName)
Set CodeMod = VBComp.CodeModule
GetCodeModule = CodeMod.Lines(1, CodeMod.CountOfLines)
End Function
Your code variable now stores the exact code that is in your Module1 you can check that by opening the Immediate Window ctrl+g.
You may want to write/use some sort of a Find function to disassemble the String components to retrieve variable names and values but I wouldn't recommend doing it as it's can get quite tricky.
You can go a step further that simply accessing the code.
Updating the excellent code from Pearson to
show that setting the reference to Microsoft Visual Basic for Applications Extensibility 5.3 isnt a pre-requisite to access the VBIDE
actually parse out any Dim XX As String in any codemodule and report it
sample output
Dim sthstring As String (Module1 at: Line: 2)
Dim strSub As String (Module2 at: Line: 2)
Dim FindWhat As String (Module2 at: Line: 11)
Dim ProcName As String (Module3 at: Line: 9)
code
Sub GetVariable()
Dim strSub As String
strSub = "As String"
Call SearchCodeModule(strSub)
End Sub
Function SearchCodeModule(ByVal strSub)
Dim VBProj As Object
Dim VBComp As Object
Dim CodeMod As Object
Dim FindWhat As String
Dim SL As Long ' start line
Dim EL As Long ' end line
Dim SC As Long ' start column
Dim EC As Long ' end column
Dim Found As Boolean
Set VBProj = ActiveWorkbook.VBProject
For Each VBComp In VBProj.VBComponents
Set CodeMod = VBComp.CodeModule
With CodeMod
SL = 1
EL = .CountOfLines
SC = 1
EC = 255
Found = .Find(target:=strSub, StartLine:=SL, StartColumn:=SC, _
EndLine:=EL, EndColumn:=EC, _
wholeword:=False, MatchCase:=False, patternsearch:=False)
Do Until Found = False
If Left$(Trim(.Lines(SL, 1)), 3) = "Dim" Then Debug.Print Trim(.Lines(SL, 1) & " (" & CStr(VBComp.Name) & " at: Line: " & CStr(SL)) & ")"
EL = .CountOfLines
SC = EC + 1
EC = 255
Found = .Find(target:=strSub, StartLine:=SL, StartColumn:=SC, _
EndLine:=EL, EndColumn:=EC, _
wholeword:=True, MatchCase:=False, patternsearch:=False)
Loop
End With
Next VBComp
End Function
This is difficult to do directly, as VBA does not have reflection, i.e. cannot directly reference itself.
However;
Since in a comment you mention that you want to write code that references itself, you can do so by referencing Microsoft Visual Basic for Applications Extensibility 5.3 in the tools/references dialog. This gives you the ability to reference VBA code, and from there you could write code that prints itself out.
See
MSFT reference 1, and
MSFT reference 2
That is likely impossible, as variable names are just pointers to data. You can have several variables that all point to the same object (although in VBA I do not believe strings are treated as objects so that would not be possible, with the exception of a ByRef function call).

Excel forms: identify unused code

I am updating an excel app written by someone else (of course :)
I found lots of unused Sub CommandButtonXX_Click() subs, and I am not always sure if the button still exists. Is there a way (program, VBE interface, external tool) to do some cleanup while avoiding to delete the still in use code ?
The list at the top of the properties box does seem to be reliable, since it is kind of context sensitive: if you're in a tab, it displays only items of that tab.
An interesting question!
I have significantly modified Pearson's code Listing All Procedures In A Module to find all CommandButtonXX_Click code on each worksheet (excluding other subs),
then tried to match each CommandButtonXX_Click code to an actual button on that sheet.
If there is no match the button is deleted, and a Msgbox at the end lists all deletions
Coding the VBA Editor can be problematic so pls save your work beforehand. I have avoided early binding with the Extensibility library that Pearson has used.
[4 Oct 2012: Updated to work on UserForms rather than Sheets]
SConst vbext_ct_MSForm = 3
Sub ListProcedures()
Dim VBProj
Dim VBComp
Dim CodeMod
Dim LineNum As Long
Dim NumLines As Long
Dim ProcName As String
Dim ObjButton
Dim ProcKind
Dim strBadButtons As String
Set VBProj = ActiveWorkbook.VBProject
For Each VBComp In VBProj.vbcomponents
If VBComp.Type = vbext_ct_MSForm Then
Set CodeMod = VBComp.CodeModule
With CodeMod
LineNum = .CountOfDeclarationLines + 1
Do Until LineNum >= .CountOfLines
ProcName = .ProcOfLine(LineNum, 0)
If ProcName Like "CommandButton*_Click" Then
Set ObjButton = Nothing
On Error Resume Next
Set ObjButton = VBComp.Designer.Controls(Replace(ProcName, "_Click", vbNullString))
On Error GoTo 0
If ObjButton Is Nothing Then
strBadButtons = strBadButtons & CodeMod.Name & "-" & Replace(ProcName, "_Click", vbNullString) & vbNewLine
.DeleteLines .ProcStartLine(ProcName, 0), .ProcCountLines(ProcName, 0)
End If
End If
LineNum = LineNum + 1
Loop
End With
End If
Next
If Len(strBadButtons) > 0 Then MsgBox "Bad Buttons deleted" & vbNewLine & strBadButtons
End Sub
There's a free add-in tool called MZ-Tools that can be used to identify unused procedures (it can do a lot more as well). Here is the link: http://www.mztools.com/v3/download.aspx
I'm developing Rubberduck, an open-source COM add-in for the VBE written in C# that has a code inspections feature that, as of version 1.3 (next release!), will include an inspection that does exactly that:
This inspection isn't specifically looking for unused click handlers as the accepted answer is doing (and if any CommandButtonX was renamed to anything meaningful, then the accepted answer will not find them - but that's not what the original post was about) - it's looking for procedures that are never called, assuming Public procedures and functions in a standard module (i.e. exposed to the host application as "macros" or "user-defined functions"), are used outside of the VBA code.
This version only finds controls on forms, not on worksheets - so handler procedures for ActiveX controls located on a worksheet would actually show up as false positives.

Resources