PDA

View Full Version : CALLBACKS, unofficial guide... (Trial and error mode)



ISAWHIM
28-10-2008, 05:09
CALLBACKS were created to help stop programs from hogging resources using endless loops to check for input and control states. Like a wake-up call, you tell windows to give you a ring when you need to wake-up and do something. (Respond to a mouse-move, button-click, window-resize, etc...)

A side-trait of a CALLBACK, was the creation of a dynamic associative handle/class. This turns anything that you can create with a handle, into a potential clone. This works much like a TYPE string. Each item with a handle becomes a virtual new element, handled by windows assigning a second handle to every element.

MyWindow (Handle: 12345) becomes...

MyWindow.(CallHandle: 87654)
MyWindow.(CallHandle: 19843)
MyWindow.(CallHandle: 65430)

Each window having the same set of code, and sharing the same controls. You don't have to define 1000 separate buttons for 1000 windows. Only ONE constant is needed for the single "Clone" window.

MyWindow (Handle: 12345)
- Button (%btnCONSTANT = 1)
becomes...
MyWindow.(CallHandle: 87654)
- Button.(CallHandle: 87654)

MyWindow.(CallHandle: 19843)
- Button.(CallHandle: 19843)

MyWindow.(CallHandle: 65430)
- Button.(CallHandle: 65430)

To see this in action, you can use a neat TOOL called a "Window-Analyzer" which shows the hWnd and the call-hWnd, child/parent levels, and even the ID constants. (Great for debugging if you are using constants that you "Think" are unique, but they are actually overlapping, causing "Issues".)

The "ANALYZER" link is the one you want.
http://allapi.mentalis.org/vbexamples/list.php?category=MISC

The "ApiViewer" and "API-Guide" are good resources also. (The samples are VB6, but it is the order of use, and constants which are important to notice. CALLBACKS being a new feature, will undoubtedly keep you waiting for new features to be developed. This will allow you to develop your own features to add, without having to wait.)

This comes in handy, for instance in a "Paint Program". Each new window holds an image, edit controls, menu's, more dialogs, buttons... You are unsure if a user will have 10 or 10,000 windows open. (That would make you mad, attempting to hand-code all those controls and constants and IF...THEN and WHILE...END and SELECT CASE statements.)

This is where a little "New" thinking has to come into play.

There is only ONE window/dialog which you NEED to code manually for full display. This MAIN APPLICATION window/dialog, MUST be created and SHOWN with a CALLBACK attached, before any other controls or dialogs are created. This MAIN APPLICATION will hold the critical (HANDLE) hWnd that your program needs for any CALLBACKS.

You can define child elements for the MAIN APPLICATION before you SHOW the window/dialog, but ONLY if those elements will not have a CALLBACK themselves. Since they are children-of-a-parent, and the parent-handle has not been SHOWN, with the CALLBACK... there is no way to create a CALLBACK for a child of a parent which does not exist at the moment. (It exists as a HANDLE, but not as a CALLBACK HANDLE.)

This is why they created the first element of a callback called a notification. There is an order to creating these CALLBACK clones.
1. WM_PARENTNOTIFY: (Prior to creation, the parent is alerted of the creation.)
This can be used to suppress, or make adjustments in memory/variables/code, prior to creation.
2. WM_CREATE: (When you request to CREATE a window/dialog/button/element.)
Creation notice comes post creation, and prior to display. Values can be set here, and display can be suppressed.
3. WM_INITDIALOG: (Since these "Windows" are "Dialog Windows", the next event is this one.)
This is where you create your controls/elements for display. The next notice will be "SHOW".
4. WM_SHOWWINDOW: (Everything is now visible, ready for use, and you now have a MAIN WINDOW.)

In this specific case, the PARENT of MAIN WINDOW is "WINDOWS 98/ME/NT/2K/XP/LH/VISTA".

This window now has TWO handles. The TB handle for the "Original Control/Dialog", plus the "Call-Handle" for this "Virtual Window". TB only suggests what the window will look like. Windows adopts full control of this "Virtual Window" and all elements created for display from this point on, which are created as a child to this window. (This will not include any DIRECT displayed or created controls that are not a child of this hWnd. EG, Creating a new dialog who is a child of "0", will be a full TB control window, until you assign that element to a CALLBACK.)

Following me here?

Quick review...
1. Create Dialog in TB, (Only the dialog), and send that info to a CALLBACK creation. "CALL MyCallbackProc()"
2. That window becomes cloned, and the "Notices" in the callback are used to setup the clone.
3. Each CALL HANDLE will step through an order of notices until the clone-dialog is displayed.
4. At the WM_INITDIALOG notice, just before the WM_SHOWWINDOW notice, the controls should be setup.
5. Use the program... Handle other calls as needed...
6. Now we have to exit...

