20. December 1997
© Stefan von Brauk, Thomas Bonk
The sourcecode shown in this this document is often avaible as a simple, but complete project, if so you find a hint in the text where to find the source.
Like described above this is a tutorial and shows only the basic usage of the OOL, in many cases the information provided here will not be enaugh for you. For further information see:
The Open Objects Library (OOL) is a C++ library wich contains classes for the GUI, datatypes, multimedia, TCP/IP and more. To use OOL it is an advantage if you have some experiance in programing using class-librarys 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.
In this version of the OOL an application object is created automaticaly when the function main() of your application is reached. This is an instance of XApplication wich 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 allways 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.
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(); }
XFrameWindow * myWindow = new XFrameWindow( /* parameters */ );This part of code produces a frame window, but is not very useful: To handle events etc. it is nessecary to overwrite some functions of the class XFrameWindow. Therfore you have to derive a class of XFrameWindow (the following code is avaible in the directory "hello1"):
class MyWindow: public XFrameWindow { public: void Draw(); BOOL DoCommand(LONG); MyWindow(); };Here two methods are overwriten and a constructor for our new class is declared.
At first we have to create the construtor. 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 color which is used to paint the background //we chose white as background color 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 implementated. 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 dont 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 dont 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(); }
If a very serious error ocures, 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(); }
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 dont have a pointer to the text-object we created in the constructor of the frame window!
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.
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!
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 allways created with the operator new(). This is nessacary because the destructor of a window-object is called automaticaly 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 ocures!
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:
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 allways 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.
//we asume that the menubar has the ID 200 MyWindow::MyWindow(): XFrameWindow( 200, "Hello world!", FRM_MENU | XFrameWindow::defaultStyle ) { }
//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); }
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.
#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 onDone so the commands can now be handled. Usualy 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 shure 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 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 ); }
void MyWindow::DoControl( XControlEvent * event) { }As you see a pointer to a XControlEvent-object is send to this method, from the event-object infomation about the event is avaible:
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.
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 recieves 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 stoped playing a file |
MEDIA_REWINDED | a media-window completed rewinding a file |
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 inerested { // for a combobox with the ID IDL_LISTNAME XString buffer; // WARNING: the following typecast ist only // allowed if you are shure, that the window // IS a combobox ((XComboBox*) event->GetWindow())->GetText( &buffer ); } break; } }
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 avaible:
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 differen handler classes by the type of event class given in the first parameter. Also the avaible information between the different event class changes (see documentation of the event classes). In this sample we receive about the method GetEventID() which mouse aktion 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 automaticaly!
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.
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 desciption 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 ENDThis resourcefile will be compiled with the resource compiler and linked to the EXE-file from which the window dscription 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 shure 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:
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 shure 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 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
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 resorces //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 avaible in the loaded DLL. Usualy bitmaps and icons are not language-dependent and should be linked only to the EXE-file. In this case you shouldnt 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 shure that the menu-resource is avaible 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 construtor settings. For frame windows defined by resources you have to disable in the dialog editor:
Usualy 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 modless dialogs (the following code is avaible in the directory "dialogs").
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,
usualy it will be a pushbutton 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.
XModelessDialog * dlg = new XModelessDialog( IDM_MODALDIALOG );Usualy you have to derive your own class from XModelessDialog and overwrite the methods DoCommand(), DoControl() etc.
OS/2 provides two types of control windows that are more difficult to handle: notebooks and container-controls.
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
XNoteBookPage * page = new XNoteBookPage( noteBook, // the owner of the page is the notebook-control ID_PAGE); // the ID of the resourceLike described above notebook pages can be loaded from resource-DLLs like frame windows (see constructor for XNoteBookPage for details).
If you want to add windows to a notebook page dynamicaly ( 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 notbokk (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 = new XContainerControl( this, XRect(100,100,30,200), ID_CONTAINER, WIN_BORDER|WIN_VISIBLE);
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 overriden. To prevent this behaviour
XContainerInfo info; //get the current setings 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);
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();
//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 .... //finaly 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 aboveContainer controls with detail view are more compilcated. 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 columnn:
obj->SetColumnData( 0, "Column 1"); //data for the first column obj->SetColumnData( 1, "Column 2"); //data for the first column //and so onThere are different overloads of SetColumnData() to set icons, text, dates etc as the column data.
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();
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 becaus 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 ist 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);
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 );
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 avaible // in the buffer "fileInfo" further information about the found // file is avaible (like size, attributes etc) };
The following secton describes the other process related classes of the OOL: in the first part the usage of the thread class XThread will be discused, then signal classes for processes (semaphores) are following. Finaly the usage of classes for data exchange between processes is shown.
The OOL contains two tread-classes:
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 nessacary: 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 automaticaly 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:
PrintThread pThread;the destructor of the class would be called when the calling method is left.
To use the multimedia classes (see directory "sound" for sourcecode):
You must make shure that a created multimedia-object is destroyed before your application is finished!