Problem with WM_SETFONT, character set and ComDlg32.dll Version 6

Zoppo
Zoppo used Ask the Experts™
on
Hi everybody,

I have a problem using WM_SETFONT to set a font for controls within a dialog. In my company's application (which is a CAD like MDI application using MFC with lots of dialogs) each document can set its own font to be used for drawing and even for edit controls in dialogs - thus it's i.e. possible to set one drawing to use 'Western' character set for one document and 'Cyrillic' for another one, both can be opened at the same time and, when opening a dialog to edit an object the dialog's edit controls use the appropriate font.

This worked quite fine for a long time, but now I have to make some changes which produce problems which drive me really crazy:
1. I have to add use of new version 6 of ComDlg32.dll to improve the GUI to have a more modern look, i.e. for progress bars - I do it by adding this line:
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
2. I need to do the same now for some other controls (CListBox, CListCtrl, CTreeCtrl, CComboBox, CMFCComboBox within CMFCToolBar, maybe more ...)

I have problems with both requirements:
1. As soon as I use the new ComDlg32.dll the previously working change of the character set for edit controls stops working. I can set a new font to the edit controls and most attributes (i.e. I tested font name, size, width, weight, underline, italic) of the new font are used, but the applied character set isn't used!
2. With or without the new ComDlg32.dll the same as in 1. written (so I can change anything but the character set) happens for all kind of controls except edit controls (including the edit control part of comboboxes, but not its list box part).

The font changes are made by creating a new font, the character set selection is done by setting the 'LOGFONT::lfCharSet' member to an appropriate value.

I have no idea why the controls don't use the character set of the font I set with WM_SETFONT nor how it is implemented in Windows - IMO this can only be done by creating a new font as a copy from the assigned one within the drawing function where for any reason the 'lfCharSet' is not used - this is very strange.

To give a better idea what's the problem about I added a screenshot below - on the left side you can see the dialog without new ComDlg32.dll (the red marked area marks the controls where the strings are partially shown correct), on the right side it's the same dialog using new ComDlg32.dll. The string I set contains both german and cyrillic characters to demonstrate the problem. Especially the possibility to mix character sets of different languages is a very important feature.

I can only see two possible solutions:
a.) Use UNICODE
b.) Override control drawing function by either handling WM_PAINT or, where available, owner drawing

But unfortunateley we either cannot or don't want to use them because
a.) It's a very large project (it contains about 10 executables including server app, some clients, about 80 DLLs use by the executables, all together about 1-1,5 Million lines of code) which is developed over the last 12 years - porting to UNICODE IMO is a matter of some man-years and, which is more important, will break the file- and database formats which is a no-go.
b.) As told there are a lot of dialogs where this needs to be done (more than one hundred) - lots of them already use owner drawn controls so it's just about finding a solution for each kind of above mentioned controls, each of the already owner drawn controls would need extra implementation. And for use of new ComDlg32.dll we even would have to implement drawing (by handling WM_PAINT) for edit controls which I know is difficult (I tried this once and didn't get it done without at least some small drawing errors i.e. with selected text and auto scrolling (but maybe this could be solved, I'm not sure) - anyhow, this even would mean a very huge amount of work so we really would like to avoid it.

I even tried to find a way to solve this by changing locales, but 1. I didn't manage to get it working and 2. it's not really an option, because font of some controls must not be changed, i.e. static texts, buttons, captions, ...


Sorry for this very long description, but I want to give as much important information as possible.


If anyone of you has an idea what I can try to find a solution I would highly appreciate it - and I can/will give lot of extra points to any helpful comment.


If you're interested in the test application I used for the screenshot please tell me, I can post it here if needed (it's done with VS 2010).


Thanks in advance,

ZOPPO



 The blue marking is a dropped down combo box
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
jkr
Top Expert 2012

Commented:
>>porting to UNICODE IMO is a matter of some man-years

Why don't you use UNICODE for this dialog only? You could either call the respective APIs explicitly as UNICODE (with the trailing 'W') or separate this dialog in a DLL which is built as UNICODE.

Author

Commented:
Thanks for your response, jkr - I'll have to think about this and to discuss with my colleagues because I think this could be a lot of work to to find all dialogs in question (as told I guess there are more than 100) in all DLLs, move them to DLLs which has to be added with takeing care of dependencies and add functionality to convert between ANSI and UNICODE wherever these dialogs are used in none-UNICODE DLLs/EXEs. Further I guess it may be difficult for some dialogs used in an EXE to be moved to a DLL since they use data or functions from the the EXE. But maybe if I cannot find another solution it may be worth to do it this way.

