The Open Objects Library (OOL) - Course of Instruction

From EDM2
Jump to: navigation, search

(Samples)

Introduction

What is the OOL?

The Open Objects Library (OOL) is a C++ library which contains classes for the GUI, data types, multimedia, TCP/IP and more. To use OOL it is an advantage if you have some experience in programming using class libraries and/or OS/2-API functions.

Very nice tests of the OOL were presented in "c't magazin" 10/96 and "Software Entwicklung" 11/96.

Currently only the most modern operating system (OS/2 Warp 3 or higher) is supported.

First Steps

Creating a basic application

In some class libraries (and in earlier versions of the OOL) it is necessary to create an instance of an application class. This application class usually represents the current process and some service functions.

In this version of the OOL an application object is created automatically when the function main() of your application is reached. This is an instance of XApplication which can be accessed with the static function XApplication::GetApplication(). E.g. if you need a pointer to the application object you will code:

int main()
{
    //now we get a pointer to the
    //application object
    XApplication * currentApp = XApplication::GetApplication();
}

You need this pointer always to make the application work, it does not start itself because you must create a window or dialog (if you use windows or dialogs in your application) before the application starts to work. Having these things in mind a simple main-method looks like:

int main()
{
    //we get a pointer to the application object
    XApplication * currentApp = XApplication::GetApplication();

    //here a window is created
    myWindow * w(...);

    //now start the application
    currentApp->Start();
}

Creating the first frame window

Usually the first thing you want to do is to create a main window. The following source describes how to create an instance of the frame window class XFrameWindow:

XFrameWindow * myWindow = new XFrameWindow( /* parameters */ );

This part of code produces a frame window, but is not very useful: To handle events etc. it is necessary to overwrite some functions of the class XFrameWindow. Therfore you have to derive a class of XFrameWindow (the following code is available in the directory "hello1"):

class MyWindow: public XFrameWindow
{
    public:
        void Draw();
        BOOL DoCommand(LONG);
        MyWindow();
};

Here two methods are overwritten and a constructor for our new class is declared.

At first we have to create the constructor. In this case we create a simple window which has no menus or other controls except a little text, we give the window the window-id 100, the title should be "Hello World" and the style of the window should be the default frame window style.

MyWindow :: MyWindow( ): XFrameWindow( 100 , "Hello world!", XFrameWindow::defaultStyle)
{
    //at first we define a colour which is used to paint the background
    //we chose white as background colour
    XColor color(COL_WHITE);
    SetBackgroundColor( &color );

    //create a text
    //the owner of the text is this window, show the text "Hello World"
    XStaticText * text = new XStaticText( this,
                                          "Hello world!",
                                          XRect( 100, 100, 150, 20));

    //Set the position of this window
    //left-lower corner is 100,100, width is 300, height is 200
    XRect rect(100,100,300,200);
    SetSize(&rect);

    //make this window the active window
    Activate();
}

Now the overwritten methods must be implemented. The method Draw() is overwritten so we can draw the content of the window, the implementation of this methods is very simple:

void MyWindow :: Draw()
{
    //just fill the background, the text will draw itself
    FillBackground();
}

The method DoCommand() allows us to receive commands which are send from menus or toolbars, in this first sample we create this method, but don't use it, but we must return a value! We return FALSE to show the library that we have not handled the command.

BOOL MyWindow :: DoCommand(LONG com)
{
     //at this point we don't care about commands
    return FALSE;
}

At least we need a main-method in which our window is created and the application starts to work:

int main()
{
    //we get a pointer to the application object
    XApplication * currentApp = XApplication::GetApplication();

    //here a window is created
    MyWindow * myWin = new MyWindow();

    //no start the application
    currentApp->Start();
}

Error handling

Usually errors appearing while your program is running are shown by the return value of a member function, e.g. a function returns FALSE if a method was not successful or an error code is returned, e.g. if a file could be opened a method returns zero, if the file couldn't be opened the error code of the operating system is returned.

If a very serious error occurs, the OOL throws exceptions of the type XException (and derived classes), you can catch them in the usual manner:

try

{
    MyWindow * window = new MyWindow( /*parameters*/ );
    // do something with the window
}
catch( XException& exception)
{
    // show the error
    exception.ShowError();
}

More about frame windows

If you have compiled the last example and tested it, you may have seen that the text "Hello world" is not moved if the size of the frame window changes. Perhaps you want to display the text centred, in this case we need to handle the sizing of the frame window. At first we have to overwrite the method XFrameWindow::DoSize() (the code is available in the directory "hello2")

 class MyWindow: public XFrameWindow
{ 
     public:
         ......
         void DoSize(XSize *);
 };

