Eigene Buttons

Eigene Buttons können auf mehrere Arten und Weisen gezeichnet werden. So können für Buttons Bitmaps oder Icons verwendet werden oder ein Button kann sogar mittels GDI-Anweisungen gezeichnet werden. Außerdem ist es möglich Buttons zu erstellen, die keine rechteckige Form besitzen. Die oberen Buttons im folgendem Bild enthalten Bitmaps und Icons während der untere Button eine runde Form besitzt und mittels GDI-Anweisungen gezeichnet wurde.

Beginnen wir mit der Erstellung von Buttons die auf Bitmaps basieren. Innerhalb des Dialogs wird der Button zunächst wie gewohnt erstellt. Die dabei eingestellte Größe des Buttons ist jedoch nicht relevant, da sie sich zur Laufzeit nach der Größe der Bitmap für den Button im Normalzustand richtet. Anschließend muss die Eigenschaft Besitzerzeichnung des Buttons gesetzt und dem Button eine eindeutige Beschriftung zugewiesen werden. Dies Beschriftung spielt gleich bei der Verknüpfung des Buttons mit seinen Bitmaps noch eine wichtige Rolle.

Als nächstes kann es an das Erstellen der Bitmaps für den Button gehen. Ein Bitmap-Button kann für jeden seiner vier Zustände (normal, selektiert, gedrückt und gesperrt) eine eigene Bitmap darstellen. Die für den jeweiligen Zustand des Buttons darzustellende Bitmap erhält nun als Namen die Beschriftung des Buttons plus einen Suffix laut nachfolgender Tabelle:

Suffix Buttonzustand
U normal
F selektiert
D gedrückt
X gesperrt

Um einer Bitmap-Ressource einen Namen (und keine ID) zuzuweisen, ist im Eigenschaftsdialog der Bitmap der Name in Anführungszeichen einzuschließen.

Damit ergeben sich im Beispiel für den Button mit der Beschriftung Button1 folgende Namen für die Bitmaps:

Da der Button im Beispiel niemals gesperrt wird ist auch nicht notwendig, hierfür eine Bitmap zur Verfügung zu stellen.

Damit hätten wir die Vorarbeiten für Bitmap-Buttons erledigt. Bleibt zum Schluss nur noch die Verbindung zwischen dem Button und den darzustellenden Bitmaps herzustellen. Unter der MFC werden Bitmap-Buttons durch die Klasse CBitmapButton repräsentiert. Diese Klasse enthält eine Methode AutoLoad(...) die als Parameter unter anderem die ID des Bitmap-Buttons erhält. AutoLoad(...) lädt nun die entsprechenden Bitmap-Ressourcen und verbindet Sie dann mit dem Button. Alles weitere erfolgt dann automatisch. Selbstverständlich darf AutoLoad(...) erst dann aufgerufen werden, wenn der Button bereits erstellt ist. Im Beispiel erfolgt dies innerhalb der Dialog-Methode OnInitDialog(...).

BOOL COwnBtnDlg::OnInitDialog()
{
    ....
    // ZU ERLEDIGEN: Hier zusätzliche Initialisierung einfügen
    m_CBmpBtn.AutoLoad(IDC_BMPBTN,this);

    return TRUE; // Geben Sie TRUE zurück, außer ein Steuerelement soll den Fokus erhalten
}

Die Erstellung eines Buttons mit einem Icon erfordert etwas weniger Aufwand wie ein Bitmap-Button. Dafür wird für alle Buttonzustände aber auch immer das gleiche Icon angezeigt. Wird die Button gedrückt, so wird die Position des Icons nur etwas nach rechts unten verschoben. Auch dieser Button wird innerhalb Dialogs normal erstellt. Zusätzlich muss noch die Eigenschaft Symbol des Buttons gesetzt werden.

Im Unterschied zum Bitmap-Button erhält der Button jetzt aber zur Laufzeit die im Dialog eingestellte Größe. Ist das später anzuzeigende Icon kleiner, so wird es standardmäßig zentriert im Button dargestellt. Größere Icons werden einfach abgeschnitten. Die Verbindung zwischen dem Button und dem anzuzeigenden Icon erfolgt in zwei Schritten. Zuerst muss das Icon mittels der CWinApp Methode LoadIcon(...) geladen werden. Das dabei zurückgelieferte Icon-Handle wird dann der Methode SetIcon(...) der CButton Klasse übergeben.

BOOL COwnBtnDlg::OnInitDialog()
{
    ....
    m_CBmpBtn.AutoLoad(IDC_BMPBTN,this);
    // Icon-Button initialisieren
    HICON hOwnIcon = AfxGetApp()->LoadIcon(IDI_BTNICON);
    CButton *pBtn = static_cast<CButton*>(GetDlgItem(IDC_ICONBTN));
    pBtn->SetIcon(hOwnIcon);

    return TRUE; // Geben Sie TRUE zurück, außer ein Steuerelement soll den Fokus erhalten
}

