Click here to Skip to main content
15,885,216 members
Articles / Desktop Programming / WTL

Fight the dialog units, DPI and Large Fonts

Rate me:
Please Sign up or sign in to vote.
4.95/5 (25 votes)
16 Nov 2003Zlib2 min read 246.2K   5.1K   44  
Guarantees pixel-to-pixel matching appearance of resource-based dialogs for different font DPIs
/****************************************************

   Set dialog DPI helper class

   Copyright (C)2003 by George Yohng

   http://www.yohng.com

   LICENSE AGREEMENT:

      You agree to go to http://www.yohng.com/music.html
      and listen completely (without skips) to the very
      first track on the list.

 ****************************************************/

#include <stdafx.h>

#include <windows.h>
#include "setdpi.h"

class helper
{
public:
	// Constants used in DLGINIT resources for OLE control containers
	// NOTE: These are NOT real Windows messages they are simply tags
	// used in the control resource and are never used as 'messages'
	enum
	{
		ATL_WM_OCC_LOADFROMSTREAM = 0x0376,
		ATL_WM_OCC_LOADFROMSTORAGE = 0x0377,
		ATL_WM_OCC_INITNEW = 0x0378,
		ATL_WM_OCC_LOADFROMSTREAM_EX = 0x037A,
		ATL_WM_OCC_LOADFROMSTORAGE_EX = 0x037B,
		ATL_DISPID_DATASOURCE = 0x80010001,
		ATL_DISPID_DATAFIELD = 0x80010002,
	};

//local struct used for implementation
#pragma pack(push, 1)
	struct DLGINITSTRUCT
	{
		WORD nIDC;
		WORD message;
		DWORD dwSize;
	};
	struct DLGTEMPLATEEX
	{
		WORD dlgVer;
		WORD signature;
		DWORD helpID;
		DWORD exStyle;
		DWORD style;
		WORD cDlgItems;
		short x;
		short y;
		short cx;
		short cy;

		// Everything else in this structure is variable length,
		// and therefore must be determined dynamically

		// sz_Or_Ord menu;			// name or ordinal of a menu resource
		// sz_Or_Ord windowClass;	// name or ordinal of a window class
		// WCHAR title[titleLen];	// title string of the dialog box
		// short pointsize;			// only if DS_SETFONT is set
		// short weight;			// only if DS_SETFONT is set
		// short bItalic;			// only if DS_SETFONT is set
		// WCHAR font[fontLen];		// typeface name, if DS_SETFONT is set
	};
	struct DLGITEMTEMPLATEEX
	{
		DWORD helpID;
		DWORD exStyle;
		DWORD style;
		short x;
		short y;
		short cx;
		short cy;
		DWORD id;

		// Everything else in this structure is variable length,
		// and therefore must be determined dynamically

		// sz_Or_Ord windowClass;	// name or ordinal of a window class
		// sz_Or_Ord title;			// title string or ordinal of a resource
		// WORD extraCount;			// bytes following creation data
	};
#pragma pack(pop)

	static BOOL IsDialogEx(const DLGTEMPLATE* pTemplate)
	{
		return ((DLGTEMPLATEEX*)pTemplate)->signature == 0xFFFF;
	}

	inline static WORD& DlgTemplateItemCount(DLGTEMPLATE* pTemplate)
	{
		if (IsDialogEx(pTemplate))
			return reinterpret_cast<DLGTEMPLATEEX*>(pTemplate)->cDlgItems;
		else
			return pTemplate->cdit;
	}

	inline static const WORD& DlgTemplateItemCount(const DLGTEMPLATE* pTemplate)
	{
		if (IsDialogEx(pTemplate))
			return reinterpret_cast<const DLGTEMPLATEEX*>(pTemplate)->cDlgItems;
		else
			return pTemplate->cdit;
	}

