MUI++ - Einsatz


von
Stephan Manske

Vor einigen Tagen mailte mich Carsten Jahn an und fragte, ob ich denn nicht was zum Thema objektorientiertes MUI-Programmieren in C++ schreiben könne, da ich mich ja in den Newsgroups mal danach erkundigt hatte. Tja nichts leichter als das, oder wie?

Nachdem ich zuerst überlegt hatte, worüber ich - um alles in der Welt - schreiben soll, fiel mir mein erstes Testprogramm ein, mit dem ich anfing MUI++ zu benutzen.

Doch zuerst mal ein bißchen allgemeines über MUI++:

1. Das Readme aus dem Aminet:

Short:    C++ wrapper classes for MUI 3.8

Author:   nick@carlton-castel.demon.co.uk

Type:     dev/mui



Allows the use of MUI 3.8 through C++ classes.

This has only been tested with the GCC compiler although it has been

written so that it should compile with other compilers.



Features:



 * Supports all attributes and methods of MUI 3.8

 * Support for NList, NListview, and HTMLtext custom classes

 * Template classes supplied for List, Listview, NList, NListview

 * Methods and attributes can be inlined for efficiency or linked with a

   link library for faster compilation.

 * Ability to convert to and from BOOPSI objects

 * Extra support for List classes including AddHead, AddTail, InsertTop,

   InsertBottom, Length.

 * Numeric classes have coercion to ints and longs

 * Lists can be treated like arrays

 * Strings and Texts have coercion to char *

 * Includes ARexx macros for generating main header file and header files

   for custom classes from autodocs.

 * Includes documentation in guide format

 * Is completely free!

2. Ein paar Worte von mir zu MUI++

(oder eine deutsche Variante der originalen Einleitung)

2.a. Was ist MUI++

MUI++ ermöglicht es C++-Programmierern MUI mittels normalen C++-Klassen zu benutzen. Dies geht meiner Meinung nach nicht nur intuitiver, sondern ermöglicht bsw. über Templates eine wesentlich sichere Typprüfung.

Deshalb sollten zwei Bedingungen vom Programmierer erfüllt werden:

2.b. Warum MUI++?

Obwohl MUI objektoriert ist, wird es in der Regel in einer prozeduralen Programmiersprache wie C benutzt. Was liegt also näher als MUI objektorientiert in einer objektorientierten Sprache wie C++ zu benutzen?

Mechanismen wie Vererbung innerhalb der MUI-Klassen sind dann z.B. auch in der Programmiersprache ausgedrückt, was durch eine Basisklasse (CMUI_Object) und deren Ableitungen realisiert wird.

Zum anderen ist MUI++ einfach wesentlich kürzer zu notieren als das klassische MUI. Beispiel:

Um einen Eintrag in eine Liste einzutragen, muß man in C folgendes schreiben:


      DoMethod (myList, MUIM_List_InsertSingle, "Hello world!",

                MUIV_List_Insert_Bottom);



In MUI++ genügt ein:



      myList.InsertSingle("Hello world!", MUIV_List_Insert_Bottom);



oder gar:



      myList.InsertBottom("Hello world!");



Wenn das kein Grund ist, zu wechseln? :-)

3. Programmbeispiel

So, nun kommen wir zu dem Beispielprogramm. Entstehen soll ein List- Fenster, in dem dynamisch Einträge eingefügt werden können und die Darstellung der Daten verändert werden soll.

Dabei muß noch angemerkt werden, daß ich den GCC benutze, und der hat ein paar kleine Probleme mit C++ und Hook-Funktionen, deshalb werde ich an diesen Stellen sowohl die bei mir funktionierende Version, als auch die ganz korrekte darstellen. Was die Funktionsdeklarationen von Hook- Funktionen für andere Compiler betrifft, kann ich leider nichts sagen.

Desweiteren sollten in fast allen Fällen Strings statt char* oder char[] benutzt werden, aber leider hat der GCC damit noch seine Probleme. :-(

Die Original-Kommentare aus dem von mir benutzten Programmrumpf habe ich belassen und zusätzlich - so gut es mir eben gelang - übersetzt.

Zunächst kommen noch ein paar Defines und so, die sollen uns aber nicht stören.


/* Simple example and test program to demonstrate use of template Listview

   class */



#define MUIPP_DEBUG         // Turn debugging mode on for invalid use of classes

//#define MUIPP_NOINLINES       // No inlines makes code compile quicker but the resulting

                            // executable is larger and slower. Best to use this

                            // option when developing and turn off for final release.

#define MUIPP_TEMPLATES     // Allows use of MUI template classes



// This is the main C++ header file



#include <libraries/mui.hpp>



#include <inline/exec.h>



Zuerst erstellen wir die Klasse für die Einträge



// These objects are inserted directly into the listview. Using template

// listview means that the objects are retrieved and inserted as Person

// objects (ie there is no need to convert to/from void *).



// Diese Objekte werden direkt in das Listview eingetragen. Da wir die

// Template-Version des Listviews benutzen, werden die Person-Objekte wie

// sie sind, benutzt und es ist kein Konvertieren von/zu void* nötig.



class Person

{

public:

    Person (const char *_name)

    {

        name = (char *)_name;

    }



    char *name;

};



//---------



Einige Deklarationen für die Libraries und die Hook-Funktionen.



typedef unsigned long (*HookFunction)();



struct UtilityBase *UtilityBase = NULL;

struct IntuitionBase *IntuitionBase = NULL;

struct Library *MUIMasterBase = NULL;



//---------  

Nun kommen die ersten interessanten Funktionen, nämlich die beiden, die für die beiden unterschiedlichen Darstellungen im Listview zuständig sind.

MUI benötigt dafür eine sogenannte Hook-Funktion, die ihre Parameter in speziellen Registern erwartet (welche findet man in den entsprechenden Autodocs).

// Hook function to display Person object in listview

Im Register a1 erwartet die Hook-Funktion, die die Darstellung in einem Listview steuert, den Datensatz, der dargestellt wwerden soll. Eben "Person".

In a2 soll der Programmierer einen Array von Char-Strings zurückgeben, der in jedem String den Eintrag für eine Spalte des Listviews enthalten soll. (Bei einer Liste von {Name, Vorname, Wohnort} müssen also drei Strings im Array sein, mit je einem der Daten.)

In der ersten Version werden nur die Namen aus "Person" in dieses Array eingetragen.




void DisplayPerson1 (void)

{

    register Person* a1 __asm("a1");

    Person* person=a1;



    register char** a2 __asm("a2");

    char** column=a2;



    static  char buf[20];



    strcpy(buf,person->name);



    *column = buf;

    return;

}    

In der zweiten Version kommt vor den Namen noch eine laufende Nummer. Dafür ist die Zeile...

sprintf(buf1,"%ld",(column[-1]+1));

...zuständig. Da das von MUI erwartete (und bereitgestellte) Array an der Position -1 die laufende Nummer mit Null beginnend hinterlegt, muß diese nur noch um eins erhöht werden.




void DisplayPerson2 (void)

{

    register Person* a1 __asm("a1");

    Person* person=a1;



    register char** a2 __asm("a2");

    char** column=a2;



    static  char buf2[20],buf1[20];



    strcpy(buf2,person->name);

    sprintf(buf1,"%ld",(column[-1]+1));





    *column++ = buf1;

    *column   = buf2;

    return;

} 

Zuletzt werden die Hook-Funktionen noch in die Amiga-Struktur HOOK eingebunden. Diese HOOKs werden nachher übergeben, wenn eine Hook- Funktion aufgerufen werden soll.


struct Hook displayHook2 = {{NULL, NULL}, (HookFunction)DisplayPerson2, NULL, NULL};

struct Hook displayHook1 = {{NULL, NULL}, (HookFunction)DisplayPerson1, NULL, NULL};



//---------

So, nun können wir MUI sagen, wie es unsere Einträge darstellen soll, damit wäre die Arbeit doch getan!?

Wie, ich habe was von wechselnder Darstellung geschrieben? Und woher soll MUI wissen, wann und wie es umschalten soll?

Okay, okay. Also zuerst einmal, wie soll MUI umstellen? (Das wann kommt später, wer in den letzten Kursen aufgepaßt hat, der wird wahrscheinlich sowieso schon an die Notifys denken.)

void ChangeList (void)

{

    register CTMUI_Listview<Person>* a2 __asm("a2");

    CTMUI_Listview<Person≶* liste=a2;



    if (liste->Format()!="BAR,")

      {

        liste->SetDisplayHook(&displayHook2);

        liste->SetFormat("BAR,");

      }

    else

      {

        liste->SetDisplayHook(&displayHook1);

        liste->SetFormat("");

      }

}



Das sieht doll aus, wah?  :-) So richtig C++-like.