Exit... Exit what? Well, there is no "Real Window" so you can't exit the normal way. You have to "Kill" the clone. Sounds like a neat game or movie, but it is just another function. At the moment, this can only be done with API code. Or with the SYSMENU-CLOSE button, if one exits on your window that you created.

The procedure to close a window is similar to the way it opens.

1. Something tells "Windows", your clone-creator, that you want to exit. "SendMessage(hWnd, %WM_SYSCOMMAND, %SC_CLOSE, 0)"
2. This CALL triggers the CALLBACK notice, "%WM_SYSCOMMAND, %SC_CLOSE". There should be code here to handle YES/NO or SAVE, etc... This event is followed by "%WM_CLOSE", which is where you may want to destroy elements or free memory before you actually exit. (NOTE: Look below to see the API call to destroy.)
3. Once that is finished, the window may trigger a %WM_SHOWWINDOW, as it disappears from the screen, but that is not a solid state notice, just a side-path that MAY happen.
4. The last notice before complete destruction is, "%WM_DESTROY". This sends a message to all children, and tells them to destroy also. It should be assumed they all still exist at this point.
5. Wait... There is one more notice... But this goes to the PARENT of this window, (Which is "Windows".) "%WM_NCDESTROY", tells the PARENT of this window, that all the children of the clone and the child-clone are all killed, and memory can be released.

Sounds like a breeze!

Here is the missing "Destroy" code. (Missing as of, 10/27/08)

Declare Function DestroyWindow _
Lib "user32.dll" _
ALIAS "DestroyWindow" _
(ByVal hWnd As Long) As Long
' -- In the "%WM_CLOSE" area where notices are handled... Add this line...
DestroyWindow(CBHNDL)

Full sample-code to follow...

ISAWHIM
28-10-2008, 06:44
Sample code. (Novel-style. LOL.)
UPDATED to #SCRIPTVERSION 1.0.0.1

This setup is for CALLBACKS as window/form controller, with all controls being similar to each child window/form.

Controls for each form are processed by each forms unique-ID, in the forms CALLBACK. All controls need only be unique within each form, to each other. They do not have to all be unique to all other controls through the project.

Specific use for a setup like this...
- Image/sound editor, which each child window holds an image/sound to be edited.
- Network or system controller, where each child window may be a remote system.
- Game editor, where each window is a unique element being edited.


' Created by by Jason DAngelo
' This is NOT a 100% complete list of all functions/calls/notices...
' This is just a wide-guide, which can be used as a generic template.
' Well, if you remove all the miles of comments, it might be a nice guide.

' NOTE: This is NOT an MDI-Child/Parent sample. These are parent/child by assignment.

#MINVERSION 1.7.0.0
#SCRIPTVERSION 1.0.0.1

USES "UI"
Declare Function DestroyWindow Lib "user32.dll" ALIAS "DestroyWindow"(ByVal hWnd As Long) As Long
' -- ID numbers of controls
BEGIN CONST
%menuFILE = 1000
%menuCHILD_OP
%menuEXIT_OP
%btnCOMMAND ' NOTE: There are three buttons, +0, +1, +2. Keep that in mind if you add more stuff.
' -- The ID's for more functions must be %MyNewConstant = %btnCOMMAND + 3 + 1
' -- The +3 is 0-2 and the +1 is the incrament designated for the new constant.
END CONST

GLOBAL hWnd, hMenu, hMenuFile, hWndChild AS LONG

' -- Create dialog here
FUNCTION TBMAIN()
DIALOG NEW PIXELS, 0, "Parent Window",-1,-1, 300, 180, _
%WS_CLIPCHILDREN OR %WS_CLIPSIBLINGS OR %WS_OVERLAPPEDWINDOW, _
%WS_EX_APPWINDOW TO hWnd
' -- Place NON-CALLBACK controls here
' -- Simple Menu
MENU NEW BAR TO hMenu
' -- FILE Menu
MENU NEW POPUP TO hMenuFile
MENU ADD POPUP, hMenu, "&File", hMenuFile, %MF_ENABLED
MENU ADD STRING, hMenuFile, "&New CHILD Window", %menuCHILD_OP, %MF_ENABLED
MENU ADD STRING, hMenuFile, "&Exit", %menuEXIT_OP, %MF_ENABLED
MENU ATTACH hMenu, hWnd
DIALOG SHOW MODAL hWnd, CALL mainProc()
END FUNCTION

