GUI Package

  1. Introduction
  2. GUI Package Structure
  3. Window Types
  4. Schematic GUI Package Usage
  5. The window Interface
  6. GUI Windows
  7. GUI Controls
  8. GUI Containers
    1. Dialogs
    2. Forms
    3. Layout of Controls in Containers
  9. Event Handling
    1. Event Models
    2. Two Types of Event Handlers Used in the GUI Package
    3. Event Listeners
    4. Event Responders
    5. Event Extensions
  10. Drawing Operations
    1. Drawing Tools
    2. Color Handling
    3. Drawing Figures
    4. Drawing Text
    5. Font Handling
    6. Coordinate Systems

1. Introduction

The 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 Package

GUI package provides the following features:

  • A set of usual GUI components: windows, dialogs, controls (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, custom controls).
  • An event handling system, that notices, when the user interacts with one of GUI components and passes this information to an application.
  • The concept of containers - objects, to which you can add controls (these are dialogs, custom controls, frame windows).
  • Layout managers that support sizing, positioning, and moving of GUI objects.
  • Support for graphic operations: draw arcs, fill polygons, clip a rectangle, etc.
  • A concept of extensions that allow controlling event processing, including whether default processing is performed or skipped.

The most important interfaces that make up the GUI package are shortly described in the next section.

2. GUI Package Structure

The 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:

  • whether a window has the title bar;
  • which title bar buttons the window title bar has;
  • which border kind a window has.

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 Types

Windows are classified by type. The GUI package supports the following window types:

  • Screen window
  • Task window
  • Document windows
  • Child windows
  • Controls
  • Container windows
  • Dialogs and Form windows

3.1. Screen Window

The 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 Window

The 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 File|Exit 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 Window

In 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. Controls

The 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 Windows

Dialogs 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 Windows

Sometimes 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 Usage

The basic idea is that you:

  • Create a default GUI project.
  • Declare some containers (dialogs or forms) having the Task window as their parent windows.
  • Declare and add some controls into these containers.
  • Add events by Code Expert and implement some event handling to control activities.
  • Show created on-screen GUI objects.
  • Interact with them and provide some program reactions.
  • Destroy on-screen GUI things (but created objects still exist and can be interacted programmatically).
  • Then the Visual Prolog engine will automatically release not used GUI objects and destroy them while garbage collection.

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 Interface

Object-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:

  • Location
  • Whether it is currently visible or invisible on the screen
  • Ability to accept input (enabled or disabled)
  • State handling
  • Adding listeners and setting responders
  • Handling timers

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 Windows

To 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).

  1. A window object creation.
  2. Initialization of the window object properties, drawing, layout management, adding event listeners, etc. (using set*, draw*, add*, etc. predicates).
  3. The window object visualization (show).
  4. User interaction with the on-screen window representation (invoke event listeners).
  5. The on-screen window representation destroyed.
  6. No visual component alive anymore but an object instance is still alive; therefore, programmatic interaction with the window object instance possible.
  7. The window object instance released (implicitly) and gathered by the garbage collector.

7. GUI Controls

The 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 Containers

In 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. Dialogs

Objects of the dialog type hold information about:

  • The dialog size and location.
  • Coordinate system to be used in the dialog.
  • The number of controls in it.
  • Tabstop order of controls.
  • Mnemonic accelerators.
  • The show() predicate, which will create and render on-screen things correspondent to the GUI objects in the dialog.

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:

  1. A dialog object creation (dialog::new/1).
  2. Creation of control objects in the dialog (control(s) new).
  3. Creation of the on-screen dialog visualization (window::show/0).
  4. In on-screen rendered dialogs you can create additional controls, which can be visualized with (control(s) show).
    Note, however, that new controls cannot be added directly to rendered modal dialogs. They should be added in dialog event handlers (listeners or responders).
  5. User interaction with the on-screen dialog representation (invoke event listeners and responders of dialog controls).
  6. Controls and the dialog visualization destroyed.
  7. No visual components exist anymore, but all object instances (dialog and controls) still exist; therefore, programmatic interaction with these objects is still possible.
  8. Dialog and control object instances are released (implicitly) and gathered by the garbage collector.

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. Forms

Forms are objects of the formWindow interface type. The formWindow interface supports the dialog and documentWindow interfaces and forms allow:

  • creating several controls inside them with positions, relative to form client rectangles;
  • managing these controls in the way that keyboard navigation is automated;
  • handling default pushbuttons;
  • handling mnemonic accelerators;
  • handling menus, etc.

There are the following main differences between dialogs and forms:

  1. Since forms are child windows of the program task window, then moving of forms are restricted to the client area of the application task window. Dialogs can be moved outside of the task window client area.
  2. In a MDI mode application. Document windows correspondent to forms created in the application will be listed in the Window menu item of the task window menu. So they can be arranged in the tile, cascade, and arrange icons formats.

In most other aspects, forms are the same as dialogs.

8.3. Layout of Controls in Containers

Normally 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.

  • Controls in a dialog (or form) can be grouped, if they will be created as child controls of a container control, which in turn must be a child of the dialog (form).
  • Each type of controls has some default size. This size is defined by the constants like:
standardWidth = 48. 
standardHeight = 12. 