	static DLGITEMTEMPLATE* FindFirstDlgItem(const DLGTEMPLATE* pTemplate)
	{
		BOOL bDialogEx = IsDialogEx(pTemplate);

		WORD* pw;
		DWORD dwStyle;
		if (bDialogEx)
		{
			pw = (WORD*)((DLGTEMPLATEEX*)pTemplate + 1);
			dwStyle = ((DLGTEMPLATEEX*)pTemplate)->style;
		}
		else
		{
			pw = (WORD*)(pTemplate + 1);
			dwStyle = pTemplate->style;
		}

		// Check for presence of menu and skip it if there is one
		// 0x0000 means there is no menu
		// 0xFFFF means there is a menu ID following
		// Everything else means that this is a NULL terminated Unicode string
		// which identifies the menu resource
		if (*pw == 0xFFFF)
			pw += 2;				// Has menu ID, so skip 2 words
		else
			while (*pw++);			// Either No menu, or string, skip past terminating NULL

		// Check for presence of class name string
		// 0x0000 means "Use system dialog class name"
		// 0xFFFF means there is a window class (atom) specified
		// Everything else means that this is a NULL terminated Unicode string
		// which identifies the menu resource
		if (*pw == 0xFFFF)
			pw += 2;				// Has class atom, so skip 2 words
		else
			while (*pw++);			// Either No class, or string, skip past terminating NULL

		// Skip caption string
		while (*pw++);

		// If we have DS_SETFONT, there is extra font information which we must now skip
		if (dwStyle & DS_SETFONT)
		{
			// If it is a regular DLGTEMPLATE there is only a short for the point size
			// and a string specifying the font (typefacename).  If this is a DLGTEMPLATEEX
			// then there is also the font weight, and bItalic which must be skipped
			if (bDialogEx)
				pw += 3;			// Skip font size, weight, (italic, charset)
			else
				pw += 1;			// Skip font size
			while (*pw++);			// Skip typeface name
		}

		// Dword-align and return
		return (DLGITEMTEMPLATE*)(((DWORD_PTR)pw + 3) & ~3);
	}

	// Given the current dialog item and whether this is an extended dialog
	// return a pointer to the next DLGITEMTEMPLATE*
	static DLGITEMTEMPLATE* FindNextDlgItem(DLGITEMTEMPLATE* pItem, BOOL bDialogEx)
	{
		WORD* pw;

		// First skip fixed size header information, size of which depends
		// if this is a DLGITEMTEMPLATE or DLGITEMTEMPLATEEX
		if (bDialogEx)
			pw = (WORD*)((DLGITEMTEMPLATEEX*)pItem + 1);
		else
			pw = (WORD*)(pItem + 1);

		if (*pw == 0xFFFF)			// Skip class name ordinal or string
			pw += 2; // (WORDs)
		else
			while (*pw++);

		if (*pw == 0xFFFF)			// Skip title ordinal or string
			pw += 2; // (WORDs)
		else
			while (*pw++);

		WORD cbExtra = *pw++;		// Skip extra data

		// cbExtra includes the size WORD in DIALOG resource.
		if (cbExtra != 0 && !bDialogEx)
			cbExtra -= 2;

		// Dword-align and return
		return (DLGITEMTEMPLATE*)(((DWORD_PTR)pw + cbExtra + 3) & ~3);
	}

};


struct dialogdata_t
{
    int       pt,has_menu;
    unsigned  weight;    
    BOOL      italic;
    LPCWSTR    faceName;    
};

static void getptsize( HDC &dc, HFONT &font, SIZE *pSize )
{
    HFONT oldfont = 0;
    static char *sym = "abcdefghijklmnopqrstuvwxyz"
                       "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    SIZE sz;
    TEXTMETRICA t;      
    oldfont = (HFONT)SelectObject(dc,font);
    GetTextMetricsA(dc,&t);
    GetTextExtentPointA(dc,sym, 52, &sz);
    pSize->cy = t.tmHeight;
    pSize->cx = (sz.cx / 26 + 1) / 2;
    SelectObject(dc,oldfont);    
}

#define word_at(ptr)  (*(WORD *)(ptr))
#define dword_at(ptr) (*(DWORD *)(ptr))

static void querydialogdata( LPCSTR data, dialogdata_t * result )
{
    WORD *p = (WORD *)data; 
    unsigned long rstyle = dword_at(p); 
    int dialogex=0;

    p += 2;

    if (rstyle == 0xffff0001)
    {
        p+=4;
        rstyle=dword_at(p);
        p+=2;
        dialogex=1;
    }else
        p+=2;

    p+=5;

    // Skip menu
    switch(word_at(p))
    {
    case 0x0000: // no menu name
        result->has_menu=0;
        p++;
        break;
    case 0xffff: // has menu        
        result->has_menu=1;
        p += 2; 
        break;
    default:     // has menu
        result->has_menu=1;     
        p+=wcslen((LPCWSTR)p)+1;
        break;
    }    

    // skip class name
    switch(word_at(p))
    {
    case 0x0000:
        p++;
        break;
    case 0xffff:        
        p += 2;
        break;
    default:
        p+=wcslen((LPCWSTR)p)+1;
        break;
    }

    // skip the caption
    p+=wcslen((LPCWSTR)p)+1;

    // get the font name 

    if (rstyle & DS_SETFONT)
    {
        result->pt = word_at(p); p++;
        if (dialogex)
        {
            result->weight = word_at(p); p++;
            result->italic = LOBYTE(word_at(p)); p++;
        }else
        {
            result->weight = FW_DONTCARE;
            result->italic = FALSE;
        }
        result->faceName = (LPCWSTR)p;              
        p += wcslen( result->faceName ) + 1;
    }else
    {
        result->faceName=L"Tahoma"; // TODO: put system font name here
        result->pt=8; // TODO
        result->weight=FW_NORMAL; // TODO
        result->italic=FALSE; // TODO
    }
}

CSetDPI::CSetDPI()
{
    hwnd=0;
    font=0;
    oldfont=0;
    inst=0;
    IDD=0;
}
CSetDPI::~CSetDPI()
{
    Detach();
}

void CSetDPI::Attach(HINSTANCE hInst,HWND _hwnd,int _IDD,double dpi)
{
    int t;

    if (hwnd) Detach();

    inst = hInst;
    IDD = _IDD;

    hwnd=_hwnd;

    helper::DLGTEMPLATEEX *lpDialogTemplate;

    if (!hInst)
	    { } // hInst = AfxFindResourceHandle(IMAKEINTRESOURCE(IDD), RT_DIALOG);

	HRSRC hResource = ::FindResource(hInst, MAKEINTRESOURCE(IDD), RT_DIALOG);
	HANDLE hDialogTemplate = LoadResource(hInst, hResource);
	lpDialogTemplate = (helper::DLGTEMPLATEEX *)LockResource(hDialogTemplate);    


	dialogdata_t dd;
    querydialogdata((LPCSTR)lpDialogTemplate,&dd);

    if ((dd.pt<0)||(dd.pt>32767))
    {
        // I don't know what to do if it happens this way
        // this is a compromise solution:
        dd.pt=-dd.pt;
        dd.pt&=0xFFFF;
    }

    font=CreateFontW(
		-(int)(dd.pt*dpi/72.0 + 0.5), // negative makes it use "char size"
    0,              // logical average character width 
    0,              // angle of escapement 
    0,              // base-line orientation angle 
    dd.weight,  // weight
    dd.italic,  // italic
    FALSE,          // underline attribute flag 
    FALSE,          // strikeout attribute flag 
    DEFAULT_CHARSET,    // character set identifier 
    OUT_DEFAULT_PRECIS, // output precision 
    CLIP_DEFAULT_PRECIS,// clipping precision 
    DEFAULT_QUALITY,    // output quality 
    DEFAULT_PITCH,  // pitch and family 
    dd.faceName  // pointer to typeface name string 
    );

    oldfont=(HFONT)::SendMessage(hwnd, WM_GETFONT, 0, 0);
    SendMessage(hwnd, WM_SETFONT, (LPARAM)font, TRUE);
  
	SIZE szf;

	
    PAINTSTRUCT ps;
    HDC dc=BeginPaint(hwnd, &ps);
    getptsize(dc,font,&szf);
    EndPaint(hwnd, &ps);
	

    double x_n=szf.cx,
           x_d=4,
           y_n=szf.cy,
           y_d=8;

	RECT rect;
	GetClientRect(hwnd,&rect);	
	
	rect.right=rect.left+(int)(lpDialogTemplate->cx*x_n/x_d+0.5);
	rect.bottom=rect.top+(int)(lpDialogTemplate->cy*y_n/y_d+0.5);

	ClientToScreen(hwnd, (LPPOINT)&rect);
	ClientToScreen(hwnd, ((LPPOINT)&rect)+1);

    AdjustWindowRectEx(&rect, lpDialogTemplate->style, dd.has_menu, lpDialogTemplate->exStyle );
	MoveWindow(hwnd,rect.left,rect.top,rect.right-rect.left,rect.bottom-rect.top,TRUE);


	helper::DLGITEMTEMPLATEEX *item=
		(helper::DLGITEMTEMPLATEEX *)helper::FindFirstDlgItem((DLGTEMPLATE *)lpDialogTemplate);

	HWND wnd;

	for(t=0;t<helper::DlgTemplateItemCount((DLGTEMPLATE *)lpDialogTemplate);t++)
	{
		// OLD IMPLEMENTATION: wnd=GetDlgItem(hwnd,item->id);

	    // TODO: check if it is the best implementation
	    //       are we sure that controls will preserve the order?

		if (!t) 
			wnd=GetWindow(hwnd,GW_CHILD);
		else
			wnd=GetWindow(wnd,GW_HWNDNEXT);
		
		while ((wnd)&&(GetDlgCtrlID(wnd) != item->id)) 
			wnd=GetWindow(wnd,GW_HWNDNEXT);

		if (!wnd) break;
	    
		MoveWindow(wnd,(int)(item->x*x_n/x_d+0.5),
		               (int)(item->y*y_n/y_d+0.5),
		               (int)(item->cx*x_n/x_d+0.5),
		               (int)(item->cy*y_n/y_d+0.5), TRUE);

        SendMessage(wnd, WM_SETFONT, (LPARAM)font, TRUE);
		
		item=(helper::DLGITEMTEMPLATEEX *)helper::FindNextDlgItem((DLGITEMTEMPLATE *)item,TRUE);
	}

    UnlockResource(hDialogTemplate);    
    FreeResource(hDialogTemplate);
}