' -- Callback Function for hWnd (MAIN) dialog/window
CALLBACK FUNCTION mainProc() AS LONG
' -- CBMSG is the primary "Notice" Message
' -- Followed by, CBCTL, which is a Control ID value (%MyConstant)
' -- Followed by, CBCTLMSG, which is the "Notice" Message of a control.
' -- NOTE: You can't have a "Message", without a control. Do not check for "Notice" first.
' -- SAMPLE: CBMSG=(%WM_COMMAND) and -> CBCTL=(%btnCOMMAND) and -> CBCTLMSG=(%BN_CLICKED)
SELECT CASE CBMSG
CASE %WM_CREATE
' -- Once the "CallBack Handle" has been assigned, and the window "Cloned"
' -- This is the first event notice that triggers.
' -- (After the %WM_PARENTNOTIFY to the parent of this child.)
CASE %WM_INITDIALOG
' -- Put code to be executed after dialog creation here.
' -- Here is where you should place any controls that have a CALLBACK
' -- Understand that there will be TWO callbacks, this one for the
' -- parent of the control, and the callback for the actual control.
CASE %WM_SHOWWINDOW
' -- This area is fired once the window is visible.
CASE %WM_COMMAND
' -- You can handle ALL hWnd (MAIN) controls here.
' -- This message is sent when the user selects a command item from
' -- a menu, when a control sends a notification message to its
' -- parent window, or when an accelerator key-stroke is translated.
SELECT CASE CBCTL
CASE %menuCHILD_OP
CreateChildWindow()
RETURN(1)
CASE %menuEXIT_OP
' -- Do not actually terminate from here.
SendMessage(hWnd, %WM_SYSCOMMAND, %SC_CLOSE, 0)
RETURN(1)
END SELECT
CASE %WM_SYSCOMMAND
SELECT CASE CBCTL
CASE %SC_MINIMIZE
CASE %SC_MAXIMIZE
CASE %SC_RESTORE
CASE %SC_MOVE
CASE %SC_SIZE
CASE %SC_CLOSE
IF MSGBOX(0, "Are you sure you want to exit the program?", %MB_YESNO OR %MB_ICONQUESTION, "CONFIRM EXIT") = %IDYES THEN
' -- This tells the program to call close, if you select YES.
' -- You can close immediately, without prompt, by sending a message
' -- directly to %WM_CLOSE, as opposed to capturing the SYSCOMMAND close.
SendMessage(hWnd, %WM_CLOSE, 0, 0)
END IF
RETURN(1)
END SELECT
CASE %WM_CLOSE
' -- Put code to be executed before dialog end here
' -- This is the API call to kill this "Virtual Window" by the "Call Handle"
' -- This will be a TB function soon, I imagine.
DestroyWindow(hWnd)
RETURN(1)
CASE %WM_DESTROY
END SELECT
END FUNCTION

SUB CreateChildWindow()
' -- NOTICE: This is NOT an MDI-Child. This is a CHILD by assignment. (Parent=hWnd to Child=hWndChild)
DIALOG NEW PIXELS, hWnd, "Test CHILD Window", -1, -1, 260, 80, _
%WS_CLIPCHILDREN OR %WS_CLIPSIBLINGS OR %WS_OVERLAPPEDWINDOW, _
0 TO hWndChild
' -- These are Children by assignment, of hWndChild.
' -- All these controls will trigger the childProc() CALL
' -- If you want to assign a callback for these controls, they need to be moved
' -- to the childProc() callback notice "%WM_INITDIALOG" area.
CONTROL ADD BUTTON, hWndChild, %btnCOMMAND+0, "BEEP", 45, 10, 170, 20
CONTROL ADD BUTTON, hWndChild, %btnCOMMAND+1, "CLOSE THIS CHILD", 45, 30, 170, 20
CONTROL ADD BUTTON, hWndChild, %btnCOMMAND+2, "CLOSE PARENT", 45, 50, 170, 20
DIALOG SHOW MODELESS hWndChild, CALL childProc()
END SUB

