Toolbar erstellen (+ Erstellen/Löschen von Buttons...)



  • Hallöchen,

    Ich hab im FAQ für WinAPI nachgesehen und nur einen Thread zu Toolbars gefunden, der auch nicht sonderlich aufklärend in der Richtung ist. Also dachte ich mir, ich mach einen FAQ-Thread für dieses Thema, Toolbars.
    Ich hab mich auch erst kürzlich darauf eingearbeitet und denke nun sind die Unklarheiten soweit beseitigt, dass ich die Toolbars nun einigermaßen für alle Nachfolgenden erklären kann, die in meiner Situation vor ein paar Tagen sind. Ich hoffe es genügt den Ansprüchen, dass es ins FAQ-Forum verschoben werden kann. 🙂

    Erstellen der Toolbar
    So, fangen wir an. Das Erste was wir machen müssen ist, die Toolbar erstellen und einem Fenster zuweisen:

    HWND tb_create( HINSTANCE instance, HWND hwnd, int style )
    {
        InitCommonControls();
    
        //Create window for toolbar, parent is current window
        HWND hwnd_toolbar = CreateWindowEx(  0,
                                        TOOLBARCLASSNAME,
                                        (LPSTR) NULL,
                                        WS_CHILD | style,
                                        0, 0, 0, 0,
                                        hwnd,
                                        (HMENU) 1,
                                        instance,
                                        NULL
                                    );
        if (!hwnd)      //Continue if creating toolbar was successfull
        {
            return NULL;
        }
    
        //Add Standard-Buttons to toolbar's Image-List
        TBADDBITMAP bitid;
        bitid.hInst = HINST_COMMCTRL;
        bitid.nID = IDB_STD_SMALL_COLOR;
        SendMessage(hwnd_toolbar, TB_ADDBITMAP, 1, (long)&bitid);
    
        SendMessage(hwnd_toolbar, TB_BUTTONSTRUCTSIZE, (WPARAM) sizeof(TBBUTTON), 0);       //Backward compatiblity
        ShowWindow(hwnd_toolbar, SW_SHOW);                                                  //Show Toolbar!
        return hwnd_toolbar;
    }
    

    Das ist auch schon alles, um eine Toolbar erst mal bereit zu stellen. Im ersten Abschnitt wird das Fenster mit der entsprechenden Klasse erstellt, ein Parent-Window muss angegeben sein. Style muss immer mindestens WS_CHILD sein. Zusätzliche Styles lassen sich per Parameter übergeben. Im zweiten Teil fügen wir mittels TB_ADDBITMAP-Message schon mal vorsichtshalber die Standard-Buttons zur Image-Liste der Toolbar hinzu. Warum dies geschieht, werd’ ich nachher erklären. Der Rückgabewert gibt das Handle auf die erstellte Toolbar zurück, welches benötigt wird, um zukünftige Button-Aufgaben zu erledigen.

    Erstellen der Buttons
    Nun wird man noch nichts sehen, es fehlen die Buttons. Also schreiben wir eine Funktion, welche Buttons hinzufügt:

    void tb_add_button(HWND hwnd_toolbar, int command, char* bitmap_filename, int style = TBSTYLE_BUTTON)
    {
        if (hwnd_toolbar != NULL)       //Continue only if there is a toolbar
        {
            //Bitmap einladen
            HBITMAP h_bitmap;
            h_bitmap = (HBITMAP)LoadImage(  NULL,
                                            bitmap_filename,	  // name or identifier of image
                                            IMAGE_BITMAP,	      // type of image
                                            16,	                  // desired width
                                            16,	                  // desired height
                                            LR_LOADFROMFILE		  // load flags
                                         );
    
            //fill ADDBITMAP-Structur and overgive Bitmap-Handle
            TBADDBITMAP bitid;
            bitid.hInst = NULL;
            bitid.nID = (UINT)h_bitmap;
    
            TBBUTTON tbbutton;      //Create TBBUTTON-Structur for saving infos about button which is to add
            tbbutton.iBitmap = SendMessage(hwnd_toolbar, TB_ADDBITMAP, 1, (long)&bitid);   
            tbbutton.idCommand = command;       //Command-Parameter to Command-Message to recognize clicks on Toolbar-Button
            tbbutton.fsState = TBSTATE_ENABLED;
            tbbutton.fsStyle = style;
            tbbutton.dwData = 0; 
            tbbutton.iString = NULL; 
    
            //At last, add the button to the Toolbar
            SendMessage(hwnd_toolbar, TB_ADDBUTTONS, (WPARAM) 1, (LPARAM) (LPTBBUTTON) &tbbutton);
        }
    }
    

    Diese Funktion ist dafür gedacht, eine Bitmap-Datei einzuladen und auf den Button zu packen. Man kann es natürlich erweitern und auf Icons zuschneiden.
    Zuerst wird also die Bitmap eingeladen, das Handle merken wir uns für später.
    Dann wird eine Struktur ADDBITMAP gefüllt, welche nötig ist, um das Bitmap zur Image-Liste hinzuzufügen. Wir wählen NULL als Instance-Member und das Handle des Bitmaps als nID-Member.
    Danach wird eine Struktur für den Button angelegt, die TBBUTTON Struktur.
    Mittels TB_ADDBITMAP fügen wir schließlich das geladenen Bitmap, bekannt aus der ADDBITMAP-Struktur der Image-Liste hinzu. Der Rückgabewert der Message ist das wichtige Index, welches wir unbedingt speichern müssen, in „iBitmap“ von TBBUTTON. Dann noch Style, Status und Command-Message festlegen. Letzteres ist die ID, welche beim Drücken auf den Toolbar-Button im LOWORD des wParam von WM_COMMAND zu finden ist. Also wichtig um nachher erkennen zu können, welcher Button gedrückt wurde.
    Schließlich kommt die TB_ADDBUTTONS-Message um den Button mit allen Infos aus den Strukturen hinzuzufügen.

    Was ist diese Image-Liste?
    Vielleicht fragt ihr euch, was diese Image-Liste ist, die da erwähnt wurde? Das ist sozusagen eine interne Liste in der die verfügbaren Bitmaps, Icons, also die Grafiken für die Toolbar gespeichert werden. Nur Grafiken aus dieser Liste können als Image für die Buttons genommen werden. Das ist auch der Grund, warum, wir beim Erstellen der Toolbar oben schon die Standard-Buttons zur Image-Liste hinzugefügt haben. Damit wir diese auch nutzen können, müssen sie auch in der Liste stehen.

    Warum haben wie die Standard-Button-Images zuerst zur Image-Liste hinzugefügt?
    Wegen den Konstanten. Die Standard-Images können mit den Konstanten wie STD_COPY angewählt werden. Diese Konstante beinhaltet einen Index für die Image-Liste der Toolbar, welcher nur stimmt, wenn die Standard-Images auch an erster Stelle in der Image-Liste stehen. Die Images werden also der Liste hinzugefügt und erhalten dann einen Index, 0, 1, 2, 3... usw. Die Konstanten beinhalten direkt diese Nummer und man kann dadurch ein Image in der Liste ansprechen. Sind diese Konstanten-Indexnummern aber bereits durch eigene eingeladenen Buttons belegt, stimmen die Konstanten nicht mehr. Etwa STD_COPY hat den Wert 1. Haben wir nun schon 2 Images hinzugefügt, so ist die Nummer 2 im Index der Image-Liste bereits durch ein eigenes Bild belegt. Wir sprechen mit der Konstante für das Standard-Bitmap also plötzlich ein ganz anderes an, als gewollt. Deshalb haben wir die Images der Standard-Buttons direkt vor allen anderen Images in die Liste geladen.

    Die Standard-Buttons
    Jetzt können wir uns noch Funktionen schreiben, welche die Standard-Buttons hinzufügt. Dies ist wesentlich einfacher, da wir das Index des Images bereits durch die Konstante kennen und auch keine weiteren Images zur Image-Liste hinzufügen müssen, sie sind ja schon drinnen, seit Erstellung der Toolbar.

    void  tb_add_std_button(HWND hwnd_toolbar, int command, int std_ID, int style = TBSTYLE_BUTTON)
    {
        if (hwnd_toolbar != NULL)       //Continue only if there is a toolbar
        {
    
            TBBUTTON tbbutton;      //Create TBBUTTON-Structur for saving infos about button which is to add
            tbbutton.iBitmap = std_ID;
            tbbutton.idCommand = command;       //Command-Parameter to Command-Message to recognize clicks on Toolbar-Button
            tbbutton.fsState = TBSTATE_ENABLED;
            tbbutton.fsStyle = style;
            tbbutton.dwData = 0;
            tbbutton.iString = NULL;
            SendMessage(hwnd_toolbar, TB_ADDBUTTONS, (WPARAM) 1, (LPARAM) (LPTBBUTTON) &tbbutton);
        }
    }
    

    Alles was wir machen müssen ist, die TBBUTTON Struktur zu füllen und als iBitmap-Index (Der Index der das Bitmap in der Image-List identifiziert) die Standard-Button-Konstante anzugeben. Dann den Button per Message hinzufügen und fertig.

    Der Seperator
    Das Ganze noch für den berühmten Separator:

    void tb_add_separator( HWND hwnd_toolbar )
    {
        if (hwnd_toolbar != NULL)       //Continue only if there is a toolbar
        {
            TBBUTTON tbbutton;      //Create TBBUTTON-Structur for saving infos about button which is to add
            tbbutton.iBitmap = NULL;   
            tbbutton.idCommand = NULL;       //Command-Parameter to Command-Message to recognize clicks on Toolbar-Button
            tbbutton.fsState = NULL;
            tbbutton.fsStyle = TBSTYLE_SEP;
            tbbutton.dwData = NULL; 
            tbbutton.iString = NULL; 
    
            SendMessage(hwnd_toolbar, TB_ADDBUTTONS, (WPARAM) 1, (LPARAM) (LPTBBUTTON) &tbbutton);
        }
    }
    

    Hier ist eigentlich nur wichtig, dass der Style auch TBSTYLE_SEP ist.

    Die Löschfunktion für Buttons
    Jetzt kann man sich beliebig noch Funktionen schreiben, die das Löschen der Buttons erledigt. Diese möchte ich noch erwähnen, da hier eine Inkonsequenz der WinAPI deutlich wird:

    void tb_delete_button(HWND hwnd_toolbar, int command)       //Delete a button , determinated by Command-Id
    {
        if (hwnd_toolbar != NULL)       //Continue only if there is a toolbar
        {
            SendMessage(hwnd_toolbar, TB_DELETEBUTTON, SendMessage(hwnd_toolbar, TB_COMMANDTOINDEX, command, 0), NULL);
        }
    }
    

    Viele Messages der Toolbars sprechen die Buttons an indem man die Command-ID (Das Ding welches in der WM_COMMAND Message übergeben wird) mit angibt. Bei der TB_DELTEBUTTON Message ist es anders. Hier wird einfach die Position des Buttons in der Toolbar angeben, angefangen bei 0, von links an. Hat man 5 Buttons, und möchte den ganz rechten löschen, so muss man die 4 wählen. Irgendwo unsinnig, warum man hier plötzlich die Stelle des Buttons angeben soll, anstatt die Command-ID welche den Button eindeutig identifizieren kann.
    Daher hab ich die Message mittels der TB_COMMANDTOINDEX Message so umgebastelt, dass man die Command-ID im Parameter angeben kann, und Windows rechnet es automatisch in Positionsangabe um, welche zum Löschen benötigt wird. Man kann diese Funktion also wieder normal handhaben und die Command-ID angeben und nicht die Position des Buttons in der Toolbar.

    So, das war’s nun. Ich hoffe es ist klar geworden, wie die Toolbar arbeitet. Bei mir war vor Allem die Sache mit der Image-List das große Problem. Ich denke, der Abschnitt oben zeigt ganz gut, wie man die Images erst der Liste hinzufügt und dann per zurückgegebener ID auswählen kann.

    Wichtige Anmerkungen:
    - Ich hab die Funktionen als Methoden erstellt und hier für das Forum in normale Funktionen umgeschrieben, ohne sie noch mal zu prüfen. Es könnte also im ungünstigsten Fall sein, dass eine Variable verwendet wird, die nicht deklariert wurde, weil sie ursprünglich zur Klasse gehörte. Sollte aber minimalste Korrekturarbeit sein.

    - Wie ich schrieb, hab ich mich erst neulich noch selbst mit dem Thema befast, es kann durchaus sein, dass sich noch ein Fehler eingeschlichen hat, den ich übersehen habe. Man möge den Breitrag in diesem Falle korrigieren oder mich verständigen. 😉

    Anhang:
    - Als Style um einen Togglebutton zu erstellen, müsst ihr für den Style TBSTYLE_CHECK wählen.
    - Der Wert LOWORD(wParam) der WM_COMMAND Message liefert die Command-ID des Buttons
    - Folgende Header müssen eingebunden werden: windows.h und commctrl.h
    - Zusätzlich dann noch folgende Library linken: comctl32.lib (für DevC++: libgdi32.a und libcomctl32.a)



  • Ich hab ein kleines Beispielprogramm geschrieben welches die Funktionen testet und deren Funktion demonstrieren soll, downloadbar hier: http://derfeind2k.de/daten/Test-Toolbar.rar

    Der Sourcecode ist für die Dev C++ IDE eingerichtet, kann damit sofort geöffnet und kompiliert werden. Gelinkt werden müssen die -lgdi32 und die -lcomctl32 Dateien.

    Außerdem hab ich die tb_create() Funktion noch etwas abgeändert, zum Einen hab ich die Funktion InitCommonControls() eingefügt, ohne die die Toolbar nicht angezeigt werden würde, zum Anderen gibt diese Funktion nun als Rückgabewert das Handle zur erstellten Toolbar zurück.



  • Also bei mir (VC++) funktioniert dein Code auf jeden Fall mal 🙂

    Vielleicht hier auch noch ganz nützlich, ist folgender Code um aus einem zum Programmverzeichnis relativen Pfad einen absoluten zu machen:

    // Programm-Verzeichnis auslesen
    CHAR szAppPath[MAX_PATH]; 
    GetModuleFileName(NULL,szAppPath,sizeof(szAppPath)); 
    *(strrchr(szAppPath,'\\')+1) = 0; 
    
    // Pfad zusammenfügen
    CHAR szImgFile[MAX_PATH]; 
    lstrcpy(szImgFile,szAppPath);
    lstrcat(szImgFile,"bitmap.bmp");
    

Anmelden zum Antworten