Custom Controls Documentation

Started by Robert Rioja, February 07, 2016, 12:21:42 AM

Previous topic - Next topic

Robert Rioja

Over the last few years, some of us have asked about custom controls.  But Paul has said many times that he does not have the time to write any documentation to explain their creation.  That is understandable since we have all kept him very busy asking for more features, bug control, etc.

So I have decided to try to write some documentation myself and donate it to our community.  The attached file is my first effort, which I wrote in about 2 hours.  I would appreciate it if anyone could take the time to read it and help me improve it.  I will continue to work on it as time permits.

UPDATE: New document posted on 2/26/2016.  Section 4.2.24 and section 5 still need more work.

UPDATE: 3/1/2016 new document posted.  Section 5 still needs work.

Robert

Jean-pierre Leroy

Hi Robert,

I'm happy to see such a document; I'm willing for a while to create my own custom control; so it will definitely help me.

Thank a lot.
Jean-Pierre

PS: Now that there are some lights at PowerBASIC Inc., I hope that Paul will restart selling and updating FireFly for PowerBASIC; It such a powerful and flexible designer. Personally I've done great things with FireFLy+PowerBasic+SQLitening+RMChart+LibHaru+VPE+MyLittleGrid.

Robert Rioja

Hi Jean-Pierre,

I am hoping that some of the more experienced members will contribute to this document.  Anyone can send me additional material and I will include it.

Thanks for the feedback.

Robert

Barry Gordon

Hi,

Slightly off topic from documentation (but still relevant).  Last year I took the plunge into FreeBasic from PowerBasic and I used the creation of a very basic custom control as my experiment.   As I use custom controls a lot I decided that I would jump in at the deep end because unless I could get them to work in FreeBasic then it was pointless me going any further.   

The result was I took a very simple PowerBasic custom control that I wrote which is the absolute simplest that you could possibly have (it creates a blank square) and then created the FreeBasic equivalent.   The Freebasic version is only slightly more complicated in that it demonstrates how you pass messages to the control  (for people who might be interested in learning to write them).

Anyway - both the PowerBAsic and the FreeBasic versions of the FireFly project files are downloadable here so that they can be compared side by side:- 

http://www.planetsquires.com/protect/forum/index.php?topic=3681.0

These short controls are the basis of what I use when I am creating a new control.  I am not saying they are perfect, but they are my way of doing the task - so I hope they are of some help to someone.

Regards
Barry
PS Just for information - there was subsequent minor change to be made to the FreeBasic version as a result of me learning something new from Paul and the issue is described here:-

http://www.planetsquires.com/protect/forum/index.php?topic=3685.0


Robert Rioja

Barry,

Thank you for your insight.  I hope that after I document the creation of custom controls in the PB version of FF, we can easily make a similar document for the FB version of FF.

Anything you can send me to include in the document would be appreciated.

Robert

SeaVipe

Hi Robert,


Thanks for your hard work; very informative.


On page 13 you asked
QuoteI don't know what the numbers after the font name mean.


On the Microsoft site [size=78%]https://msdn.microsoft.com/en-us/library/windows/desktop/dd145037(v=vs.85).aspx[/size] is the LOGFONT Structure.


I hope this is what you are looking for.
Clive Richey

Robert Rioja

Hi SeaVipe,

Thank you for your info.  I have added it to the document.  The updated version is attached.

Robert

Rolf Brandt

Excellent White Paper to start with!
Thank you, Robert.

Rolf
Rolf Brandt
http://www.rbsoft.eu
http://www.taxifreeware.com
I cook with wine, sometimes I even add it to the food.
(W. C. Fields)

Paul Squires

Hi Robert, thanks so much for putting together this document. I just downloaded it. I will read through it and try to offer some insight into why I made the interface the way that I did. It has been a long time since I designed the code so I will have to look at my existing FF3 custom controls and the internal FF source code that ties it all together.
Paul Squires
PlanetSquires Software

Robert Rioja

Paul,
It would be helpful if you could answer some of the things in red in the document.
Thanks,
Robert

Paul Squires

Here are a few notes....

4.2.13 tooltip: It originally had a purpose that it would display as you mouse over the control on the form, but that has long since been removed/deprecated.

4.2.15 uniqueid. Very important. This value is used to distinguish every control from each other. It also distinguishes the same control but different versions of that control (for example, you could theoretically have multiple different versions of an image control in a project). You can easily create an id using PB's GUID functionality.

4.2.16 use_loadlibrary. Some controls/dll's must use LoadLibrary in order to initialize them. This is very specific to the way that the control's author has designed their dll. Sigrid was one control that had to have LoadLibrary used prior to it being used. If this flag is set then Firefly adds code to the application to load the library/dll.

