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);
}