and create an implementation:

 void MyWindow :: DoSize(XSize * size )
 {
 }

As you can see we have a little problem: we don't have a pointer to the text-object we created in the constructor of the frame window!

There are two methods to solve this problem:

1. Store the pointer

The simple way is to store the pointer to the object in the class MyWindow, simply declare a member like

    class MyWindow: public XFrameWindow
    {
        private:
            XStaticText * text;
        public:
            //methods
    };

Once created you access the text object about this pointer.

This method is simple and fast, but expensive: for ever window created on the frame window 4 bytes are used.

2. Use window-IDs

The second method is to give unique IDs for the child windows. Each window gets an ID in its constructor like

    XStaticText * text = new XStaticText( this,
                                          "Hello world!",
                                          XRect(),
                                          TX_CENTER,
                                          300);

(here the ID is 300). If the window is created you can access it with the member function GetWindow(), eg.

   XWindow * text = GetWindow(300);

or, if you have to make a typecast

   XStaticText * text = (XStaticText*) GetWindow(300);

This method is not so expensive like the first one but is a little bit slower (I prefer the second).

Warning: if a window does not exist with the requested ID GetWindow() returns NULL!

Assuming we chose the second method, the constructor of the text object must be changed to:

XStaticText * text = new XStaticText( this,
                                      "Hello world!",
                                      XRect( 100, 100, 150, 20),
                                      TX_CENTER,
                                      300);

The method DoSize() of the frame window looks like:

void MyWindow :: DoSize(XSize * size )
{
    XWindow * text = GetWindow(300);

    if(text) //is the text object created?
    {
        //calculate the new size/position of the text object
        XRect newSize;
        newSize.SetX( size->GetWidth() / 2 - 75 );
        newSize.SetY( size->GetHeight() / 2 - 10 );
        newSize.SetWidth( 150 );
        newSize.SetHeight( 20 );
        text->SetSize( &newSize );
    }
}

You may have noticed above that windows are always created with the operator new(). This is necessary because the destructor of a window-object is called automatically when the window is closed. The destructors of all child windows, menus and toolbars are called also. If you would code in the main()-function

int main()
{
    //....
    MyWindow( /* parameters */ );
    //....
}

the destructor of myWindow would be called two times: first time when the user close the window, second time when the main()-function will be left, an error occurs!

The same effect is reached if you create child windows in the constructor of the frame window like:

class myWindow: public XFrameWindow
{
    XEntryField entryField;
    public:
    //methods
};

MyWindow::MyWindow(): XFrameWindow(/* parameters */), entryField(/* parameters */)
{
    //other code
}

Here the destructor of entryField would be called two times too:

  1. The destructor of entryField is called automatically from the OOL when MyWindow is closed
  2. When MyWindow is destructed the destructor of entryField is called

Another error related to this problem:

MyWindow::MyWindow(): XFrameWindow( /* parameters */)
{
    XEntryField entryField(/* parameters */);
}

Here the destructor is called when the constructor of MyWindow is left!

To beware yourself from the described problems always use new(). Like described above you may store the pointer to created child windows in your frame window class or you can use the method GetWindow() to access child windows. Windows with menus and toolbars You usually want to give the user the chance to select some actions to be executed by your application. One method is to create a menu bar for your frame window. At the first step you create a menu in the program resource file (*.rc, see Toolkit-documentation for details). In the source code you can load this menu with two methods (the following code is available in directory "menu1"):

You give the frame window the same ID like the menu bar and specify the style FRM_MENU which shows the OS that a menu bar should be created.

This way is very simple:

    //we asume that the menubar has the ID 200
    MyWindow::MyWindow(): XFrameWindow( 200,
                                        "Hello world!",
                                        FRM_MENU | XFrameWindow::defaultStyle )
    {
    }

You may want to load the menu bar dynamically. This method has the advantage that you can chose at runtime which menu to load (e.g. if you have menu bars for multiple languages). In this case you code:

    //we asume that the menubar has the ID 200
    MyWindow::MyWindow(): XFrameWindow( 0, //ID is not nessacary
                                        "Hello world!",
                                        XFrameWindow::defaultStyle )
    {
        XMenuBar * menu = new XMenuBar(this, 200);
    }

(remember that the destructors of the menu bar are called automatically). Independent of which way you created the menu bar you need a method where you can handle the commands send from the menu bar when the user selected an item of the menu. Like described above you have to overwrite the method DoCommand():

class MyWindow: public XFrameWindow
{
    public:
    //methods
    BOOL DoCommand(LONG command);
};

