Das Web Browser Control stiehlt den Fokus wenn ein Dokument geladen wurde

Wenn man ein Web Browser Control einbindet und dieses eine Seite lädt, dann wird der Fokus in dieses Browser Control gesetzt. Dagegen ist kein Kraut und keine Notification gewachsen.
Genaugenommen ist nicht das Webbrowser Control schuld, sondern der Scriptcode der auf der Seite läuft und den Fokus umsetzt. Das sieht man schnell wenn man den Moment der WM_KILLFOCUS Nachricht im Debugger abpasst und sich den Stacktrace ansieht.

0013b84c 4a570824 USER32!NtUserSetFocus 
0013b858 4a5ce628 mshtml!CDoc::TakeFocus+0x2a 
0013b880 4a63fc0b mshtml!CElement::BecomeCurrent+0x167 
0013b8b4 4a63fb72 mshtml!CElement::focusHelper+0xcc 
0013b8c0 4a587c85 mshtml!CElement::focus+0x1d 
0013b8cc 4a5d7477 mshtml!Method_void_void+0x17 
0013b94c 4a57fae8 mshtml!CBase::ContextInvokeEx+0x462 
0013b97c 4a575413 mshtml!CElement::ContextInvokeEx+0x72 
0013b9b0 76fa5295 mshtml!CElement::ContextThunk_InvokeEx+0x44 
0013b9e8 76fa5208 jscript!IDispatchExInvokeEx2+0xa9 
0013ba20 76fa5323 jscript!IDispatchExInvokeEx+0x56 
0013ba90 76fa577b jscript!InvokeDispatchEx+0x78 
0013bad8 76fa57c6 jscript!VAR::InvokeByName+0x1c1 
0013bb18 76fa4ab0 jscript!VAR::InvokeDispName+0x43 
0013bb3c 76fa5a14 jscript!VAR::InvokeByDispID+0xfb 
0013bd30 76fa46d8 jscript!CScriptRuntime::Run+0x195b 
0013bdf4 76fa506e jscript!ScrFncObj::Call+0x69 
0013be6c 76fa5f6a jscript!CSession::Execute+0xb8 
0013bf6c 76fa672f jscript!NameTbl::InvokeDef+0x183 
0013c040 76fa5295 jscript!NameTbl::InvokeEx+0xd2

Dummerweise gibt es kein Event mehr, das danach gefeuert wird, wenn der Skript-Code abläuft. Das letzte Event bevor das aktive Fenster den Fokus verliert ist OnDocumentComplete.

Es gibt auch einige Threads die dieses Thema behandeln, aber keine vernünftige Lösung. Von so manchen Timerlösungen halte ich nichts, die da so vorgeschlagen werden, wer weiß schon wann eine Seite geladen ist?
Besonders ärgerlich auch, wenn man das Browser Control nicht mal auf einem sichtbaren Fenster hat, sondern nur in einem versteckten Fenster hält. Auch in diesem Fall verliert das aktive Fenster den Fokus.

Aber mit einem kleinen Trick bekommt man es doch hin ( 🙂 warum sonst schreibe ich den Artikel )

  1. Man baut einen OnDocumentComplete Handler ein.
  2. Wenn das Event eintritt, besorgt man sich mit GetFocus das Fenster, dass aktuell noch den Fokus inne hat.
  3. Nun sendet man mit PostMessage eine selbst definierte Nachricht (#define WM_RESTOREFOCUS (WM_APP+x)) an den Container des Webbrowser Controls und übergibt als wParam einfach das Handle des Fensters, dass man soeben mit GetFocus ermittelt hat.
  4. Nach diesem Event wird der Skript-Code ausgeführt, der den Fokus stiehlt. Das stört uns nicht.
  5. Irgendwann kommt die Messageloop jetzt wieder an die Reihe und zieht die benutzerdefinierte Nachricht WM_RESTOREFOCUS aus der Queue.
  6. Man hat natürlich einen Handler für diese Nachricht im Container des Webbrowser Controls. Dieser macht nun nichts anderes als einen SetFocus auf das HWND Handle auszuführen, das im wParam übergeben wurde. Ein Test zuvor mit IsWindow versteht sich von selbst.

Dadurch, dass die Nachricht in der Message-Queue gepostet wird, wird sie zeitnah ausgeführt sobald wirklich der User wieder eine Chance selbst Eingaben zu machen. Problem zufriedenstellend gelöst.

Das sollte sich sogar mit C# oder VB hinbekommen lassen 😉

Neue MSDN Foren nun auch in Deutsch

In der englisch sprechenden Welt haben die MSDN Foren den NNTP Foren weitgehend den Rang abgelaufen.
Mit dem Launch von VS-2005 gab es auch den Versuch diese in Deutsch zu starten, was aber wirklich in die Hose ging, mangels Werbung, Angebot, Verweis und Akzeptanz der Pros und Regulars. Es wurden hier gerade einmal Untergruppen für die Visual-Express Versionen geschaffen. Dieses alte Forum ist noch zu finden unter:
http://forums.microsoft.com/msdn-de/default.aspx?siteid=9
Nicht mal 1000 Threads 6 Bereichen in mehr als 3 Jahren sind ein ziemlich müdes Ergebnis.

Nun wird ein zweiter Versuch gemacht mit einem weitaus größeren Angebot unter
http://social.msdn.microsoft.com/Forums/de-DE/categories/
Der Inhalt der alten Foren wurde auch in die neuen Foren Bereiche migriert.

Der Vorteil gegenüber dem alten System ist offensichtlich:

  • Im alten System waren die Threads und Foren nach Sprache getrennt. Im neuen System erscheinen alle Threads über alle MSDN Foren aller Sprachen in einer Liste.
  • Die UI hat sich entschieden verbessert.
  • Ein Bewertungsbereich und Scoring Bereich für die Mitglieder wurde erweitert.
  • … und scheinbar wird auch versucht etwas mehr Werbung zu machen.

Nun bleibt abzuwarten ob diese Foren wirklich genutzt werden für C++ und Visual Studio allgemein. Oder ob die C++ Regulars lieber doch auf NNTP mit nntp://microsoft.public.de.vc verbleiben oder das bekannte Forum auf http://www.c-plusplus.de/forum nutzen. Über die Niveauunterschiede zwischen diesen beiden Foren brauche ich nichts zu sagen 😉 …

Anmerkung:
Ein Anachronismus ist sicherlich, dass man über die Community Links in der lokalen MSDN Installation oder auch über die Menüpunkte im Visual Studio (MSDN-Foren) sowohl bei der 2005er wie auch der 2008er Version, immer noch nur das alte Englische MSDN Forum erreicht, dass auch schon längst abgelöst wurde, und das man aktuell keine Möglichkeit hat aus VS, die deutschen Communities zu erreichen.
Ich frage mich manchmal warum die Community Aktivitäten von Microsoft, gerade im nicht Englisch sprechenden Bereich, selten einmal koordiniert erscheinen. Es kommt mir so vor, als ob die Betreuung dieser Bereiche einfach jemanden aufgedrückt wird, der genug Anderes zu tun hat und diese Bereiche eher nebenbei betreuen „muss“… und auf Befehl von irgendwo oben wird dann eben mal mit Hauruck „Irgendwas“ gemacht, weil man muss ja mal was machen…

Standby Modus in einem Programm auch unter Vista verhindern

Manchmal kann man den Standby Mode wirklich nicht gebrauchen. Besonders, wenn man ein Programm hat, dass evtl. viel im Hintergrund mit einer Datenbankverbindung macht.

Wie macht man es unter Windows Vista richtig, denn BROADCAST_QUERY_DENY bei einem PBT_APMQUERYSUSPEND oder PBT_APMQUERYSUSPENDFAILED zurückzugeben funktioniert nicht.
Man muss SetThreadExecutionState verwenden!

Äußerst dumm nur, dass man dies auch wirklich für Windows Vista und Windows 7 berücksichtigen muss. Ich hatte natürlich für Windows 2000 und XP entsprechenden Code vorgesehen, die den Standby verhindern, aber wie schon erwähnt, das geht eben unter Vista nicht mehr.
Es gibt Inkompatibilitäten zwischen des OSen, die man wirklich explizit behandeln muss. Es lohnt sich ein Blick in die Dokus, den ich selber oft genug nicht mache… 😉

// Every 30 secs we disallow the standby and hibernate!
// We have to use ES_AWAYMODE_REQUIRED on Vista and Windows Server 2008
// If we allow standby and hibernation we get problems with the DB connection.
DWORD dwAwaymodeRequired = 0;
OSVERSIONINFO os;
::ZeroMemory(&os,sizeof(os));
os.dwOSVersionInfoSize = sizeof(os);
if (::GetVersionEx(&os) && os.dwMajorVersion>=6)
    dwAwaymodeRequired = ES_AWAYMODE_REQUIRED;
VERIFY(::SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED |
	dwAwaymodeRequired));

LVM_GETSUBITEMRECT mit LVIR_ICON liefert andere Ergebnisse unter Vista als unter XP

Das damit auch die Funktion CListCtrl::GetSubItemRect aus der MFC betroffen ist, ist dann auch  klar.
Manche Sachen ärgern einen einfach. Vor allem wenn man nichts am Code ändert und doch falsches Verhalten erntet.

Wieder mal ist die Vista UI eigentümlich ungereimt, in diesem Fall bei einem List View.

Folgendes ist gegeben:

  • Ein List View (SysListView32) in einem Dialog oder anderen Fenster
  • Der List View hat den Stil LVS_REPORT
  • Der List View hat hat mehr als eine Spalte.
  • Dem List View wurde eine Imagelist zugewiesen.

Führt man nun auf Windows XP LVM_GETSUBITEMRECT /CListCtrl::GetSubItemRect mit LVIR_ICON aus, dann erhält man immer ein Rectangle zurück mit der entsprechenden Weite der Imagelist Symbole. Das Verhalten ist:

  • vollkommen unabhängig ob ein Manifest für COMCTL32.DLL Version 6.0 vorhanden ist oder nicht
  •  es ist auch unabhängig ob LVS_EX_SUBITEMIMAGES gesetzt ist oder nicht.

Macht man das ganze unter Vista, dann liefert LVM_GETSUBITEMRECT /CListCtrl::GetSubbItemRect ein RECT / CRect mit der Weite der Symbole immer dann wenn:

  • kein Manifest für COMCTL32.DLL Version 6.0 vorhanden ist
  • oder LVS_EX_SUBITEMIMAGES gesetzt ist

Das heißt in dem Fall

  • ein Manifest für COMCTL32.DLL Version 6.0 ist
  • und LVS_EX_SUBITEMIMAGES ist nicht gesetzt .

erhält  man ein Rectangle mit der Weite 0 (Null) 😕

Anmerkung:
 Man kann sich natürlich streiten was nun richtig ist. Wenn LVS_EX_SUBITEMIMAGES nicht gesetzt ist, dann macht LVIR_ICON zugegebenermaßen wenig Sinn. Aber es leuchtet irgendwie nicht ein, dass ohne Manifest und ohne LVS_EX_SUBITEMIMAGES, wieder ein Wert zurückgeliefert wird. Entweder ist die Weite von LVS_EX_SUBITEMIMAGES abhängig oder eben nicht.
Das Ganze ist in jedem Falle mal ungereimt und nicht kompatibel ❗

Nachtrag 26.03.2009:
Das List-Control liefert für das Subitem 0 immer ein korrektes Rectangle für LVIR_ICON! Nur wenn wirklich ein Subitem (>0) abgefragt wird, tritt das Problem auf.

Memory Dumps on the fly

Ich hatte in einem unserer Release-Kandidaten ein massives Problem. In bestimmten nicht reproduzierbaren Situationen, blieb zeitgleich auf allen angeschlossenen Arbeitsstationen das Programm stehen. Und nun?

Der Deadlock, der auftrat war so fatal, dass ich nicht mal mehr über eine versteckte Funktion einen Speicherdump auslösen konnte. Dazu verwende intern üblicherweise eine reservierte Tastenkombination. Nur wenn keine Nachrichten mehr abgearbeitet werden, gibt es auch keine Funktionen, die man per Tastatur aufrufen kann.

Glücklicherweise wurde auf allen betroffen Rechner Windows Vista eingesetzt. Und die Lösung für diesen Fall ist unter Vista so einfach wie genial. Im Task-Manager unter Vista findet sich im Kontextmenü ein unauffälliger Menüpunkt: „Abbilddatei erzeugen“:

Memory dump on demand

Jupp! Er macht genau was ich brauchte. Durch diesen netten Befehl wird im %TEMP% Verzeichnis des Benutzers ein voller Speicherdump erzeugt.

Ich musste von 6 Dumps genau 2 durchsehen, bis ich das Problem lokalisiert hatte.
Eine wirklich nette und nützliche Funktion des Taskmanagers.

Unter Windows XP kann man ähnliches machen nur ist es hier ungleich komplizierter, aber es geht auch mit dem mitgelieferten symbolischen Debugger NTSD und den folgenden Schritten:

  • PID über den Task-Manager ermitteln (entsprechende Spalte einblenden lassen)
  • NTSD starten mit der entsprechenden PID
    NTSD -p 4656
  • Dump erzeugen:

    0:001> .dump /f c:\temp\crash\full.dmp
    Creating c:\temp\crash\full.dmp – user full dump
  • Wird der Debugger mit Quit (q) verlassen wird auch der Prozess beendet.

MS-SQL: Universelles Datumsformat

Datumsangaben in SQL Queries sind immer wieder eine Freude. Im Gegensatz zu allen anderen Datentypen hat man es hier auch noch mit einer sprachspezifischen Einstellung (US English, Deutsch etc.) zu tun.
Also was macht der Entwickler, er schreibt SQL Code wie diesen, der die Funktion CONVERT oder CAST verwendet:

CREATE VIEW dbo.International_Dates AS
SELECT PurchaseOrderID, TotalDue
FROM AdventureWorks.Purchasing.PurchaseOrderHeader
WHERE OrderDate < CONVERT(DATETIME,'2002.05.01',102);

Man kann eben nicht sicher sein wie der Datumswert interpretiert wird. Das folgende Statement würde also nur mit einer englischen Locale funktionieren:

CREATE VIEW dbo.USA_Dates AS
SELECT PurchaseOrderID, TotalDue
FROM AdventureWorks.Purchasing.PurchaseOrderHeader
WHERE OrderDate < 'May 1, 2002';

Dabei gibt es ein Datmsformat im MS-SQL Server, dass immer gleich interpretiert wird, das so genannte Unseparated String Format . Netterweise geht das sogar für DateTime Werte wenn man diesen im Format yyyyMMdd hh:mm:ss.ttt angibt.
Dies entspricht den CAST/CONVERT Formaten 112 und 114!

Man kann den obigen Query also komplett locale-unabhängig so schreiben.

CREATE VIEW dbo.Uniform_Date AS
SELECT PurchaseOrderID, TotalDue
FROM AdventureWorks.Purchasing.PurchaseOrderHeader
WHERE OrderDate < '20020501';

Man kann sich CAST und CONVERT also oft genug sparen wenn man Queries aufbaut, da Datum-Strings im Format yyyyMMdd hh:mm:ss.ttt, eben sprach/localeunabhängig sind.
Ich habe eine spezielle Funktion die genau dieses MS-SQL-Format benutzt um ein COleDateTime in das entsprechende Stringformat für einen SQL Query zu bringen, wenn ich mal keine Parameter in einem Query benutzen will oder kann.

Nachtrag: es ist ohne Probleme möglich die Uhrzeit auch abgekürzt zu übergeben, wie z.B. auch in der Form: yyyyMMdd hh:mm, oder yyyyMMdd hh:mm:ss

Selbst reingelegt beim Test von „XP oder später“

Manche Codezeilen schreibt man ja einfach im Schlaf so in etwa wie diesen hier

static const OSVERSIONINFO &GetOSVersionInfo()
{
 static OSVERSIONINFO osVersionInfo;
 if (osVersionInfo.dwOSVersionInfoSize==0)
 {
  osVersionInfo.dwOSVersionInfoSize = sizeof(osVersionInfo);
  ::GetVersionEx(&osVersionInfo);
 }
 // return pointer to struct
 return osVersionInfo;
}

bool OSIsWinXP()
{
 // Check if OS is XP or later
 const OSVERSIONINFO &osvi= GetOSVersionInfo();
 return (osvi.dwPlatformId & VER_PLATFORM_WIN32_NT)!=0 &&
     osvi.dwMajorVersion>=5 &&
     osvi.dwMinorVersion>=1;
}

Der Sinn und Zweck ist eindeutig. Ich benötige diese Funktion um zu Testen ob Windows XP oder ein späteres OS wie Vista oder Windows 7 installiert ist. Dumm nur das dieser Code dämlich falsch ist.
Die Betriebssysteme haben die folgenden internen Versionsnummern
5.0 – Windows 2000
5.1 – Windows XP
5.2 – Windows Server 2003
6.0 – Windows Vista
6.1 – Windows 7 (Anmerkung: idotisch, dass hier nicht 7.0 verwendet wird)

Als ich den Code schrieb war Windows XP gerade draußen und selbst Windows Server 2003 gerade am Horizont. Dämlicherweise schrieb ich in dem Test osvi.dwMajorVersion>=5 && osvi.dwMinorVersion>=1.
ohne natürlich daran zu denken, dass ein späteres OS wieder mit einer 0 als minor Version kommen könnte.
Dadurch ergibt sich natürlich das der Test für alle Betriebssysteme nach Windows XP funktioniert nur nicht für Windows Vista  weil eben die Minor Version hier 0 ist. Ich war drauf und dran mal wieder einen Bug einzureichen bis mir schlagartig klar war, dass nicht Vista einen Fehler hat sondern mein eigener Code.

Der korrekte Test muss natürlich so lauten:

bool OSIsWinXP()
{
 // Check if OS is XP or later
 const OSVERSIONINFO &osvi= GetOSVersionInfo();
 return (osvi.dwPlatformId & VER_PLATFORM_WIN32_NT)!=0 &&
   (osvi.dwMajorVersion>5 ||    // May be vista or later
       (osvi.dwMajorVersion==5 &&    // that's XP
        osvi.dwMinorVersion>=1));
}

Code wie im Schlaf zu schreiben bringt es manchmal eben nicht. 😉

Tooltips und Customdraw

Customdraw ist für mich erste Wahl, wenn es um das Anpassen von Ausgaben in Controls geht, zudem ein subclassing von WM_PAINT mit Erhalt der Grundfunktionen eigentlich nicht möglich ist (ich werde dazu demnächst noch mal schreiben).

Liest man die Anleitung zu NM_CUSTOMDRAW und Tooltips, bekommt man den Eindruck, dass man wie bei einem List Control an jeder Stelle eingreifen kann. Vor dem Zeichnen, nach Löschen des Hintergrundes und so weiter: CDRF_NOTIFYITEMDRAW, CDRF_NOTIFYPOSTERASE, CDRF_NOTIFYPOSTPAINT, CDRF_NOTIFYSUBITEMDRAW werden in der Doku erwähnt.

Diese Informationen sind komplett irreführend, denn nur CDRF_NOTIFYPOSTPAINT wird vom Tooltip akzeptiert und beachtet. Man kann z.B. nicht auf die Ausgabe des Textes alleine übernehmen und nur das Löschen des Hintergrundes dem Control überlassen. Es wird wirklich nur CDRF_NOTIFYPOSTPAINT berücksichtigt ( ich habe mich durch den Assembler-Code der COMCTL32.DLL durch gedebuggt).
Auch die Rückgabe von CDRF_NEWFONT kann man sich sparen. Man muss nur einen neuen Font selektieren und er wird berücksichtigt.

Was in der Dokumentation auch zu kurz kommt, ist, dass NM_CUSTOMDRAW mit CDDS_PREPAINT zweimal kurz hintereinander aufgerufen wird. Beim ersten Mal ist DT_CALCRECT in NMTTCUSTOMDRAW::uDrawFlags gesetzt ist und beim zweiten mal nicht mehr. Man hat dadurch die Möglichkeit die Größe des Controls mit NMCUSTOMDRAW::rc zu kontrollieren. Gut beschrieben ist das nicht in der MSDN sondern in einem alten Artikel des MSJ aus dem Otkober 1996, wer weiß wann der verschwinden wird.