Just now I'm testing around if it's somehow possible to do a workaround by hooking GDI text-drawing AP functions - I hope I can somehow detect those drawing operations which use the font I initially set and use this font directly there. Unfortunateley I didn't yet get it working - I found some code for hooking APIs but using it for ExtTextOutW (which seems to be the function which is used for most controls on dialogs) the ways I tried for any reason I'm still searching for often crashs. Maybe I'll ask for some help later on here ...

ZOPPO
jkr
Top Expert 2012

Commented:
Ah, OK - I was under the impression that it was just a single dialog, if it is more than that, my suggestion might not be as handy as I thought first...

Author

Commented:
Hi again,

now I managed to get my API hooks running and analyzed different cases where drawing text using a font with cyrillic char set works or not.

I hooked the API of all text drawing functions I know (TextOut, ExtTextOut, DrawText, DrawTextEx, TabbedTextOut) in both ANSI and UNICODE (so the ...A and ...W functions). I found that in nearly all cases only 'ExtTextOutW' is used to draw control's texts.

In my tests I use this as ANSI test string:
'ËÅÑÍÎÉ': 0xcb-0xc5-0xd1-0xcd-0xce-0xc9-0x00

Open in new window


As font (which below is shown as 'Original font') I use this for WM_SETFONT and TextOut:
Font 0x3b0a2bef:
   LONG lfHeight: 0xfffffff2 (-14)
   LONG lfWidth: 0x00000000 (0)
   LONG lfEscapement: 0x00000000 (0)
   LONG lfOrientation: 0x00000000 (0)
   LONG lfWeight: 0x00000190 (400)
   BYTE lfItalic: 0x00 (0)
   BYTE lfUnderline: 0x01 (1)
   BYTE lfStrikeOut: 0x00 (0)
   BYTE lfCharSet: 0xcc (204)
   BYTE lfOutPrecision: 0x00 (0)
   BYTE lfClipPrecision: 0x00 (0)
   BYTE lfQuality: 0x00 (0)
   BYTE lfPitchAndFamily: 0x00 (0)
   CHAR lfFaceName[LF_FACESIZE]: Times New Roman

Open in new window

Beside the drawing of the dialog caption (which is somehow strange but irrelevant since I don't need it for my app) I tested four cases which IMO are interesting - here I just list the important parts of my logging (all font-members I don't list here are identical to the members shown above):

1. Working (edit control with old ComDlg32.dll):
fuOption: 0x00000014 - string (wcslen: 6):0x0245-0x023f-0x024b-0x0247-0x0248-0x0243-0x0000-
Original font: 0x3b0a2bef
Current font: Font 0x270a2be9:
   BYTE lfUnderline: 0x00 (0)

Open in new window

2. Not working (edit control with new ComDlg32.dll):
fuOption: 0x00000014 - string (wcslen: 6):0x00c9-0x0063-0x0066-0x00cb-0x00cc-0x0065-0x0000-
Original font: 0x3b0a2bef
Current font: Font 0xad0a2bcd:
   BYTE lfUnderline: 0x00 (0)

Open in new window


3. Not working (button control with old ComDlg32.dll):
fuOption: 0x00000010 - string (wcslen: 6):0x00c9-0x0063-0x0066-0x00cb-0x00cc-0x0065-0x0000-
Original font: 0x3b0a2bef
Current font: Font 0xb30a455b:
   BYTE lfUnderline: 0x00 (0)

Open in new window


4. Working (Drawing text with 'TextOut' in WM_PAINT handler):
fuOption: 0x00001000 - string (wcslen: 11):0x041b-0x0415-0x0421-0x041d-0x041e-0x0419-0x3ca3-
Original font: 0x3b0a2bef
Current font: Font 0x3b0a2bef:
   BYTE lfUnderline: 0x01 (1)

Open in new window

(If someone wants it I can attach log with complete output of LOGFONT members as TXT file, but you can believe me all other members are equal in any case)
   
A strange thing is that the passed string differs 4 times:
1. 	0x0245-0x023f-0x024b-0x0247-0x0248-0x0243-0x0000
2./3. 	0x00c9-0x0063-0x0066-0x00cb-0x00cc-0x0065-0x0000
4. 	0x041b-0x0415-0x0421-0x041d-0x041e-0x0419-0x3ca3

Open in new window

The two not working cases 2. and 3. are nearly equal (2. has the 'ETO_CLIPPED' fuOption set, 3. hasn't). In both cases the text drawn on the controls is the same as the above shown ANSI text.

Between the working cases 1. and 4. the differences I found are:
a.) the strings passed are different.
b.) only in 4. the font selected into the DC is the same as that one I created.
c.) the fuOption is different, with 4. the flag 'ETO_IGNORELANGUAGE' is set. This I guess is the reason why both work allthough the passed string differs. Interesting: If I add code to remove this flag and call the original 'ExtTextOutW' it my hook function is directly called again with a new (identical) font selected into the DC and the 'ETO_IGNORELANGUAGE' set again repeatedly until stack overflow. This makes me think that somehow the original 'ExtTextOutW' finds from analyzing the passed string how it has to be drawn.
d.) the result of 'wcslen' differs (!) - this IMO is very strange since I thought always UNICODE strings are passed so 'wcslen' should always return the same length 6 (which is always passed correctly via 'cbCount' argument).

Further a strange thing is the underline thing:
a.) for both working cases I see the text underlined allthough in 1. the 'lfUnderline' of the current font is 0.
b.) for both none-working cases 'lfUnderline' is even 0, but most controls draw the text underlined too (only static text are drawn none-underlined).


My big problem now is that I don't really have much experience with UNICODE/MBCS - I think it I could manage to convert the string passed in case 2. and 3. back to the original '0xcb-0xc5-0xd1-0xcd-0xce-0xc9-0x00' and next to one of the two representations used with 1. or 4. I could solve my problem.


If someone has an idea if/how it's possible to convert that string correctly it would be great.


Thanks,

ZOPPO

Author

Commented:
PS: Please excuse that I posted such a long comment again, but this IMO strange behavior drives me crazy so I still think it's better to post more info instead of posting to less info ...

Author

Commented:
Addition: Using WideCharToMultiByte and MultiByteToWideChar with codepage 1251 I am able to convert the string passed in 4.) to the original string and back again to the passed string. Unfortunateley I'm not able yet to do the same with the 3 other cases. I even tried WideCharToMultiByte with a loop for all possible codepage codes, but without success. I have no idea which kind of string-/encoding is used there ...
Ok, I now managed to add functionality to my ExtTextOut hook which solves the displaying issue at least mostly - it still doesn't work for dialog caption and it doesn't work for edit-controls with 'old' ComDlg32.dll, but as mentioned I need to use 'new' ComDlg32.dll and I don't need it for dialog captions.

It was a bit difficult to find out how the data passed to ExtTextOut has to be interpreted - in some cases the string even doesn't contain any info about its characters but hold a list of indicies to glyphs which are mapped by the DC depending on the selected font. Therefore I had to implement a function which translates such a list of indicies back to ASCII, then I generate a UNICODE (wide-)string with 'MultiByteToWideChar' and draw the result using the original 'ExtTextOut'.

For everyone who's interested here's the code of my ExtTextOut-hook inclluding the helper function to convert these indicies to ASCII:
typedef BOOL (WINAPI *EXTTEXTOUTPROC_W)( HDC, int, int, UINT, CONST RECT*, LPCWSTR, UINT, CONST INT* );

// here the pointer to original 'ExtTextOutW' is stored by functionality which hooks the API
EXTTEXTOUTPROC_W g_OrigExtTextOutProcW = NULL;

...

int ConvertGlyphIndiciesToASCII( HDC hdc, short* pStringIndicies, char* pszRet, int nCount )
{
	const int nChars = 255;

	// we make this static here to avoid it needs to be filled anew with every call
	static char pszChars[nChars] = { 0 };

	if ( 0 == pszChars[0] )
	{
		// initialize the string with all prinatble ASCII characters
		for ( int n = 0; n < nChars; n++ )
		{
			pszChars[n] = (char)n + 32;
		}
	}

	DWORD dwRes = GetFontUnicodeRanges( hdc, NULL );

	if ( 0 == dwRes )
	{
		ASSERT( 0 );
		return false;
	}

	GLYPHSET* pGlyphs = (GLYPHSET*)new BYTE[ dwRes ];
	pGlyphs->cbThis = dwRes;

	GetFontUnicodeRanges( hdc, pGlyphs );

	WORD* pGlyphIndicies = new WORD[ nChars ];

	GetGlyphIndices( hdc, pszChars, nChars, pGlyphIndicies, 0 );

	std::map < short, char > charMap;

	for ( int n = 0; n < nChars; n++ )
	{
		if ( charMap.end() != charMap.find( pGlyphIndicies[n] ) )
		{
			continue;
		}

		charMap[ pGlyphIndicies[n] ] = pszChars[n];
	}

	int nRetCount = 0;

	for ( int n = 0; n < nCount; n++ )
	{
		if ( charMap.end() != charMap.find( pStringIndicies[n] ) )
		{
			pszRet[n] = charMap[ pStringIndicies[n] ];
			nRetCount++;
		}
	}

	delete [] pGlyphs;
	delete [] pGlyphIndicies;

	return nRetCount;
}