The method DoCommand() allows you to handle the commands, the parameter command has the ID of the selected menu item. Usually you use symbolic names for the menu IDs, like

 #define IDM_MAIN      200  //the ID of the main menu
 #define IDM_FILE      210  //ID of the submenu "file"
 #define IDM_FILE_OPEN 211  //ID of the menu item "file-open"
 //and so on

Done so the commands can now be handled. Usually you create a "switch()"-block to determine which command was send:

BOOL MyWindow::DoCommand(LONG command)
{
    switch( command )
    {
        case IDM_FILE_OPEN:
            //handle the command to open a file
            break;
        case IDM_FILE_CLOSE:
            //handle the command to close a file
            break;
        default:
            return FALSE;   //show the library that we did not handle the command
    }
    return TRUE; //show the library that we have handled the command
}

Another way to allow the user to make some choices is to create a toolbar.

XToolBar * toolBar = new XToolBar( this );

Now you can add control-windows like buttons, combo-boxes etc. to this toolbar. If you add push-buttons to the toolbar every time this button is pressed a command with the ID of that button is send to the method DoCommand() of the frame window the toolbar belongs to. From this reason you must make sure to give the buttons unique IDs (it is a good idea to give a button the ID of the corresponding menu item).

Adding controls to toolbars is done with two steps: the first is to create the control with the toolbar as parent, the second step is to add the control with the toolbars method AddWindow():

MyWindow::MyWindow():XFrameWindow(/* parameters */)
{
    XToolBar * toolBar = new XToolBar( this );
    XPushButton * button = new XPushButton( toolbar,
                                         XRect( 0,0,40,20),
                                         IDM_FILE_OPEN,
                                         WIN_VISIBLE,
                                         "Test");
    toolbar->AddWindow( button );
}

More about windows

With the last section you are able to create a basic window, but the functionality of that window may be not enough complex for your purpose.

The next sections describe how to program windows in a more difficult way.

Event Handling

Often you need more information of the user's action than simple commands send by menus, e.g. you need to know if the user selected an item of a listbox or something else. This information is provided by events. The following sections describe the difference between commands and events and how to use events.

Events vs. Commands

At first we have to know the difference between events and commands:

  1. Commands are sent from menubars, popup-menus and push-buttons when the user selected a menu item or pressed a push-button. The only available information is the ID of menu item/push-button which was selected, this ID is send to the DoCommand()-method of the window the menu/button belongs too (in the case of toolbars the commands are sent from the toolbar to the owner window).
  2. Events are more complex: if the user edits the text of an entry-field or selects an item of a listbox an event is sent from the library which can be handled by the application.

The concept of handling events in the OOL

Like described above events are more complex than commands. The OOL has two types of events: simple events and complex events:

  1. A simple event occurs if the user edits a text or selects an item of a listbox. These events are encapsulated in the class XControlEvent, if an event occurs an instance of XControlEvent is sent to the method DoControl() of the owner window of the control window that send the event. To catch these events you need to overwrite this method.
  2. There are some more complex events you might to handle like keyboard-input, mouse-events or drawing events. For this events special event-classes are designed like XMouseEvent, XItemDrawEvent etc., to catch these events there are special handler classes needed: e.g. if you want to handle a XMouseEvent you need a XMouseHandler, for XKeyboardEvents a XKeyboardHandler is needed ...

Handling simple events

A simple event occurs when a control-window like a listbox is scrolled, an item is selected etc. To catch these events you need to overwrite the method DoControl() of the owner window.

void MyWindow::DoControl( XControlEvent * event)
{
}

As you see a pointer to a XControlEvent-object is sent to this method, from the event-object information about the event is available:

  • Which window send the event?

With the member method XControlEvent::GetWindowID() you access the window-ID of the window which send the event, with XControlEvent::GetWindow() you access a pointer to that window.

  • What type of event?

With XControlEvent::GetEventID() you receive the ID of the event. In XControlEvent this IDs are possible:

WIN_CHANGED 	the content of the client has changed
WIN_DBLCLICK 	the user double-clicked on the window
WIN_PAINT 	the window will be redrawn
WIN_ENTER 	the user pressed ENTER
WIN_SELECTED 	an item of the window was selected
WIN_VSCROLL 	the window scrolls it contents
WIN_HSCROLL 	the window scrolls it contents
WIN_SETFOCUS 	the window receives the focus
WIN_KILLFOCUS 	the window lost the focus
WIN_SHOWLIST 	the list of a XComboBox will be displayed
WIN_TRACK 	the user tracks the window (in XSlider)
WIN_ENDTRACK 	the user stopped tracking (in XSlider)
WIN_UPARROW 	the user pressed the arrow "up" (in XSpinButton)
WIN_DOWNARROW 	the user pressed the arrow "down" (in XSpinButton)
MEDIA_PLAYED 	a media-window has completed playing a file
MEDIA_PAUSED 	a media-window paused playing a file
MEDIA_STOPED 	a media-window stopped playing a file
MEDIA_REWINDED a media-window completed rewinding a file

The types of events you can catch are depending on the used controls, e.g. a simple entry field cannot send an event with the ID WIN_VSCROLL, events with the prefix MEDIA_ can only send by multimedia-windows (see below).

If we have created a frame window with some child windows like (the following source is available in directory "event1"):

MyWindow :: MyWindow(): XFrameWindow( /*parameters*/ )
{
    //create a combobox
    XComboBox * combo = new XComboBox( this, XRect(20,100,200,90),
                        IDC_LISTNAME, CB_DROPDOWNLIST | WIN_VISIBLE);

    //create a simple entry field
    XEntryField * entry = new XEntryField( this, XRect( 20, 60, 100, 20),
                          IDE_ENTRYNAME, ES_MARGIN | EN_LEFT | WIN_VISIBLE);
}

we can code the event handling for these child windows:

void MyWindow::DoControl( XControlEvent * event)
{
    switch( event->GetEventID())                        // what type of event?
    {
        case WIN_CHANGED:                               // the content of the window changed
            if( event->GetWindowID() == IDE_ENTRYNAME)  // in this case we are only interested
            {                                           // for the window with the ID IDE_ENTRYNAME
                XString buffer;
                event->GetWindow()->GetText( &buffer ); // here we read the new text of the window
                                                        // do something with the text
            }
            break;
        case WIN_SELECTED:                              // an item was selected
            if( event->GetWindowID() == IDC_LISTNAME)   // in this case we are only interested
            {                                           // for a combobox with the ID IDL_LISTNAME
                XString buffer;                         // WARNING: the following typecast is only
                                                        // allowed if you are sure, that the window
                                                        // IS a combobox
                ((XComboBox*) event->GetWindow())->GetText( &buffer );
            }
            break;
    }
}

Handling complex events

To handle complex events unfortunately more code is needed. Like described above for each type of complex event a pair of event class and event handler class is need like: XMouseEvent - XMouseHandler.

The handler-classes work is to catch events of the needed type: XMouseHandler can only catch events of the type XMouseEvent. Currently in the OOL following handler-classes (and related event classes) are available:

  • XBackgroundDrawHandler
  • XContainerHandler
  • XDragHandler
  • XKeyboardHandler
  • XItemDrawHandler
  • XMouseHandler
  • XNoteBookHandler
  • XStyleHandler
  • XTimer

To catch and handle events you have to overwrite the method HandleEvent() of an event handler class. E.g. if you want to handle mouse events for a window you code (source is available in directory "event2"):

class MyMouseHandler: public XMouseHandler
{
    public:
        MyMouseHandler( XWindow * w): XMouseHandler(w) {;}
        BOOL HandleEvent(XMouseEvent * ); // in this method our work will be done
};

The method HandleEvent() differs between the different handler classes by the type of event class given in the first parameter. Also the available information between the different event class changes (see documentation of the event classes). In this sample we receive about the method GetEventID() which mouse action is to handle:

BOOL MyMouseHandler :: HandleEvent(XMouseEvent * event)
{
    switch( event->GetEventID())        // which event?
    {
        case MOU_BTN1DOWN:              // left mouse button pressed
            XProcess::Beep( 200, 200);  // use the horn
            break;
        case MOU_BTN2DOWN:              // right mouse button pressed
            XProcess::Beep( 400, 200);
            break;
    }
    return TRUE; //show the library that we handled the event
}

At least we must attach the handler to the window for which events should be handled:

MyWindow::MyWindow(): XFrameWindow( /*parameters*/ )
{
    //init-code
    MyMouseHandler * handler = new MyMouseHandler(this);
}

Like child-windows, menus etc. the destructors of handlers are called automatically!

For all handler classes the way of coding is the same: Derive a class of the needed handler class, overwrite the constructor and the method HandleEvent(), code your event-handling in the method and attach the handler to the needed window. The difference between the events is the functionality of the event-classes and the event-IDs returned by these classes.

Creating frame windows from resources

One method to create frame windows is to create the frame window and its child windows at runtime like shown in the samples above: first you create a frame window, then the child windows are created dynamically.

Another way is to build a resource file with a dialog editor and then create the window from this resource file. In the resource file the description of the window looks like.

DLGTEMPLATE ID_MYINDOW LOADONCALL MOVEABLE DISCARDABLE
BEGIN
    DIALOG  "", ID_MYWINDOW, 31, 32, 153, 106, NOT FS_DLGBORDER | WS_VISIBLE
    BEGIN
        ENTRYFIELD      ", 106, 45, 86, 99, 8, ES_MARGIN
        LTEXT           "Name:", 105, 13, 86, 25, 8
        //other child windows
    END
END

This resource file will be compiled with the resource compiler and linked to the EXE file from which the window description can be loaded.

If you want to create a frame window this way the code looks like

MyWindow::MyWindow():XFrameWindow( ID_MYWINDOW,
                                   "Hello world",
                                   XFrameWindow::defaultStyle,
                                   XRect(),
                                   NULL,
                                   TRUE)
{
}

The last parameter send to the constructor of XFrameWindow shows the library to create the window from the resources (make sure that the used ID in the constructor is the same as used for the resource).

Another way to build frame windows from resources is that the resources are not linked to the EXE-file but are linked to a DLL (see your compiler documentation for details). In this case we need to use the class XResource. This class is used to identify a resource:

  1. the ID of the resource
  2. the "container" of the resource: in which DLL or executable the resource resides

E.g. if your application should support two languages you create two resource-DLLs, one for the first language, one for the second one. At runtime you decide which DLL to use:

    XResourceLibrary * resourceLib;

    if(language == ENGLISH)
        resoucelib = new XResourceLibrary( "english.dll" );
    else
        resoucelib = new XResourceLibrary( "german.dll" );
    XResource resource( ID_MYWINDOW, resourceLib);

A resource constructed this way you can pass to the constructor of XFrameWindow. If you have enabled the build-from-resource the frame window is loaded from the DLL you have specified (make sure that the ID of the resource is the same in the DLLs):

MyWindow::MyWindow( XResource * resource):XFrameWindow( resource, // <=
                                                        "Hello world!",
                                                        XFrameWindow::defaultStyle,
                                                        XRect(),
                                                        NULL,
                                                        TRUE)

Another method is to change the resource-library used by XApplication. The class XApplication creates a XResourceLibrary which holds the resources linked to the EXE-file. All resources which are identified only by their ID and not with a XResourceLibrary are loaded from the XResourceLibrary of the XApplication class. If we want to load all resources now from a DLL we can replace the XResourceLibrary used by XApplication with our resource-DLL

void main (void)
{
    XResourceLibrary * resourceLib;

    if(language == ENGLISH)
        resoucelib = new XResourceLibrary( "english.dll" );
    else
        resoucelib = new XResourceLibrary( "german.dll" );

    //we set the resource-library as the actual library for the application, all resources
    //will be loaded from this library
    XApplication::GetApplication()->SetResourceLibrary( resourceLib );

    //other code follows here
    ....
}

Warning: if you code this way all resources you use must be available in the loaded DLL. Usually bitmaps and icons are not language-dependent and should be linked only to the EXE-file. In this case you shouldn't replace the XResourceLibrary from XApplication and work with XResource instead.

More warnings: If you load a frame window from a resource-DLL and specify the style FRM_MENU you must make sure that the menu-resource is available in the loaded DLL. The settings you make in the dialog editor for the frame window are not used, the style of the frame is defined by the constructor settings. For frame windows defined by resources you have to disable in the dialog editor:

  1. system menu
  2. title bar

It is a very good idea to use dialogs instead frame-windows if you want to load your window from resources (see next section).

Dialogs

Dialogs vs. frame windows

As you have seen above frame windows are very flexible to use but sometimes a little bit complicated to code. The advantage of dialogs is that they are simple to use and work (sometimes) a little bit faster.

Usually a dialog is designed in a dialog editor, the dialog is then created from the resource file produced by the dialog editor. In the OOL you have the choice between modal and modeless dialogs (the following code is available in the directory "dialogs").

Modal Dialogs

Modal dialogs are working application-modal: if a modal dialog is open no other control except these from the modal dialog can be used.

To create a modal dialog the dialog must be defined in the resources, then it can be created:

// "this" is a pointer to a parent window
XModalDialog * dlg = new XModalDialog( IDM_MODALDIALOG, this);

(you can also create a dialog from a resource-DLL like described for frame windows). At this point the modal dialog exists but does not work, we have to call the Start()-method of the dialog:

LONG result = dlg->Start();

