IonicWind Software

IWBasic => Tutorials => Create a Custom Control => Topic started by: LarryMc on August 18, 2011, 08:55:50 PM

Title: 13. Creation Command
Post by: LarryMc on August 18, 2011, 08:55:50 PM
This section will cover the command that the user will use to invoke an instance of our control.  
We can't use the IWBasic CONTROL command because that command only works with controls that are support internally by IWBasic.

IWBasic has the CONTROLEX command which was designed to handle controls that aren't covered by the CONTROL command.

We also have the option to go with the CreateWindow API command.

I usually try to stay with IWBasic commands when I can.  I find they usually save me from extra coding and it a lot of cases have additional safeguards built in.

So, for our purposes, using CONTROLEX is the way to go.  Our typical command will look like this:

CONTROLEX win, "LM_Gage1_Class", "", l, t, d, d, 0, 0, id

From our previous review we know the following:
win       - the control's parent window variable
"LM_Gage1_Class"- the unique name for the class our control is associated with
""       - a blank title string
l       - the left edge of our control
t       - The top edge of our control
d       - The width of our control
d       - The height of our control
0       - style_flags
0       - exStyle_flags
id       - control identifier unique to its parent.

What's the deal with the two d's, you ask.  Since our control is going to be a round gage that means its enclosing box is a rectangle with its width and height equal to the diameter of the gage.  And, as for the flags both being 0. That can be due to either not having gotten to them yet or we're not going to use them.  It is the latter.  We're not going to use them.

Pretty simple... But wait.  Back in our review we talked about the need to 'register' a class before we can create a window/control that belongs to that class.
So we need another function to register our class before we create an instance of our control. Our code now looks like this:

RegisterGage1ClassLM()
CONTROLEX win,"LM_Gage1_Class","",l,t,d,d,0,0,id


We picked a unique name for our new function.  We'll cover what goes in it in a few minutes.

So with this scheme the user has to enter two lines of code to create a single control.  IWBasic only needs one line for the user to create a control. So, let's do what IWBasic does; put these two lines inside a subroutine.  Modifying the above we get:

SUB CreateGageRLM(win as WINDOW,l as INT,t as INT,d as INT,id as UINT )
RegisterGage1ClassLM()
CONTROLEX win,"LM_Gage1_Class","",l,t,d,d,0,0,id
  RETURN
ENDSUB


The subroutine name has to unique enough to not potentially conflict with any other routine name out in the world.  If you duplicate an existing name your potential users might be using you will force them to modify the include file we are going to furnish them in order to 'alias' our routine names.  So you need to strike a balance between the name being descriptive of its function, uniqueness, and length.

Notice that we have reduced the number of parameters being passed.  There is no need to pass something twice or something we aren't going to use. We only need to pass the parent window, the upper left corner coordinates, the diameter of the gage, and the control's ID.

We'll put our finished subroutine in Section 5 of the CCT_lib.iwb file.  It is the only thing that goes in Section 5.  But we are still not through with this subroutine.  

We have to add the code that will allow the user to access the function.

When a static library file is created, all the functions that are to be available to the user have to be declared as such.  It is done with the GLOBAL declaration. Therefore, we need to add the following line of code:

GLOBAL CreateGageRLM

We will put this line in Section 1 of the CCT_lib.iwb file.  We will put more lines there later.

For the user to call the function from their application they will need a standard subroutine declaration.  That line of code looks like this:

DECLARE EXTERN CreateGageRLM(win as WINDOW,l as INT,t as INT,d as INT,id as UINT )

This line is placed in Section 1 of the CCT_test.iwb file.  We will put more lines there later also.  This section will be turned into an include file when development is completed and furnished to the user with the library file.

Now we turn our attention to our registering subroutine we called above.

SUB RegisterGage1ClassLM()

ENDSUB


We'll start our discussion by saying that regardless of how many instances of our control are created in a user's application we only need to register our class one time.

From the way we constructed our CreateGageRLM function above the RegisterGage1ClassLM function will be called every time the user creates a new instance of our control.  So the first thing we need to do is fix the the internals of the register function so our class is only registered prior to the creation of the first instance of our control.  It is really simple, with the recent release of IWBasic 2.x.
We modify our function as shown:

SUB RegisterGage1ClassLM()
  STATIC INT classRegistered = 0
  IF classRegistered = 0

     classRegistered = 1
  ENDIF
ENDSUB


