The Open Objects Library (OOL) - Course of Instruction

(Samples)

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.

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 this 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 *); }; </PRE> and create an implementation:  void MyWindow :: DoSize(XSize * size ) { } </PRE> 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 }; </PRE> 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); </PRE> (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); </PRE> 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 ); } } </PRE> 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 */ ); //.... } </PRE> 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 } </PRE> Here the destructor of entryField would be called two times too: Another error related to this problem:  MyWindow::MyWindow: XFrameWindow( /* parameters */) {   XEntryField entryField(/* parameters */); } </PRE> Here the destructor is called when the constructor of MyWindow is left!
 * 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

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 ) {   } </PRE> 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); } </PRE> (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); }; </PRE> 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  //and so on </PRE> 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 } </PRE> 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).
 * 1) define IDM_MAIN     200  //the ID of the main menu
 * 2) define IDM_FILE     210  //ID of the submenu "file"
 * 3) define IDM_FILE_OPEN 211 //ID of the menu item "file-open"

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 ); } </PRE>

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 users 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 informations are 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 send 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 send 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 send 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. This events are encapsulated in the class XControlEvent, if an event occurs an instance of XControlEvent is send to the method DoControl of the owner window of the control window that send the event. To catch this 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: eg. 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 this 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 send 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); } </PRE> we can code the event handling for these child windows: <PRE> 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; } } </PRE>

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: To catch and handle events you have to overwrite the method HandleEvent of a event handler class. Eg. if you want to handle mouse events for a window you code (source is available in directory "event2"): <PRE> class MyMouseHandler: public XMouseHandler {   public: MyMouseHandler( XWindow * w): XMouseHandler(w) {;} BOOL HandleEvent(XMouseEvent * ); // in this method our work will be done }; </PRE> 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: <PRE> 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 } </PRE> At least we must attach the handler to the window for which events should be handled: <PRE> MyWindow::MyWindow: XFrameWindow( /*parameters*/ ) {   //init-code MyMouseHandler * handler = new MyMouseHandler(this); } </PRE> Like child-windows, menus etc. the destructors of handlers are called automatically!
 * XBackgroundDrawHandler
 * XContainerHandler
 * XDragHandler
 * XKeyboardHandler
 * XItemDrawHandler
 * XMouseHandler
 * XNoteBookHandler
 * XStyleHandler
 * XTimer

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. <PRE> 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 </PRE> 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 <PRE> MyWindow::MyWindow:XFrameWindow( ID_MYWINDOW,                                  "Hello world",                                   XFrameWindow::defaultStyle,                                   XRect,                                   NULL,                                   TRUE) { } </PRE> 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: 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: <PRE> XResourceLibrary * resourceLib;
 * 1) the ID of the resource
 * 2) the "container" of the resource: in which DLL or executable the resource resides

if(language == ENGLISH) resoucelib = new XResourceLibrary( "english.dll" ); else resoucelib = new XResourceLibrary( "german.dll" ); XResource resource( ID_MYWINDOW, resourceLib); </PRE> 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): <PRE> MyWindow::MyWindow( XResource * resource):XFrameWindow( resource, // <=                                                       "Hello world!",                                                        XFrameWindow::defaultStyle,                                                        XRect,                                                        NULL,                                                        TRUE) </PRE> 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 theire 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 <PRE> void main ( void) {   XResourceLibrary * resourceLib;

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

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

//other code follows here .... } </PRE> 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: It is a very good idea to use dialogs instead frame-windows if you want to load your window from resources (see next section).
 * 1) system menu
 * 2) title bar

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 decribed 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: As seen at the complex events you may also add a event handler class to a control window if more complex event should be handled, eg. if you want to draw the content of a list box yourself.
 * 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

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 ore 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 send in the form of a XNoteBookEvent, to catch them you have to install a handler class (see above) of the type XNoteBookHandler.

Container controls
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.

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);
 * Creating the container

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 this 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 <PRE> XContainerInfo info( "Departments", CO_TREE | CO_TITLE | CO_TREELINE );
 * Init

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

//enable the changes container->SetInfo( &info); </PRE> 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: e.g. <PRE> XContainerInfo info;
 * query the current settings of the container
 * change the settings you need

//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); </PRE>
 * 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. <PRE> 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; </PRE>

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: 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: 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: <PRE> 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 ocured, see OS/2 online documentation {    // for the meaning of returnCode

}

file.Close; </PRE>

Read/Write from/to a file
To read/write data from/to a file the member functions Write and Read are available. This 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 <PRE> XDate date; date.GetCurrentDate;

file.Write( date );

// to read a date: file.Read( &date ); </PRE> 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: <PRE> 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); </PRE>

Information about files
To get more information about a file or directory the class XFileInfo is used, you can use it With the method XFile::GetPathInfo you simply code: <PRE> XFileInfo fileInfo;
 * with the class XFileFind like described below
 * wih the static member function XFile::GetPathInfo
 * if you have opend a file with the method XFile::GetFileInfo

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 ); </PRE>

Finding files
Often you need to find one or more files, eg. find all files with the suffix ".cpp". To find these files the class XFileFind is used: <PRE> XFileFind fileFinder( "*.cpp" );

// we need two buffers to hold the informations 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) }; </PRE>

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: 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.
 * XPMThread
 * XThread

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: <PRE> 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; } </PRE> 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.

Multimedia
The OOL contains two classes for basic multimedia support: For all classes MMPM/2 must be installed (on Warp 4 it is always installed).
 * XSound is a class to play WAV and MIDI files
 * XVideo can play digital videos (depending on which CODECs are installed)
 * XCDPlayer

To use the multimedia classes (see directory "sound" for source code): 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.
 * create an instance of the needed class
 * load a file
 * use the methods Play, Stop etc.

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!)