The great advantage of modal dialogs is that the code which follows after the call of Start() is not executed until the method Start() returns! (If you would create here a frame window the code would be executed when the constructor of the frame window is left).

When the method Start() is finished it returns the ID of the control window that finished the modal dialog, usually it will be a push button like "OK" or "Cancel", so you can decide how to continue. Warning: when the method Start() returns the dialog-object is destroyed! You cannot access windows of the dialog neither its data. From this reason you should derive your own class from XModalDialog and handle in the method DoCommand() the commands.

Modeless Dialogs

Unlike modal dialogs modeless dialogs are not modal %-), that means other windows can be used while the modeless dialog is open. To create a dialog (it must be defined in the resources) you simply cal the constructor:

XModelessDialog * dlg = new XModelessDialog( IDM_MODALDIALOG );

Usually you have to derive your own class from XModelessDialog and overwrite the methods DoCommand(), DoControl() etc.

More about control windows

In the samples above you have seen that usually it is very simple to use a control window like an entry field or a list box:

  1. create the control window with the new() operator or load a complete frame window/dialog from the resources
  2. init the control window, e.g. set the text or fill the list box
  3. if necessary use the DoControl()-method of the owner window to react to events send from the control window

As seen at the complex events you may also add an event handler class to a control window if more complex event should be handled, e.g. if you want to draw the content of a list box yourself.

OS/2 provides two types of control windows that are more difficult to handle: notebooks and container-controls.

Notebooks

Notebooks are especially used for settings of programs etc. A notebook contains one or more pages, at first you have to create the notebook itself, then add the pages.

1. The notebook can be created like any other control, you can use new() or define the notebook in the resources for a frame window or dialog (code is available in the directory "notebook"):

XNoteBook * noteBook = new XNoteBook( this,       // "this" is a pointer to the owner window
                                      XRect( 10,10,200,300), // position and size
                                      ID_NOTEBOOK,           // ID
                                      NB_TABBEDDIALOG|WIN_VISIBLE|NB_SOLIDBIND|
                                      NB_BACKPAGESBR|NB_SQUARETABS|
                                      NB_TABTEXTCENTER|NB_STATUSTEXTLEFT,
                                      "8.Helv");             // font to use

2. The second step is to fill the notebook with pages (you must not use a notebook without pages!). Pages are like dialogs defined in the resources of the application or can be created dynamically at runtime:

XNoteBookPage * page = new XNoteBookPage( noteBook,  // the owner of the page is the notebook-control
                                          ID_PAGE);  // the ID of the resource

Like described above notebook pages can be loaded from resource-DLLs like frame windows (see constructor for XNoteBookPage for details).

Notebook pages have the same functionality like dialogs/frame windows, if you need to catch events from controls belonging to the page you have to derive your own class from XNoteBookPage and overwrite the necessary methods like DoControl() etc.

If you want to add windows to a notebook page dynamically (using new()) you have to set the page to the top first:

page->SetTop();
XPushButton * button = new XPushButton( page,   //the page is the owner
                                        XRect( 60, 20, 70, 25),
                                        ID_OK,
                                        WIN_VISIBLE,
                                        "OK" );

As described above the simple events should be caught with the DoControl() method of the pages, complex events send from the notebook (e.g. if the user selects a page) are sent in the form of a XNoteBookEvent, to catch them you have to install a handler class (see above) of the type XNoteBookHandler.

Container controls

A container can be created like any other control using new() or resources, but it is more difficult to init them and fill them with data.

Creating the container

the first step is to create the container, you may define it in the resources for a window/dialog or create it dynamically (the following code is available in directory "contain", see "samples\sample4" for more detailed instructions programming container controls):

container = new XContainerControl( this, XRect(100,100,30,200),
                                   ID_CONTAINER, WIN_BORDER|WIN_VISIBLE);
Init

The container control provides different views like icon, text or tree view, other settings like size of icons/bitmaps etc. can made too. To make these settings the class XContainerInfo is used to query/set this information. Any of this information can be changed at runtime. To change settings of a container control you would code

XContainerInfo info( "Departments", CO_TREE | CO_TITLE | CO_TREELINE );

//we use only a very small icon
XSize size(16, 16);
info.SetBitmapSize( &size );
info.SetTreeBitmapSize( &size );

//enable the changes
container->SetInfo( &info);

As you see the container control is initialized with the XContainerInfo. If you would do so to change the container settings at runtime all settings would be overridden. To prevent this behaviour:

  • query the current settings of the container
  • change the settings you need

e.g.

XContainerInfo info;

//get the current settings
container->GetInfo( &info);

//we use only a very small icon
XSize size(16, 16);
info.SetBitmapSize( &size );
info.SetTreeBitmapSize( &size );

//enable the changes
container->SetInfo( &info);
Inserting columns (detailed view only)

If your container control is in detailed view you have to insert columns, the class XContainerColumn is used for this.

XContainerColumn * col1 = new XContainerColumn( container,   //the owner
                                                "Column 1",  //title of the column
                                                0,           //first column
                                                COL_HORZSEPARATOR | COL_STRING |
 COL_SEPARATOR, COL_LEFT |
 COL_FITITLEREADONLY |
 COL_HORZSEPARATOR | COL_TOP ); //settings
//insert the column
container->InsertColumn( col1 );

XContainerColumn * col2 = new XContainerColumn( container,   //owner
                                                "Column 2",  //title
                                                1,           //second column
                                                COL_SEPARATOR | COL_HORZSEPARATOR |
 COL_STRING, COL_LEFT | COL_FITITLEREADONLY |
 COL_HORZSEPARATOR | COL_TOP );
//insert the second column behind the first one
container->InsertColumn( col2, col1 );

//redraw the container
container->UpdateColumns();
Adding objects

Objects of a container are represented by the class XContainerObject. To add objects to a container control. The most simple method (eg. for icon- or text-view) is:

//create an object
XContainerObject * obj = new XContainerObject(container);

//give the object a title
obj->SetTitle( "Object 1" );

//you may want to see it with an icon
obj->SetIcon( &icon );

//add the object to the container
container->AddObject( obj );

//continue like above for all objects to insert
...

//finally let the container show the objects
container->InvalidateObject();

For container controls in tree view the shown method must be changed only at the Line AddObject(), here you often have to specify a parent object for the object to add:

//first create the parent object, eg. the root of the tree
XContainerObject * root = new XContainerObject(container); 

//create the next object
XContainerObject * child = new XContainerObject(container);

//add the second object as a child for the root
container->AddObject( child, root );

//continue like above

Container controls with detail view are more complicated. At first you have to specify in the constructor how many columns are used so the library can allocate the needed memory:

//we use four columns in this sample
XContainerObject * obj = new XContainerObject(container, 4);

Done so we have to set the data for each column:

obj->SetColumnData( 0, "Column 1"); //data for the first column
obj->SetColumnData( 1, "Column 2"); //data for the first column
//and so on

There are different overloads of SetColumnData() to set icons, text, dates etc. as the column data.

Handling container events

Complex container events are caught with a XContainerHandler, handle them as described above. In the case of drag/drop or direct editing (the user edits the text of a container object) special event classes are used (XContainerDragEvent/XContainerEditEvent), in the method HandleEvent() of your class of XContainerHandler you can typecast (see online documentation).

Retrieving container objects

To retrieve objects added to a container you can use the methods GetObject() and FindObject() from XContainerControl, the methods returns a pointer to XContainerObject (you can typecast if you know the class for sure).

Files and information about files

The most important method to save and restore data is to use files. This chapter describes how to use files with the OOL.

Open a file

To have read and/or write access to a file, you have to open it with the method XFile::Open(). With the different parameters of Open() you can specify the access-mode and how the file is shared with other processes. To open a file for read-write mode and lock the file for other processes you would code:

XFile file;
ULONG returnCode;

returnCode = file.Open( "c:\\config.sys",                      // the path of the file to open
                        XFILE_FAIL_IF_NEW|XFILE_OPEN_EXISTING, // if the file does not exist, return
                        XFILE_READWRITE,                       // read/write-access
                        XFILE_SHARE_DENYREADWRITE );           // lock the file

if( returnCode == 0) // ok, the file is open
{
      // perform your read/write actions here
}
else  // error occurred, see OS/2 online documentation
{     // for the meaning of returnCode

}

file.Close();

Read/Write from/to a file

To read/write data from/to a file the member functions Write() and Read() are available. These methods have multiple overloads for the different data classes like XString, XDate, XTime and for some "C"-data types like LONG, CHAR etc.

Assuming we have opened a file for read/write mode we can code

XDate date;
date.GetCurrentDate();

file.Write( date );

// to read a date:
file.Read( &date );

To read/write a XString it is a little more complicated because on default the terminating NULL of the string is not saved. A good way is to write/read strings in the "Pascal"-way if you use your own file format, that means you store the length of the string first, then the content of the string without terminating NULL:

XString string;

// here the length is stored as a char so the string must
// not be longer than 255 chars
file.Write( (CHAR) string.GetLength());
file.Write( string );