Die Implementierung hier ist einfach halbherzig, leider. Die Möglichkeiten wären so genial, hier ein bisschen Fettgedrucktes, dort noch mal ein Icon… Schade…

VS Tipps & Tricks: Der unbekannte CString Konstruktor…

CString nimmt natürlich auch in einem seiner Konstruktoren einen LPCTSTR . Dieser Konstruktor hat aber noch eine versteckt eingebaute Funktion. Man kann CString auch mit MAKEINTRESOURCE(id) verwendet. Aus der Dokumentation geht dies nicht eindeutig hervor, aber zumindest findet man es am Rande erwähnt.
Ist also das High-WORD des LPCTSTR gleich NULL dann wird das LOWORD verwendet um die entsprechende String-Ressource zu laden.
Ist die Ressource nicht vorhanden wird in der Debugausgabe ein entsprechender Trace erzeugt.

Benötigt man also temporär eine String Variable so kann man direkt in einem Statement die entsprechende String-Resource laden:

CString strMyText(MAKEINTRESOURCE(IDS_MY_STRING));
strMyText += strSomeOther;
strMyText += CString(MAKEINTRESOURCE(IDS_MY_TEXT_TO_APPEND));

Slow DrawText performance in Vista and Windows 7. Please comment…

Sorry for blogging in English. I know that I am breaking my own rules 😉

Reasons for this article:
In the last years I found it more an more complicated to get bugs fixed in current software produced by Microsoft. My experience showed me, that the Microsoft product teams always look for new versions and new features and the Microsoft support teams and engineers don’t produce enough pressure that well known bugs get fixed in the currently used software products.

The reason why the Microsoft support rejected a request for a fix was very often that not enough customers are affected by the problem or bug. And this is often a problem for me and my company. We are just a small ISV so we can’t point to millions of affected users. And yes I know that hotfixes are expensive.
So usually we have to be flexible and have to find work arrounds.
But my feeling is that there would be enough impact and pressure for a hotfix, if it would be known to the public what problems exist and that there are others who have a request to get a special problem fixed.
Also a lot of programmers tend to avoid the creation of a support request, because the experience  shows: You put energy, time (and money) into a support request and the answers are in most cases: Will not get fixed, by design, and so on…

Another experience in the last years was, that even being an MVP doesn’t help to get anything fixed by Microsoft even if a lot of MVPs point out that there is a real need for a change or fix. 

So I just want to choose a new way, because there is no way to get votes of other people to a standard bug in a Microsoft product like it is possible on Microsoft Connect. But Connect is absolutely worthless if the product is already shipped!
I want to collect votes for this problem via my blog. I also informed the Microsoft support engineer who cares about the current incident.

If you agree and if you see a larger problem in this bug too, or if you have the feeling or knowledge that it has impact on your own software, than please write a comment to this blog article. Please leave your real email address or homepage URL. The email address will not be visible to the public, and I will forward it only to Microsoft if the poster agrees.

Description of the bug:

On January 17th, 2009 I reported a reproducible bug in Windows Vista in the article DrawText unter Vista gegenüber XP um bis zu Faktor 50 langsamer!  (The article is written in German, you find a description in English below in the next lines)

Fact:
Windows Vista and Window 7 are up to 50 times slower in the execution of a simple DrawText function with DT_CALCRECT set compared to Windows XP.
You can find the source code for a sample code (VS-2005 MFC project) that shows the bug here:  TestDrawText.zip. If you are just interested in the executable you can find it here TestDrawTextExe.zip.

And as far as I can see the overall performance of DrawText is affected.

My request:
If you find it important that this need a fix, or if you have the feeling or the knowledge, that you software might be effected. Please leave a comment here ❗

Or feel free to vote on the bug I filed for Windows 7 on Microsoft Connect:
https://connect.microsoft.com/windows7/feedback/ViewFeedback.aspx?FeedbackID=401007

Thank you

Final Note (11.07.2009):
Read on here
http://blog.m-ri.de/index.php/2009/07/11/ausloesung-drawtext-unter-vista-gegenueber-xp-um-bis-zu-faktor-50-langsamer/