Dynamischer
Cursor
Ein dynamischer Cursor ist ein Cursor, dessen Aussehen zur Programmlaufzeit
durch die Anwendung gezeichnet wird. Er ist nicht zu verwechseln mit einem animierten
Cursor. Das Aussehen eines animierten Cursor wird schon bei dessen Erstellung festgelegt
und kann zur Programmlaufzeit nicht beeinflusst werden.
Um einen dynamischen Cursor zu erstellen, wird eine Bitmap sowie
zwei Bitmasken benötigt. Die Größe der Bitmap bzw. Bitmasken muss der Größe des
Cursors (in der Regel 32x32) entsprechen. Die Bitmap wird als monochromes CBitmap-Objekt
erstellt. In diese Bitmap wird dann später, mit den bekannten CDC-Methoden,
das Aussehen des dynamischen Cursor gezeichnet. Damit nachher der neue, selbst gezeichnete
Cursor auch verwendet wird, muss zusätzlich noch in der Fensterklasse das Cursor-Handle
auf NULL gesetzt werden. Damit ergeben sich für die Initialisierung folgende Anweisungen.
void CDynCursorView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO: Speziellen Code hier einfügen und/oder Basisklasse aufrufen
int
nBmpSize; // Groesse der Cursorbitmap (in
Bytes)
CWindowDC hDC(this);
// DC holen
// Standardgroesse des Cursor
holen und entsprechend
// grosse einfarbige Bitmap anlegen
m_nXCurSize = GetSystemMetrics(SM_CXCURSOR);
m_nYCurSize = GetSystemMetrics(SM_CYCURSOR);
m_CBitmap.CreateBitmap(m_nXCurSize, m_nYCurSize,1,1,NULL);
// Speicher-DC anlegen und Bitmap
selektieren
// Bitmap bleibt bis zum Programmende selektiert!
m_CMemDC.CreateCompatibleDC(&hDC);
m_CMemDC.SelectObject(&m_CBitmap);
// Groesse der Bitmap-Masken
fuer den Cursor berechnen
// AND und XOR Bitmapfelder anlegen
nBmpSize = (m_nXCurSize/8)*m_nYCurSize;
m_pvAND = new char[nBmpSize];
m_pvXOR = new char[nBmpSize];
// Fenster-Cursor loeschen
m_hOldCursor = 0;
::SetClassLong(*this,GCL_HCURSOR,NULL);
m_OldPos.x = -1;
m_OldPos.y = -1;
} |
Die beiden letzten Anweisung (m_OldPos) dienen dazu, später das
Flackern des Cursors zu minimieren.
Nun kann's an das Erstellen des Cursors gehen. Jedes Mal, wenn der
Cursor neu dargestellt werden muss, sendet WINDOWS eine WM_SETCURSOR Nachricht an
die Anwendung. Um diese Nachricht zu verarbeiten wird der MFC-Nachrichtenbearbeiter
OnSetCursor(...) verwendet. Für unseren Zweck sind nur die ersten beiden
Parameter der Methode relevant. Der erste Parameter der Methode zeigt auf das Fenster,
innerhalb dessen sich der Cursor gerade befindet. Denken Sie immer daran, dass der
Cursor eine gemeinsame Ressource aller Fenster ist, die zu einer Fensterklasse gehören.
Als nächster Parameter folgt die Cursorposition in kodierter Form. Diese Kodes beginnen
alle mit dem Präfix HTxxxx. So gibt z.B. der Kode HTCLIENT an, dass sich der Cursor
innerhalb der Client-Area befindet. Enthält der Kode z.B. den Wert HTCAPTION,
so befindet sich der Cursor innerhalb der Titelzeile des Fensters. Eine vollständige
Übersicht über alle HTxxx Kodes finden Sie in der Online-Hilfe unter dem Stichwort
OnNcHitTest.
Kehren wir jetzt zu unserem Beispiel zurück. Um einen Cursor zu erstellen, wird
die API-Funktion CreateCursor(...) aufgerufen. Da diese Funktion für unser
Beispiel von zentraler Bedeutung ist, sehen wir uns einmal das Prototyping der Funktion
an:
HCURSOR CreateCursor( HINSTANCE hInst, int xHotSpot,
int yHotSpot,
int nWidth, int nHeight,
CONST VOID *pvANDPlane, CONST VOID *pvXORPlane ); |
hInst ist das Handle der Instanz, die den Cursor erzeugt. Sie können
für diesen Parameter den Aufruf der MFC-Funktion AfxGetInstanceHandle(...)
einsetzen. xHotSpot und yHotSpot spezifizieren die X- bzw. Y-Koordinate
des Hotspots innerhalb des Cursors. Die beiden nächsten Parameter nWidth
und nHeight legen die Größe des Cursors fest. In der Regel 'berechnen'
Sie diese beiden Parameter mittels der Funktion GetSystemMetrics(...).
Am interessantesten sind die beiden letzten Parameter pvANDPlane und
pvXORPlane. Sie bestimmen das Aussehen des Cursors laut nachfolgender Tabelle:
|
AND Bitmaske
|
XOR Bitmaske
|
Anzeige
|
| 0 |
0 |
schwarzer Punkt
|
| 0 |
1 |
weißer Punkt |
| 1 |
0 |
transparenter
Punkt |
| 1 |
1 |
invertierter
Punkt |
Als Returnwert liefert die Funktion
das Handle des erstellten Cursors. Wird der erstellte Cursor nicht mehr benötigt,
so muss er mit der API-Funktion DestroyCursor(...) wieder gelöscht
werden.
Im Beispiel soll nun die aktuelle Mausposition innerhalb der
Client-Area als Cursor dargestellt werden. Dazu muss zunächst abgeprüft werden,
ob sich die Maus innerhalb der Client-Area des Fensters befindet. Ist dies der Fall,
so wird die aktuelle Cursor-Position ausgelesen und in Client-Koordinaten umgerechnet.
Da der Cursor unabhängig von der Hintergrundfarbe des Fensters immer sichtbar sein
soll, wird der Cursor invertiert dargestellt. Dazu wird die AND-Bitmaske zunächst
komplett auf '1' gesetzt. Anschließend wird in das CBitmap-Objekt, das
bei der Initialisierung erstellt wurde, der Cursor gezeichnet. Da für den Cursor
anschließend nur die Bits der Bitmap (und nicht der Bitmap-Vorspann) benötigt werden,
werden die Bits mittels der CBitmap-Methode GetBitmapBits(...)
extrahiert. Zum Schluss wird dann der Cursor mit CreateCursor(...)
erstellt und der vorherige Cursor gelöscht.
BOOL CDynCursorView::OnSetCursor(CWnd*
pWnd, UINT nHitTest, UINT message)
{
// TODO: Code für die Behandlungsroutine für Nachrichten hier
einfügen und/oder Standard aufrufen
CPoint
ActCurPos;
HCURSOR hTempCur;
int nXLen, nYLen;
char acXPos[10],acYPos[10];
// Falls Cursor im Fenster und
im Client-Bereich
if (*pWnd == *this && nHitTest
== HTCLIENT)
{
// akt.
Cursor-Position holen und in Client-Koord. umrechnen
GetCursorPos(&ActCurPos);
if (ActCurPos == m_OldPos)
return TRUE;
m_OldPos = ActCurPos;
ScreenToClient(&ActCurPos);
// Alle
Pixel der AND-Bitmaske auf '1' setzen
memset(m_pvAND,0xFF,(m_nXCurSize/8)*m_nYCurSize);
// Bitmap
mit schwarzen Brush ausfuellen
m_CMemDC.SelectStockObject(BLACK_BRUSH);
m_CMemDC.PatBlt(0,0,m_nXCurSize,m_nYCurSize,PATCOPY);
// Cursor
zeichnen
m_CMemDC.SetTextColor(RGB(255,255,255));
m_CMemDC.SelectStockObject(WHITE_PEN);
m_CMemDC.MoveTo(0,0);
m_CMemDC.LineTo(0,2);
m_CMemDC.MoveTo(0,0);
m_CMemDC.LineTo(2,0);
m_CMemDC.MoveTo(0,0);
m_CMemDC.LineTo(8,8);
// Cursor-Pos.
nach ASCII wandeln und in Bitmap einzeichnen
nXLen
= wsprintf(acXPos,"%d",ActCurPos.x);
nYLen = wsprintf(acYPos,"%d",ActCurPos.y);
m_CMemDC.SetBkMode(TRANSPARENT);
m_CMemDC.TextOut(8,0,acXPos,nXLen);
m_CMemDC.TextOut(8,15,acYPos,nYLen);
// Bitmap-Bits
nach XOR Bitmapfeld kopieren
m_CBitmap.GetBitmapBits((m_nXCurSize/8)*m_nYCurSize,m_pvXOR);
// Cursor
aus beiden Bitmapfeldern erstellen
hTempCur
= ::CreateCursor(AfxGetInstanceHandle(),0,0,
m_nXCurSize, m_nYCurSize,m_pvAND,m_pvXOR);
// neuen
Cursor setzen
::SetCursor(hTempCur);
// und
alten Cursor loeschen
if (m_hOldCursor
!= 0)
::DestroyCursor(m_hOldCursor);
m_hOldCursor = hTempCur;
return TRUE;
}
// Falls Cursor nicht im Clientbereich,
// Cursor wie bisher zeichnen
else
return CView::OnSetCursor(pWnd, nHitTest,
message);
} |
Das fertige Beispiel finden Sie unter 07Ressourcen\DynCursor.
|