Wer komplexere UIs baut, der kommt um Drag&Drop oder andere Extrafunktionen mit der Maus nicht herum. Um den Benutzer gut zu unterstützen verwendet man natürlich auch spezielle Cursor.
Auffällig ist aber oft genug der Unterschied zwischen den schönen Standardzeigern im 3D-Stil und den oft flachen Zeigern, die gerade mal zusätzlich ein Plus oder ein Stoppzeichen tragen. Oder ganz und gar der Unterschied , wenn man eigene Cursorstile verwendet, wie z.B. Dirigent oder Hände.
Ich habe mich bei meinem aktuellen Projekt gefragt, warum man eigentlich nicht den aktuellen Standard-Cursor verwendet und diesen zusätzlich mit dem entsprechenden Symbol versieht (Pluszeichen, Stopp, Dragframe etc). Das würde besser aussehen und Arbeit sparen, weil man sich nur noch Gedanken um die Extrasymbolik machen muss und der Benutzer behält seinen eingestellten normalen Cursor.
Schaut man sich den Explorer unter XP+Vista an, dann macht man die Entdeckung, dass der es genauso macht. Der aktuelle Cursor wird beim Drag&Drop Vorgang um ein Stopp- oder Pluszeichen ergänzt.
Warum es also nicht selber auch so machen ❓
Meine Suche im Internet zu diesem Thema: „Wie kombiniert man zwei Cursor zu einem?“, war nicht von Erfolg gekrönt. Beispielcode dazu habe ich nicht gefunden. Aber auch wirklich gar nichts.
Also selbst Hand anlegen.
Bei meinem ersten Versuch wollte ich die Cursor-Bitmaps direkt manipulieren. Bei meiner Untersuchung und dem ersten Democode wurde klar, dass Windows gar keinen Unterschied mehr zwischen Icons und Cursor macht. Erstaunlich ❗ Ob Icon oder Cursor spielt eigentlich keine Rolle. HCURSOR und HICON sind identische Typedefs. Also egal ob toller Farbcursor oder monochromer Cursor, alles gleich.
Mit dieser Entdeckung nahm ich schnell Abstand von BitBlt und Konsorten und verwendete eine Imagelist, die genau diese Funktion des Overlays von Symbolen bereits unterstützt und zudem auch noch perfekt mit Icons umgehen kann.
Herausgekommen ist der folgende Code, der es erlaubt zwei Icons, oder Cursor zu überlagern und ein neues Icon oder einen neuen Cursor zu erzeugen.
Man muss sich aso nur mit LoadCursor, den aktuellen Cursor laden und kann diesen damit ganz einfach modifzieren. Nicht vergessen: Das erzeugte Icon/Cursor muss allerdings mit DestroyIcon auch wieder entsorgt werden. Das Ganze habe ich der Einfachheit halber mit MFC Funktionen geschrieben, aber das Ganze in pure WinAPI zu transformieren dürfte nicht schwer sein. 😉
Hier nun der Code. Have fun!
HICON CombineIcons(HICON hIcon1, HICON hIcon2)
{
// Remember that HCURSOR and HICON are identical!
// hIcon1 is overlayed by hIcon2.
// hIcon2 isn't adjusted in size or position.
// It just overlays hIcon1
// Get bitmaps of icon 1
ICONINFO iconInfo;
::ZeroMemory(&iconInfo,sizeof(iconInfo));
if (!GetIconInfo(hIcon1,&iconInfo))
return NULL;
// Attach the bitmaps to get them automatically freed
// upon error.
CBitmap bitmap, mask;
bitmap.Attach(iconInfo.hbmColor);
mask.Attach(iconInfo.hbmMask);
// Get size and width
BITMAP bm;
if (bitmap.m_hObject)
bitmap.GetObject(sizeof(bm),&bm);
else
mask.GetObject(sizeof(bm),&bm);
// Get the color depth from the icon and create an image list
// Remember we need a
UINT flags = 0;
switch (bm.bmBitsPixel)
{
case 4: flags = ILC_COLOR4; break;
case 8: flags = ILC_COLOR8; break;
case 16: flags = ILC_COLOR16; break;
case 24: flags = ILC_COLOR24; break;
case 32: flags = ILC_COLOR32; break;
default: flags = ILC_COLOR4; break;
}
CImageList il;
// be ware that the monochrom cursor bitmap is twice the height
if (!il.Create(bm.bmWidth,
bm.bmHeight/(iconInfo.hbmColor!=NULL ? 1 : 2),
ILC_MASK|flags,2,2))
return NULL;
// Load the both icons into the image list
il.Add(hIcon1);
il.Add(hIcon2);
// Define the second icon as an overlay image
il.SetOverlayImage(1,1);
// Get a new icon with icon 2 overlayed
HICON hCombined = ImageList_GetIcon(il.m_hImageList,0,
ILD_NORMAL|INDEXTOOVERLAYMASK(1));
if (!hCombined)
return NULL;
// Need the icon infos for this new icon
ICONINFO iconInfoCombined;
::ZeroMemory(&iconInfoCombined,sizeof(iconInfo));
if (!GetIconInfo(hCombined,&iconInfoCombined))
return NULL;
// Destroy the combined icon, we just have the bitmap and the mask
::DestroyIcon(hCombined);
// Get the bitmaps into objects to get them automatically freed
CBitmap bitmapCombined, maskCombined;
bitmapCombined.Attach(iconInfo.hbmColor);
maskCombined.Attach(iconInfo.hbmMask);
// Get the hotspotinto and cursor data from
// the ICONINFO of hCursor1
iconInfoCombined.fIcon = iconInfo.fIcon;
iconInfoCombined.xHotspot = iconInfo.xHotspot;
iconInfoCombined.yHotspot = iconInfo.yHotspot;
// OK we have can create a new Cursor out of the target
// Don't forget to use DestroyIcon for the new Cursor/Icon
return ::CreateIconIndirect(&iconInfoCombined);
}
Ja, sowas habe ich auch mal irgendwann gemacht, als ich den Windows-Finder vom Spy++ emulieren mußte. Die ganze Zeit hatte ich Schweißperlen auf der Stirn, weil mir das Wort „Hardware-Cursor“ im Kopf rumspukte und ich nicht mehr wußte, welchen Einfluß das hat und ob ich es bei Windows oder nur bei Spielen gehört hatte. Hast Du da mal drüber nachgedacht?
Also Hardware-Cursor in dem Sinne von „fest-codierten-Cursor-in-der-Grafikkarte“ kommen unter Windows nach meinem Wissen schon seit Windows 3.1 nicht mehr zum Einsatz.
Heute ist das nur noch ein fester Speicherbereich in der Grafikkarte, der die Cursor-Anzeige ohne Flackern regelt.
Wie sonst sollten sonst, die verschiedenen Custom-Cursor Varianten funktionieren.
BTW: Mein Code ist getestet und funktioniert auf 3 verschiedenen Laptop-Grafikkarten Varianten und 5 verschiedenen Desktop-Grafikkarten.
Siehe auch:
http://support.microsoft.com/kb/127139/en-us
Ich hätte noch nichtmal in meinen kühnsten Träumen auch nur vermutet, daß Du je ungetesteten Code posten würdest. Ich hatte nur von meiner damaligen inneren Unruhe berichtet. 😉
Hallo Martin, interessanter Beitrag! Aber würde das nicht auch einfach mit ImageList_Merge funktionieren? Oder hat ImageList_Merge Abgründe, von denen man wissen sollte?
Das würde aber eine neue Imagelist erzeugen und ich müsste das Icon immer noch mit ImageList_GetIcon holen, das kann ich mir sparen indem ich einfach die Overlay Funktion nutze.
Aber ansonsten kommt sicherlich das selbe dabei raus…
Nettes Beispiel, nur leider fehlt mir da genau die Info die ich momentan verzweifelt suche….
Wie komme ich an die Overlays von Windows ran ?
Mein Ansatz ist genau der im Artikel beschriebene:
Ich möchte das die Cursors beim Drag&Drop genau so aussehen wie im (Windows)Explorer.
Also bruache ich eigentlich nur die beiden Overlays (Stopp & Plus)…
… aber wie bekomme ich die ??
Möglicherweise mit LoadCursor( IDC_??? ), aber da gibt’s kein IDC-Wert für die Overlays.
Die gibt es so nicht offiziell. Wenn wären die im Explorer vermutlich selbst drin und Du müsstest diese Icons aus der Explorer.exe laden. Lade die Datei doch einfach und schau rein.
Das ist aber sicherlich nicht portabel.
Bau Sie Dir doch selber. Das ist doch wirklich einfach.
Nein im Explorer sind Sie nicht 🙁
Vermutlich in irgend einer DLL, aber dort überall nachzuschauen kostet wirklich zu viel Zeit.
Klar kann (und habe) ich die Momentan selber gezeichnet, schöner und IMHO besser währe es jedoch dir Original Images zu nutzen.
(Gleicher Grund wie: Warum nehm ich keinen eigene (Drag-)Cursor).
Na macht ja nichts, dann bleibt es halt bei den selbst gezeichenten…
Mfg, Günter
Sowas habe ich im Jahre 2000 selber gebastelt, als ich die Aufgabe hatte, einen Explorer nachzuprogrammieren. Frag nicht nach Details. Ich war jung, ich brauchte das Geld.
Man läßt sich via SHGetFileInfo eine SystemImageList erzeugen. Zum Beispiel auf ein gefaktes com-File. Jetzt erzeugt man sich ein ebenso gefaktes lnk-File. Beide in %TEMP% angelegt. Jetzt nur noch IShellFolder und GetOverlayIndex und schon hat man alle Windows bekannten OverlayIcons in seiner Imagelist. Danach nur noch sauber mit SetOverlayIndex und Konsorten arbeiten, und Windows macht alles von alleine.
Zumindest damals. Ein Versuch, diese Sachen außerhab der Shell, mit reiner WIN32-API herauszubekommen, klingt für mich jedenfalls etwas nach Masochismus. 🙂 Aber vielleicht geht das ja heute alles einfacher.