We've created a STATIC variable and initialized it to 0.  The name is of no importance as long as it is unique to the subroutine and is not defined elsewhere as a GLOBAL variable.  For those who haven't used a static variable at the time of reading this, it functionally works this way.

When the subroutine is called the very first time the variable is set to 0 when the STATIC line of code is encountered.  When the subroutine is exited the value of the variable is remembered.  On subsequent calls of the subroutine the STATIC line of code is ignored.  So the value of the variable is whatever it was the last time the sub was called.  With the code structure shown, the first time our routine is called the IF statement will be TRUE.  Our registration will be inside the IF/ENDIF block so it will be executed.  The last statement sets our variable to 1 (or any non-zero value).  When subsequent calls are made the IF statement will be FALSE since the STATIC variable will not be equal to 0.

Having that resolved we now turn our attention to the actual registration.  

The OS has a dedicated API function for registering a class; RegisterClassEx.  A single system UDT type variable is passed to the function. The following is the definition of the UDT structure:
TYPE WNDCLASSEX
DEF cbSize AS INT
DEF style AS INT
DEF lpfnWndProc AS INT
DEF cbClsExtra AS INT
DEF cbWndExtra AS INT
DEF hInstance AS INT
DEF hIcon AS INT
DEF hCursor AS INT
DEF hbrBackground AS INT
DEF lpszMenuName AS POINTER
DEF lpszClassName AS POINTER
DEF hIconSm AS INT
ENDTYPE


Since we are using the "windowssdk.inc" include file we don't have to worry about putting this code in our project.  If we created a variable of type WNDCLASSEX and assign the proper values to the necessary elements we can pass the variable to the API function.  If it is successful the function will return a non-zero value.  Our resulting function will look like this:

SUB RegisterGage1ClassLM()
  STATIC INT classRegistered = 0
  IF classRegistered = 0
     WNDCLASSEX wc
     wc.cbSize         = len(WNDCLASSEX)
     wc.style         = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW
     wc.lpfnWndProc   = &MyProc_CC
     wc.cbClsExtra     = 0
     wc.cbWndExtra     = 0
     wc.hInstance     = _hinstance
     wc.hIcon         = NULL
     wc.hCursor       = LoadCursor(NULL, IDC_ARROW)
     wc.hbrBackground = GetStockObject(WHITE_BRUSH)
     wc.lpszMenuName   = NULL
     wc.lpszClassName = "LM_Gage1_Class"
     wc.hIconSm       = NULL
     classRegistered = RegisterClassEx(wc)
  ENDIF
ENDSUB

The following lists each element of the WNDCLASSEX structure along with the value we have assigned that element:

cbSize - len(WNDCLASSEX)
The size, in bytes, of the WNDCLASSEX structure.

style - CS_GLOBALCLASS | CS_HREDRAW |CS_VREDRAW
The class style flags that define how to update the  control after moving or resizing it, how to process double-clicks of the mouse, how to allocate space for the device context, and other aspects of the window.

lpfnWndProc -  &MyProc_CC
Pointer to the function that processes all messages sent to controls in the class and defines the behavior of the control. This is the name of our handler routine in Section 3 of the CCT_lib.iwb file.

cbClsExtra - 0
Defines amount of extra memory to dedicate to the class. We're not using it.

cbWndExtra - 0
Defines amount of extra memory to dedicate to the control. We're not using it.

hInstance - _hinstance
Identifies the handle to the process that is registering our class. _hinstance is an IWBasic global variable that contains the handle.

hIcon - NULL
We are not associating a large icon with our control.

hCursor - LoadCursor(NULL, IDC_ARROW)
Defines the cursor that will be shown when it is over our control.

hbrBackground - GetStockObject(WHITE_BRUSH)
Used to prepare the background of our control if we do not handle it in our handler.

lpszMenuName - NULL
Our control has no menu associated with it.

lpszClassName - "LM_Gage1_Class"
A process unique name for our class.

hIconSm - NULL
We are not associating a small icon with our control.


If our call to RegisterClassEx is successful it returns a non-zero value.  Otherwise it will return 0.
Because of this we can change our original line of code:
     classRegistered = 1
to
     classRegistered = RegisterClassEx(wc)

All that remains is to place our completed registration routine in Section 4 of the CCT_lib.iwb file.  It is the only thing that goes in Section 4. Nothing else is required in support of the registration function since it is for internal use only.

_________________________

Coming Next - Control Configuration