4.2.17 use_initialize_action. Similar to use_loadlibrary, if a control requires code to be run to initialize it prior to its use. This is pretty rare and the vast majority of controls are designed in such a way that this will not be needed.

4.2.18  Needed if the DLL requires any special initialization code to be run in order to get it to display within FireFly itself. The format is the same as that used by the create_action property.

4.2.19 This property is deprecated.

4.2.20 & 4.2.21 (Message and Property counts). Was relevant in pre-version 3 versions of FireFly. FireFly v3 and greater no longer requires these properties.

4.2.22 allow_system_colors. If set to 1 then colors used in properties of the control can take the format of "SYS,2", etc... to represent that built-in Windows predefined constants can be used (eg. Button Face, Button Shadow, etc...).

4.2.23 isgroupbox. Need to set this property if the control needs to be treated like a Frame control. These controls simply get special treatment in relation to their zorder and redrawing because other controls could potentially be obsured if placed inside the control.

4.2.24 create_action. The first 3 parameters are not always: USER32.DLL|CreateWindowEx|CreateWindowExA|
Take a look at a control like "FireImage" to see that it is called like: FIREIMAGE.DLL|FF_FIREIMAGE|FF_FIREIMAGE
The second and third parameters are not necessarily repeated. The second parameter is the name of the function that is called to performa the initialization, whereas the third paramter is the "alias" name (should one be needed). FireFly currently always calls the 3rd parameter regardless.
BYVAL ASCIIZ MYCONTROL Explain? <-- this is the Class name of the control.
BYVAL ASCIIZ //CTRL_CAPTION// Explain?  <-- this is the text that displays in the control if applicable. For example, if this was a Label or TextBox type of control then this would be the default text for the control. The text "//CTRL_CAPTION//" gets replaced by whatever text was specified by the user in the PropertyList "Caption" property.
BYVAL LONG //CTRL_INST// Explain?  <-- handle to the instance of the program. FireFly inserts this during code generation.
BYVAL ANY %NULL Explain?  <-- This is the last parameter for the CreateWindow(Ex) function call is normall always passed as a null.

4.3.3 itemtype
itemtype = Edit|1
This is an editable text box. I don’t know what the second value does. <-- If a 1 is specified then it requires that the user press the ENTER key before any changes take effect. If the value is 0, then changes take effect after every keypress.


Paul Squires
PlanetSquires Software

Paul Squires

Here are some of the other values that can be used in the create_action call:
"STRING", "ASCIIZ",  "BYTE", "WORD",  "DWORD", "UDT", "INTEGER", "LONG", "ANY", "QUAD", "DOUBLE", "EXT", "VARPTR"

The parameters are pushed onto the stack using ASM and then the function is called dynamically also using ASM.  I use code from the PB forum from a long time ago called "CallDll". Here is what I use in FF:


  '----------------------------------------------------------------------------
  ' Target of this code:
  '         - create a generic function able to call API DLL procedures at RunTime
  '         - avoid static linking of DLL when possible
  '         - use CallDll function inside a Basic Interpreter to be able to
  '           load external modules/functions (DLL) at interpreted time
  '----------------------------------------------------------------------------
  '
  'http://www.powerbasic.com/support/forums/Forum4/html/005356.html
  'http://www.powerbasic.com/support/forums/Forum4/HTML/007445.html
  '
  '
  '
  '----------------------------------------------------------------------------
  'Equates for every data type defined
  '----------------------------------------------------------------------------
  %ParamType_Byte    =  1
  %ParamType_Integer =  2
  %ParamType_Word    =  3
  %ParamType_Long    =  4
  %ParamType_DWord   =  5
  %ParamType_String  =  6
  %ParamType_Double  =  7
  %ParamType_Quad    =  8
  %ParamType_Ext     =  9
  %ParamType_Asciiz  = 10
  %ParamType_UDT     = 11
  '
  '----------------------------------------------------------------------------
  'Possible methods for passing data
  '----------------------------------------------------------------------------
  %ByType_ByVal      = 1
  %ByType_ByRef      = 2
  %ByType_ByCopy     = 3
  '
  '----------------------------------------------------------------------------
  'Used to store all different data type value
  '----------------------------------------------------------------------------
  Type ParamValue                            ' --|
    lByte    As Byte                         '   |
    lInteger As Integer                      '   |
    lWord    As Word                         '   |
    lLong    As Long                         '   |--------|
    lDWord   As Dword                        '   |        |
    lString  As Long                         '   |        |
    lDouble  As Double                       '   |        |
    lQuad    As Quad                         '   |        |
    lExt     As Ext                          '   |        |
    lUDT     As Dword                        ' --|        |
  End Type                                   '            |
  '
  '-------------------------------------------------------|--------------------
  'Used to store function parameters                      |
  '-------------------------------------------------------|--------------------
  Type Parameter                             '            |
    Used         As Long                     '            |
    ByType       As Long                     '            |
    DataType     As Long                     '            |
    DataValue    As ParamValue               '  <<--------|
  End Type
 

  '----------------------------------------------------------------------------
  'Generic function for API function calling
  'Parameters:
  '  sLibMain  : name of the DLL file containing sProcName
  '  sProcName : name of the function to call
  '  sParams() : Array of parameters. Must be in the following format: BYTYPE DATATYPE THEDATA
  '              Example:  Param(0) = "BYVAL LONG 123534"
  '                     
 
  '----------------------------------------------------------------------------
  Function CallDll(ByVal hProc As Long, sParams() As String) As Long
  '----------------------------------------------------------------------------
    Dim pCount        As Long
    Dim RetVal        As Long
    Dim PushParams()  As Parameter
    Dim PushParam     As Long     
   
    Local lb          As Long   'lbound of PushParams()
    Local ub          As Long   'ubound of PushParams()
    Local TheData     As String
                                   
    Local VarPtrAsciiz As Asciiz * %MAX_PATH
   
      lb = LBound(sParams)
      ub = UBound(sParams)
     
      ReDim PushParams(lb To ub) As Parameter
      ReDim DynamicStrings(lb To ub) As String
                         
                         
      If hProc <> %Null Then


         '// PARSE THE PASSED PARAMETERS //
         For pCount = lb To ub
             
              'If there is nothing defined for this parameter then simply
              'jump to the section that calls the DLL
              If RTrim$(sParams(pCount)) = "" Then
                 GoTo DoCall
              End If
             
             
              '// GET THE BYTYPE INDENTIFICATION //
              Select Case UCase$(Trim$(Parse$(sParams(pCount), " ", 1)))   
                Case "BYVAL" : PushParams(pCount).ByType = %ByType_ByVal
                Case "BYREF" : PushParams(pCount).ByType = %ByType_ByRef
                Case "BYCOPY": PushParams(pCount).ByType = %ByType_ByCopy
                Case Else    : PushParams(pCount).ByType = %ByType_ByRef
              End Select                               
             
         
              '// GET THE DATA ITSELF //
              'The data is immediately after the second blank space
              f& = Instr(1, sParams(pCount), " ")
              If f& Then
                 f& = Instr(f&+1, sParams(pCount), " ")
              End If                   
              TheData = Mid$(sParams(pCount), f& + 1)
             
             
              '// GET THE DATA TYPE //
              Select Case UCase$(Trim$(Parse$(sParams(pCount), " ", 2)))   
                Case "STRING"
                     'if the string was passed ByRef then the address to the
                     'string descriptor was passed. Therefore, treat this
                     'information like a LONG value.
                     If PushParams(pCount).ByType = %ByType_ByRef Then
                         PushParams(pCount).DataType           = %ParamType_Long
                         PushParams(pCount).DataValue.lLong    = CLng(Val(TheData))
                     Else
                         'ByVal/ByCopy
                         DynamicStrings(pCount) = TheData
                         PushParams(pCount).DataType = %ParamType_String
                         PushParams(pCount).DataValue.lString = StrPtr(DynamicStrings(pCount))   
                     End If
                     
                Case "ASCIIZ"
                     PushParams(pCount).DataType          = %ParamType_Asciiz
                     MemString PushParams(pCount).DataValue.lString, TheData
               
                Case "BYTE"
                     PushParams(pCount).DataType           = %ParamType_Byte
                     PushParams(pCount).DataValue.lByte    = CByt(Val(TheData))
         
                Case "WORD"
                     PushParams(pCount).DataType           = %ParamType_Word
                     PushParams(pCount).DataValue.lWord    = CWrd(Val(TheData))
         
                Case "DWORD"
                     PushParams(pCount).DataType           = %ParamType_DWord
                     PushParams(pCount).DataValue.lDWord   = CDwd(Val(TheData))

                Case "UDT"
                     PushParams(pCount).DataType           = %ParamType_UDT
                     PushParams(pCount).DataValue.lUDT     = CDwd(Val(TheData))

                Case "INTEGER"
                     PushParams(pCount).DataType           = %ParamType_Integer
                     PushParams(pCount).DataValue.lInteger = CInt(Val(TheData))
               
                Case "LONG", "ANY"
                     PushParams(pCount).DataType           = %ParamType_Long
                     PushParams(pCount).DataValue.lLong    = CLng(Val(TheData))
               
                Case "QUAD"
                     PushParams(pCount).DataType           = %ParamType_Quad
                     PushParams(pCount).DataValue.lQuad    = CQud(Val(TheData))
               
                Case "DOUBLE"
                     PushParams(pCount).DataType           = %ParamType_Double
                     PushParams(pCount).DataValue.lDouble  = CDbl(Val(TheData))

                Case "EXT"
                     PushParams(pCount).DataType           = %ParamType_Ext
                     PushParams(pCount).DataValue.lExt     = CExt(Val(TheData))
                     
                Case "VARPTR"
                     VarptrAsciiz = TheData
                     PushParams(pCount).DataType           = %ParamType_Long
                     PushParams(pCount).DataValue.lExt     = VarPtr(VarptrAsciiz)
                     
              End Select
             
        Next
 
        'Push parameters into the stack in reverse order
        For pCount = ub To lb Step -1

          Select Case PushParams(pCount).DataType

              Case %ParamType_String, %ParamType_Asciiz
                PushParam = PushParams(pCount).DataValue.lString
                ! push PushParam

              Case %ParamType_Byte 
                PushParam = PushParams(pCount).DataValue.lByte
                ! push PushParam 

              Case %ParamType_Word
                PushParam = PushParams(pCount).DataValue.lWord
                ! push PushParam 

              Case %ParamType_DWord 
                PushParam = PushParams(pCount).DataValue.lDWord
                ! push PushParam 

              Case %ParamType_UDT
                PushParam = PushParams(pCount).DataValue.lUDT
                ! push PushParam 

              Case %ParamType_Integer
                PushParam = PushParams(pCount).DataValue.lInteger
                ! push PushParam 

              Case %ParamType_Long
                PushParam = PushParams(pCount).DataValue.lLong
                ! push PushParam 
               
              Case %ParamType_Quad
                Select Case PushParams(pCount).ByType
                    Case %ByType_ByRef, %ByType_ByCopy
                      PushParam = VarPtr(PushParams(pCount).DataValue.lQuad)
                      ! push PushParam
                    Case %ByType_ByVal
                      PushParam = VarPtr(PushParams(pCount).DataValue.lQuad)
                      !mov eax,PushParam  ;point EAX TO the 8 BYTE variable
                      !mov edx,[eax+4]    ;GET latest 4 bytes
                      !push edx           ;push them
                      !mov edx,[eax]      ;GET the 1st 4 bytes
                      !push edx           ;push them
                  End Select

              Case %ParamType_Ext
                Select Case PushParams(pCount).ByType
                    Case %ByType_ByRef, %ByType_ByCopy
                      PushParam = VarPtr(PushParams(pCount).DataValue.lExt)
                      ! push PushParam
                    Case %ByType_ByVal
                      PushParam = VarPtr(PushParams(pCount).DataValue.lExt)
                      !mov eax,PushParam  ;point EAX TO the 8 BYTE variable
                      !mov edx,[eax+8]    ;GET latest 4 bytes
                      !push edx           ;push them
                      !mov edx,[eax+4]    ;GET mid    4 bytes
                      !push edx           ;push them
                      !mov edx,[eax]      ;GET the 1st 4 bytes
                      !push edx           ;push them 

                  End Select

              Case %ParamType_Double
                Select Case PushParams(pCount).ByType
                    Case %ByType_ByRef, %ByType_ByCopy
                      PushParam = VarPtr(PushParams(pCount).DataValue.lDouble)
                      ! push PushParam
                    Case %ByType_ByVal
                      PushParam = VarPtr(PushParams(pCount).DataValue.lDouble)
                      !mov eax,PushParam  ;point EAX TO the 8 BYTE variable
                      !mov edx,[eax+4]    ;GET latest 4 bytes
                      !push edx           ;push them
                      !mov edx,[eax]      ;GET the 1st 4 bytes
                      !push edx           ;push them
                  End Select
          End Select

        Next


DoCall:
 
        'Call the function
        ! Xor eax, eax
        ! Call hProc
        ! mov RetVal, eax

        'If necessary, deallocate allocated memory
        For pCount = lb To ub
          Select Case PushParams(pCount).DataType
            Case %ParamType_Asciiz
              MemFreeString PushParams(pCount).DataValue.lString
          End Select
        Next
 
        Function = RetVal
      Else
         MsgBox "Error loading function " '+ sProcName
      End If
 
  End Function


Paul Squires
PlanetSquires Software

Robert Rioja

Paul,

Thank you for this small treasure.  I will start to go through it all and update my document accordingly.  It will take me some time but I will post a new version as soon as I can.  I know that many others will be able to use it and create more controls.  I do believe that more contributions from other programmers will make it easier for FF live on.

Robert