rubberduck-vba / Rubberduck

Every programmer needs a rubberduck. COM add-in for the VBA & VB6 IDE (VBE).
https://rubberduckvba.com
GNU General Public License v3.0
1.92k stars 301 forks source link

Auto generate Model Code #4985

Closed SmileyFtW closed 4 years ago

SmileyFtW commented 5 years ago

I find myself starting a model (or proxy) like this a lot:

Private Type TModel
   Property1 As String
   Property2 As Long
   MyCollection As Collection
   MyObject As MyObjectModel
End Type
Private this as TModel

Then writing all the Get/Let procedures. It sure would be nice to write the Type and this declarations and then hit a button to have the Get/Let procedures be automatically generated.

Vogel612 commented 5 years ago

I wonder whether this could be co-opted in the "Encapsulate Field" refactoring we already have...

retailcoder commented 5 years ago

somewhat related: #4805, #875

SmileyFtW commented 5 years ago

I don't really know what that means (co-opted in the "Encapsulate Field") ... but then I have only begun my RD journey. If you can point me in that direction I'll happily explore it.

retailcoder commented 5 years ago

@Vogel612 I think it should be a separate "Unwrap Private Type" refactoring, or something like it.

@SmileyFtW the operation is somewhat similar to that of the existing "Encapsulate Field" refactoring; Vogel is suggesting to enhance the existing refactoring with the ability to "unwrap" (I totally just came up with that) a private type into a bunch of properties.

SmileyFtW commented 5 years ago

I am having a blast using RD and applying the principles I've found on the WordPress site. Thanks for everything y'all do.

SmileyFtW commented 5 years ago

Since I was "wishing", I thought I would add another item to this request. When an item in the Type declaration is an Object also include the Class Init and Terminate procedures with an instantiation of the object(s) and setting them to Nothing.

retailcoder commented 5 years ago

@SmileyFtW caveat: can only work for classes that are creatable.

Set this.Thing = New Object 'invalid
SmileyFtW commented 5 years ago

That would be fine, i would think, since doing something like that as part of the Type declaration would need special attention by the coder anyhow. But all valid declarations would still be a nice convenience.

retailcoder commented 5 years ago

I agree, however I'd rather avoid generating code we know is invalid in the first place :)

aostromecki commented 5 years ago

@SmileyFtW : I with you on this one.

@Vogel612 & @retailcoder : (as a reference) in mzTool there is a properties helper. We declare property name, type and private property name, pick GET/SET/LET'ers.

It works nice for a single property per step. It produces code like this.

Private _fooBar As String   '_ is default  prefix 

Public Property Get FooBar() As String
    FooBar = this.FooBar
End Property

Public Property Let FooBar(ByVal NewValue As String)
    this.FooBar = NewValue
End Property
djrivers commented 5 years ago

This will be a very very helpful addition. :)

SmileyFtW commented 5 years ago

I am in the process of writing VBA to do this for me and my limited use cases. It'll end up being an add in. It will look for all open workbooks that have at least one list object (table) and present the workbooks as candidates (optionally Browse... to open a workbook). Selecting a candidate will list all worksheets that have tables. User will select the tables to be used. Models and Proxies will be created along with Presenters and UserForms with Apply and Cancel buttons. IData and IDialog interfaces implemented in the appropriate related modules.

SmileyFtW commented 5 years ago

I have completed the initial cut at this and created a repository for it on GitHub (I hope that is the proper place for something like this: https://github.com/SmileyFtW/CreateCodeWithCode).

There are 2 files in the zip file. One is the file that does the work and the other ("TestWorkbook") is a proper file for doing the code creation. A "proper file" is one that has at least one worksheet with one table and has not been processed previously by the code creation file. I did not do any Unit Testing set up and would greatly appreciate any input on what would make sense to do unit testing on in the code creation file as well as other observations, comments, etc.

Open both files. With the code creator as the active file, a menu in the ribbon has a button that starts the process. I trust it is self explanatory what to do once launched.

The end goal is to turn this into an add-in, but for now it is just a macro-enabled file.

SmileyFtW commented 4 years ago

DId it a little differently here: https://github.com/SmileyFtW/GeneratePropertyGettersSettersFromBackingFieldsInXLVBA

BZngr commented 4 years ago

@SmileyFtW

I don't really know what that means (co-opted in the "Encapsulate Field") ... but then I have only begun my RD journey. If you can point me in that direction I'll happily explore it.

Maybe you already have investigated the EncapsulateField refactoring, but just in case you have yet to:

Based on your initial example, you can get what you are after if you let RD/EncapsulateField create the Private Type based on individual fields.

  1. Simply start with the field declarations you are interested in:
    Public Property1 As String
    Public Property2 As Long
    Public MyCollection As Collection
    Public MyObject As MyObjectModel
  2. Right-mouse-click Property1 and use the Rubberduck context menu to navigate: Rubberduck => Refactoring => Encapsulate Field

Using the EncapsulateField dialog:

  1. Click 'Select All'
  2. Check the 'Wrap Fields in Private Type' checkbox (The Preview shows you what will be generated)
  3. Click 'OK'

The generated/refactored code:

Private Type TModule1
    Property1 As String
    Property2 As Long
    MyCollection As Collection
    MyObject As MyObjectModel
End Type

Private this As TModule1

Public Property Get Property1() As String
    Property1 = this.Property1
End Property

Public Property Let Property1(ByVal value As String)
    this.Property1 = value
End Property

Public Property Get Property2() As Long
    Property2 = this.Property2
End Property

Public Property Let Property2(ByVal value As Long)
    this.Property2 = value
End Property

Public Property Get MyCollection() As Collection
    Set MyCollection = this.MyCollection
End Property

Public Property Set MyCollection(ByVal value As Collection)
    Set this.MyCollection = value
End Property

Public Property Get MyObject() As MyObjectModel
    Set MyObject = this.MyObject
End Property

Public Property Set MyObject(ByVal value As MyObjectModel)
    Set this.MyObject = value
End Property

Use RenameRefactoring to adjust any identifiers.

SmileyFtW commented 4 years ago

Wow.. Guess I need to look into it. I've jut gotten into the habit of starting with Private Type and putting in the backing fields. Does the Refactoring have the ability to start with the Private Type?

BZngr commented 4 years ago

Yes it does.

Using your example: Select just the this identifier from Private this As TModel. Do not check the 'Wrap Fields in Private Type' checkbox (or you end up wrapping this in a new UDT)

SmileyFtW commented 4 years ago

Now that sounds precisely like what I was after. sigh.... but then it was a good learning experience for me to write what I did

SmileyFtW commented 4 years ago

That is wonderful! RD keeps on surprising me with everything it does.

retailcoder commented 4 years ago

@BZngr FYI the Encapsulate Field refactoring is now a "highlight" on the website's home page carousel; every time the home page is served, 5 features are picked at random, and Encapsulate Field is representing the Refactorings feature.