UINT GetCodePageFromCharSet( BYTE nCharSet )
{
	switch( nCharSet )
	{
	case( EASTEUROPE_CHARSET ):
		return 1250;
	case( RUSSIAN_CHARSET ):
		return 1251;
	case( GREEK_CHARSET ):
		return 1253;
	case( TURKISH_CHARSET ):
		return 1254;
	case( HEBREW_CHARSET ):
		return 1255;
	case( ARABIC_CHARSET ):
		return 1256;
	case( BALTIC_CHARSET ):
		return 1257;
	}

	return CP_ACP;
}

BOOL WINAPI MyExtTextOutW( HDC hdc, int X, int Y, UINT fuOptions, CONST RECT* lprc, LPCWSTR lpString, UINT cbCount, CONST INT* lpDx )
{
	LOGFONT lf;
	HFONT hFont = (HFONT)GetCurrentObject( hdc, OBJ_FONT );
	HFONT hNewFont = NULL;

	GetObject( hFont, sizeof( lf ), &lf );

	// the character set is encoded in 'lfEscapement' - this is done because
	// 'lfEscapement' seems to be preserved through all invoked system calls
	// and multiplying it with 3600 doesn't change the appearance. Further
	// 'lfEscapement' is a LONG, so multiplying 3600 with 'lfCharSet' cannot
	// overflow since 'lfCharSet' is just a BYTE
	UINT nCharSet = ( lf.lfEscapement > 3600 ) ? lf.lfEscapement / 3600 : lf.lfCharSet;
	UINT nCodePage = GetCodePageFromCharSet(  nCharSet );

	if ( CP_ACP != nCodePage && 0 == ( fuOptions & ETO_IGNORELANGUAGE ) )
	{
		BOOL bRet = TRUE;

		wchar_t* pszWstr = new wchar_t[cbCount];
		char* pszAscii = new char[cbCount];

		if ( NULL != ( fuOptions & ETO_GLYPH_INDEX ) )
		{
			lf.lfCharSet = ANSI_CHARSET;
			hNewFont = ::CreateFontIndirect( &lf );
			SelectObject( hdc, hNewFont );

			if ( ConvertGlyphIndiciesToASCII( hdc, (short*)lpString, pszAscii, cbCount ) > 0 )
			{
				MultiByteToWideChar( nCodePage, 0, pszAscii, cbCount, pszWstr, cbCount );
				fuOptions &= ~ETO_GLYPH_INDEX;
			}
			else
			{
				bRet = FALSE;
			}

			SelectObject( hdc, hFont );
			DeleteObject( hNewFont );
		}
		else
		{
			WideCharToMultiByte( CP_ACP, 0, lpString, cbCount, pszAscii, 1024, 0, NULL );
			MultiByteToWideChar( nCodePage, 0, pszAscii, cbCount, pszWstr, 1024 );
		}
		fuOptions |= ETO_IGNORELANGUAGE;

		bRet = g_OrigExtTextOutProcW( hdc, X, Y, fuOptions, lprc, FALSE != bRet ? pszWstr : lpString, cbCount, NULL );

		delete [] pszAscii;
		delete [] pszWstr;

		return bRet;
	}

	// pass original values to the original system call
	return g_OrigExtTextOutProcW( hdc, X, Y, fuOptions, lprc, lpString, cbCount, lpDx );
}

Open in new window


The part of hooking the API isn't shown here, but if you're interested here's where I found a good sample which I only had to modify a little bit: Q: GDI TextOut Hook


Unfortunateley my problem isn't yet solved: The calculation of the character's/string's extents is wrong now (I guess they're done using ASCII characters in any case) so i.e. texts are cut in list- or tree-controls, selection and cursor position in edit controls is wrong (see screenshot below).

So I guess I have to try to hook this functionality too ... I'll keep you up to date.

ZOPPO

 Screenshot

Author

Commented:
ok, after long time working on this I found some kind of a solution I can live with. I decided that for different char sets it's ok to have proporional fonts as a waorkaround for the edit-control-selection problem mentioned in my last post. Further I found the API to hook to solve that size-calculation problem.

I tried to upload my test project here but unfortunateley some file types aren't allowed - if anyone is interested in seeing my solution please contact me ...

I'll send a close request for this question.

Thanks anyway, best regards,

ZOPPO

Author

Commented:
I found a suitable solution myself ...

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial