Share My Creation MashSkeletor: Class / CustomView/SQLite Code Helper

Ola

THIS PROJECT IS NO LONGER BEING MAINTAINED

UPDATE: You might want to also check the BANano Custom View Creator

MashSkeletor Download

I have wanted to do this for like forever. MashSkeletor is a class / custom view/sqlite helper tool that enables one to create class and custom view skeletons including db structures. Yes, it generates useful code, that's what I love about it. It's like a plugin kinda thing. Eventually this coding will be repetitive in nature. The generated code can then be copied to your class / custom view b4j classes.

This has been inspired by and thanks to @klaus B4X booklet on CustomViews.

This uses the MashPropertyBag

Part 1:

Part 1 is just the basic of how this purports to work. For this part, the Events, Designer Properties and other properties are generated and this can be put on your code. I think I forgot something. Will add it later.


Events Completion


Part 2: This will delve more on classes and other things like types and the rest will come along on a need to know basis and what I have learned here. Lol.


NB: This is supposed to generate excel spreadsheets for reporting, thus the un-avoidable jPOI (15MB) takes everything up.

PS: Should you have any ideas of what can be added here, any feedback is welcome.
 
Last edited:

Mashiane

Expert
Licensed User
Longtime User
Now, lets look at the source code. What is behind the scenes?

1. One needs to create a new / open an existing class project. On new, this creates a database with the respective needed structure. I have opted on a dynamic structure as the development of this will evolve as we go along. As an example,

B4X:
DBUtils.addFieldOfInteger("Active",True,False)

creates an integer field that should be indexed and is not unique.

B4X:
'ensure the database structure is ok
Sub PrepareDatabase
    'create the events database
    DBUtils.InitializeNewTable("Events","id","id")
    DBUtils.AddFieldOfText("EventName",True,True)
    DBUtils.addFieldOfInteger("Active",True,False)
    DBUtils.addFieldOfInteger("Parameters",True,False)
    DBUtils.AddFieldOfText("Description",True,False)
    DBUtils.AddFieldOfText("ParameterDef",False,False)   
    DBUtils.CreateTableFromDefinition(prjDB)
    'create the designer properties
    DBUtils.InitializeNewTable("DesignerProperties","id","id")
    DBUtils.AddFieldOfText("KeyName",True,True)
    DBUtils.AddFieldOfText("DisplayName",True,False)
    DBUtils.AddFieldOfText("FieldType",True,False)
    DBUtils.AddFieldOfText("DefaultValue",True,False)
    DBUtils.AddFieldOfText("Description",True,False)
    DBUtils.AddFieldOfText("MinRange",True,False)
    DBUtils.AddFieldOfText("MaxRange",True,False)
    DBUtils.AddFieldOfText("List",True,False)
    DBUtils.addFieldOfInteger("Active",True,False)
    DBUtils.addFieldOfInteger("SetRanges",True,False)
    DBUtils.CreateTableFromDefinition(prjDB)
    'create the properties
    DBUtils.InitializeNewTable("Properties","id","id")
    DBUtils.AddFieldOfText("KeyName",True,True)
    DBUtils.AddFieldOfText("DisplayName",True,False)
    DBUtils.AddFieldOfText("FieldType",True,False)
    DBUtils.AddFieldOfText("DefaultValue",True,False)
    DBUtils.AddFieldOfText("Description",True,False)
    DBUtils.addFieldOfInteger("Active",True,False)
    DBUtils.addFieldOfInteger("IsConstant",True,False)
    DBUtils.AddFieldOfText("Scope",True,False)
    DBUtils.AddFieldOfText("HasGet",True,False)
    DBUtils.AddFieldOfText("HasSet",True,False)
    DBUtils.CreateTableFromDefinition(prjDB)
End Sub

2. Any existing Events, Designer Properties and Properties are loaded as soon as the end user selects an entry from the tree view. The code below opens up a database, reads a table and load the records in an editable tableview. That's the awesome MashPropertyBag thing. That comes with the source code here in the forum.

B4X:
Sub treeV_SelectedItemChanged (SelectedItem As TreeItem)
    If SelectedItem.IsInitialized = False Then Return
    nodeText = SelectedItem.Text
    Select Case nodeText
    Case "Events"
        LoadEvents
    Case "Designer Properties"
        LoadDesignerProperties
    Case "Properties"
        LoadProperties
    End Select
End Sub

2.1 Events

B4X:
'load the events in this project
Sub LoadEvents()
    SetTitle("Events")
    Singular = "event"
    pnlEmpty.RemoveAllNodes
    pnlEmpty.LoadLayout("vTable")
    splitter.SetSizeLimits(2,600,600)
    tbl.SetParentForm(MainForm)
    tbl.SetReportViewer
    tbl.AddLabelProperty("ID","-1","Id","id")
    tbl.AddTextBoxProperty("Event Name","","Event Name","eventname")
    tbl.AddTextAreaProperty("Description","","Description","Description")
    tbl.AddLabelProperty("Parameters","0","Parameters","Parameters")
    tbl.AddCheckBoxProperty("Active","1","Active","Active")
    Dim structure As Map = CreateMap("id":40,"EventName":200,"Description":200)
    tbl.setreportwidths(structure)
    tbl.SetDataSource(File.DirApp,ProjectDB,"Events","id","EventName",True,True)
    tbl.SQLQuery = $"select * from events order by lower(EventName)"$
    tbl.ShowDelete = True
    tbl.ShowClone = True
    tbl.ShowUpdate = True
    tbl.ShowAddChild = True
    tbl.RefreshColumns
    tbl.LoadSQLData(Null)
    SetButtons(True)
    LastEvent = Null
    LastRow = -1
End Sub

2.2 Design Properties

B4X:
load the designer properties
Sub LoadDesignerProperties()
    LastEvent = Null
    LastRow = -1
    SetTitle("Designer Properties")
    Singular = "designer property"
    pnlEmpty.RemoveAllNodes
    pnlEmpty.LoadLayout("vTable")
    splitter.SetSizeLimits(2,1,1)
    tbl.SetParentForm(MainForm)
    tbl.SetReportViewer
    tbl.AddLabelProperty("ID","-1","Id","id")
    tbl.AddTextBoxProperty("Key Name","","Key","KeyName")
    tbl.AddTextBoxProperty("Display Name","","DisplayName","DisplayName")
    Dim propTypes As List = jMash.CreateList(",","String,Int,Boolean,Color")
    tbl.AddComboBoxProperty("Field Type","String","FieldType","FieldType",propTypes,True)
    tbl.AddTextBoxProperty("Default Value","","DefaultValue","DefaultValue")
    tbl.addtextAreaproperty("Description","","Description","Description")
    tbl.AddTextBoxProperty("Min Range","","MinRange","MinRange")
    tbl.AddTextBoxProperty("Max Range","","MaxRange","MaxRange")
    tbl.addtextAreaproperty("List","","List","List")
    tbl.AddCheckBoxProperty("Active","1","Active","Active")
    Dim structure As Map = CreateMap("id":40,"Active":60,"SetRanges":60,"DisplayName":200,"FieldType":100,"MinRange":100,"MaxRange":100,"Description":200,"List":200)
    tbl.setreportwidths(structure)
    tbl.SetDataSource(File.DirApp,ProjectDB,"DesignerProperties","id","KeyName",True,True)
    tbl.SQLQuery = $"select * from DesignerProperties order by lower(KeyName)"$
    tbl.ShowDelete = True
    tbl.ShowClone = True
    tbl.ShowUpdate = True
    tbl.RefreshColumns
    tbl.LoadSQLData(Null)
    SetButtons(True)
End Sub

2.3 Properties

B4X:
'load the properties
Sub LoadProperties()
    LastEvent = Null
    LastRow = -1
    SetTitle("Properties")
    Singular = "property"
    pnlEmpty.RemoveAllNodes
    pnlEmpty.LoadLayout("vTable")
    splitter.SetSizeLimits(2,1,1)
    tbl.SetParentForm(MainForm)
    tbl.SetReportViewer
    tbl.AddLabelProperty("ID","-1","Id","id")
    tbl.AddTextBoxProperty("Property","","Key","KeyName")
    tbl.AddTextBoxProperty("Display Name","","DisplayName","DisplayName")
    Dim scope As List = jMash.CreateList(",","Private,Public")
    tbl.AddComboBoxProperty("Scope","Private","Scope","Scope",scope,True)
    Dim propTypes As List = jMash.CreateList(",","String,Byte,Int,Long,Map,List,Object,Canvas,Boolean,Short,Float,Double,Char")
    tbl.AddComboBoxProperty("Field Type","String","FieldType","FieldType",propTypes,True)
    tbl.AddTextBoxProperty("Default Value","","DefaultValue","DefaultValue")
    tbl.addtextAreaproperty("Description","","Description","Description")
    tbl.AddCheckBoxProperty("Constant","0","IsConstant","IsConstant")
    tbl.AddCheckBoxProperty("Get","1","HasGet","HasGet")
    tbl.AddCheckBoxProperty("Set","1","HasSet","HasSet")
    tbl.AddCheckBoxProperty("Active","1","Active","Active")
    Dim structure As Map = CreateMap("id":40,"Active":60,"HasGet":60,"HasSet":60,"IsConstant":80,"DisplayName":200)
    tbl.setreportwidths(structure)
    tbl.SetDataSource(File.DirApp,ProjectDB,"Properties","id","KeyName",True,True)
    tbl.SQLQuery = $"select * from Properties order by lower(KeyName)"$
    tbl.ShowDelete = True
    tbl.ShowClone = True
    tbl.ShowUpdate = True
    tbl.RefreshColumns
    tbl.LoadSQLData(Null)
    SetButtons(True)
End Sub

3. A closer look at Events, these can have parameters or not. So the uncoming version will have parameter settings so one can define the parameters for the events and CallSub statements generated for those to raise them.

4. And then finally, the source code generation. This is nothing fancy actually. The CustomView booklet details how this can be done (manually). After all, we code manually. Anyway, this collects all the information entered in the events, design properties and properties to generate the needed skeleton (thus Skeletor in the name)

B4X:
'build the source code
Sub btnBuild_Click
    SetTitle("Skeleton Source Code")
    pnlEmpty.RemoveAllNodes
    pnlEmpty.LoadLayout("vCode")
    splitter.SetSizeLimits(2,1,1)
    SetButtons(False)
    Dim sb As StringBuilder
    sb.Initialize
    sb.Append(BuildEvents)
    sb.Append(BuildDesignerProperties)
    sb.Append(BuildProperties)
    'set the code
    jMash.CodeAreaSetText(txtCode,sb.ToString,"1",True)
End Sub

'build code for events, only process active ones
Sub BuildEvents() As String
    Dim sb As StringBuilder
    sb.Initialize
    jMash.AddComment(sb,"This code has been generated with MashSkeletor")
    jMash.AddComment(sb,"The Class / CustomView code generated here can be used as a start to create your classes or custom views")
    jMash.AddComment(sb,"The idea behind this is encouraging the planning of one's code FIRST")
    jMash.AddComment(sb,"*****")
    jMash.AddComment(sb,"Conceptualized, created and developed by Mash on [email protected]")
    jMash.AddComment(sb,"Powered by B4X from Anywhere Software, www.b4x.com")
    jMash.AddComment(sb,jMash.LongDateTimeToday)
    jMash.AddComment(sb,"*****")
    jMash.AddComment(sb,"***** START OF EVENTS *****")
    Dim sb1 As StringBuilder
    sb1.Initialize
    Dim rEvents As List = DBUtils.ExecuteMaps(prjDB,"select * from events where Active = 1 order by lower(EventName)",Null)
    For Each emap As Map In rEvents
        Dim sEventName As String = jMash.GetDefault(emap,"EventName","")
        Dim sdescription As String = jMash.GetDefault(emap,"description","")
        Dim sparameterdef As String = jMash.GetDefault(emap,"parameterdef","")
        'add code to build the parameters
        If sdescription <> "" Then
            jMash.AddComment(sb,sdescription)
        End If
        sb.Append($"#Event: ${sEventName}()"$).Append(CRLF)
        sb1.Append($"#RaisesSynchronousEvents: ${sEventName}"$).Append(CRLF)
    Next
    jMash.AddComment(sb,"*****")
    sb.Append(sb1.ToString)
    jMash.AddComment(sb,"***** END OF EVENTS *****")
    sb.append(CRLF)
    Return sb.tostring
End Sub

'build the code for the designer properties
Sub BuildDesignerProperties() As String
    Dim sb As StringBuilder
    sb.Initialize
    jMash.AddComment(sb,"***** START OF DESIGNER PROPERTIES *****")
    Dim rDP As List = DBUtils.ExecuteMaps(prjDB,"select * from DesignerProperties where active = 1 order by lower(KeyName)",Null)
    For Each dpmap As Map In rDP
        Dim sKeyName As String = jMash.GetDefault(dpmap,"KeyName","")
        Dim sDisplayName As String = jMash.GetDefault(dpmap,"DisplayName","")
        Dim sFieldType As String = jMash.GetDefault(dpmap,"FieldType","")
        Dim sDefaultValue As String = jMash.GetDefault(dpmap,"DefaultValue","")
        Dim sDescription As String = jMash.GetDefault(dpmap,"Description","")
        Dim sMinRange As String = jMash.getdefault(dpmap,"MinRange","")
        Dim sMaxRange As String = jMash.GetDefault(dpmap,"MaxRange","")
        Dim sList As String = jMash.GetDefault(dpmap,"List","")
        'remove any blank spaces on the name
        sKeyName = sKeyName.Replace(" ","").trim
        'do we have min and max range
        Dim def As String = $"#DesignerProperty: Key: ${sKeyName}, DisplayName: ${sDisplayName}, FieldType: ${sFieldType}, DefaultValue: ${sDefaultValue}"$
        If sMinRange <> "" And sMaxRange <> "" Then
            Dim minmax As String = $", MinRange: ${sMinRange}, MaxRange: ${sMaxRange}"$
            def = def & minmax
        End If
        If sList <> "" Then
            def = def & $", List: ${sList}"$
        End If
        def = def & $", Description: ${sDescription}"$
        'build the def up
        sb.Append(def).Append(CRLF)
    Next
    jMash.AddComment(sb,"***** END OF DESIGNER PROPERTIES *****")
    sb.append(CRLF)
    Return sb.tostring
End Sub

'build the properties for the class
Sub BuildProperties() As String
    Dim sbCG As StringBuilder
    sbCG.Initialize
    Dim sbPrp As StringBuilder
    sbPrp.Initialize
    Dim sb As StringBuilder
    sb.initialize
    Dim pp As List = DBUtils.ExecuteMaps(prjDB,"select * from Properties where active = 1 order by lower(KeyName)",Null)
    For Each pmap As Map In pp
        Dim sKeyName As String = jMash.GetDefault(pmap,"KeyName","")
        Dim sFieldType As String = jMash.GetDefault(pmap,"FieldType","")
        Dim sDefaultValue As String = jMash.getdefault(pmap,"DefaultValue","")
        Dim sDescription As String = jMash.GetDefault(pmap,"Description","")
        Dim sIsConstant As String = jMash.GetDefault(pmap,"IsConstant","0")
        Dim sScope As String = jMash.GetDefault(pmap,"Scope","")
        Dim sHasGet As String = jMash.GetDefault(pmap,"HasGet","0")
        Dim sHasSet As String = jMash.GetDefault(pmap,"HasSet","0")
        sKeyName = sKeyName.Replace(" ","").trim
        If sIsConstant = "1" Then sIsConstant = "Const"
        'if this has no get or set then sit in class globals
        If sHasGet = "0" And sHasSet = "0" Then
            sbCG.Append($"${sScope} ${sIsConstant} ${sKeyName} As ${sFieldType}"$)
            If sDefaultValue <> "" Then
                sbCG.Append(" = ")
                If sFieldType = "String" Then sbCG.Append(QUOTE)
                sbCG.Append(sDefaultValue)
                If sFieldType = "String" Then sbCG.Append(QUOTE)
            End If
            If sDescription <> "" Then
                sbCG.Append($" '${sDescription}"$).Append(CRLF)
            End If
        Else
            sbCG.Append($"Private m${sKeyName} As ${sFieldType}"$)
            If sDefaultValue <> "" Then
                sbCG.Append(" = ")
                If sFieldType = "String" Then sbCG.Append(QUOTE)
                sbCG.Append(sDefaultValue)
                If sFieldType = "String" Then sbCG.Append(QUOTE)
            End If
            If sDescription <> "" Then
                sbCG.Append($" '${sDescription}"$).Append(CRLF)
            End If
            'this has get or set
            If sHasGet = "1" Then
                If sDescription <> "" Then
                    sbPrp.Append($"'Gets ${sDescription}"$).Append(CRLF)
                End If
                sbPrp.Append($"${sScope} Sub get${sKeyName} As ${sFieldType}
                Return m${sKeyName}
                End Sub"$).Append(CRLF)   
            End If
            'this has get or set
            If sHasSet = "1" Then
                If sDescription <> "" Then
                    sbPrp.Append($"'Sets ${sDescription}"$).Append(CRLF)
                End If
                sbPrp.Append($"${sScope} Sub set${sKeyName}(${sKeyName} As ${sFieldType})
                m${sKeyName} = ${sKeyName}
                End Sub"$).Append(CRLF)
            End If
        End If
    Next
    jMash.AddComment(sb,"***** START OF PROPERTIES *****")
    sb.Append(sbCG.ToString).Append(CRLF)
    sb.Append(sbPrp.ToString).Append(CRLF)
    jMash.AddComment(sb,"***** END OF PROPERTIES *****")
    sb.Append(CRLF)
    Return sb.tostring
End Sub
 

Mashiane

Expert
Licensed User
Longtime User
Continuing on behind the scenes...

The BuildEventCalls method, based on captured events builds up the code for the CallSub events for the class. As there are two parameters that should be specified for events, this just does that simply.

B4X:
'build code for events calls, only process active ones
Sub BuildEventsCalls() As String
    Dim sb As StringBuilder
    sb.Initialize
    jMash.AddComment(sb,"***** START EVENT CALLS *****")
    Dim rEvents As List = DBUtils.ExecuteMaps(prjDB,"select * from events where Active = 1 order by lower(EventName)",Null)
    For Each emap As Map In rEvents
        Dim sEventName As String = jMash.GetDefault(emap,"EventName","")
        Dim sdescription As String = jMash.GetDefault(emap,"description","")
        Dim sParameters As String = jMash.GetDefault(emap,"Parameters","0")
        Dim sparameter As String = jMash.GetDefault(emap,"parameter","")
        'add code to build the parameters
        If sdescription <> "" Then
            jMash.AddComment(sb,sdescription)
        End If
        sb.Append($"Public Sub ${sEventName}(${sparameter})"$).Append(CRLF)
        sb.Append($"If SubExists(CallBack, EventName & "_${sEventName}") Then"$).Append(CRLF)
        Select Case sParameters
        Case 0
            sb.Append($"CallSub(CallBack, EventName & "_${sEventName}")"$).Append(CRLF)
        Case 1
            sb.Append($"CallSub2(CallBack, EventName & "_${sEventName}", ${sparameter})"$).Append(CRLF)
        Case 2
            sb.Append($"CallSub3(CallBack, EventName & "_${sEventName}", ${sparameter})"$).Append(CRLF)
        End Select
        sb.Append("End If").Append(CRLF)
        sb.Append("End Sub").Append(CRLF)
    Next
    jMash.AddComment(sb,"***** END OF EVENT CALLS *****")
    sb.append(CRLF)
    Return sb.tostring
End Sub

Also the rest of the custom view code is added, because we need the properties and designer properties to be defined in the right locations and also read in the right places, so I added...

B4X:
Sub BuildOtherCode() As String
    Dim sb As StringBuilder
    sb.Initialize
    jMash.AddComment(sb,"***** START OTHER CODE *****")
    sb.Append($"Sub Class_Globals
    Private fx As JFX
    Private mEventName As String 'ignore
    Private mCallBack As Object 'ignore
    Private mBase As Pane
    ${sbCG.tostring}
End Sub"$).append(CRLF)

sb.append($"Public Sub Initialize (Callback As Object, EventName As String)
    mEventName = EventName
    mCallBack = Callback
End Sub"$).append(CRLF)

sb.append($"Public Sub DesignerCreateView (Base As Pane, Lbl As Label, Props As Map)
    mBase = Base
    ${sbDE.tostring}
End Sub"$).append(CRLF)

sb.append($"Private Sub Base_Resize (Width As Double, Height As Double)
End Sub"$).append(CRLF)

sb.append($"Public Sub GetBase As Pane
    Return mBase
    End Sub"$).Append(CRLF)
    jMash.AddComment(sb,"***** END OTHER CODE *****")
    Return sb.tostring
End Sub

I also decided to add a nice status bar at the bottom of my page to indicate the total number of events, designer properties and properties.

B4X:
Sub CreateStatusBar
    'add the status bar at the bottom
    
    lblEvents.Initialize("lblEvents")
    lblEvents.Text = "Events:"
    lblDesignerProperties.Initialize("lblDesignerProperties")
    lblDesignerProperties.Text = "Designer Properties:"
    lblProperties.Initialize("lblProperties")
    lblProperties.Text = "Properties:"
    
    Dim sep1 As Separator
    sep1.Initialize("")
    Dim sep2 As Separator
    sep2.Initialize("")
    
    Dim blnk1 As Label
    blnk1.Initialize("")
    blnk1.Text = " "
    
    Dim blnk2 As Label
    blnk2.Initialize("")
    blnk2.Text = " "
    
    Dim blnk3 As Label
    blnk3.Initialize("")
    blnk3.Text = " "
    
    Dim blnk4 As Label
    blnk4.Initialize("")
    blnk4.Text = " "
    
    Dim lblNote As Label
    lblNote.Initialize("")
    lblNote.Text = "Conceptualized, Designed and Developed by Anele 'Mashy' Mbanga - [email protected]"
    
    StatusBar1.LeftItems.AddAll(Array(lblEvents,blnk1,sep1,blnk2,lblDesignerProperties,blnk3,sep2,blnk4,lblProperties))
    StatusBar1.RightItems.AddAll(Array(lblNote))
End Sub

And this is updated with...

B4X:
Sub SetCounters(e As Int, dp As Int, p As Int)
    lblEvents.Text = "Events: " & e
    lblDesignerProperties.Text = "Designer Properties: " & dp
    lblProperties.Text = "Properties: " & p
End Sub

As those label definitions are defined in Class_Globals
 

Daren463

Member
Licensed User
Longtime User
I tried to use you MashSkeletor. It looks like a great time saver. I am having a couple of issues though,
1) when you move from one page to the next the data is not displayed when you return, but the data is in the database.
2) when you build I get a generic build, none of my information.

Im not sure if I'm not doing something right or what.
 
Top