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
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.
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
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
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
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 (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.
Hi SeaVipe,
Thank you for your info. I have added it to the document. The updated version is attached.
Robert
Excellent White Paper to start with!
Thank you, Robert.
Rolf
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,
It would be helpful if you could answer some of the things in red in the document.
Thanks,
Robert
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.
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,
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