void CSetDPI::Detach()
{
    if (!hwnd) return;
    SendMessage(hwnd, WM_SETFONT, (LPARAM)oldfont, TRUE);


	HRSRC hResource = ::FindResource(inst, MAKEINTRESOURCE(IDD), RT_DIALOG);
	HANDLE hDialogTemplate = LoadResource(inst, hResource);
	helper::DLGTEMPLATEEX *lpDialogTemplate = 
		(helper::DLGTEMPLATEEX *)LockResource(hDialogTemplate);    


	helper::DLGITEMTEMPLATEEX *item=
		(helper::DLGITEMTEMPLATEEX *)helper::FindFirstDlgItem((DLGTEMPLATE *)lpDialogTemplate);

	int t;
	HWND wnd;
	for(t=0;t<helper::DlgTemplateItemCount((DLGTEMPLATE *)lpDialogTemplate);t++)
	{
		// OLD IMPLEMENTATION: wnd=GetDlgItem(hwnd,item->id);

	    // TODO: check if it is the best implementation
	    //       are we sure that controls will preserve the order?

		if (!t) 
			wnd=GetWindow(hwnd,GW_CHILD);
		else
			wnd=GetWindow(wnd,GW_HWNDNEXT);
		
		while ((wnd)&&(GetDlgCtrlID(wnd) != item->id)) 
			wnd=GetWindow(wnd,GW_HWNDNEXT);

		if (!wnd) break;

        SendMessage(wnd, WM_SETFONT, (LPARAM)oldfont, TRUE);		
		item=(helper::DLGITEMTEMPLATEEX *)helper::FindNextDlgItem((DLGITEMTEMPLATE *)item,TRUE);
	}

    UnlockResource(hDialogTemplate);    
    FreeResource(hDialogTemplate);


    DeleteObject(font);
    hwnd=0;
    font=0;
    oldfont=0;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The zlib/libpng License


Written By
Web Developer
Austria Austria
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions