Ein eigenes Maussymbol für eine Schaltfläche

Veröffentlicht: 15. Dez 2001 | Aktualisiert: 18. Jun 2004

Von Paul DiLascia

Win32-Know-how

Manchmal soll ein bestimmtes Steuerelement einen eigenen Mauszeiger bekommen. Dieser Artikel zeigt, wie dies realisiert werden kann.

Auf dieser Seite

 Frage
 Antwort

Diesen Artikel können Sie hier lesen dank freundlicher Unterstützung der Zeitschrift:

Bild03

Frage

Ich würde für eine bestimmte Schaltfläche in einem Dialog gerne die Cursorform ändern. Wie geht das?

 

Antwort

Unter Windows stehen Ihnen zwei Wege frei, den Cursor zu ändern. Sie können in der WNDCLASS-Struktur einen globalen Cursor definieren, sobald Sie die Hauptfensterklasse anmelden, oder Sie ändern den Cursor dynamisch, und zwar als Reaktion auf die Nachricht WM_SETCURSOR. Ein MFC-Programm setzt den Cursor normalerweise nach der ersten Methode und meldet das Hauptfenster automatisch mit einem Pfeil als Cursor an. Diese Vorgehensweise können Sie ändern, indem Sie WM_SETCURSOR selbst auswerten, sei es im Hauptfenster oder im Kindfenster.

// bearbeite WM_SETCURSOR in der Button-Klasse 
BOOL CMyButton::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT msg)  
{ 
    ::SetCursor(m_hMyCursor); 
    return TRUE; 
}

Sobald nun der Anwender die Maus auf Ihre Schaltfläche schiebt, schickt Windows der Schaltfläche eine WM_SETCURSOR-Nachricht (sofern die Maus nicht von einem anderen Objekt eingefangen wurde - Stichwort SetCapture). Die Nachricht liefert das Handle des Fensters, auf dem die Maus gerade liegt, in diesem Fall also das Handle der Schaltfläche. Außerdem liefert die Nachricht noch einen Testcode, wie er im WM_NCHITTEST-Treffertest benutzt wird (Tabelle T1 in meinem Artikel "Verhindern von Benutzereingaben" in diesem Heft), und die Kennung des Maus-Ereignisses, das diese Nachricht ausgelöst hat, zum Beispiel WM_MOUSEMOVE. WM_SETCURSOR ist Ihre große Chance, den Cursor auszuwechseln. Wenn Sie das tun, sollten Sie TRUE zurückgeben, damit Windows auf die übliche Standardbearbeitung der Nachricht verzichtet.

Welcher Art könnte solch eine Standardbearbeitung sein? Die Standard-Fensterfunktion schickt zuerst dem Elternfenster eine WM_SETCURSOR-Nachricht, sofern es ein Elternfenster gibt. Kümmert sich das Elternfenster um die WM_SETCURSOR-Nachricht (es gibt also TRUE zurück), so braucht Windows nichts mehr zu tun. Interessiert sich das Elternfenster aber nicht für WM_SETCURSOR (es gibt FALSE zurück), so verschafft Windows dem Kindfenster die Gelegenheit, diese Nachricht zu bearbeiten. Sollte auch das Kindfenster nichts tun (es gibt FALSE zurück), verwendet Windows den globalen Cursor oder einen Pfeil, falls es keinen globalen Cursor gibt.

Was bedeutet das nun für Sie? Es bedeutet, dass Sie sich bei einer dynamischen Auswechselung des Cursors entscheiden müssen, ob Sie WM_SETCURSOR vom Elternfenster oder vom Kindfenster bearbeiten lassen. Je nach den Umständen ist der eine oder der andere Ort besser geeignet. Im Allgemeinen sollte man den Objekten selbst die Gelegenheit geben, ihre Eigenschaften festzulegen. In diesem Fall ist die Schaltfläche betroffen. Daher sollte sie auch für die Cursoränderung zuständig sein. Allerdings wird dadurch die Ableitung einer neuen Schaltflächenklasse erforderlich, mit einer eigenen Nachrichtentabelle und allem, was so dazu gehört. Das ist eine Menge Schreibarbeit. Oder Geklicke, falls man eher zum Klassenassistenten neigt (Benutzt eigentlich jemand diesen "Wizard"?). Falls Sie bereits eine eigene Schaltflächenklasse haben, sollten Sie darin WM_SETCURSOR bearbeiten. Sofern Sie aber noch keine Schaltflächenklasse abgeleitet haben oder sowieso gerade Ihre faulen fünf Minuten haben, ist es auch in Ordnung, wenn Sie WM_SETCURSOR im Dialog berücksichtigen. Verraten Sie nur keinem OO-Puristen, dass ich Ihnen das gesagt habe.

Bild01

B1 MyCursor und die Schaltflächen.

L1 Ausschnitt aus CMyDialog

