What is the brightest difference between traditional non-event driven programming and GUI OOP technique? This difference we can see at the program flow control. Old styled programs are being executed from the entry point, defined in the language. The flow control comes through all program's code step-by-step to the last line of the program. This approach to programming is typical for any single-task system. The core of a program is a loop: the data input, computing, output. And so on.
Now the situation changed. Modern programming tools should conform to the Graphic User Interface (GUI) standard. What does it mean for a programmer? It means, that now, the flow control depends on user actions. So, the main role in program execution lies on User. He can ask for any available service at any time. Generally speaking the main loop of a GUI application is hidden inside Windows System. Is this only a syntactic sugar? The answer is No. Hiding the execution loop means that all you need is to react on events such as a mouse click or menu item selection. Now, you should divide your programs into two parts : a GUI interface and data processing code.
The features called by the system historically called callbacks. What is the main difference between an ordinary feature and callback? The answer is simple - "the caller". As the rule, the caller for a callback is the Windows System kernel. In most cases you should not doing it by yourself.
What is the syntactical difference between the features and callbacks? In [Mey92] chapter 10.13 we read that Eiffel has attractive alternative to classic callback scheme. The main idea is to write the event handling loop in the parent class and to supply it with deferred features being called from the loop. The real contents of handlers we determine in the heirs of the parent class. Thus you can look at callbacks programming as effecting and redefining features being called on some events.
Look at the feature adaptation section of some heir of the APPLICATION class:
class MY_APPLICATION inherit APPLICATION rename make as a_make redefine on_command, make_menu, make_speedbar select on_command, make_menu, make_speedbar, init end; end;
As you can see in GRAPE we usually redefine the on_command callback. What does it mean? It means all actions caused by menu item selection (and of course caused by hot key pressing) will be processed in feature on_command in class MY_APPLICATION. We have no interest in details of "how this menu works". All we need to know is that all menu commands call our function. Not a bad thing, is it? This is a real black box: you need only define menu actions and build a menu tree structure. And that's all!
The next important callback is on_paint. You must provide your own version of this feature. This feature is responsible for window redrawing. Feel the difference - in so called classical approach nobody can destroy your image and you can draw it step-by-step. Now your window must be able to redraw itself at any time. For example assume that a dialog box overlaps your window. After the dialog has been closed the Windows System calls the on_paint callback to redraw overlapped part of the window. Thus if your window doesn't redefine the on_paint feature then the situation might have very unpleasant consequences for you - instead of your pretty graph in the window you will get nothing.
Class TILE has two predefined constants: processed and not_processed. These constants must be returned as the value of any callback. As you can understand from their names they let system know about callback processing. If you put in callback
Result := Processed
it means all actions to be processed by this callback has been done. Otherwise (Result := not_processes) the system performs the default actions.
Now we briefly describe almost all groups of callbacks. For detailed help look at our On-Line Help Reference.
on_paint : INTEGER on_size ( w, h : INTEGER ) : INTEGER on_move ( x, y : INTEGER ) : INTEGER on_minmax ( min, max : SIZE ) : INTEGER on_parent_resize ( pw, ph : INTEGER )
This group maintains window image and location on the screen. Other callbacks are executed on window resizing, moving, minimizing, maximizing. By redefining the feature you can also determine the minimal and maximal sizes of your window, make your application always being minimized and so on.
on_activate : INTEGER on_deactivate : INTEGER on_create : INTEGER can_destroy : INTEGER on_destroy : INTEGER
This group describes the life cycle of a window object. If you want to make any operations at the moment of creating or destroying a window, redefine this callbacks. If you want to initialize the window properties, create some children and so on, do it in the on_create feature - not inside the window creation procedure make.
For better understanding of these callbacks test the demo example into CALLBCKS\PLATFORM.
on_hscroll ( s : SCROLLER ) : INTEGER on_vscroll ( s : SCROLLER ) : INTEGER
This is a very simple group of callbacks. It notifies the window about its scroller-s behavior. SCROLLER is a special GRAPE interface class corresponding to the window scrollbars. This feature allows you to check the state of these scrollbars.
For better understanding of these callbacks see the example into the CALLBCKS\SCROLLER directory.
on_lbutton_down ( x, y : INTEGER ) : INTEGER on_lbutton_up ( x, y : INTEGER ) : INTEGER on_rbutton_down ( x, y : INTEGER ) : INTEGER on_rbutton_up ( x, y : INTEGER ) : INTEGER on_lbutton_double ( x, y : INTEGER ) : INTEGER on_rbutton_double ( x, y : INTEGER ) : INTEGER on_mouse_move ( x, y : INTEGER ) : INTEGER
A group of mouse events. Parameters define the point of the mouse cursor. This set of callbacks allows you to handle mouse events.
on_key_down ( key : INTEGER ) : INTEGER on_character ( ch : CHARACTER ) : INTEGER on_key_up ( key : INTEGER ) : INTEGER
A group of the keyboard event handlers. In GRAPE you will use these callbacks very seldom. If you want to input something you can do it through dialog boxes.
on_timer : INTEGER
This is a useful callback for timing. It used with such features as set_timer / stop_timer. You can set one of the Windows timers to call this feature every specified time interval. It is used for time-related purpose, for example polling any event by time. Note this call cannot guarantee precise time measuring. It works roughly and cannot be used in real-time applications.
There are no new callbacks in class GROUP. Three callbacks can_destroy, on_activate, on_size simply are redefined from TILE. These callbacks are propagated upon all GROUP members.
WINDOW is a class inherited from GROUP with new callbacks relating to children.
on_command ( command : INTEGER ) : INTEGER on_sys_command ( cmd_code : INTEGER ) : INTEGER on_menu_selection ( command : INTEGER ) : INTEGER on_menu_init : INTEGER
These callbacks allow you to provide actions for menu items. On_command is the most popular callback from this group - others are redefined rarely.
on_changed ( control : TILE ) : INTEGER on_clicked ( control : TILE ) : INTEGER on_double_clicked ( control : TILE ) : INTEGER
These callbacks notify the parent window about its child control events. For example, a dialog window can receive notification about a listbox click or double-click. It can be useful to maintain this event in the parent - not in the child itself. And if the child is a standard control this is the only way to receive control events.
For better understanding of these callbacks examine the example into the CALLBCKS\NOTIFY directory.
on_hslide ( slider : SCROLLER ) : INTEGER on_vslide ( slider : SCROLLER ) : INTEGER on_validation_error ( t : TILE ) on_child_load ( t : TILE ) : TILE
These callbacks are used very seldom.
For better understanding of these callbacks play with the demo in CALLBCKS\SCROLLER.
Other callbacks described in all other chapters are related to specialized classes. For example SPEEDBAR has two redefined callbacks and their usage is described in the chapter dedicated to SPEEDBAR especially.
Let's look at using callbacks in our demo.
This example shows the usage of the on_rbutton_down callback. Simply put your handler in the body of callback, and it will be executed after the right mouse button has been pressed.
inherit WINDOW redefine on_rbutton_down ... feature ... on_rbutton_down(x : INTEGER; y : INTEGER) : INTEGER is do str := "Right mouse button pressed" repaint Result := Processed end
Look at the typical content of this callback. Remember that it is the proper place for most window-related initializations. We don't recommend perform initialization inside any window creation feature. Only here you can be guaranteed that the Windows vis-a-vis has been created by the system (not only Eiffel object itself).
on_create() : INTEGER is local slwp : STATUSLINE_CELL_WITH_PICTURE p : PICTURE do post_command ( IDM_NEW ); statusline.set_hint("Test hintbox") !!p.make_predefined(p.CALCULATOR_PICTURE) !!slwp.make_fixed(p,p.get_width) statusline.add(slwp) end;
A very important callback. There you must define actions for all menu commands. Its structure is a huge the inspect block with each when .. then branching to one of the menu commands.
on_command ( cmd : INTEGER ) : INTEGER is -- This is a switch which responds to menu commands. local desktop_element : WINDOW_WITH_PICTURE; fd : FILE_DIALOG font_d : FONT_DIALOG i : INTEGER wp : WINDOW_WITH_TEXT do Result := processed; inspect cmd when IDM_NEW then !!desktop_element.make ( "c.bmp" ); desktop.add ( desktop_element ); when IDM_OPEN then !!fd.make_open("Open bitmap", << "Bitmap files (*.bmp)", "All files (*.*)">>) i := Current.execute(fd) if i /= 0 then !!desktop_element.make (fd.selected_path); else !!desktop_element.make ("c.bmp"); end desktop.add ( desktop_element ); when IDM_SELECT_FONT then !!font_d.make if Current.execute(font_d) /= 0 then wp ?= get_user_window if wp /= Void then wp.set_font(font_d.selected_font) wp.repaint end end when IDM_PROGRESS then prog_num := 0 statusline.start_progress("Progress line",100) statusline.update_indicator(prog_num) start_timer(500) when IDM_TILE then -- Standard desktop operations desktop.tile(); when IDM_CASCADE then desktop.cascade(); when IDM_ARRANGE then desktop.arrange(); when IDM_CLOSE_ALL then desktop.wipe(); when IDM_CLOSE then if desktop.get_current /= VOID then desktop.get_current.destroy(); end; -- if when IDM_QUIT then terminate(); else Result := not_processed; end; -- inspect end; -- on_command
All your applications will be similar to the example above. In this function all menu actions are short but in a real program we recommend you to implement each action as a feature to minimize the size of the on_command callback body.
Now let's discuss an example for the on_paint callback. In this example picture is stretched to the full window. Look at the usage of the Memory Graphics Context and the begin_paint / end_paint pairs ("our brackets").
on_paint : INTEGER is local gc : DISPLAY_GRAPHICS_CONTEXT mc : MEMORY_GRAPHICS_CONTEXT p : PICTURE gc_rect : RECT w,h : INTEGER do !!gc.make_with_assignment(Current) gc.begin_paint gc_rect := gc.client_area w := pic.get_width h := pic.get_height !!p.make_compatible(gc,w,h) !!mc.make_compatible(gc) mc.begin_paint mc.set_drawing_surface(p) mc.put_picture(pic, 0.0, 0.0) mc.stretch_to(gc,0,0,w,h,0,0,gc_rect.w,gc_rect.h, mc.SRCCOPY); mc.end_paint gc.end_paint Result := Processed end
To examine the timer callback look at the application in EXAMPLES\CALLBCKS\TIMER. To rotate some text string inside a window you need to add only three callbacks:
Figure 3.1 Some text string is being rotated inside the window using on_timer callback.
on_create() : INTEGER is do start_timer(55); end; on_destroy() : INTEGER is do stop_timer end; on_timer() : INTEGER is do repaint current_step := ( current_step + 10 ) \\ 360; Result := processed; end;