GUI Package
1. IntroductionThe primary aim of the GUI package is to provide a convenient way for working with windows, controls, events, and other GUI features. The GUI package presents the object-oriented windows, dialogs, and controls handling. This package is built on top of the VPI layer and delivers a new level of abstraction and ease of use while retaining compatibility with VPI programs since both the GUI and VPI programming styles can be applied in the same program. 1.1. Services Provided by the GUI PackageGUI package provides the following features:
The most important interfaces that make up the GUI package are shortly described in the next section. 2. GUI Package StructureThe central notion in the GUI package is window. The root window interface is supported by all windows, dialogs, and controls defined in the GUI package. This interface defines the general window handling predicates, which can be applied to any GUI components. The window interface is the construction type for the window class, which implements the general window manipulations for all GUI package interfaces. However, application programmers should never use the window::new/1 constructor of the window class to create window type objects in their programs explicitly. They should always create windows using constructors of applicationWindow, documentWindow, childWindow, or dialog types. Notice that it is reasonable to create window type objects, which wrap specified VPI package windows, with the following window class constructor window::wrapVPIWin/1. frameDecoration interface provides declarations of domains and predicates, which handle object features of the window border and the window title bar. Predicates from this interface handle:
applicationWindow interface corresponds to the application Task window. The show predicate creates and displays the Task window on a screen. The Task window is the main window for a GUI application; each GUI application can have one and only one Task window. documentWindow interface supports the window and frameDecoration interfaces. Document (or top-level) windows are the windows, which can have frame decorations. Document windows can be minimized, maximized, and restored. In MDI applications document windows can be arranged using the tiled and cascade formats; icons of all minimized document windows can be arranged. formWindow interface supports the dialog and documentWindow interfaces. Form windows have both properties of top-level document windows and dialogs. Forms are top-level windows, which can contain controls, support Tab navigation between controls, handle default pushbuttons and mnemonic accelerators, etc. The pair containerWindow interface - control interface produces the functionality of handling controls. It defines how controls can be added, retrieved, and deleted from GUI objects. The purpose of containers is to hold several controls or other containers. A container is essentially a rectangular portion of the screen, which allows you to treat several individual controls as a group. checkButton, editControl, iconControl, listBox, listButton, listEdit, pushButton, radioButton, scrollControl, and textControl are interfaces for the standard Windows controls. containerControl interface represents a control, which can hold other controls or containers. That is, it allows other controls to be added into this control. From the other hand, it can be itself added to other container windows or container controls (as a control). This is very useful, when composite controls or sub-dialogs are required. customControl interface represents a custom control, which is just a control, which should be extended by some drawing or inner controls. This interface supports text and edit event redirection so that composite controls may be perceived as integrated, autonomous visual objects. dialog interface represents dialogs. This interface provides additional predicates to handle size, position, suspend and resume layout, handling tab stop order, keyboard navigation, mnemonic accelerators, etc. drawWindow interface contains predicates to change draw settings and predicates to handle keyboard and mouse listeners. If a control window (like push button) is not supposed to use drawing, then it does not support drawWindow interface. windowGDI interface contains predicates to make drawing. The object of windowGDI interface comes on paintResponder. gui_native class contains Visual Prolog declarations of constants, domains, and predicates required to use native Windows functions handling MS Windows native GUI components from Visual Prolog code. 3. Window TypesWindows are classified by type. The GUI package supports the following window types:
3.1. Screen WindowThe Screen (desktop) window is an abstraction that represents the entire screen. The Screen window is always the parent of all Task windows. Top-level windows of SDI applications may also have the Screen window as their parent, which allows moving them outside of correspondent Task windows. Such Top-level windows will be listed in the Windows task bar and you can shift to such Top-level windows using the Alt+Tab keys. 3.2. Task WindowThe Task window is the main window for an application. It is created by the applicationWindow class constructors. The VDE Code Expert automatically generates the call to the applicationWindow class constructor. Application initialization and termination are always associated with the Task window. Initialization should be programmed in the Task window window::showListener. Clean-up on termination should be programmed in the Task window window::destroyListener. Application termination results from the predicate window::destroy/0 applied to the Task window. One way this might occur would be in a response to the user selecting the menu item. This will typically trigger an event resulting in a call to the window::destroy/0 predicate. The Task window also receives some special VPI events that are not sent to other windows. For example, various VPI Dynamic Data Exchange (DDE) events used to communicate with other applications are also sent to the Task window event-handler. 3.3. Document WindowIn the GUI package, a Document window is a window that has either the Screen window or the Task window as the parent window. These windows will often contain a menu, and they can usually be minimized and maximized. Under Windows Multiple Document Interface (MDI) mode, the Task window is the container for all Document windows created by the application and for their minimized icons. It is the Task window, which carries the menu of any active MDI window. In MDI mode Document windows are created by constructors of the documentWindow class. In SDI (single document interface) mode, any Document window can nominate the Screen window as its parent window. Such Top-level windows are located separately from the Task window and they can have got their own menu bars. 3.4. ControlsThe term Controls usually applies to small windows with a specialized appearance and fixed functionality. They are used in fixed arrangements inside other windows mainly as components of dialogs and form windows. Controls provide the user with means to type texts, choose options, or invoke commands. The GUI package supports the standard controls (which are supplied in the standardControl package in the standardControl subdirectory) and common controls. Standard controls consist from several predefined controls. Predefined controls are: check buttons, edit controls, group boxes, icon controls, list boxes, list buttons, list edit controls, push buttons, radio buttons, horizontal and vertical scroll controls, static text controls, and custom controls. At the moment common controls are dateControl, editorControl, listViewControl, messageControl, splitControl, treeViewControl. However, new common controls will be added. Notice that using the GUI object approach it is considerably easier to make new user controls. In an interface, declaring a new control, you should specify that it supports the GUI package control interface (or/and may be some other interfaces). This will provide to your control declarations of all features (domains, predicates, and constants) generally required to controls. Then, if in implementation of your control class, you specify that it inherits from classes, which have specified supported interfaces as construction types, then your control class will also have implementations of all required properties. Therefore, your control will be as good for the GUI system as any of standard GUI package controls. Now you need only declare and implement additional properties of your control. Moreover, here it is easy to follow the general GUI package event-handling model. You need to declare domains for additional listeners and responders and declare/implement predicates to add/set these listeners/responders to control objects. Control events activate listeners and responders added/set to the control object causing correspondent reactions. 3.5. Dialogs and Form WindowsDialogs and Form Windows are windows, which contains controls. The operating system provides the ability to navigate between controls by using the Tab and Shift+Tab keys to shift the point of user interaction from one control to another. Dialogs provide the ability to navigate between grouped controls using the arrow keys. Dialogs handle default pushbuttons, mnemonic accelerators, etc. Modal dialogs suspend application execution until closing. Forms are document windows, which also support dialog functionality. 3.7. Child WindowsSometimes it is not necessary to create controls. Then Child windows can be used to control a part of screen with some drawings. Child windows are created by constructors of the childWindow class. When a parent window is destroyed, then before the parent is destroyed first automatically destroyed all child windows and controls created inside the parent window.
4. Schematic GUI Package UsageThe basic idea is that you:
Notice that constructors of GUI package classes create objects of correspondent GUI components in the program memory, but they do not create correspondent on-screen representations of these components. To create an on-screen representation of some GUI component, one should call the show predicate of this object GUIComponentObject:show() to create a graphic representation of the object and to render it on the screen. 5. The window InterfaceObject-oriented programming fits well with windows graphical user interface systems. The concept of making new GUI components by subclassing existing ones and overriding part of their behavior saves time and effort. It also serves to better understanding of relations between GUI components. In the GUI package the parent interface is window. It is the parent for most things, which can appear on the screen. The basic components of the GUI (windows, dialogs, controls) are all sub-interfaces of the window interface. The window is the interface that holds information about the visible behavior and appearance of on-screen objects:
The window has predicates to get, add, and set many of these features. See help for window interface for details of correspondent predicates. 6. GUI WindowsTo create objects for on-screen windows, programmers should use constructors of classes derived from the window, they can use constructors from applicationWindow, documentWindow, formWindow, and childWindow classes. The lifecycle of a window can be described as following: A window object creation (with new constructors).
7. GUI ControlsThe GUI package supports several types of controls. Each type of controls has the correspondent interface, they are: checkButton (check buttons), editControl (edit controls), groupBox (group boxes), iconControl (icon controls), listBox (list boxes), listButton (list buttons), listEdit (list edit controls), pushButton (push buttons), radioButton (radio buttons), scrollControl (horizontal and vertical scroll bars), textControl (static text controls), and customControl (custom controls). Control objects are created by constructors of correspondent classes. A control could be created only in a container window (dialog, container control, frame window); therefore, such container window should be specified while a control creation. For example, a call template to create a new edit control object can be like this: EditCtlObj = editControl::new(ParentDialog), Each control has predicates appropriate to what it does. For example, scroll bars have predicates to set/get scroll range, scroll proportion, and thumb position. See help on each control interface/class for more information. 8. GUI ContainersIn the GUI package containers are the kind of windows whose purpose are to hold several controls (and may be other nested containers). Usually you do not display controls directly; you add them to a container and it is the dialog (form), which is displayed on the screen (using the show() predicate). 8.1. DialogsObjects of the dialog type hold information about:
Objects of the dialog type have predicates to get and set these attributes and to add or remove controls from them. Because the dialog inherits to the drawWindow, then it supports all predicates for cursor handling, drawing, etc. The lifecycle of a dialog and of controls in it can be described as following:
Dialogs can be: modal, modeless, and child dialogs (a kind of modeless). A modal dialog forces the user to complete input in this dialog before proceeding or using other windows and dialogs of the application. From a programmatic perspective modal dialogs prevents further program execution after call to a show predicate until the user completes and closes the dialog. The dialog object instance, which had created the modal dialog, is still alive even after the on-screen representation of the dialog is destroyed, that means that inquiries to the object instance may be conducted after the show and hence after the user has completed the input. Modal dialogs are typically presented with their own title bar and decoration and may be moved by the user. Modeless dialogs do not restrict the user in which window or dialog to use or input data in. Likewise, program execution continues past show method call, i.e. while the dialog is opened. Listeners must be registered in order to react on user input within the dialog. Modeless dialogs are typically presented with their own title bar and decoration like modal dialogs. Child dialogs are kind of modeless but restricted in moving to the parent window. Child dialogs are used when controls management are required outside normal dialog contexts. Here is an example of the default code automatically generated by the VDE for a newly created dialog. For each dialog, the VDE generates the interface declaration, the class declaration, and the class implementation files. The class declaration file declares the constructor new(), which is implemented like this: inherits dialog
...
clauses
new(Parent) :-
dialog::new(Parent),
generatedInitialize().
% This code is maintained by the VDE. Do not update it manually
facts
buttonok : pushButton.
buttoncancel : pushButton.
buttonhelp : pushButton.
predicates
generatedInitialize : ().
clauses
generatedInitialize():-
Font = vpi::fontCreateByName("MS Sans Serif", 8),
setFont(Font),
setText("New Dialog"),
setRect(rct(50,40,290,160)),
setType(wd_Modal),
setDecoration(titlebar([closebutton()])),
buttonok := pushButton::newOk(This),
buttonok:setText("&OK"),
buttonok:setPosition(12, 102),
setDefaultButton(buttonok),
buttoncancel := pushButton::newCancel(This),
buttoncancel:setText("Cancel"),
buttoncancel:setPosition(96, 102),
buttonhelp := pushButton::new(This),
buttonhelp:setText("&Help"),
buttonhelp:setPosition(180, 102).
% end of automatic code
First of all, it states that it inherits from the class dialog. As the result, this class implementation inherits implementations of all predicates of dialog interface. Second, it defines clauses for the class constructor new(). This constructor creates a dialog type object by the call dialog::new(Parent) and then calls the generatedInitialize() predicate, which initialize creation of the dialog default buttons, dialog decorations, sets the dialog type, font, dialog title, etc. Notice that code of the generatedInitialize() predicate is placed into the code section, which is automatically maintained by the VDE, so the programmer should not modify code in this section. This section starts from the line "% This code is maintained by the VDE. Do not update it manually". In this section we see declaration of the generatedInitialize() predicate and definition of the generatedInitialize() predicate clauses. This separate generatedInitialize() predicate is required since the programmer can need to add his own code into the new() constructor and this code should not be cleared while automatic code updating. The first thing the generatedInitialize() predicate does is to call the vpi::fontCreateByName predicate to create the #Font font, which will be set (by window::setFont/1) as the font to be used for drawing text in the dialog. The window::setText/1 predicate sets the dialog title "New Dialog". The window::setType/1 predicate specifies that the created dialog has the modal type. The frameDecoration::setDecoration/1 predicate specifies that the dialog will have the title bar with the close button. (Notice that you cannot specify the frameDecoration::minimizeButton or frameDecoration::maximizeButton without frameDecoration::closeButton.) Next, we add the OK, Cancel, and Help buttons to the dialog. These are done by creating instances of correspondent push button controls with calls to matched constructors of the pushButton class (pushButton::newOk/1, pushButton::newCancel/1, and pushButton::new/1). Control constructors expect a container to which they should be added and here the container is specified by This variable. Here This variable specifies the dialog type object, which in turn supports the container. This dialog type object is created by the dialog::new(Parent) predicate called in the constructor new() of the dialog. Let us discuss code, which the VDE generates to handling controls. buttonok := pushButton::newOk(This),
buttonok:setText("&OK"),
...
From the first line, you see that the VDE uses fact variables, for example buttonok for OK push button, to store created objects. In the second line, you see how the VDE uses this buttonok fact variable to refer to the object created by the constructor newOk called in the previous line. Notice that usage of fact variables is a standard way used by the VDE to store (and refer to) created GUI component objects. Please notice that in the GUI package creating of GUI component objects (which is done with constructors) and handling of their states and properties are separated from creation of correspondent visual things and rendering them on-screen. The visual representation of dialogs or any other GUI components is always created with show predicates. All created GUI objects are merely data that eventually can be visualized by calling to the show predicate of a correspondent object. If you add creation of this dialog object and visualize the created dialog object with the show predicate, then if you compile and run this code, you can see:
The Default New Dialog Created by the VDE Notice that since the dialog is modal, after calling to the show predicate, the program does not continue execution until the dialog is closed (the show predicate does not return). Only after the show predicate returns, instructions following show are executed. 8.2. FormsForms are objects of the formWindow interface type. The formWindow interface supports the dialog and documentWindow interfaces and forms allow:
There are the following main differences between dialogs and forms:
In most other aspects, forms are the same as dialogs. 8.3. Layout of Controls in ContainersNormally the programmer simply designs a dialog or a window with required shapes and controls behavior in the correspondent WYSIWYG style dialog or window editor. Then the VDE code experts automatically generate all required code to proper sizing and positioning of controls, designed tab order navigation between controls, designed grouping of controls and arrow navigation between grouped controls, etc. Therefore, here we will discuss only features, which require some special attention of programmers.
standardWidth = 48. standardHeight = 12.
9. Event HandlingUnlike procedural (console) programs, which were executed sequentially from the beginning to the end, window GUI applications are asynchronous. The programmer cannot determine an order in which the user will activate menu commands or controls. The GUI system is actively involved in the execution sequencing of applications. When the user does something (presses a key, moves the mouse, clicks on an icon, etc.) an event is generated by the hardware interface, the GUI system sends this event to its correct owner, usually the window that is active (having the focus). These events can arrive asynchronously and they determine the real sequencing of application actions. Accordingly such programs are called event-driving applications. In event-driving applications instead of sequential execution from the beginning to the end, the GUI system uses a "window main loop" waiting for the user input. When the user touches any on-screen component of an application, the window system catches correspondent event and passes it to the previously supplied/registered event handler. This is known as a callback, and an event handler is a callback predicate because the window system calls back to it when an event happens. For example, if a push button title is Help, then the correspondent event handler must normally activate some help system. That is, handling of a push button event means detecting that the button is clicked and executing of an associated operation. The event model is the framework that converts a GUI interaction (pressing buttons, mouse clicking) into a call to a predicate, which handles it. That is, the event model describes how to connect arbitrary windowing system actions to some predefined set of events, which in turn activate correspondent event handlers (event handling predicates determined in your application) that handle the initial GUI system actions. The GUI system cannot simply call appropriate event handling predicates in your application! Because the GUI system knows nothing about your code until it is ordered to run it. It is initially unknown to the GUI system, which GUI components your application will use and which event handlers are in your application to handle their events. They should be specified somehow. 9.1. Event ModelsThere are two event models used in Visual Prolog version 6.2. The VPI event model. This event model uses the set of events (like vpiDomains::e_Create, vpiDomains::e_Control, vpiDomains::e_Char, vpiDomains::e_MouseUp, vpiDomains::e_Update, etc.). All events related to some window are sent to the same event-handling predicate (the window event handler) associated with this window. We will not discuss this event model here; you can find detailed description of this event model in the VPI package help. The GUI package event model. Separate specialized listeners or / and responders are individually registered to each kind of events in each instance of objects of GUI components. The GUI event model can be named "registration based" event handling. Instead of registering a single event handler for a window, your GUI program code tells to window system:
It is a much clearer model. Your dialog has several controls, which can cause (or generate) events (button presses, mouse clicking, etc.), and your code should register several listener (or / and responder) predicates. Each of them should handle different kind (for which it is intended) of events. According to the GUI package model, each GUI component (a dialog, a control, a window, etc.) is represented as a separate object. Each such object can fire some events. If you wish to handle some kinds of events from a GUI object, then you need to add to this object a separate callback listener (or / and responder) predicate for each kind of events, which you need to handle. Such registered listener will be called by the GUI system when an object fired an event of the correspondent type. Such listeners look very much like workers sitting in their offices and waiting for telephone calls. Each worker has his own telephone and his own single function. When one of the telephones rings, then the correspondent worker answers to the call and accomplish his action. For example, a railroad station dispatcher can phone to a switchman and order "set the switch to the side-track." Then the switchman resets the switch from the highway to the side-track and answers "Done!". For more programmatic example, let us decide to accomplish some action on a window creation, then we can write some winShowListener predicate, which should be declared like: predicates
winShowListener : window::showListener.
Clauses of this predicate can accomplish required actions. Then you should add/register this predicate to the window object. You can do this by call to the window::addShowListener/1 predicate like this: Child = childWindow::new(ParentWin), Child:setClientRect(rct(50,100,200,150)), Child:addShowListener(winShowListener), Child:show(), With this code, when the on-screen representation of the Child window (correspondent to the Child object) will be created by the Child:show() predicate (but before it will be shown on the screen), then the GUI system will call the registered listener winShowListener, which will handle the Child window creation event. 9.2. Two Types of Event Handlers Used in the GUI PackageThere are 2 types of event handlers, which are handled in two different ways in the GUI package. Some events (such as a window creation, a window gets focus, a window is moved, an application is deactivated, etc.) are always handled by the GUI system in the same standard way. This handling is independent of actions implemented in a user-defined event handler, which catches and processes such events. About such events we can say: "When an event of this type is fired, it activates the correspondent event handler (registered for this event). Then clauses (of this event handler) execute processing the caught event. After the event handler execution has finished, it returns and then the GUI system will provide always the same standard handling of this event. This standard handling of the event will always be the same independently of the event handler execution results." Events of the second type (such as a character pressing event; an event, which specifies that a Windows session should be terminated; a push button activation event; etc.) are handled by the GUI system in different ways. These ways are depended upon a result returned by the executed event handler. Therefore, event handlers of such events MUST return some response. Ordinary this response can be returned in one argument. Therefore, such event handling predicates returning one argument can ordinary be represented as functions. About such event handlers we can say: "When an event of the second type is fired, it activates the correspondent event handler. This is the event handler registered to this event or the default one (which must always exist) for this type of events. Clauses of his event handler are executed and after the event handler execution has finished, it returns some response to the GUI system. Then the GUI system will handle this event in the way dependent upon the response obtained from the executed event handler." Therefore, we can with some approximation say:
So the GUI package has 2 kinds of event handlers: event listeners and event responders. The programmer can define and register (or not) event listeners to some events; the GUI window system does not require registering of event listeners. From the other hand, event responders must always exist. If the programmer does not define and register an event responder explicitly, then the GUI package provides some predefined default event responder. 9.3. Event ListenersAn event listener is a callback predicate that can be assigned to a GUI component object to listen and handle some type of events, which are sent to this GUI object. 9.3.1. Types of Event ListenersIn the GUI package basic event listeners are defined in window and drawWindow interfaces. Notice that in all these event listeners, the first argument is used to pass a window type object, to which this listener is registered, to a listener. The GUI package also has some event listeners, which are declared only to some specific GUI components. These are:
9.3.2. How to Implement an Event ListenerSpecified above event listeners can be registered to most of GUI components: windows, dialogs, controls, etc. Each event listener requires the following pieces of code. First, you need to declare an event listener predicate for a desired type of events. For example, this can be done with code like this: predicates dialogShowListener : window::showListener. Here we declare event listener, which we will use as a dialog creation listener. clauses
dialogShowListener(_, _) :-
EditCtlObj = editControl::new(This),
EditCtlObj:setPosition(10,4),
EditCtlObj:setSize(60,12),
EditCtlObj:show().
Here you should remember that window::showListener predicate domain is declared having the procedure mode: showListener = ( window Source, integer CreationData ) procedure (i,i). When you have defined event listeners, you can register them as event listeners to objects of GUI components. This can be done with add_ EventType_ Listener predicates. Here _ EventType_ is abbreviation for the events listed above in the Types of Event Listeners paragraph. For example, you can register the defined dialogShowListener listener as the event listener to the creation event of a dialog object like this: DialogObj = dialog::new(ParentWin),
DialogObj:setRect(rct(100,100,194,190)),
DialogObj:setText("Modal dialog"),
DialogObj:addShowListener(dialogShowListener),
DialogObj:show(),
Here we use the DialogObj:addShowListener(dialogShowListener) predicate to add the dialogShowListener event listener predicate as the listener to the create event for the DialogObj dialog object. If you will compile and run such code, then after a call to the DialogObj:show() predicate, the dialog correspondent to the DialogObj object will be created and rendered on the screen. Notice that for a modal dialog the DialogObj:show() predicate does not return until the rendered modal dialog will be closed. So after a call to the DialogObj:show() predicate you cannot add any controls or anything else into the rendered dialog. However, while handling of the create event for the on-screen representation of the DialogObj dialog, the window system will call the registered callback dialogShowListener predicate, which will create EditCtlObj edit control that will be placed into the DialogObj dialog and will be rendered in its on-screen representation. A registered (to the DialogObj object) create listener predicate (dialogShowListener) is called when the on-screen representation of the dialog has been created but it is not yet visible on the screen. It is a good time to do the one-time housekeeping (such as database assertions) related to the dialog. The call that creates the on-screen representation of the dialog (DialogObj:show()), will not succeed (and the dialog will not appear on the screen) until the code of the called dialogShowListener create event listener has completed execution. Notice that it is possible to register to a GUI object any number of event listeners for any specific event type. When such event is fired, all registered event listeners of the event are called in the order in which they were added to the object. An event listener ones added to an object cannot be deleted; it will exist until the object will be deleted. 9.4. Event RespondersAs we have said, it is only the programmer decision whether to register or not event listeners. If the programmer does not register to some abject an event listener to certain type of events, then the GUI system will just provide the default handling to this type of events. To provide this default handling the GUI system does not need any response from any event listeners. That is why such type of event handlers can be called "listeners". This is because: "They listen to their events and can react on them, but do not provide any response back to the GUI system. The GUI system itself provides the same default handling of such events independently of result of such event listener execution." However, there are some kinds of events, which are handled by the GUI system in different ways depending on response, which the GUI system receives back from the called event handler. That is why such event handlers are called event responders. Setting new event responders can be done in the following way: Somewhere in your code declare a new event responder, for example, newActivatedResponder : pushButton::activatedResponder. Define its clauses like this: newActivatedResponder(_PB) = pushButton::noAction():-
write("I am a new activatedResponder").
Now you can set this event responder to any pushButton type GUI object. For example, you can write: % create dialog
DialogObj = dialog::new(ParentWin),
...
% Button Cancel
CancelButton = pushButton::newCancel(DialogObj),
...
OldActivateResponder = CancelButton:
setActivatedResponder(newActivatedResponder),
Now can you compile and run such code. If you press the Cancel push button correspondent to the CancelButton object, then it does not close the DialogObj dialog and prints "I am a new activatedResponder". In this example, we use the pushButton::setActivatedResponder predicate to set the new newActivatedResponder push button activation event responder to the CancelButton GUI object. Notice that the setActivatedResponder predicate returns the previously used default activation event responder OldActivatedResponder for the CancelButton object. If you save it somewhere, you will be able to restore this old OldActivatedResponder push button activation event responder to the CancelButton object, or set it to any other push button GUI object in your program. It is also possible to restore default responder using pushButton::resetActivatedResponder/0. 9.5 Event ExtensionsSometimes it is necessary to redefine default behavior for an event, which has no responder. In such case the concept of extensions can be used. Please notice however that VDE does not generate a code for extensions. It is supposed to be coded manually. For example, implement myWindow inherits documentWindow supports documentWindowExtension delegate interface documentWindowExtension to defaultExtension facts I.e. it is responsibility of the extension to invoke the default behavior.
10. Drawing OperationsThere are a number of GUI predicates declared in the window interface, which draw text, pixels, various geometric shapes, and icons. These predicates are all started with the draw prefix. For example, windowGDI::drawArc/5, windowGDI::drawFloodFill/2, etc. The windowGDI object is retrieved on paintResponder for a drawWindow type objects. Drawing operations in a window use the current drawing attributes for that window, which are referred to as drawing tools. The drawing tools are: Pens, Brushes, DrawMode, Fonts, Foreground Color, Background Color, and Background Filling Mode. 10.1. Drawing ToolsIt is possible to retrieve the settings of all the current drawing tools for a window type object into a single variable and then use this variable to restore them at a later point. This can be done using the following two predicates getDrawTools and setDrawTools. Here is the code sequence: OldTools = GdiObject:getDrawTools(), % Change the drawing settings and draw with different settings GdiObject:setDrawTools( OldTools ), % restore saved drawing tool settings The sub-components of the drawing tools are defined in the vpiDomains::drawtools domain: drawtools = draw_tools(
pen Pen,
brush Brush,
drawMode DrawingMode,
font Font,
color ForegroundColor,
color BackgroundColor,
bk_mode BackgroundColorDrawingWhileTextDrawing).
% See Drawing Text for more information
Notice that all domains and constants used to define drawing tools are declared in the vpiDomains class. Normally you would use one or more of the tool-specific predicates to change only the tools that need to be changed. Predicates, domains, and constants used for handling individual drawing tools are described below. 10.1.1. PensLines and the periphery of figures are drawn using a Pen. The way a line will look, i.e. its width, style and color, can be changed by setting the pen attributes before the drawing is done. The domains for pens are: pen = pen(
penwidth PenWidthInPixels,
penStyle PenStyle,
color PenColor).
penWidth = integer.
penStyle = integer.
color = integer.
Pen styles can be specified using the predefined vpiDomains::penStyle domain constants vpiDomains::ps_***. The windowGDI::setPen predicate changes the pen used for drawing in a given window. The following code sets a red pen with the solid style and with two pixel width. PenWidth = 2,
Pen = vpiDomains::pen(PenWidth , vpiDomains::ps_Solid,
vpiDomains::color_Red),
WindowTypeObject:setPen( Pen),
10.1.2. BrushesFigures are filled using a Brush. The way a brush paints is determined by the brush attributes, which consist of a color and a fill pattern style. brush = brush(patStyle, color). patStyle = integer. Brush fill patterns can be specified using the predefined vpiDomains::patStyle domain constants vpiDomains::pat_*** . The windowGDI::setBrush predicate changes the current brush for a window. 10.1.3. Drawing ModesThe drawing mode specifies how pixels that are drawn on the screen combine with those already there. The drawing mode options are specified by constants of the vpiDomains::drawMode domain. Predefined vpiDomains::drawMode domain constants are vpiDomains::dm_*** . The normal drawing mode is vpiDomains::dm_CopyPen, which just sets the pixels to the new values. The vpiDomains::dm_XorPen or vpiDomains::dm_Not can be used to draw rubber bands, where the 'stretching' effect is created by drawing a rectangle, and erasing it by drawing a second time. The windowGDI::setDrawMode predicate sets a new drawing mode: WindowTypeObject:setDrawMode( vpiDomains::dm_CopyPen), 10.2. Color HandlingThe GUI package uses a standard RGB color model, which allows you to use 16 million colors. You compose your desired color from values specifying how much Red (0 to 255), how much Green (0 to 255) and how much Blue (0 to 255) you want in your color. As opposed to coloring by painting, which is termed subtractive because each added color blocks the color below, coloring by the use of light is termed additive because the color of each pixel on a display is specified by adding the intensity values for each of the primary colors: red, green, and blue. For example, if the minimum intensity on a given display was specified as 0 and maximum intensity was specified as 255, then a white pixel would be identified by the RGB triplet (255, 255, 255). A black pixel would be identified by the RGB triplet (0, 0, 0), and a cyan pixel would be identified by the RGB triplet (0, 255, 255). Compare this with painting scheme, when mixing of three zero power colors (0,0,0) creates a black pixel and mixing of three maximum power colors (255,255,255) creates something similar to a white pixel. Depending on your video adapter and printer you might actually only be able to display a smaller set of the possible colors. A monochrome printer will produce output in black and white. However, the underlying native GUI software provides advanced color mappings and color compositions, so if you try to draw 2000 rectangles each with a different color, the underlying GUI can often arrange the colors and pixels in these rectangles to approximate the 2000 different colors. On monochrome devices, shades of gray produced by printing of pixel patterns are used to represent colors. In the GUI package, colors are represented by the vpiDomains::color domain. Some ordinary used colors can be specified using vpiDomains::color_*** predefined constants. If you want to make your applications look good on monochrome equipment, you should use the color selections vpiDomains::color_Black , vpiDomains::color_DkGray , vpiDomains::color_Gray , vpiDomains::color_LtGray , and vpiDomains::color_White . There is an attribute vpiDomains::attr_have_color that can be used in vpi::getAttrVal to test if the display hardware supports color. Otherwise, make sure that the color contrast is large enough that two adjacent but different colors are not mapped to the same gray color. The predicate vpi::composeRGB can be used to create a color from the basic red, green and blue primary colors. The luminosity of each primary color is specified as an integer but only the low order 8 bits are significant, giving a range 0 to 255. The vpiCommonDialogs::getColor common dialog can be used to let the user choose any of the possible colors. Use the windowGDI::getPixelColor predicate to get color of the specified pixel in the current window. When choosing colors for window backgrounds, the application should use the default settings selected in the Windows Color scheme. For this purpose use the window::getAttrVal predicate with the vpiDomains::attr_color_window, which will return the current window client area background color, and other vpiDomains::attr_color_*** Windows Color scheme attributes. 10.3. Drawing FiguresThe following summarizes the available drawing operations. For displaying bitmaps the predicate windowGDI::pictDraw can be used. The windowGDI::drawPixel/2 predicate paints a given pixel with a given color. The reverse operation is performed by the windowGDI::getPixelColor predicate. It returns the color of the pixel at the logical coordinate pnt(X, Y) in the current window. To fill an area with the current brush use the windowGDI::drawFloodFill/2 predicate. This predicate fills the area of the current window with the current brush specified by the last called windowGDI::setBrush/1 predicate for the window. It fills the area specified with the StartPoint and bounded by the specified BoundaryColor color. The windowGDI::drawIcon/3 predicate retrieves an icon with the specified resource identifier ResourceIdentifier from the resource segment of the active application and draws it in the current window. The icon top left corner is placed at logical coordinates X, Y. Drawing Open FiguresThe following three predicates draw figures using with the current Pen specified to the current window by the last called windowGDI::setPen/1 predicate. The windowGDI::drawLine/2 predicate draws a line beginning in the StartPoint and finished in the EndPoint. The windowGDI::drawPolyline/1 predicate draws lines between points specified in the point list PointList. The windowGDI::drawArc/5 predicate draws an elliptical arc in the current window. Drawing Filled FiguresShapes around the drawn figures are drawn by the current pen. The drawn figures are then automatically filled by the current Brush. The windowGDI::drawEllipse/1 predicate draws an oval circle (ellipse) in the current window. The center of the ellipse is the center of the bounding rectangle BoundRectangle. The windowGDI::drawPie/5 predicate draws an elliptical arc in the current window. An ellipse that fits the specified bounding rectangle BoundRectangle defines the curve of the pie. The curve begins at the point where the ellipse intersects the first radial (passing through the Start point) and extends counterclockwise to the point where the ellipse intersects the second radial (passing through the Stop point). The windowGDI::drawPolygon/1 predicate draws a polygon in the current window. The system closes open polygons by drawing a line from the last point in the PointList to the first. The windowGDI::drawRect/1 predicate draws the specified rectangle Rectangle in the current window. The windowGDI::drawRoundRect/3 predicate draws a rectangle with rounded corners in the current window. 10.4. Drawing TextThe windowGDI::drawText/3 predicate draws specified String in the current window. The windowGDI::drawText/4 predicate draws the first DrawLength characters from specified String in the current window. XStart and YStart specify logical coordinates for the top left corner of the first letter in the string. The windowGDI::drawTextInRect function writes the first DrawLength characters from String within clipping Rectangle, using the currently selected font. The predicate can format text according to specified list Flags of flags vpiDomains::dtext_***. It is often important to know beforehand what the presentation size of a text string will be. The windowGDI::getTextExtent/4 predicate returns the Width, and Height of the rectangle that would contain the first Length characters from the specified String if they will be drawn using the font assigned to the current drawWindow object. String = "This is a test", GDIObject:getTextExtent( String, -1, Width, Height), The predicate windowGDI::setForeColor/1 sets the specified Color to be the foreground (drawing text) color and the predicate windowGDI::setBackColor/1 sets the background color for the drawing text in the current window object. Modes of Background Color Drawing in Text Drawing OperationsWhen drawing text in the current window with the windowGDI::drawText/3 and the windowGDI::drawText/4 predicates, the color used for drawing the text background depends upon the background color handling mode, which are specified by the last sub-item BackgroundColorDrawingWhileTextDrawing in the drawing tools vpiDomains::bk_mode structure. (See Drawing Tools.) If the current background color drawing mode is vpiDomains::bk_Opaque, then the drawText predicates first fill a rectangle occupied by a drawing string with the color specified in the last issued for the current window windowGDI::setBackColor/1 predicate, then the string is drawn. If the background color drawing mode is vpiDomains::bk_Transparent, then the drawText predicates draw the specified string on the existing background. To change the current background color drawing mode for text drawing, use the windowGDI::setBackMode/1 predicate. % Draw Text in White on Yellow background GdiObject:setForeColor(vpiDomains::color_White), GdiObject:setBackColor(vpiDomains::color_Yellow), GdiObject:setBackMode(vpiDomains::bk_Opaque), Text = "This is some text", X = 20, Y = 20, GdiObject:drawText(X, Y, Text), 10.5. Font HandlingEach window has an attached font, which is also used when drawing text. This font can be retrieved by the window::getFont predicate. This font can be changed by calling the window::setFont/1 predicate. In the GUI package, fonts belong to the vpiDomains::font domain, which is declared as ::binary domain. This means that fonts can be stored even in external files to be used later. It is not possible to inspect font settings directly but the predicate vpi::fontGetAttrs can retrieve the most necessary information. A font can either be selected by the user in a call to the common font dialog vpiCommonDialogs::getFont, or it can be created based on font family (one of the vpiDomains::ff_*** predefined constants of vpiDomains::fontFamily domain), font style (list of the vpiDomains::fs_*** predefined constants of vpiDomains::fontStyle domain), and font size by a call to the vpi::fontCreate or vpi::fontCreateByName predicates. Note that the font produced by these predicates is the best approximation that the current Windows version can make to the requested characteristics. In some cases, it might be of an unexpected size. This size however should not normally exceed the size requested, but that can happen too. You can change font style and font size for the given font with the vpi::fontSetAttrs predicate. Font size metrics for the current font can be obtained by using the vpi::winGetFontMetrics/4 predicate. 10.6. Coordinate SystemsIn the default logical coordinate system used in the GUI package, the X-axis goes from the left to the right, and the Y-axis from the top to the bottom. By default, the logical units will be screen picture elements (pixels), so that (0,0) would represent the top left corner pixel of a presentation space and lie just immediately inside the top left corner of the bounding rectangle. It is also possible to work with other logical units and coordinate mapping systems. When creating, moving, or re-sizing a child window, the coordinates are relative to the parent window (Parent Coordinates). In manipulations with task windows and dialogs, the coordinates are always relative to the screen (Screen Coordinates). When drawing or doing any similar operations in a window, the coordinates are relative to the window client rectangle (Client Coordinates). Sometimes, however, it is necessary to map coordinates in one window to those of another window, for this purpose there are the predicates window::mapPointTo and window::mapPointsTo that can convert coordinates which are relative to the current window into coordinates relative to another window object. When working with rectangles the values are always rct(Left, Top, Right, Bottom). Important Note: The GUI package does NOT specify rectangles as (X, Y, Width, Height). Normally coordinates are expressed in pixels, which is the right choice when working with editors, menus, trees, toolbars, etc. in windows on the screen. Screen resolution is usually misstated as numbers of pixels in the X and Y directions (e.g. 800 x 600 or 1024 x 768) whereas more properly it should be a measure of pixels per centimeter or inch. The "dot pitch" is the center-to-center distance of adjacent physical pixel areas, which of course is the inverse of the resolution. Higher resolution screens have greater numbers of pixels, enabling more information to be presented at the same time on the screen. Twice the resolution would permit presentation of 4 times as much information without loss of detail. However, each item displayed becomes a quarter the size, necessitating much closer viewing or a display with four times the area (twice the diagonal measurement of the active area). Mapping, Scaling, Panning and ZoomingThese words all cover the same basic operation - mapping. The mapping facility allows the application to change logical coordinate and scaling systems, which are converted and scaled by the GUI window system to the actual physical window coordinates, thus making the application device independent. A call to the predicate drawWindow::setMapMode/1 will change a window mapping mode. The possible mapping modes are determined by the vpiDomains::mm_*** predefined constants of the vpiDomains::mapmode domain. The default setting is always vpiDomains::mm_Text, so that the coordinates correspond to the actual pixels on the screen or printer. The vpiDomains::mm_Text is device dependent mode while the other modes are device independent. The Metric, English, and Twips mapping modes allow the application to work with actual physical sizes, which is especially useful for printing. The mode vpiDomains::mm_Arbitrary allows any range of logical coordinates to be mapped to the actual range of coordinates in a window. The drawWindow::setMapScale/4 predicate can be used to set up any scaling in logical coordinates. When the mapping mode is vpiDomains::mm_Arbitrary, the following coordinate transformations will be done for both the X and the Y coordinate: DevCoord = DevOrg + (LogCoord - LogOrg) * DevExt / LogExt Here LogCoord is the coordinate in logical units and DevCoord is the resulting coordinate in physical units. By changing only the DevOrg argument, it is possible implement panning (moving the origin of the coordinate system). By changing the DevExt and LogExt it is possible to scale and zoom a drawing. Using the same scaling on each axis gives Isotropic scaling, and using different scaling gives Anisotropic scaling. The drawWindow::convertDPtoLP and drawWindow::convertLPtoDP predicates can be used to convert device coordinates (screen pixels, printer or fax dots) to logical coordinates and back. This is useful because the values in the Mouse, Update events, etc. are always given in pixel coordinates. |
|
|