Also was passiert da:



Zuerst wird wieder ein Register übergeben, ist ja auch eine Hook-Funktion.



Jede MUI-Liste enthält ein Format-Datum, welches unter anderem die Anzahl

der Spalten enthält. In unserem Fall wird geprüft, ob das Format NICHT wie

folgt aussieht:



  xxx | yyy

  zzz | ttt

  aaa | eee



"BAR" steht für eine Trennlinie, das Komma für eine zweite Spalte.



    if (liste->Format()!="BAR,")



Wenn dem also nicht so ist, wird die neue DiplayHookFunktion ausgewählt

(eben die mit den zwei Spalten und das Format wird auf zwei Spalten

umgestellt.



        liste->SetDisplayHook(&displayHook2);

        liste->SetFormat("BAR,");



Haben wir allerdings schon zwei Spalten, so wird die einspaltige Display-

Funktion gewählt und das Format einspaltig gemacht.



        liste->SetDisplayHook(&displayHook1);

        liste->SetFormat("");





Soweit zur Theorie, in GCC geht das leider nicht so einfach, da C++-

Konstruktionen in Hook-Funktionen nicht unterstützt werden.



void ChangeList (void)

{

    register CTMUI_Listview<Person>* a2 __asm("a2");

    CTMUI_Listview<Person>* liste=a2;



    char* list_format;



Hier muß zuerst das MUI-Attribut "MUIA_List_Format" auf konventionellem

Weg aus dem MUI_Listview geholt werden.



    GetAttr(MUIA_List_Format,(Tag)liste,(ULONG)&list_format);



Und alle MUI_Attribute müssen via SettAttrs gesetzt werden:



    if (list_format!="BAR,")

      {

        SetAttrs((Tag)liste,MUIA_List_DisplayHook,(ULONG)&displayHook2,TAG_DONE);

        SetAttrs((Tag)liste,MUIA_List_Format,(ULONG)"BAR,",TAG_DONE);

      }

    else

      {

        SetAttrs((Tag)liste,MUIA_List_DisplayHook,(ULONG)&displayHook1,TAG_DONE);

        SetAttrs((Tag)liste,MUIA_List_Format,(ULONG)"",TAG_DONE);

      }

}



Zuletzt erfolgt in beiden Fällen das Eintragen in einen HOOK:



struct Hook changeListHook = {{NULL, NULL}, (HookFunction)ChangeList, NULL, NULL};



//---------



Um zu zeigen, daß auch fern ab von jeder graphischen Darstellung die

MUI-Klassen voll funktionsfähig in C++ sind, habe ich diese Funktion

geschrieben. Sie stellt den Inhalt des Listviews in einem CLI-Fenster

dar und beim Programmieren müssen wir uns nicht darum kümmern, daß wir

mit MUI++-Klassen arbeiten.



Nebenbei: auch hier wären C++-Konstrukte wie "cout" zur Ausgabe sinnvoller.



// Although the listview is passed by value it is actually passed by reference

// always. This is because the class is only a wrapper to the BOOPSI class

// and only has one attribute - the BOOPSI object pointer. Hence, it is the

// equivalent of passing an Object * on the stack.



void

PrintPersonList (CTMUI_Listview<Person> list)

{

    // You can use Length() or Entries() to get the length of a list



    int numPeople = list.Length();



    printf ("Number of people in list = %d\n", numPeople);



    // You can treat Listviews just like arrays!!



    for (int i = 0; i < numPeople; i++)

    {

        printf ("%d %s\n", i, list[i].name);

    }

}



//---------





Tatata! Und schon kommen wir zum Hauptprogramm:



int

main (void)

{



Zuerst werden die Libraries geöffnet.



    // Open libraries required



    if ((IntuitionBase = (struct IntuitionBase *)OpenLibrary ("intuition.library", 0)) == NULL)

    {

        printf ("Could not open intuition.library\n");

        return 10;

    }



    if ((MUIMasterBase = OpenLibrary ("muimaster.library", 0)) == NULL)

    {

        printf ("Could not open muimaster.library\n");

        return 10;

    }



So jetzt kommen wir zur ersten MUI++-Klasse, dem CTMUI_Listview.



Kurz was zur Nomenklatur, die MUI++-Klasse heißt so, wie ihr MUI-

Gegenstück mit dem Zusatz "CMUI_". Handelt sich um eine Templete-

Version heißt sie "CTMUI_...".



Also, wir wollen uns ein Listview erstellen und nehmen also CTMUI_Listview.

Und da es um "Person" geht: CTMUI_Listview<Person>.



    // Declare template Listview of type Person to display list of people

    // NOTE: This does no initialization, it's just a declaration.



    CTMUI_Listview<Person> list;



Dazu kommen dann die übliche MUI-Klassen wie Fenster, Slider, Buttons:



    CMUI_Window window;



Achtung, hier wurde eben nur deklariert, nicht definiert!



    CMUI_Slider mySlider (MUIA_Numeric_Min, 0,MUIA_Numeric_Max, 100,MUIA_Numeric_Value,30,TAG_DONE);

       

Die ganz normalen MUI-Tag-Notation geht also auch hier.



    CMUI_Button myButton ("_Ok");



Andererseits gibt es auch spezielle Konstruktoren für (leider erst) einige

Klassen, die den Umgang noch einfacher machen.



    // Create Application object. I am not using any shortcuts here to create

    // the objects. I actually prefer the layout like this than when using

    // shortcuts. If you prefer the old way of creating objects by using the

    // shortcuts then you can still do this. See the shortcuts.cpp example

    // for details as some shortcuts have had to change name so as not to clash

    // with class member functions.

Nun wollen wir die MUI-Applikation erstellen. Einige Objekte tragen wir nur noch ein, andere erzeugen wir an Ort und Stelle.


    CMUI_Application app

    (

        MUIA_Application_Title,         "TListview",

        MUIA_Application_Author,        "Nicholas Allen",

        MUIA_Application_Base,          "TEST",

        MUIA_Application_Copyright,     "AllenSoft",

        MUIA_Application_Description,   "Test Program For Template Listview class",

        MUIA_Application_Version,       "$VER: Test 1.0 (17.9.96)",

        SubWindow, window = CMUI_Window

        (

            MUIA_Window_Title, "Test Program For Template Listview class",

            MUIA_Window_ID, 10,

            WindowContents, CMUI_VGroup

            (

                Child, list = CTMUI_Listview

                (

                    MUIA_Listview_List, CMUI_List

                    (

                        MUIA_List_DisplayHook, &displayHook1,

                        InputListFrame,

                        TAG_DONE

                    ),

                    MUIA_CycleChain, 1,

                    MUIA_ShortHelp, "Listview created using templates!!",

                    TAG_DONE

                ),



                Child, mySlider,Child, myButton,

                TAG_DONE

            ),

            TAG_DONE

        ),

        TAG_DONE        // Don't forget these if you're not using shortcuts!

    ); 



Dann überprüfen wir, ob alles geklappt hat:



    // Any MUI object created as a C++ class can be tested for validity by

    // calling its IsValid() method. This method just checks that the

    // BOOPSI object pointer is not NULL.



    if (!app.IsValid())

    {

        printf ("Could not create application!\n");

        return 10;

    }  



Nun fügen wir einige Personen in bester C++-Manier ein:



    // Insert some new people into the listview!!



    list.InsertBottom(new Person ("Nick"));

    list.InsertBottom(new Person ("Dom"));

    list.InsertBottom(new Person ("Mart"));

    list.InsertBottom(new Person ("Nicky"));



    // This only copies 4 bytes onto stack!! It is the same as passing a

    // BOOPSI Object *



Und drucken diese in das CLI-Fenster:



    PrintPersonList (list);



    // Setup close window notification.

    // Because Notify() is a variable args method we have to pass sva as the

    // first parameter. Failing to do this will result in an error at

    // COMPILE time so there won't be any weird crashes by forgetting to do

    // this.



    // Nur soviel: Um Notify als Member-Funktion aufzurufen, benötigen

    // wir ein dummy namens sva, das von MUI++ mitgeliefert wird.

    // Ansonsten läuft das Eintragen in ein Notify wie bei MUI.

    // Das erste Objekt wird über das aufrufende Objekt definiert.





Zuerst ändern wir das das Listview-Layout das erste Mal:



   myButton.Notify(sva, MUIA_Pressed, false,

                    list, 2,MUIM_CallHook, &changeListHook);



Standart-Notify für das Beenden der Applikation:



    window.Notify(sva, MUIA_Window_CloseRequest, TRUE,

                  app, 2, MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);

              

Wir öffnen das Fenster:



    window.SetOpen(TRUE);



Und nun fügen wir dynamisch eine weitere Person ein, mich. :-)



    list.InsertBottom(new Person ("Stephan"));



Zum Schluß kommt die Schleife, in der das Programm verharrt, bis es beendet wird. Auch hier vereinfacht die C++-like-Schreibweise das ganze. Bsp:



        switch (app.NewInput(&sigs))



Und im Zusammenhang:



    ULONG sigs = 0;

    BOOL running = TRUE;



    while (running)

    {

        switch (app.NewInput(&sigs))

        {

            case MUIV_Application_ReturnID_Quit:

                running = FALSE;

            break;

        }



        if (sigs)

        {

            sigs = Wait (sigs | SIGBREAKF_CTRL_C);

            if (sigs & SIGBREAKF_CTRL_C) break;

        }

    }



    // This disposes of the application and all windows and objects in the

    // windows.



    app.Dispose();



    CloseLibrary ((struct Library *)IntuitionBase);

    CloseLibrary (MUIMasterBase);



    return 0;

} 