defined in interfaces declaring controls. Notice that these constants are specified in assumption that all sizes are measured in the Dialog Base Units.

  • Dialog Base Units. By default, sizes of dialogs (forms) and controls are multiples of sizes of the font chosen for a dialog. By default, it is the system font, which might be changed by the user. This means that coordinates of dialogs and controls are scaled in the Dialog Base Units (DBU). When the system font changes, sizes of controls will be changed and a whole dialog will be scaled accordingly.
    The predicate vpi::getBaseUnits/2 returns the pixel sizes of the horizontal (width) #DBU_Width and the vertical (height) #DBU_Height dialog base units. They are calculated as the average width/4 and height/8 of the characters for the system font. These values should be used for positioning and layout of dialogs that depend on the system font.
    When the font different from the system is set to a dialog box, then the actual base units for the dialog (#BU_Width and #BU_Height) differ from the dialog base units retrieved by vpi::getBaseUnits/2 predicate. #BU_Width and #BU_Height are derived from the average width and height of characters in the given font. These values can be determined using the vpi::winDialogRatio/2 predicate:
    vpi::winDialogRatio(DialogWinHandle, Ratio), 
    Ratio = pnt(Width_Ratio, Height_Ratio),
    BU_Width = DBU_Width * Width_Ratio / 100,
    BU_Height = DBU_Height * Height_Ratio / 100,
    The programmer can define that dialog components should be scaled in pixels (instead of the default dialog base units). This can be done with the dialog::setUnit/1 predicate call like this:
    dialog::setUnit(vpiDomains::u_Pixels),
    This predicate should be called onto a dialog before the dialog graphical representation is rendered on-screen with the window::show/0 predicate.
    The dialog::getUnit predicates retrieves the coordinate system used in the current dialog object. It can be vpiDomains::u_DlgBase (if coordinates are in dialog base units) or vpiDomains::u_Pixels (if coordinates are in pixels).
    Notice that VDE dialog editors always work with dialog base units. Correspondently VDE code experts generating code to render designed dialogs assume that all items are measured in dialog base units. If you use the dialog::setUnit/1 predicate to specify that pixel (vpiDomains::u_Pixels) coordinates are used, then sizes of all items (and the default sizes also) in such dialogs become incorrect. Therefore, the programmer should manually write code, which will set the correct sizes for such dialogs and controls.
    Fortunately, the GUI package provides predicates which helps in this task. These are the dialog::pntUnit2Pixel, dialog::rectUnit2Pixel, etc. predicates to convert dialog base unit coordinates to device (pixel) coordinates.
    Remember that predicates like dialog::getPosition/2, control::setSize/2, etc. get/set sizes/positions measured in coordinate system currently used in a dialog. This can be retrieved by the dialog::getUnit predicate.

9. Event Handling

Unlike 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 Models

There 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:

  • "send each kind of events in a GUI component to a separate listening predicate especially registered for this kind of events in this instance of the GUI component object ".

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 Package

There 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:

  1. Event handlers of the first type are just listening to their events. The GUI system does not need any response from such event handlers and always provide the same standard handling to each event type processed by such event handlers. Therefore, it is possible do not provide such event handlers (and even default event handlers of such kind can do not exist). So, such event handlers can be called event listeners.
  2. On the other hand, event handlers of the second type are not only listening to their events, but also provide responses (return values) to the GUI system. The GUI system provides different default handling of a fired event depending on an obtained response. Therefore, such event handlers can be called event responders. Since the GUI system needs response from such event responders, so event responders must always exist (the GUI provides the predefined default event responders for such events).

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 Listeners

An 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 Listeners

In 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 Listener

Specified 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.
Now we can define clauses to this event listener. For example, like this:

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 Responders

As 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 Extensions

Sometimes 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
defaultExtension : documentWindowExtension. clauses
new(Parent):-
defaultExtension := getDefaultDocumentWindowExtension(),
documentWindow::newExtended(This,Parent). clauses
onFocus(Source):-
% Do something different
% ...
% Call default handling. Optional, but recommended
defaultExtension:onFocus(Source).
end implement myWindow

I.e. it is responsibility of the extension to invoke the default behavior.

 

10. Drawing Operations

There 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 Tools

It 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. Pens

Lines 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. Brushes

Figures 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 Modes

The 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 Handling

The 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 Figures

The 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 Figures

The 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 Figures

Shapes 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 Text

The 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 Operations

When 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 Handling

Each 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 Systems

In 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 Zooming

These 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.
Once a mapping mode has been set for a window, the application must give and will receive logical coordinates instead of the physical coordinates of the actual device. For example, if the logical X coordinate ranges from 10000 to 20000 and the logical Y coordinate ranges from 10000 to -10000, then mapping can be set to map these ranges into the actual physical pixel window coordinates. For example, with physical X ranging from 0 to 349 and physical Y from 0 to 249.

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.

 

Home | Company | News | Products Downloads | Shop | Support | Visual Prolog Features | Visual Prolog Compiler | FAQ | Tutorials | Examples | Knowledge Base | Discussion Forum | wiki | Site Map
 

Prolog Development Center A/S - H.J. Holst Vej 3-5C - 2605 Broendby, Denmark - Tel +45 3636 0000 - Fax +45 3636 0001 - sales@visual-prolog.com