// and now read that string:
CHAR size;

file.Read( &size );
file.Read( string, size );

If you need to save the terminating NULL you would code:

XString string;

file.Write( string, string.GetLength() + 1);

Information about files

To get more information about a file or directory the class XFileInfo is used, you can use it

  • with the class XFileFind like described below
  • with the static member function XFile::GetPathInfo()
  • if you have opened a file with the method XFile::GetFileInfo()

With the method XFile::GetPathInfo() you simply code:

XFileInfo fileInfo;

XFile::GetPathInfo( "c:\\config.sys" );

To use XFile::GetFileInfo() you have to open the file first:

XFileInfo fileInfo;
XFile     file;

file.Open( "c:\\config.sys" );
file.GetFileInfo( &fileInfo );

Finding files

Often you need to find one or more files, e.g. find all files with the suffix ".cpp". To find these files the class XFileFind is used:

XFileFind fileFinder( "*.cpp" );

// we need two buffers to hold the information
XString    fileName;
XFileInfo  fileInfo;

// find all files
while( fileFinder.Find( &fileName, &fileInfo))
{
   // perform here your actions with the found files
   // in the buffer "fileName" the name of the found file is available
   // in the buffer "fileInfo" further information about the found
   // file is available (like size, attributes etc)
};

Process classes and process-communication

As described above XApplication is your main process class. It is created automatically when your program starts and is terminated and destroyed when your program ends. The class contains some service methods and the functions derived from XProcess.

The following section describes the other process related classes of the OOL: in the first part the usage of the thread class XThread will be discussed, then signal classes for processes (semaphores) are following. Finally the usage of classes for data exchange between processes is shown.

Threads

One advantage of modern operating systems is their multitasking concept. To use this concept in an application you have to use threads (for a detailed discussion of threads see OS/2 developer online documentation).

The OOL contains two tread-classes:

  • XPMThread
  • XThread

The threads differ in their usage, XThread is designed for a work which does not need access for windows or other graphical elements, a typical work for this class is handling pipes, files, sockets etc. XPMThread has full access to the window classes and can handle windows, receive messages etc.

A very popular ...is to print within a thread: while the document is printed the user can work with the printing application and don't need to wait for the printer. The following example shows how this job can be done:

class PrintThread: public XPMThread
{
   public:
      PrintThread( ) { }
      void Init()
      {
         XPrinterDevice printer;
         //initiate the printer here
         ....

         //create here objects to print
         .....
         printer.Draw();
         printer->ClosePrinterJob( );

         XMessageBox("document printed");

         //ugly, but necessary:
         delete this;
      }
};

BOOL MyWindow::DoCommand( LONG command)
{
   if(command == ID_PRINT)
   {
      PrintThread * pThread = new pThread();
      p->Run();
   }
   return TRUE;
}

As you see above the printing is done in the Init() - method of the thread class. This is the method (unlike the constructor of the thread class) where you have full control of the thread and where the class is executed as a single thread. The Init() - method is called automatically from the library, you must not call it yourself.

The second section of the sample shows how to create and start a thread-class. There are two critical points:

  1. The thread-instance must be created with the new() operator. If you would create the instance like
        PrintThread pThread;
    the destructor of the class would be called when the calling method is left.
  2. The thread must be started with the member method Run(), if you don't call it the thread is never started.

Semaphores

Pipes

Named Pipes vs. Unnamed Pipes

Unnamed Pipes

Named Pipes

Sockets

DDE

Enhanced topics

Multimedia

The OOL contains two classes for basic multimedia support:

  • XSound is a class to play WAV and MIDI files
  • XVideo can play digital videos (depending on which CODECs are installed)
  • XCDPlayer

For all classes MMPM/2 must be installed (on Warp 4 it is always installed).

To use the multimedia classes (see directory "sound" for source code):

  • create an instance of the needed class
  • load a file
  • use the methods Play(), Stop() etc.

The methods to play a file are usually derived from the class XMediaWindow, this methods send a notification code to the owning frame window when the action has finished, this notification codes can be caught in the method DoControl() of the frame window. E.g. if you have started to play a video in the DoCommand() method you receive an object of the type XControlEvent which has the ID MEDIA_PLAYED.

You must make sure that a created multimedia-object is destroyed before your application is finished!

Using the OOL and other class libraries

Objects of the OOL are tested with the Standard Template Library (STL). To use the data-objects XString, XDate and XTime with the STL the operators "<" and "==" and the copy-constructors are overwritten in these classes, you shouldn't have any problems to use this classes with sorted containers like set or bag (never, never put a window-object in a container!)