Kommen wir nun zum letzten Fall, dass ein Button mittels GDI-Anweisungen gezeichnet werden soll. Für diesen Fall müssen Sie sich für den Button zuerst eine eigene Klasse, abgeleitet von CButton, erstellen. Dieser neuen Klasse ist dann die virtuelle Methode DrawItem(...) hinzuzufügen. Aber bitte nicht mit der Methode für die WINDOWS-Nachricht WM_DRAWITEM verwechseln! Und innerhalb dieser neu hinzugefügten Methode DrawItem(...) können Sie dann den Button zeichnen. Dazu erhält die Methode einen Zeiger auf eine Struktur vom Typ DRAWITEMSTRUCT übergeben, die unter anderem die auszuführende Aktion beinhaltet. So enthält z.B. das Element itemAction Informationen darüber, weshalb der Button gezeichnet werden soll. Außerdem enthält die Struktur auch das Handle für den zum Zeichnen zu verwendenden Device Context. Damit Sie aber nachher mit den CDC Methoden der MFC zeichnen können, müssen Sie diesen HDC zuerst mittels der CDC Methode FromHandle(...) in ein CDC Objekt umwandeln. Der runde Button im obigen Dialog wird im Beispiel mit folgenden Anweisungen erstellt:

 void CBtnDraw::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    // TODO: Code einfügen, um das angegebene Element zu zeichnen

    CString CTitle;     // Button-Beschriftung
    CBrush *pBtnBrush;  // Button-Farbe
    // CDC-Objekt aus uebergebenen hDC erstellen
    CDC *pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
    // Buttonzustand auswerten
    switch (lpDrawItemStruct->itemAction)
    {
    // Button komplett neu zeichnen
    case ODA_DRAWENTIRE:
        pBtnBrush = new CBrush(RGB(255,255,0));
        CTitle = "Klick me!";
        break;
    // Button besitzt Focus
    case ODA_FOCUS:
        pBtnBrush = new CBrush(RGB(0,255,0));
        CTitle = "You found me!";
        break;
    // Button ist ausgewaehlt
    case ODA_SELECT:
        // Falls Button gedrueckt
        if (lpDrawItemStruct->itemState & ODS_SELECTED)
        {
            pBtnBrush = new CBrush(RGB(255,0,0));
            CTitle = "That was a hit!";
        }
        // Button ist nicht gedrueckt
        else
        {
            pBtnBrush = new CBrush(RGB(0,255,0));
            CTitle = "That's good!";
        }
        break;
    }
    // Groesse des Buttons holen
    CRect CBtnColor = m_CBtnRect;
    // Weissen Brush erstellen
    CBrush CBtnBackBrush(RGB(0xff,0xff,0xff));
    pDC->SelectObject(&CBtnBackBrush);
    // und Ellipse damit zeichnen
    pDC->Ellipse(&m_CBtnRect);
    // Ellipse um jeweils 5 Einheit verkleinern
    CBtnColor.DeflateRect(5,5);
    // Farbe je nach Buttonzustand setzen
    pDC->SelectObject(pBtnBrush);
    // Ellipse zeichnen
    pDC->Ellipse(&CBtnColor);
    // Beschriftung ausgeben
    pDC->SetBkMode(TRANSPARENT);
    pDC->DrawText(CTitle,m_CBtnRect,DT_CENTER|DT_VCENTER|DT_SINGLELINE);

    delete pBtnBrush;
}

Je nach Zustand erhält der Button eine andere Farbe und Beschriftung.

Doch einen kleinen Fehler hätte der so erstellte Button noch. Da ein Button im Dialogeditor immer rechteckig angelegt wird, würde auch ein Klick innerhalb dieses rechteckigen Bereichs und außerhalb der gezeichneten Ellipse als Buttonklick erkannt werden. Um den Button jetzt abzurunden (und nicht nur rund zu zeichnen), wird eine elliptische Region erstellt und diese Region dann als Fenster-Region ausgewählt. Denken Sie immer daran, dass auch ein Button nur ein Fenster ist! Damit beim Erstellen des Dialogs der Button normal erstellt werden kann, wird noch der Buttonstil auf BS_OWNERDRAW gesetzt (Besitzerzeichnung).

void CBtnDraw::PreSubclassWindow()
{
    // TODO: Speziellen Code hier einfügen und/oder Basisklasse aufrufen

    CButton::PreSubclassWindow();

    // Elliptische Region erstellen die den urspruenglichen
    // Button ausfuellt
    CRgn CBtnRgn;
    GetClientRect(&m_CBtnRect);
    CBtnRgn.CreateEllipticRgnIndirect(&m_CBtnRect);
    // Die erstellte Region als Fenster-Region setzen
    SetWindowRgn(CBtnRgn,TRUE);
    // Button-Stil jetzt noch auf Besitzerzeichnung setzen
    ModifyStyle(0,BS_OWNERDRAW);
}

Der Button wird zum Schluss im Dialogeditor wie gewohnt erstellt und dann über den Klassen-Assistenten mit der soeben neu erstellen Klasse verbunden (Membervariable vom Typ Control).

Das fertige Beispiel mit allen 3 Buttons finden Sie unter 08Dialoge\OwnBtn.

Auf der nächsten Seite, übrigens der letzten in diesem Kurs, können Sie sich noch eine Aufstellung von zusätzlichen Programmen ansehen.



Copyright © 2004

Senden Sie Emails mit Fragen oder Kommentaren zu dieser Website an: mailto:info@cpp-tutor.de
 Wolfgang Schröder, Lerchenweg 23, D-72805 Lichtenstein. Tel: +49 7129 6470