ComboBox DropDown Höhe wird nicht mehr durch die Ressourcen definiert

Vor Jahren habe ich für die microsoft.public.de.vc FAQ den folgenden Beitrag geschrieben:
Warum klappt meine ComboBox im DropDown-Stil in einem Dialog nicht auf?

Beim Erstellen einer ComboBox in einem Dialog Template muss auch die Größe mit angegeben werden, die die ComboBox haben soll, wenn Sie denn aufgeklappt wird. Dies kann auf zwei Methoden geschehen.

Methode 1: ComboBox aus der Werkzeugleiste einfach durch einen Mausklick einsetzen. Anschließend auf den „DropDown“-Schalter klicken und nun die gewünschte Größe einstellen.

Methode 2: ComboBox durch Ziehen eines Rechteckes auf dem Dialog einsetzen. In diesem Fall wird die Größe gleich korrekt bestimmt. Nachträgliche Änderung der Größe erfolgt dann wieder durch anklicken des „DropDown“-Schalters.

Anmerkung: Die Größe einer ComboBox mit dem Stil CBS_DROPDOWN und CBS_DROPDOWNLIST im NICHT aufgeklappten Zustand kann beim Erzeugen nicht verändert werden. Diese Größe bestimmt Windows automatisch. Die Größe die bei CreateWindow/CreateWindowEx angegeben wird ist immer die Größe des Control im aufgeklappten Zustand. Nachdem ein gültiger Windowshandle auf die ComboBox existiert kann mit CComboBox::SetItemHeight die Höhe der Items bzw. des Editfelds der ComboBox verändert werden.

Jetzt habe ich entdeckt, dass dieser Beitrag eigentlich überflüssig geworden ist, seit dem es COMCTL32 in der Version 6.0 gibt.
Wenn ein Manifest für die 6.0 Version der Common Controls vorhanden ist, dann bestimmt die COMCTL32 DLL automatisch selbst anhand der Höhe des Monitors und der Position der ComboBox wie groß der DropDown-Bereich sein kann.

Aufgefallen ist mir das, als ich in einer RC-Datei sah, dass eine ComboBox mit der Höhe gerade einmal 20 DLUs angegeben wurde. Da in der RC Datei normalerweise immer nur die DropDown-Höhe eingetragen ist, fragte ich mich warum bisher niemandem aufgefallen war, dass diese ComboBox, nicht aufklappt. Ein kurzer Test, zeigte allerdings, dass alles normal war und die Box, den halben Monitor in der Höhe einnahm.
Ein weiter Test mit und ohne Manifest zeigte mir dann schnell, dass sich das Standardverhalten von Comboboxen offensichtlich verändert hat.

Nachtrag (07.01.2011):
Nur die neuen Common Controls ab Vista und Windows 7 verhalten sich wie oben beschrieben. Windows XP (einschließlich SP3) verhält sich noch gemäß der MSDN WinAPI Doku. D.h. die Höhe wird nicht automatisch angepasst. Man kann sich eben auf nichts verlassen.

CDialog::SetDefID und DM_SETDEFID, des Tastaturfreunds Liebling

Die Frage um die Eingabe-Taste in Dialogen und wie man diese „missbraucht“ (sage ich mal provokant) ist eine regelmäßige Frage in allen Foren.

Die Intention ist oft klar. Man möchte mit der Eingabetaste eine bestimmte Aktion verbinden, die evtl. sehr oft ausgeführt werden soll und nicht den Dialog schließen.
Die üblichen Wege sind schon mehrfach diskutiert worden und auch in meinem Blog finden sich dazu ein Artikel .

Zu kurz kommt bei dieser Diskussion die Funktion CDialog::SetDefID bzw. DM_SETDEFID Nachricht.
Was macht diese Funktion/Nachricht?
Sie definieren die Button-ID, die als Default-Aktion in einem Dialog ausgelöst werden soll und das ist nichts anderes als die Aktion die geschehen, soll wenn die Eingabe-Taste gedrückt wird.
Viele Entwickler definieren einfach OnOK um. Aber das eigentlich tolle ist mit SetDefID den Button in Abhängigkeit der Daten umzusetzen und das hat auch einen visuellen Effekt für den Nutzer.

Mal ein Beispiel:
Wir haben einen Dialog mit zwei List Views. Links Elemente die zur Auswahl stehen, rechts die Elemente in der Reihenfolge, die der Benutzer ausgewählt hat.
Der Mausschubser wird einfach die Einträge auf der linken oder rechten Seite doppelklicken und damit auswählen oder entfernen. Entsprechende Buttons für Hinzufügen und Entfernen wird es auch geben. Man kann also auch links oder rechts markieren und dann den Hinzufügen oder Entfernen Schalter nutzen.

Dem Tastaturnutzer können wir helfen indem wir intelligent CDialog::SetDefID / DM_SETDEFID verwenden. Die Vorgehensweise ist einfach.

  • Wir richten uns nur danach in welchem List View wir uns befinden, d.h. befinden wir uns im linken List View steuern wir den Hinzufügen Schalter, und im rechten List View steuern wir den Entfernen Schalter.
  • Wird also im linken List View ein Item ausgewählt, setzen wir mit CDialog::SetDefID / DM_SETDEFID die ID des Hinzufügen Schalters.
  • In dem Moment wird der Hinzufügen Schalter zum Default-Button. Der Nutzer kann nun die Eingabe-Taste drücken und die Items werden in die rechte Box verschoben.
  • Links liegt jetzt nun noch der Fokus, aber es sind keine Items mehr markiert. D.h. wir setzen nun den Default Button zurück auf IDOK.
  • Jetzt kann der Nutzer erneut ein Item markieren. Der Default-Button wird wieder der Hinzufügen/Entfernen Schalter und die Eingabetaste macht was der Nutzer gerne hätte.
  • Ist kein Item mehr markiert schließt die Eingabetaste wieder über IDOK den Dialog.

Ohne Maus kann man also mit den Pfeiltasten, der Leertaste (evtl. Strg-Taste) und der Eingabetaste diesen Dialog bedienen. Und das sogar intuitiv, denn der entsprechende Default Button wird ja in der UI schön umrandet und hervorgehoben.
Das freut jeden Tastaturfreund. Und man muss gar nichts groß machen mit der Behandlung Eingabetaste.

Damit das Ganze nicht so abstrakt ist, habe ich ein kleines Sample gebaut, dass diese Anwendung zeigt. Es hat keine Implementierung für Drag&Drop aber es macht deutlich, wie man dem Tastaturnutzer entgegen kommen kann indem man die Controls geschickt aktiviert.

Manche Änderungen in den UIs machen einen wahnsinnig, heute das MS-SQL Management Server Studio

Ich nutze viel das MS-SQL Server Management Studio für MS-SQL 2005. Besonders um eben auch SQL-Statements zu testen und zu entwerfen.

Häufig lasse ich mir dazu einfach eine aktuelle Tabelle anzeigen. Mit Strg+3 kann man sich nun das SQL-Statement anzeigen lassen und es ändern. Also z.B. eine WHERE Bedingung hinzufügen.
Jeder der den Enterprise Manager gewohnt ist haut nun auf die F5-Taste, es passiert natürlich nichts 😮 . OK times are changing, wir benutzen also brav Strg+R um das neue Statement auszuführen.

OK nun klicken wir auf den Datenbanknamen und wählen hier im Kontextmenü Neue Abfrage aus. Ein bisschen SQL Statement schreiben und wir drücken Strg+R . 😮 Was denn nun? Wir bemühen also das entsprechende Menü und finden das hier der entsprechende Hotkey F5 ist.

Wer nun wieder mit MS-SQL Server 2008 arbeitet wird nun mit Strg+R kein Glück haben. Jetzt ist alles wieder gut und hier funktioniert nur noch F5. Jetzt ist dafür der Abfrage-Designer ist nun ein separater Dialog geworden, der das entsprechende Statement in das Abragefenster überträgt.
(Nachtrag 2009-05-06) Wenn man aus im Management Studio 2008 den Punkt Zeilen bearbeiten auswählt, hat man das selbe miese Verhalten wie im Management Stusio 2005. Man kommt nur nicht mehr so offensichtlich in diesen Bearbeitungsmodus und landet eher auf einer Abfrage, in der man dann aber das Ergebnis nicht bearbeiten kann.

Es gibt Momente in denen ich mich beherrschen muss nicht in die Tischplatte zu beißen… 😡

Aus zwei mach eins: Wie man zwei Cursor kombinieren kann

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

VS Tipps & Tricks: Kontextmenüs in Visual Studio anpassen

Man kann ja ohne weiteres Toolbars und die Menüs in Visual Studio anpassen, was ich auch gerne nutze.

Besonders interessant ist es aber auch die verschiedenen Kontextmenüs anzupassen. Dazu muss man zu einem kleinen Trick greifen, denn die Kontext Menüs stehen normalerweise nicht automatisch im Zugriff beim Anpassen der UI.

  • Man wählt Tools -> Customize -> Toolbars
  • Dort dann Context Menus auswählen -> Kontextmenü

Es öffnet sich nun ein neuer Toolbar, in dem die Kontextmenüs alle erscheinen und können wie gewohnt angepasst werden.

Gar nicht schwierig, man muss nur wissen wie man dran kommt 😉

SYSTEM_FONT, DEFAULT_GUI_FONT und der Font der eigentlich benutzt wird…

Was sind eigentlich  die beiden Fonts SYSTEM_FONT und DEFAULT_GUI_FONT, die durch GetStockObject zurückgegeben werden?
Sind sie ein schneller Weg um an den „MS Shell Dlg“ bzw. „MS Shell Dlg 2“?

Es ist ein Irrtum wenn man glaubt man bekommt den Standard UI Font mit DEFAULT_GUI_FONT oder SYSTEM_FONT. Dieser Irrtum basiert auf der Annahme es gebe nur einen Font den die Windows UI verwendet!

Der richtige Weg vorab: Der einzige korrekte Weg einen richtigen Font für die Default UI zu erhalten ist SystemParametersInfo mit SPI_GETNONCLIENTMETRICS. Bzw., in Dialogen sollte immer als Font „MS Shell Dlg“ bzw. „MS Shell Dlg 2“ verwendet werden (siehe auch The old new thing). Man beachte den Plural ❗

Und was sind diese beiden Fonts nun?
Es sind Relikte aus alten Tagen, die kein Mensch und vor allem kein Windows Entwickler bei Microsoft mehr verwendet.  (Anmerkung des Autors: Ich habe zumindest aus zuverlässiger Quelle gehört, die Entwickler bei Microsoft wären immer noch Menschen 😉 )

  • In alten Tagen von Windows 2.0 wurden SYSTEM_FONT für die Dialoge verwendet. Dieser Font hieß auch System. Man glaubt es kaum, dieser Font ist immer noch der Standard-Dialog-Font, den aber wirklich keiner mehr will. Es ist ein Bitmap Font und er kann nicht mit Anti-Aliasing verwendet werden.
  • Die Geschichte von DEFAULT_GUI_FONT ist weniger spektakulär. Er wurde mit Windows 95  eingeführt und man dachte damit einen Standard gesetzt zu haben. Aber das wurde noch im selben Release zugunsten von SystemParametersInfo  fallen gelassen. Es ist geblieben als weiteres Relikt aus alten Tagen.

Alle Fonts die GetStockObject liefert sind Bitmap Fonts, die auch kein ClearType unterstützen.

Siehe auch:
What are SYSTEM_FONT and DEFAULT_GUI_FONT?
What about logical fonts?
DEFAULT_GUI_FONT really stinks