///////////////////////////////////////////////////////////////// 
// System Journal, Mai/Juni 2001 
// 
// Sofern dieser Code funktioniert, stammt er von Paul DiLascia. 
// Falls nicht, können wir Ihnen den Autoren leider nicht nennen. 
// Lässt sich mit Visual C++ 6.0 kompilieren und läuft unter 
// Windows 98 (und wohl auch unter Windows NT). 
// 
// MyCursor demonstriert, wie sich der Cursor dynamisch ändern  
// lässt, sobald die Maus auf einem bestimmten Steuerelement steht 
// oder sich in einem bestimmten Bereich bewegt. 
// 
// Die folgenden Zeilen zeigen nur die wichtigen Details. Den  
// vollständigen Quelltext finden Sie auf der Begleit-CD. 
// 
#include "stdafx.h" 
#include "resource.h" 
#include "TraceWin.h" 
#include "StatLink.h" 
... 
class CMyDialog : public CDialog { 
public: 
   CMyDialog(CWnd* pParent = NULL); 
   ~CMyDialog(); 
protected: 
... 
   // Der Schaltflächen-Cursor 
   HCURSOR m_hButtonCursor; 
   virtual BOOL OnInitDialog(); 
   afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT msg); 
   DECLARE_MESSAGE_MAP() 
}; 
... 
BEGIN_MESSAGE_MAP(CMyDialog, CDialog) 
   ON_WM_PAINT() 
   ON_WM_QUERYDRAGICON() 
   ON_WM_SETCURSOR() 
END_MESSAGE_MAP() 
CMyDialog::CMyDialog(CWnd* pParent /*=NULL*/) 
   : CDialog(IDD_MYDIALOG, pParent) 
{ 
} 
CMyDialog::~CMyDialog() 
{ 
} 
BEGIN_MESSAGE_MAP(CMyDialog, CDialog) 
   ON_WM_PAINT() 
   ON_WM_QUERYDRAGICON() 
   ON_WM_SETCURSOR() 
END_MESSAGE_MAP() 
////////////////// 
// Initialisiere den Dialog 
// 
BOOL CMyDialog::OnInitDialog() 
{ 
   CDialog::OnInitDialog(); 
   ... 
   // andere Initialisierungen 
   // lade den Schaltflächen-Cursor 
   m_hButtonCursor = AfxGetApp()->LoadCursor(IDC_MYHAND); 
   return TRUE;  // gib TRUE zurück, sofern der Fokus nicht 
}                // auf ein Steuerelement gesetzt wird 
... 
////////////////// 
// Wechsele den Cursor aus, falls die Maus über die Schaltfläche 
// läuft 
// 
BOOL CMyDialog::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT msg) 
{ 
   CString sClassName; 
   ::GetClassName(pWnd->GetSafeHwnd(),sClassName.GetBuffer(80),80); 
   if (sClassName=="Button" && m_hButtonCursor) { 
      ::SetCursor(m_hButtonCursor); 
      return TRUE; 
   } 
   return CDialog::OnSetCursor(pWnd, nHitTest, msg); 
}

Ich habe eine kleine Anwendung auf Dialogbasis geschrieben, die beide Methoden demonstriert. Wenn Sie die Maus in diesem Programm NoCursor auf die Schaltfläche schieben, verwandelt sich der Cursor in einen blauen Zeigefinger (Bild B1). Für diesen Wechsel sorgt der Code aus Listing L1, und zwar mit der Funktion OnSetCursor aus der Dialogklasse CMyDialog. Sobald Sie die Maus aber auf einen der unterstrichenen Hyperlinks schieben, ändert sich der Cursor in eine andere Art von Zeigefinger, nämlich in den normalen Handcursor von Windows (Bild B2). Dafür ist die Kindklasse CStaticLink verantwortlich (Listing L2). CStaticLink ist eine Hyperlink-Allzweckklasse, die in meinen Artikeln des Öfteren auftaucht. Der größte Teil des Codes in CStaticLink::OnSetCursor beschäftigt sich mit der Beschaffung des gewünschten Handcursors aus der winhlp32.exe und hat nichts mit dem Setzen des neuen Cursors zu tun. Daher habe ich ihn der Übersichtlichkeit halber weggelassen. Falls Sie sich für solche Einzelheiten interessieren, sollten Sie im Quelltext nachschauen, den Sie auf der Begleit-CD dieses Hefts finden.

Bild02

B2 MyCursor und Hyperlinks.

L2 Die CStaticLink-Klasse

//////////////////////////////////////////////////////////////// 
// System Journal, Mai/Juni 2001 
// Autor: Paul DiLascia. 
// 
// Lässt sich mit Visual C++ 6.0 kompilieren und läuft unter 
// Windows 98 (und wohl auch unter Windows NT). 
// 
// CStaticLink implementiert ein statisches Steuerelement (ein  
// Beschriftungsfeld), das eine Verknüpfung mit einer beliebigen 
// Datei auf dem Desktop oder im Web darstellen kann. Wird es 
// angeklickt, öffnet es die Datei/den URL. 
// 
// Die folgenden Zeilen wurden gekürzt und zeigen nur die wichtigen 
// Einzelheiten. Den vollständigen Quelltext finden Sie auf der  
// Begleit-CD. 
// 
#include "StdAfx.h" 
#include "StatLink.h" 
#ifdef _DEBUG 
#define new DEBUG_NEW 
#undef THIS_FILE 
static char THIS_FILE[] = __FILE__; 
#endif 
... 
////////////////// 
// Setze den "Hand"-Cursor und weise den Anwender somit darauf hin, 
// dass es sich um einen Hyperlink handelt. Falls die Anwendung 
// g_hCursorLink nicht gesetzt hat, versuche, den Cursor aus der 
// winhlp32.exe auszulesen, Ressource 106. Ein wenig holprig,  
// aber es funktioniert. 
// 
BOOL CStaticLink::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{ 
   if (g_hCursorLink == NULL) { 
      ... 
       // lade g_hCursorLink aus win32hlp.exe 
   } 
   if (g_hCursorLink) { 
      ::SetCursor(g_hCursorLink); 
      return TRUE; 
   } 
   return FALSE; 
}