void __fastcall ForceRefresh();
Die Methode ForceRefresh() erzwingt ein Neuzeichnen der Komponente.
Implementation
Die Klasse TTransBitmap setzt intern auf TCustomControl auf und erbt deren elementare Fähigkeiten. Die wesentlichen Neuerungen, die von TTransBitmap hinzugefügt werden, ist die flexible Anzeige von Text und Bitmap. Die zentrale Funktion ist daher die Methode Paint(), die intern auf mehrere Service-Funktionen zur Bestimmung von Größe und Plazierung des Caption-Textes und der Bitmap zurückgreift.
void __fastcall TTransBitmap::Paint()
{
if (FAutoRefresh)
{
TRect Region; // Region innerhalb derer wir zeichnen dürfen
TRect TmpBmpRect, BmpRect; // für Bitmappositionierung
TColor TopColor, BottomColor; // Farben für 3D Effekt
Graphics::TBitmap *TmpBitmap; // Bitmap
TRect StretchRegion; // Region für Stretching der Bitmap
TPoint BmpPt, TxtPt; // Position der Bitmap und 1. Caption
int CrtCaptionLine; // zur Anzeige der verschiednen Zeilen,
int CaptionLineTop; // die den Captiontext bilden
Als erstes prüft die Methode die Größe der Control, was insbesondere für das erstmalige Anzeigen von Interesse ist:
CheckSize();
Anschließend muß festgestellt werden, ob sich die Maus über der Control befindet oder nicht und abhängig vom Resultat, ob der Wert von FHasFocus geändert werden muß. Dieser Abschnitt löst insbesondere das von Popup geschaffene Problem, daß dann, wenn ein Popup-Menü der Control zugewiesen ist und der Anwender einen Menüpunkt aus diesem Popup-Menü wählt, der Fokus entfernt wird. Als Reaktion darauf versendet Windows eine WM_PAINT-Nachricht, die zum Aufruf der Methode Paint() führt. Hier wird geprüft, ob die Control den Fokus besitzt oder nicht.
TPoint CursorPos;
if (GetCursorPos(&CursorPos)) {
CursorPos = ScreenToClient(CursorPos);
if (FHasFocus && !OverControl(CursorPos)) {
SetCaptureControl(NULL);
FHasFocus = false;
}
}
Erst jetzt kann das eigentliche Zeichnen in Angriff genommen werden. Dazu wird als erstes erforderlich, den Client-Bereich und die tatsächliche Region zu bestimmen, innerhalb derer der Platz für die Control bereitgestellt wird:
Region = GetClientRect();
GetWorkableRegion(Region);
Die Bitmap selbst wird über eine temporäre Kopie mit einem transparenten Hintergrund angezeigt, wobei zunächst der Hintergrund erzeugt wird. Dessen Farbe hängt vom Typ der Control ab. Handelt es sich um loButton, berücksichtigt die Methode den Status der Control sowie die Tatsache, ob die Control aktuell Mausklicks abfängt oder nicht:
TmpBitmap = new Graphics::TBitmap;
try
{
if (FLook != loButton)
Canvas->Brush->Color = FBackgroundColor;
else if (((FState == stDown) && !MouseCatched) ||
((OrigState == stDown) && MouseCatched))
Canvas->Brush->Color = clBtnHighlight;
else
Canvas->Brush->Color = clBtnFace;
Canvas->FillRect(Region); // Hintergrund anzeigen
TmpBitmap->Width = BitmapWidth;
TmpBitmap->Height = FBitmap->Height;
Zur Anzeige wird die rechte Bitmap ausgewählt, abhängig von der Anzahl der Bitmaps sowie dem aktuellen Status der Control. Der Defaultwert zeigt die gesamte Bitmap:
TmpBmpRect = Rect(0, 0, TmpBitmap->Width,
TmpBitmap->Height);
BmpRect = Rect(0, 0, BitmapWidth, FBitmap->Height);
switch (FState)
{
case stDisabled:
if (FBitmapNumber >= bn2)
BmpRect = Rect(BitmapWidth, 0, 2 * BitmapWidth,
FBitmap->Height);
break;
case stDown:
if (FBitmapNumber >= bn3)
BmpRect = Rect(2* BitmapWidth, 0,
3* BitmapWidth, FBitmap->Height);
break;
}
Spezialfall: Es gibt eine vierte Bitmap für den Fokuszustand und die Control hat den Fokus und darf ihn bekommen:
if (FAllowFocus && FHasFocus && (FBitmapNumber == bn4))
BmpRect = Rect(3 * BitmapWidth, 0, 4 * BitmapWidth,
FBitmap->Height);
Nun kann die richtige Bitmap kopiert werden:
TmpBitmap->Canvas->CopyRect(TmpBmpRect, FBitmap->Canvas,
BmpRect);
Existiert eine transparente Farbe in der Bitmap, so wird die Bitmap transparent gemacht:
TmpBitmap->Canvas->Brush->Color = Canvas->Brush->Color;
TmpBitmap->Canvas->BrushCopy(Rect(0, 0, TmpBitmap->Width,
TmpBitmap->Height),
TmpBitmap,
Rect(0, 0, TmpBitmap->Width,
TmpBitmap->Height),
TmpBitmap->TransparentColor);
Zur Anzeige der Bitmap selbst stehen zwei Modi zur Auswahl:
- normale Größe
- gestreckte Größe
Soll die Bitmap gestreckt angezeigt werden, so hängt der verfügbare Bereich davon ab, ob der Captiontext relativ zur Bitmap angezeigt werden soll oder nicht.
if (FStretch)
{
StretchRegion = Region;
switch (FCaptionDisplay)
{
case cdBitmapLeft:
StretchRegion.Right -= CaptionWidth;
break;
case cdBitmapRight:
StretchRegion.Left += CaptionWidth;
break;
case cdBitmapTop:
StretchRegion.Bottom -= CaptionHeight;
break;
case cdBitmapBottom:
StretchRegion.Top += CaptionHeight;
break;
}
Canvas->StretchDraw(StretchRegion, TmpBitmap);
}
else
{
Für die normale Größe hängt die Plazierung sowohl von dem Property FBitmapDisplay als auch FCaptionDisplay ab:
GetBitmapPos(Region, BmpPt);
Canvas->Draw(BmpPt.x, BmpPt.y, TmpBitmap);
}
} catch(...) {}
delete TmpBitmap;
Nachdem die Bitmap angezeigt ist, kann man daran gehen, den Captiontext auszugeben. Die Anzeige erfolgt ebenfalls mit transparentem Hintergrund und muß gegebenenfalls mehrfach durchgeführt werden, wenn die Zeile zu lang ist und mehrere Zeilen erlaubt sind. Ferner ist zu ermitteln, ob hoch- oder tiefgestellter Text zu berücksichtigen ist.
Zunächst wird die Ausgabeposition der ersten Zeile bestimmt:
if (CaptionList->Count)
{
Canvas->Brush->Style = bsClear;
GetCaptionPos(Region, TxtPt);
CaptionLineTop = TxtPt.y;
Die Anzeige der Textzeilen erfolgt über die Methode CanvasTextOut(), die sämtliche interne Berechnungen vornimmt:
for (CrtCaptionLine = 0; CrtCaptionLineCount;
CrtCaptionLine++) {
CanvasTextOut(TxtPt.x + (CaptionWidth - CanvasTextWidth(
CaptionList->Strings[CrtCaptionLine])) / 2,
CaptionLineTop,
CaptionList->Strings[CrtCaptionLine]);
CaptionLineTop += CanvasTextHeight(
CaptionList->Strings[CrtCaptionLine]);
}
}
Als letztes verbleibt noch, das Aussehen der Control festzulegen und sichtbar zu machen, da das Verhalten der Control - egal, ob Button oder Panel – komplett über die Komponente und deren Methoden gesteuert wird.
Zunächst wird die Ausgaberegion bestimmt und im Falle eines Fokuses ein 3D-Rahmen gezeichnet:
Region = GetClientRect();
if (FAllowFocus && FFocusWidth)
{
if (FHasFocus)
Frame3D(Canvas, Region, FFocusColor, FFocusColor,
FFocusWidth);
else
Frame3D(Canvas, Region, clBtnFace, clBtnFace,
FFocusWidth);
}
Handelt es sich um einen Button, so besitzt dieser zwei Rahmen mit unterschiedlichen Farben und Funktionalitäten abhängig vom Property FState:
if (FLook == loButton)
switch (FState)
{
case stUp:
case stDisabled:
Frame3D(Canvas,Region, clBtnHighlight, clBlack, 1);
Canvas->Pen->Color = clBtnShadow;
Canvas->MoveTo(Region.Left, Region.Bottom - 1);
Canvas->LineTo(Region.Right - 1,Region.Bottom - 1);
Canvas->LineTo(Region.Right - 1, Region.Top - 1);
break;
case stDown:
Frame3D(Canvas,Region, clBlack, clBtnHighlight, 1);
Canvas->Pen->Color = clBtnShadow;
Canvas->MoveTo(Region.Left, Region.Bottom - 1);
Canvas->LineTo(Region.Left, Region.Top);
Canvas->LineTo(Region.Right, Region.Top);
break;
}
else
{
Ein Panel wird in drei Schritten angezeigt:
- Außenrand
- Rand
- Innenbereich
Ist FBevelOuter ungleich bvNone, wird eine entsprechende Reaktion notwendig:
if (FBevelOuter != bvNone) {
if ((FLook == loClickablePanel) && (FState == stDown))
AdjustColors(FBevelOuter, BottomColor, TopColor);
else
AdjustColors(FBevelOuter, TopColor, BottomColor);
Frame3D(Canvas, Region, TopColor, BottomColor,
BevelWidth);
}
Frame3D(Canvas, Region, FBackgroundColor,
FBackgroundColor, BorderWidth);
Ansonsten wird der innere Rand gezeichnet, wenn FBevel- Inner ungleich bvNone ist:
if (FBevelInner != bvNone) {
if ((FLook == loClickablePanel) && (FState == stDown))
AdjustColors(FBevelInner, BottomColor, TopColor);
else
AdjustColors(FBevelInner, TopColor, BottomColor);
Frame3D(Canvas, Region, TopColor, BottomColor,
BevelWidth);
}
}
}
}
Nach diesen Arbeiten ist die Control komplett angzeigt.
Als wichtigste Support-Routine fehlt noch die Methode CanvasTextOut() zur Anzeige des Captiontextes.
int __fastcall TTransBitmap::CanvasTextWidth(const String &aText)
{
int Result = Canvas->TextWidth(aText);
if (FAllowSubAndSuperChars) {
String CharToTest;
int FontShift;
int CrtMode = 0; // 0 = normal, 1 = sub, 2 = super
int aTextLength = aText.Length();
int DefFontSize = Canvas->Font->Size;;
Result = 0;
Für jeden einzelnen Buchstaben muß geprüft werden, ob es sich um ein Escape-Zeichen handelt oder ob eine Umschaltung zwischen normaler Darstellung und hoch- bzw. tiefgestelltem Text handelt:
for (int I = 1; I < aTextLength; I++) {
CharToTest = aText.SubString(I, 1);
if (CharToTest == FEscapeChar) {
CharToTest = aText.SubString(I + 1, 1);
I ++;
}
else if (CharToTest == FSubChar) {
CrtMode = (CrtMode == 1)?0:1;
CharToTest = "";
}
else if (CharToTest == FSuperChar) {
CrtMode = (CrtMode == 2)?0:2;
CharToTest = "";
}
switch (CrtMode) {
case 0: // Normal
FontShift = 0; break;
case 1: // Sub
FontShift = -FSubFontSizeDiff; break;
case 2: // Super
FontShift = -FSuperFontSizeDiff; break;
}
Canvas->Font->Size = DefFontSize + FontShift;
Result += Canvas->TextWidth(CharToTest);
}
Letztes Zeichen:
CharToTest = aText.SubString(aTextLength, 1);
if ((CharToTest != FEscapeChar) &&
(CharToTest != FSubChar) &&
(CharToTest != FSuperChar))
Result += Canvas->TextWidth(CharToTest);
Canvas->Font->Size = DefFontSize;
}
return Result;
}
|