|
Wie können benutzergezeichnete Buttons in eigene Anwendungen integriert werden?
| Frage |
|
Windows zeichnet alle Standard-Elemente automatisch, wenn die entsprechenden WM_PAINT-Nachrichten eintreffen. Eine Ausnahme hiervon bilden die sogenannten OwnerDraw-Elemente, die für Buttons ebenso wie für Menüs und Listboxen möglich sind.
Allerdings können auch die benutzergezeichneten Elemente mit den Ressourcen-Editoren erstellt und zugeordnet werden. Dazu muß für die jeweiligen Elemente lediglich das Flag "OwnerDraw" markiert werden. Damit enthebt Windows den Programmierer von der Pflicht, auch die Tastaturkürzel auszuwerten. Diese können auch für benutzergezeichnete Buttons direkt in der Dialogvorlage definiert werden, indem der entsprechende Text eingegeben wird, also beispielsweise "&Info" für einen Info-Button, wobei hier die Tastenkombination + zugeordnet wird.
| Lösung |
|
Die Anwendung muß nur die Controls selber zeichnen. Dazu gibt es mehrere Ansätze. So kann man beispielsweise mehrere Bitmaps für einen Button verwenden. Man kann jedoch auch mit einem Bitmap pro Button sowie global zwei Bitmaps Overhead auskommen. Dies resultiert aus der Überlegung, daß es für einen Button normalerweise nur drei mögliche Zustände gibt:
| Controls zeichnen |
- Nicht fokussiert und nicht gedrückt
- Nicht fokussiert und gedrückt
- Fokussiert und gedrückt
Hinzu kommt noch der Zustand deaktiviert, der jedoch kein neues Bitmap erfordert, da das Bitmap in diesem Fall invertiert ausgegeben werden kann.
| Mögliche Zustände eines Buttons |
|
Betrachtet man sich die Liste der möglichen Zustände, so erkennt man, daß sich die Zustände 2 und 3 nur durch einen etwas dickeren Fokus-Rahmen unterscheiden. Ansonsten ist die verwendete Bitmap jedoch gleich. Der Unterschied zwischen einem gedrückten und einem nicht gedrückten Button besteht im wesentlichen in den verwendeten Rahmen, die den 3D-Effekt verdeutlichen sollen. Für gewisse Effekte kann man natürlich auch zwei verschiedene Bitmaps verwenden, die dann eine Bewegung im Button simulieren können. In diesem Fall sind pro Button zwei Bitmaps zu laden. Ansonsten kann analog zum Standard verfahren werden.
| Gedrückte Buttons |
|
Der 3D-Effekt besteht im wesentlichen darin, daß eine einfallende Lichtquelle simuliert wird. Dazu müssen der linke und obere Rand hell gezeichnet werden, während der rechte und untere Rand dunkel erscheinen, wenn der Button nicht gedrückt, also in Normalstellung, ist. Der Rahmen ist für diese Stellung einmal in der erforderlichen Größe im Ressourcen-Editor zu erstellen:
Abbildung 5/2.1.5.9-1: Rahmen für Button in Normalstellung
Ist der Button gedrückt, werden der linke und obere Rand verdunkelt:
Abbildung 5/2.1.5.9-2: Rahmen für Button in gedrückter Stellung
| 3D-Effekt |
|
In beiden Fällen befindet sich außerhalb des schwarzen Rahmens eine weitere Rasterzeile, die mit dem Hintergrund der Oberfläche eingefärbt ist. Diese Hintergrundfarbe kann man gegebenenfalls flexibel halten. Wenn man jedoch nur eine Hintergrundfarbe verwendet, kann man sich eine Zeichenoperation sparen, indem gleich die benötigte Farbe hinterlegt wird. Allerdings sollte man sich – speziell im Hinblick auf Windows 95 – nicht auf eine bestehende Hintergrundfarbe verlassen, sondern so flexibel wie möglich sein.
| Fokusrahmen |
|
Die gezeigten Rahmen können für jeden Button verwendet werden. Lediglich die jeweiligen Informations-Bitmaps müssen noch in die Rahmen kopiert werden. Dies kann jedoch zur Laufzeit des Programms erfolgen. Beim Zeichnen muß man jedoch darauf achten, daß die Größen übereinstimmen, damit eine spätere Skalierung entfallen kann. Die Informati-ons-Bitmaps müssen demzufolge 10 Rasterzeilen kleiner als die Rahmen sein, damit sie genau auf die innere graue Fläche passen.
Abbildung 5/2.1.5.9-3: Informations-Bitmap
| Info-Bitmaps einfügen |
|
Der Schriftzug für die Bezeichnung wird gleich in die Bitmap eingefügt, wobei der Aktivierungs-Buchstabe unterstrichen wird. Dieser Buchstabe muß mit dem in der Button-Definitions eingetragenen Text übereinstimmen; hier also "&Info" und "I".
| Korrespondierende Tastenbezeichner |
|
Der 3D-Effekt im Schriftzug ist recht einfach zu erzielen, indem der Schriftzug zweimal ausgegeben wird. Einmal in weiß als Hintergrund und einmal in schwarz als Vordergrund, wobei diese Kopie um jeweils ein Pixel nach links oben gegenüber dem weißen Schriftzug verschoben ist.
| 3D-Effekt im Schriftzug |
|
Das Laden der Bitmaps sollte gleich zu Beginn des Programms, also am besten in der Methode SetupWindow() geschehen, damit nicht bei jeder Zeichenaktion ein Zugriff auf die Ressourcen notwendig wird. Dies ist besonders zeitsparend, da die Rahmen für alle Buttons gleicher Größe gleich sind. Soll jedoch Speicher gespart werden, kann auch vor dem Zeichnen die jeweilige Bitmap aus den Ressourcen geladen werden.
| Bitmaps laden |
|
Analog dazu müssen die Ressourcen vor dem Beenden, am besten also im Destruktor, wieder über DeleteObject() freigegeben werden:
| Ressourcen freigeben |
|
Das Zeichnen der Buttons erfolgt auf Anweisung von Windows. Dazu muß die Nachricht WM_DRAWITEM beantwortet werden, die im lParam einen Zeiger auf eine DRAWITEMSTRUCT-Struktur übergibt, dessen Inhalt man entnehmen kann, welcher Button zu zeichnen ist und in welchem Zustand er sich gerade befindet. Diese Struktur ist wie folgt definiert:
| Buttons zeichnen |
typedef struct tagDRAWITEMSTRUCT {
WORD CtlType;
WORD CtlID;
WORD itemID;
WORD itemAction;
WORD itemState;
WORD hwndItem;
HDC hDC;
RECT rcItem;
DWORD itemData;
} DRAWITEMSTRUCT;
Das Strukturmitglied CtrlType bezeichnet den Typ der Control, die durch die Struktur beschrieben wird. Mögliche Werte sind:
| Struktur DRAWITEM-STRUCT |
|
Konstante
|
Control
|
|
ODT_BUTTON
|
benutzergezeichneter Button
|
|
ODT_COMBOBOX
|
benutzergezeichnete Combobox
|
|
ODT_LISTBOX
|
benutzergezeichnete Listbox
|
|
ODT_MENU
|
benutzergezeichneter Menüeintrag
|
CtlID enthält die ID eines Buttons bzw. einer List- oder Combobox, wie im Ressourcen-Editor festgelegt. Für Menüs wird dieses Feld nicht benutzt.
| Element CtrlType |
|
Das Element itemID spezifiziert die ID für einen Menüeintrag oder den Index auf den entsprechenden Eintrag einer List- oder Combobox, wobei auch der Wert "-1" erlaubt ist, damit beispielsweise auch für eine leere Listbox ein Fokusrahmen gezeichnet werden kann.
| Element itemID |
|
In itemAction ist bitcodiert die Information über die erforderliche Zeichenkette enthalten. Mögliche Wert sind:
|
Konstante
|
Bedeutung
|
|
ODA_DRAWENTIRE
|
Die komplette Control muß neu gezeichnet werden.
|
|
ODA_FOCUS
|
Die Control erhält oder verliert den Focus, wobei das Element itemSate den jeweiligen Status spezifiziert.
|
|
ODA_SELECT
|
Der Selektionsstatus hat sich geändert.
|
| Element itemAction |
|
itemState enthält bitweise codiert den Zustand des Buttons. Unterstützt werden fünf verschiedene Zustände:
|
Konstante
|
Bedeutung
|
|
ODS_CHECKED
|
Nur in Menüs: Item ist markiert.
|
|
ODS_DISABLED
|
Control muß im Zustand "deaktiviert" gezeichnet werden.
|
|
ODS_FOCUS
|
Das Item besitzt den Focus
|
|
ODS_GRAYED
|
Nur in Menüs: Der Eintrag soll grau dargestellt werden.
|
|
ODS_SELECTED
|
Das Item ist selektiert.
|
| Element itemState |
|
In hwndItem liefert Windows für Combo- und Listboxen sowie Buttons das Handle des Fensters der Control und für Menüs das Handle (HMENU) des Menüs, das den Eintrag besitzt.
| Element hwndItem |
|
Das Strukturmitglied hDC übergibt einen DeviceContext, der für Zeichenoperationen verwendet werden kann.
| Element hDC |
|
In rcItem ist das Rechteck spezifiziert, in dem das Element auszugeben ist.
Von den möglichen ODS-Konstanten sind für benutzergezeichnete Buttons nur drei interessant:
- ODS_DIABLED
- ODS_FOCUS
- ODS_SELECTED
Das Zeichnen der Buttons gliedert sich in vier logische Schritte. Im ersten Schritt wird zunächst einmal die Größe der Rahmen-Bitmap bestimmt. Dies ist ein Vorgang, der zur Optimierung auch aus der Zeichen-Routine herausgezogen werden kann, sofern alle Buttons die gleiche Größe haben.
| Element rcItem |
|
Ermittelt wird die Größe der Button-Bitmap über die Funktion GetObjekt, die eine TBitmap-Struktur mit den Informationen aus dem Handle der Rahmen-Bitmap füllt. Dies ist aber nur dann problemlos möglich, wenn alle Buttons die gleiche Größe haben. Ansonsten müßte man hier bereits individualisieren und die Größe eines jeden Buttons bestimmen.
| Bitmap-Größe ermitteln |
|
Die Ausgabe der Rahmen-Bitmap erfolgt in Abhängigkeit des itemState durch ein SelectObject und einem nachfolgenden BitBlt, für das die Größe der Bitmap bekannt sein muß. Ferner wird für diesen Vorgang ein kompatibler DeviceContext benötigt. Je nach Status wird dazu der passende Rahmen ausgewählt.
| Rahmen-Bitmap ausgeben |
|
Besitzt der auszugebende Button den Fokus, muß zusätzlich ein schwarzer Rahmen um die Rahmenbitmap gezeichnet werden. Das Innere des Rechtecks wird mit einem durchsichtigen Pinsel gefüllt, damit der Hintergrund des Rahmens nicht zerstört wird.
| Fokusrahmen einzeichnen |
|
Nachdem der Rahmen komplett ausgegeben ist, wird die individuelle Bitmap in den Rahmen hineinkopiert. Dazu muß der Offset (4, 4) bezogen auf die linke obere Ecke des Buttons benutzt werden, damit nicht der Rahmen selbst überschrieben wird.
| Bitmap plazieren |
|
Die hier aufgezeigten Schritte sind für jeden Button und jede Zeichenaktion erforderlich. Haben die verwendeten Buttons eine einheitliche Größe, so können diverse Optimierungen vorgenommen werden, da die Größe nur einmal bestimmt zu werden braucht. Allerdings sollte man sich auch bei Dialogen nicht auf eine universelle Größe verlassen, da auch Dialoge skaliert werden können, wie der Tip 5/2.1.5.7 zeigt.
Zur Vereinfachung der Bitmap-Adressierung können aufeinanderfolgende Bitmap-IDs eingesetzt werden, die dann eine Verwaltung in einem Array mit Direktzugriff auf das jeweilige Element über die ID erlauben. Aber auch dies ist abhängig vom jeweiligen Einsatzzweck des benutzergezeichneten Buttons, was hier nicht weiter erörtert werden kann. Ziel dieses Tips war es vielmehr, die allgemeine Vorgehensweise zu erläutern.
| Optimierungen
|