patternMinor
Renaming form controls and underlying code
Viewed 0 times
controlsunderlyingcoderenamingandform
Problem
I was working on adjusting some variable naming on an Excel project and ran into an issue that the MSForm control names needed to be updated. When you change the properties of the control on the form, the underlying code for the form doesn't seem to update accordingly.
Examples:
name of the control.
Anyhow, I've always wanted to mess around to get VBA to do stuff in the VBE, so this was my chance!
The macro finds the target project and target form and then updates the control names and appends the string
Ideally, the array of new names would be populated by the user with descriptive names and would be populated outside of the
I'm sure there's some clever refactoring that can be done, but I wasn't sure if I'd break it.
Also, I'm worried I misunderstand how Forms call their macros given it's not designed the same way as controls on sheets.
```
Public Sub FindReplaceInEntireModule()
Const TARGET_ADDIN As String = "bUTLAddIn"
Const TARGET_FORM As String = "form_chtGrid"
Dim targetProject As VBIDE.VBProject
Dim targetModule As VBIDE.VBComponent
Dim targetCode As VBIDE.CodeModule
Dim controlIndex As Long
Set targetProject = Application.VBE.VBProjects(TARGET_ADDIN)
Set targetModule = targetProject.VBComponents(TARGET_FORM)
Dim targetForm As Object
Set targetForm = targetModule.Designer
Dim numberOfControls As Long
numberOfControls = targetForm.Controls.count
Dim newControlNames() As String
ReDim newControlNames(1 To numberOfControl
Examples:
- You rename
btn_button1tobtn_populateList. Now when you click the button it still wants to callbtn_button1_Click()rather than
btn_populateList_Click().- You rename
inputbox1tolistToPopulatebut when you run pre-existing code, it still refers toinputbox1instead of the new
name of the control.
Anyhow, I've always wanted to mess around to get VBA to do stuff in the VBE, so this was my chance!
The macro finds the target project and target form and then updates the control names and appends the string
NEW to oldName such that it's oldNameNEW. This was done so I could easily revert the names back using the same procedure with left(len()-3). Ideally, the array of new names would be populated by the user with descriptive names and would be populated outside of the
For loop, obviously.I'm sure there's some clever refactoring that can be done, but I wasn't sure if I'd break it.
Also, I'm worried I misunderstand how Forms call their macros given it's not designed the same way as controls on sheets.
```
Public Sub FindReplaceInEntireModule()
Const TARGET_ADDIN As String = "bUTLAddIn"
Const TARGET_FORM As String = "form_chtGrid"
Dim targetProject As VBIDE.VBProject
Dim targetModule As VBIDE.VBComponent
Dim targetCode As VBIDE.CodeModule
Dim controlIndex As Long
Set targetProject = Application.VBE.VBProjects(TARGET_ADDIN)
Set targetModule = targetProject.VBComponents(TARGET_FORM)
Dim targetForm As Object
Set targetForm = targetModule.Designer
Dim numberOfControls As Long
numberOfControls = targetForm.Controls.count
Dim newControlNames() As String
ReDim newControlNames(1 To numberOfControl
Solution
You've basically implemented (parts of) a rename refactoring.
The problem is that the VBIDE API only gives you strings to work with, so you essentially have a "smart find & replace", but it's still a find & replace.
A LOT of things can go wrong when processing code without a symbol table.
Imagine a control named
Your code will rename
UserForm controls are
And that code will break when you rename
Or, you could have conflicting but unambiguous names in different scopes, that a simple find & replace will break:
Naming is hard. Renaming is even harder.
Having worked on Rubberduck for over two years, I can assure you that there is absolutely no way to implement any kind of rename refactoring without a symbol table - and that means lexing, parsing and resolving the VBA code you want to refactor.
Ctrl+H isn't a refactoring tool. Rubberduck is. I don't mean to sound like an ad, but your code needs to know exactly which code across the entire project is referencing the control you're renaming - and that simply isn't possible to implement reliably in VBA. Heck, Rubberduck has a proper parser and resolver, and still struggles with a number of edge cases.
I give you this code to play with - it's EVIL beyond words, I know... but it's 100% legal VBA code, and Rubberduck sorts it all out:
Your strategy (I know you're only looking at controls, but the principle is the same) will fail to correctly rename
Your code looks great though - good naming, clean code, nice spacing, indentation, casing, a bit large for a single procedure though, but as I said I don't think any of it is relevant, since there's no way any identifier-renaming VBA macro can work [in all cases] simply by looking at context-less strings.
The problem is that the VBIDE API only gives you strings to work with, so you essentially have a "smart find & replace", but it's still a find & replace.
A LOT of things can go wrong when processing code without a symbol table.
Imagine a control named
MyButton that you want to rename to SomeButton. Then you have this code in the module:Dim MyButtonForeColor As Long
MyButtonForeColor = vbBlackYour code will rename
MyButtonForeColor too, because it contains MyButton - but did you mean to? In this case, probably.UserForm controls are
Public, so you can have code outside the form that does this:With New UserForm1
.Show vbModal
ActiveSheet.Cells(1, 1) = .SomeTextBox.Text
End WithAnd that code will break when you rename
SomeTextBox to anything else.Or, you could have conflicting but unambiguous names in different scopes, that a simple find & replace will break:
UserForm1.SomeButton.Caption = UserForm2.SomeButton.CaptionNaming is hard. Renaming is even harder.
Having worked on Rubberduck for over two years, I can assure you that there is absolutely no way to implement any kind of rename refactoring without a symbol table - and that means lexing, parsing and resolving the VBA code you want to refactor.
Ctrl+H isn't a refactoring tool. Rubberduck is. I don't mean to sound like an ad, but your code needs to know exactly which code across the entire project is referencing the control you're renaming - and that simply isn't possible to implement reliably in VBA. Heck, Rubberduck has a proper parser and resolver, and still struggles with a number of edge cases.
I give you this code to play with - it's EVIL beyond words, I know... but it's 100% legal VBA code, and Rubberduck sorts it all out:
'Project Name = MyProject
'Module Name = MyModule
Option Explicit
Private MyVar As MyProject
Private MyVar1 As MyModule
Private Type MyProject
MyModule As String
MySub As String
End Type
Private Type MyModule
MyVar As String
String As MyProject
End Type
Private Type MySub
MyVar As MyModule
MyVar1 As MyProject
End Type
Private Type MyVar
MyProject As MyModule
End Type
Sub MySub()
Dim MyProject As MyProject
Dim MySub As MySub
Dim MyVar As MyVar
MyVar.MyProject.MyVar = "Smith"
MySub.MyVar1.MySub = MyProject.MySub
'My brain hurts....
End SubYour strategy (I know you're only looking at controls, but the principle is the same) will fail to correctly rename
MySub here (whichever you pick).Your code looks great though - good naming, clean code, nice spacing, indentation, casing, a bit large for a single procedure though, but as I said I don't think any of it is relevant, since there's no way any identifier-renaming VBA macro can work [in all cases] simply by looking at context-less strings.
Code Snippets
Dim MyButtonForeColor As Long
MyButtonForeColor = vbBlackWith New UserForm1
.Show vbModal
ActiveSheet.Cells(1, 1) = .SomeTextBox.Text
End WithUserForm1.SomeButton.Caption = UserForm2.SomeButton.Caption'Project Name = MyProject
'Module Name = MyModule
Option Explicit
Private MyVar As MyProject
Private MyVar1 As MyModule
Private Type MyProject
MyModule As String
MySub As String
End Type
Private Type MyModule
MyVar As String
String As MyProject
End Type
Private Type MySub
MyVar As MyModule
MyVar1 As MyProject
End Type
Private Type MyVar
MyProject As MyModule
End Type
Sub MySub()
Dim MyProject As MyProject
Dim MySub As MySub
Dim MyVar As MyVar
MyVar.MyProject.MyVar = "Smith"
MySub.MyVar1.MySub = MyProject.MySub
'My brain hurts....
End SubContext
StackExchange Code Review Q#145953, answer score: 8
Revisions (0)
No revisions yet.