' -- Callback Function for hWndChild (CHILD) dialog/window
CALLBACK FUNCTION childProc() AS LONG
SELECT CASE CBMSG
CASE %WM_CREATE
CASE %WM_INITDIALOG
DIALOG SET TEXT CBHNDL, "Child CB-Handle: " & CBHNDL
RETURN(1)
CASE %WM_SHOWWINDOW
CASE %WM_COMMAND
SELECT CASE CBCTL
' -- I have use three %btnCOMMAND constants.
' -- Each three have a virtual handle that mathes the parent they
' -- are attached to. (They could also have another handle, if they
' -- had thier own btnProc() CALLBACK. But that requires a complex
' -- setup, because all buttons would trigger that same callback.)
CASE %btnCOMMAND+0
IF CBCTLMSG = %BN_CLICKED THEN
BEEP
END IF
RETURN(1)
CASE %btnCOMMAND+1
IF CBCTLMSG = %BN_CLICKED THEN
' -- Close this child by the "Virtual Handle", using the SYSCOMMAND close
SendMessage(CBHNDL, %WM_SYSCOMMAND, %SC_CLOSE, 0)
END IF
RETURN(1)
CASE %btnCOMMAND+2
IF CBCTLMSG = %BN_CLICKED THEN
' -- Close parent (MAIN), using the SYSCOMMAND close
' -- This can be the real handle, because it is only one window
' -- If this were a virtual child, with others using the same mainProc()
' -- You would want to send the CBHNDL of the window when it was created.
' -- That would require additional tracking in an ARRAY, of virtual CBHNDL's
SendMessage(hWnd, %WM_SYSCOMMAND, %SC_CLOSE, 0)
END IF
RETURN(1)
END SELECT
CASE %WM_SYSCOMMAND
SELECT CASE CBCTL
CASE %SC_MINIMIZE
CASE %SC_MAXIMIZE
CASE %SC_RESTORE
CASE %SC_MOVE
CASE %SC_SIZE
CASE %SC_CLOSE
SendMessage(CBHNDL, %WM_CLOSE, 0, 0)
RETURN(1)
END SELECT
CASE %WM_CLOSE
DestroyWindow(CBHNDL)
RETURN(1)
CASE %WM_DESTROY
END SELECT
END FUNCTION

I have updated this code to use RETURN(1) values. (Seems the "Close" button likes to continue to CLOSE, even though you explicitly block it. The values of (1) are generic and only a temp-guide. See the note below.)

When you deal with CALLBACKS, windows expects a return-value to let it know the CALLBACK was "Processed". I had originally left that RETURN value out, due to the fact that I have not tested it well enough, to even guess if I am doing it correctly. It is my understanding that 90% of the time, it expects a ONE value. However, there is a small group of calls that expects a ZERO or a specific VALUE greater than ONE.

EG, I did not want to say... "Return(1)", is what you "Need" to send, when that is not the case. What it will come down to, is returning what the program expects for a return value. In short... You should research what "Return value" is expected from any element you add to a callback. Many elements don't expect anything, but sending back a "Processed" value will stop that control from sending repeated call-triggers.

If your program hangs for an unexpected reason... Assume that the reason is related to a RETURN-VALUE desire.

Other "Button" command "Notifications"...
BCN_DROPDOWN
BCN_HOTITEMCHANGE
BN_CLICKED
BN_DBLCLK
BN_DISABLE
BN_DOUBLECLICKED
BN_HILITE
BN_KILLFOCUS
BN_PAINT
BN_PUSHED
BN_SETFOCUS
BN_UNHILITE
BN_UNPUSHED
NM_CUSTOMDRAW (button)
WM_CTLCOLORBTN

ErosOlmi
28-10-2008, 18:52
Very interesting.
Can I get some parts for thinBasic help file?

Thanks
Eros

ISAWHIM
28-10-2008, 22:06
Dissect as desired... I am working on a reverse version. Where it shows how to use one set of Proc() for all forms, and another set of Proc() for all other elements. This will bring the VB5 and VB6 users into a little more of a comfort zone. These are the controls we have grown to love, but no other programming language provides. It is also the same thing that has driven most of us away from all the ".NET" versions. They dropped focus of CALLBACKS and resorted to OBJECT-RELATION creation and response. Still a CALLBACK, but one which makes it more complex than it has to be, or is desired to be. (Buttons, Lists, Images, etc... With universal communication, and all having the ability to have a separate code-section for each response.)

That setup in the first two posts is semi-specific to a parent/child window setup. Where it is expected to operate similar to an MDI, without being an MDI setup. As opposed to being a single WINDOW, with unique POPUP DIALOG WINDOWS as children.

Both would have to be combined to simulate a program with both styles... Multiple windowProc() and elementProc() for complete control that will drive you mad in the process.

The "Paint Program" which I had in mind, when talking about the setup, was "Gimp", found at http://www.gimp.org/

Gimp uses a non-MDI style setup. It spans multiple monitors better that way. Using CALLBACKS is how each window communicates with the main window that holds the tools.