Fertig? FERTIG!!!

Zum Abschluß möchte ich noch anmerken, daß auch das Generieren von Headern für eigene MUI-Klassen, die sich dann wunderbar in die Vererbungslinie einpassen, mittels AREXX-Scripten unterstützt wird. Nur wie, weiß ich noch nicht genau, aber sobald ich mich da rein gearbeitet habe (und eigene MUI- Klassen geschrieben habe) werde ich diesen Kurs sicher fortsetzen!

Ein letztes noch zum Include-File "mui.hpp":

Da "mui.hpp" natürlich "mui.h" und andere System-Libs einbindet, muß dieses ab der 170. Zeile von "mui.hpp" in ein 'extern "C" { .... };' gekapselt werden, zumindestens bei mir, wer also Probleme hat, versuche dies:



#ifndef MUI_NOSHORTCUTS

#define MUI_NOSHORTCUTS

#endif



extern "C" {



#ifndef LIBRARIES_MUI_H

#include <libraries/mui.h>

#endif



// Include prototypes for MUI and BOOPSI function calls



#ifdef __GNUC__

#include <inline/muimaster.h>

#include <inline/intuition.h>

#else

#include <clib/muimaster_protos.h>

#include <clib/intuition_protos.h>

#endif



#include <clib/alib_protos.h>



};


Wenn es noch Fragen gibt, mailt mich an! (Adresse s.oben)


Prev Inhaltsverzeichnis Next
©`98 Der AmZeiger