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.



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