dimensions of parent window depend on user input

I am designing a small windows app for a class project. While I have completed most of the required functionality, there is an extra feature I would like to implement.

As it stands, the app consists of the parent window and its children (the buttons, combobox, and text window.) I would like to add a button that increases the dimensions of the parent and children windows to make the text easier to read for people with vision problems, like the magnify feature of windows 7. I have an idea on how to do this, and I would really like some feedback on my idea.

The biggest problem that I see is that the parent window would have to destroyed and a new parent window created using the updated dimension parameters. Unless I am mistaken, the destruction of the parent would terminate the application. What if I created a new parent window that was non-visible, and THAT window was the parent to the main application window, and the buttons and text fields became grandchildren to the invisible window? And instead of hardcoding the dimensions in the CreateWindow function, I create 2 structs containing the dimension values for all the windows and pass those values?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct _LargeWindowDimensions
{
    /* main application window dimensions */
    int main_x      CW_USEDEFAULT;
    int main_y      CW_USEDEFAULT;
    int main_width  800;
    int main_height 900;
    /* static text window dimensions */
    int static_x    20;
    ....
} LWDim;

struct _SmallWindowDimensions
{
    /* main application window dimensions */
    int main_x      CW_USEDEFAULT;
    int main_y      CW_USEDEFAULT;
    int main_width  450;
    int main_height 500;
    /* static text window dimensions */
    int static_x    10;
    ....
} SWDim;


If the 'enlarge text' button is pressed, the main app window (child) is destroyed, the dimensions structure is switched, and a new app window is created with the updated dimensions.

Would my solution work, or is there a more practical (standard?) way of doing this?
Last edited on
To resize your parent window you don't destroy it; you simply call MoveWindow() with the desired new x, y, cx, and cy coordinates.

Users can use the 'Display' Control Panel Applet to set the resolution they prefer. I wouldn't code that myself. Thing is though, if they do change the DPI settings (Dots Per Inch), that can mess up your screen layout if you haven't coded 'High DPI Aware'. Its really not too hard to do that. I could show you if you like.
So calling MoveWindow() for resizing the parent window will resize its children as well?
Is there a similar function to change the font size of the window text? My aim is to increase only the app window dimensions and text size, not alter the resolution systemwide.

If that can't be done, then I will try to use the dpi aware code you mentioned. I would appreciate it if you could show me the basic code for that.

So calling MoveWindow() for resizing the parent window will resize its children as well?


No, its not that easy. You would need to make seperate MoveWindow() calls for each window. The 1st parameter of the call is the HWND of the window to resize/reposition.

You can change the font of any control (child window, i.e., button, edit control, etc.) by sending it a WM_SETFONT message with SendMessage(). You need to create the font somehow though, likely through CreateFont() or CreateFontIndirect(). When finished you would call DeleteObject() to destroy the font (or leak the memory otherwise if the app doesn't terminate).

The purpose of DPI aware code is to permit your application to be usable if it is used on a machine whose DPI/Resolution settings are different from the settings on your development box. Not sure if that's where you are going though. You say that isn't what you are interested in. You apparently want to be able to change your app's appearance by clicking a button or something.
... You apparently want to be able to change your app's appearance by clicking a button or something.


Yes, this is what I'm interested in. In the global namespace, there is a boolean flag, a structure declaration, 2 instances of the structure, and a pointer to the structure type.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int isLargeSelected = 0;
struct WindowSizeParams
{
     int parent_x;
     int parent_y;
     int parent_height;
     int parent_width;
     ...and so on...
     ...for each window in the app...
} SmallDims, LargeDims;

SmallDims = { CW_USEDEFAULT, CW_USEDEFAULT, 400, 500, .... };     /*default dimensions for parent and children windows */
LargeDims = { CW_USEDEFAULT, CW_USEDEFAULT, 800, 1000, .... };  /*larger window dimensions */
WindowSizeParams * currSelect = &SmallDims;


The values in the currSelect struct are used as arguments in the CreateWindow() calls, instead of hardcoded values.

One of the children is a button that says something like "Click here for larger text." Clicking the button sends a message like IDI_LARGETEXT with the following case
1
2
3
4
5
6
7
8
9
10

case IDI_LARGETEXT:
{
     isLargeSelected = (isLargeSelected == 0 ? 1 : 0)
     if(isLargeSelected)
          currSelect = &LargeDims;
     else
          currSelect = &SmallDims;
     /* now we call MoveWindow() for the parent window, passing the structure values as arguments. This should trigger WM_PAINT, 
where the children get repainted, also using the updated structure values. */


EDIT: Apparently, that last code comment is wrong; according to MSDN, resizing the parent window triggers WM_SIZE, which makes the parent get the current child window dimensions and passes them to the EnumChildWindows(), which I have to define. That's where the children are resized.
Last edited on
I spent a day or so trying to implement my idea and have come to the conclusion that it doesn't work as well as I thought it would. I spent some time on MSDN looking for high DPI aware applications without much luck. All the articles there suggest DPI aware apps are a good idea but don't really explain how to code that functionality. The MSDN page that seemed the most promising had a short bullet list of the steps taken to implement the feature, but spent most of the article space about how it applies to bitmaps.

If you have the time, could you show the code to make the app high DPI aware?
Yes, I was wondering how long it would take you to come to the conclusion you eventually reached.

In the Linux world they are huge on GUI apps that respond correctly to user resizing through dragging the edges one way or another. I believe 'layout management' might be the technical term for that. And it can get complicated, naturally.

For myself, I've never worried about it. I tend to design a UI to look a certain way, then define the various window styles so as to prevent the user from changing the cx and cy of the form. However, where the problem comes in is that various monitors have varying resolutions and technical specifications which can significantly alter the sizes and usability of a given app if the app writer (you or I) haven't taken steps to insure its usability across a wide range of display devices.

The proper way to go about this whole business is to design the app as High DPI Aware, and then let the user adjust his/her system through ...

Control Panel >> Display

...so that it suits himself or herself. Older folks particularly are app to change the resolution and DPI settings to make things larger.

The good news is its pretty easy to do. I'll scrape up some links for you and put together a small sample app showing how to do it. Give me a little time though for the app. Actually, I have a couple sample apps available but they are coded in PowerBASIC. Likely I'll convert them over to C++.
Here is a link about DPI ...

https://msdn.microsoft.com/en-us/library/windows/desktop/dn469266(v=vs.85).aspx

The individual I learned from how to do this stuff is named Jose Roca and he's a genius. He's from Spain. Here are some quotes of his ...


Not DPI–aware applications are always virtualized to 96 DPI (100%). The type of scaling that DPI virtualization uses when scaling up in size is based on pixel stretching. While this enables applications to be appropriately sized, blurriness occurs due to the stretched pixels.

The type of scaling that DPI virtualization uses when scaling down in size is based on pixel sampling. While this enables applications to be appropriately sized, some wash-out or loss of information occurs due to the sampling of pixels.


What the issue is, is that if you write an app that isn't 'DPI Aware', Windows the operating system is aware of that, and does various 'funny business' behind the scenes to make things look more or less OK if the app is run on a system where the DPI settings and resolution have been changed significantly from the 'norm'. What happens is that crispness of image is lost. That is the blurriness Jose is speaking of above. To really see it you have to compare the same app running high DPI aware and non DPI aware on the same screen. Then you see it and it really jumps out at you.
Last edited on
Link isn't working. Let me try to fix it.

Got it working.

Don't be put off by the complexity and length of that article. It really isn't hard to do.
Last edited on
I've converted my PowerBASIC example to C++. What the example program does is create a really small mock up of a Log On Dialog Box (not using the Windows Dialog Engine, which I dislike, but CreateWindowEx() calls). So there really is only the main Form/Window/Dialog itself, and four CreateWindowEx() calls for the labels and text boxes. This example shows to good effect how to scale GUI objects to be High DPI Aware.

A word of caution to beginning Windows coders. My program architecture will throw you a curve ball big time. It looks complicated but all it does is eliminate the typical switch construct with which you are no doubt familiar which maps Windows Messages received in the Window Procedure to the message handling code which handles the messages under 'case' labels. What my code does is associate the numeric value of a message, which is an integer (WM_CREATE is zero I believe) with the runtime address of a code procedure which handles that particular message. These two things are associated in a struct named EVENTHANDLER which you can see in DpiAware.h. My code creates an array of these objects and iterates through them with a for loop in fnWndProc() which is the program's Window Procedure. Here is DpiAware.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//DpiAware.h
#ifndef Main_h
#define Main_h

#define IDC_USERNAME          1500
#define IDC_PASSWORD          1505
#define IDC_SUBMIT            1510
#define IDC_CANCEL            1515

#define dim(x)                (sizeof(x) / sizeof(x[0]))  // For Getting The Number Of Elements In Array
#define SizX(x)               (int)(x * rxRatio)          // For DPI Scaling Calculations And Purposes
#define SizY(y)               (int)(y * ryRatio)          // For DPI Scaling Calculations And Purposes

struct WndEventArgs
{
 HWND                         hWnd;
 WPARAM                       wParam;
 LPARAM                       lParam;
 HINSTANCE                    hIns;
};

long fnWndProc_OnCreate       (WndEventArgs& Wea);
long fnWndProc_OnCommand      (WndEventArgs& Wea);
long fnWndProc_OnDestroy      (WndEventArgs& Wea);

struct EVENTHANDLER
{
 unsigned int                 iMsg;
 long                         (*fnPtr)(WndEventArgs&);
};

const EVENTHANDLER EventHandler[]=
{
 {WM_CREATE,                  fnWndProc_OnCreate},
 {WM_COMMAND,                 fnWndProc_OnCommand},
 {WM_DESTROY,                 fnWndProc_OnDestroy}
};
#endif 


I'll post Main.cpp in next post....
Here is Main.cpp. I'll discuss it a bit afterwards...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//Main.cpp                    // Program Shows How To Code
#include <windows.h>          // High DPI (Dots Per Inch)
#include <cstdio>             // Aware Apps
#include "DpiAware.h"
#define MyDebug


void SetMyProcessDpiAware()   // 2000/XP Doesn't Have SetProcessDPIAware()
{                             // So Only Call Function If Its Present
 BOOL (__stdcall *pFn)(void);

 HINSTANCE hInstance=LoadLibrary(L"user32.dll");
 if(hInstance)
 {
    pFn=(BOOL (__stdcall*)(void))GetProcAddress(hInstance,"SetProcessDPIAware");
    if(pFn)
       pFn();
    FreeLibrary(hInstance);
 }
}


long fnWndProc_OnCreate(WndEventArgs& Wea)      // Constructor For Main Window.  Processes
{                                               // WM_CREATE Message
 float sngFont, dpiX, dpiY, rxRatio, ryRatio;
 HFONT hFont=NULL;
 HWND hCtl=NULL;
 FILE* fp=NULL;
 HDC hDC;
 
 #ifdef MyDebug
    fp=fopen("Output.txt","w");
    fprintf(fp,"Entering fnWndProc_OnCreate()\n");
    fprintf(fp,"  Wea.hWnd            = %p\n",Wea.hWnd);
    fprintf(fp,"  IsProcessDPIAware() = %d\n",IsProcessDPIAware());
 #endif
 Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
 sngFont=8.0;                                   // Set Font Size To 8.0.  Get Handle To Device Context For Calls
 hDC = GetDC(NULL);                             // To GetDeviceCaps().  This Latter Function Obtains Pixel Resolution
 dpiX=(float)GetDeviceCaps(hDC, LOGPIXELSX);    // Of Current Display Setting.  Divide This By 96 To Obtain
 dpiY=(float)GetDeviceCaps(hDC, LOGPIXELSY);    // Scaling Factor.  Note SizX() and SizY() macros in DpiAware.h.
 rxRatio=(dpiX/96);                             // These Are Used To Scale ALL App Window/Control x, y, cx, and cy 
 ryRatio=(dpiY/96);                             // Dimensions.  Note Use in MoveWindow() and CreateWindowEx() calls
 #ifdef MyDebug                                 // Below.
    fprintf(fp,"  dpiX                = %8.0f\n",dpiX);      // When the app is set up this way, it will respond
    fprintf(fp,"  dpiY                = %8.0f\n",dpiY);      // correctly to any system wide changes the user
    fprintf(fp,"  rxRatio             = %8.2f\n",rxRatio);   // makes to DPI/Resolution settings in Control Panel.
    fprintf(fp,"  ryRatio             = %8.2f\n",ryRatio);
 #endif
 MoveWindow(Wea.hWnd, SizX(200), SizY(100), SizX(300), SizY(124), FALSE);
 hFont=CreateFont(-MulDiv((int)sngFont,(int)dpiY,72),0,0,0,FW_NORMAL,0,0,0,DEFAULT_CHARSET,0,0,0,0,L"Tahoma");
 ReleaseDC(NULL,hDC);
 SetWindowLongPtr(Wea.hWnd,GWLP_USERDATA,(LONG_PTR)hFont);
 hCtl=CreateWindowEx(0,L"static",L"User Name",WS_CHILD|WS_VISIBLE|SS_RIGHT,SizX(10),SizY(12),SizX(90),SizY(16),Wea.hWnd,(HMENU)-1,Wea.hIns,0);
 SendMessage(hCtl,WM_SETFONT,(WPARAM)hFont,0);
 hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,L"edit",L"",WS_CHILD|WS_VISIBLE|WS_TABSTOP,SizX(110),SizY(10),SizX(160),SizY(20),Wea.hWnd,(HMENU)IDC_USERNAME,Wea.hIns,0);
 SendMessage(hCtl,WM_SETFONT,(WPARAM)hFont,0);
 hCtl=CreateWindowEx(0,L"static",L"Password",WS_CHILD|WS_VISIBLE|SS_RIGHT,SizX(10),SizY(36),SizX(90),SizY(16),Wea.hWnd,(HMENU)-1,Wea.hIns,0);
 SendMessage(hCtl,WM_SETFONT,(WPARAM)hFont,0);
 hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,L"edit",L"",WS_CHILD|WS_VISIBLE|WS_TABSTOP,SizX(110),SizY(34),SizX(160),SizY(20),Wea.hWnd,(HMENU)IDC_PASSWORD,Wea.hIns,0);
 SendMessage(hCtl,WM_SETFONT,(WPARAM)hFont,0);
 hCtl=CreateWindowEx(0,L"button",L"Submit",WS_CHILD|WS_VISIBLE|WS_TABSTOP,SizX(120),SizY(60),SizX(60),SizY(20),Wea.hWnd,(HMENU)IDC_SUBMIT,Wea.hIns,0);
 SendMessage(hCtl,WM_SETFONT,(WPARAM)hFont,0);
 hCtl=CreateWindowEx(0,L"button",L"Cancel",WS_CHILD|WS_VISIBLE|WS_TABSTOP,SizX(200),SizY(60),SizX(60),SizY(20),Wea.hWnd,(HMENU)IDC_CANCEL,Wea.hIns,0);
 SendMessage(hCtl,WM_SETFONT,(WPARAM)hFont,0);
 #ifdef MyDebug
    fprintf(fp,"Leaving fnWndProc_OnCreate()\n");
    fclose(fp);
 #endif
 
 return 0;
}


long fnWndProc_OnCommand(WndEventArgs& Wea)
{
 switch(LOWORD(Wea.wParam))
 {
   case IDC_SUBMIT:
     MessageBox(Wea.hWnd,L"You Clicked The Submit Button!",L"Button Click Report!",MB_OK);
     break;
   case IDC_CANCEL:
     MessageBox(Wea.hWnd,L"You Clicked The Cancel Button!",L"You Want Out!",MB_OK);
     SendMessage(Wea.hWnd,WM_CLOSE, 0, 0);
     break;
 }

 return 0;
}


long fnWndProc_OnDestroy(WndEventArgs& Wea)
{
 HFONT hFont=NULL;

 hFont=(HFONT)GetWindowLongPtr(Wea.hWnd,GWLP_USERDATA);
 if(hFont)
    DeleteObject(hFont);
 PostQuitMessage(0);
 return 0;
}


LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 WndEventArgs Wea;

 for(unsigned int i=0; i<dim(EventHandler); i++)
 {
     if(EventHandler[i].iMsg==msg)
     {
        Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*EventHandler[i].fnPtr)(Wea);
     }
 }

 return (DefWindowProc(hwnd, msg, wParam, lParam));
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 wchar_t szClassName[]=L"DpiAware";
 WNDCLASSEX wc;
 MSG messages;
 HWND hWnd;

 memset(&wc,0,sizeof(wc));
 SetMyProcessDpiAware();
 wc.lpszClassName = szClassName;                wc.lpfnWndProc = fnWndProc;
 wc.cbSize        = sizeof(WNDCLASSEX);         wc.hInstance   = hInstance;
 wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;    wc.cbWndExtra  = sizeof(void*);
 RegisterClassEx(&wc);
 hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,0,0,0,0,0,0,hInstance,0);
 ShowWindow(hWnd,iShow);
 while(GetMessage(&messages,NULL,0,0))
 {
    if(!IsDialogMessage(hWnd,&messages))
    {
       TranslateMessage(&messages);
       DispatchMessage(&messages);
    }
 }

 return (int)messages.wParam;
}


Where the Dpi Aware coding starts is in WinMain() with a call to SetMyProcessDpiAware(). Windows 7 and onward actually have a function named SetProcessDPIAware() which could be called much easier than what I did here, but the problem is that Windows 2000/XP don't have that function, and I'm guessing a crash could result on those operating systems if the function was called. So what I had to do was call the function through LoadLibrary()/GetProcAddress(). If done this way the code will work on older systems.

After that call check out fnWndProc_OnCreate(), which would coincide with the WM_CREATE code for folks who use the switch construct. There you'll see float variables dpiX, dpiY, rxRatio, and ryRatio which are used to obtain the necessary scaling factors to be applied to all visual dimensions of windows and controls. Note in DpiAware.h are the macros ...

1
2
#define SizX(x)               (int)(x * rxRatio)          // For DPI Scaling Calculations And Purposes
#define SizY(y)               (int)(y * ryRatio)          // For DPI Scaling Calculations And Purposes 


These are used to form the multipliers that are applied to the dimensions.


continued...
Last edited on
You can see a #define at top as follows ...

 
#define MyDebug 


If you leave this active, i.e., not commented out, an "Output.txt" file will be created in the app's directory. Here is what got output on a run on my Dell M6500 laptop running at standard (recommended) DPI and resolution settings of 96 DPI and 1980 X 1200 resolution...

1
2
3
4
5
6
7
8
Entering fnWndProc_OnCreate()
  Wea.hWnd            = 0000000000160418
  IsProcessDPIAware() = 1
  dpiX                =       96
  dpiY                =       96
  rxRatio             =     1.00
  ryRatio             =     1.00
Leaving fnWndProc_OnCreate()


Running under those settings the app is extremely small and one might consider the text hard to read. Note too I create a rather small font of size 8.0. Now, the interesting part comes in if one goes to Control Panel >>> Display and chooses 'Large Fonts', and possibly alters the screen resolution too. Here is an output run where I just chose 'Large Fonts' and left the resolution where it was ...
1
2
3
4
5
6
7
8
9

Entering fnWndProc_OnCreate()
  Wea.hWnd            = 0000000000110214
  IsProcessDPIAware() = 1
  dpiX                =      144
  dpiY                =      144
  rxRatio             =     1.50
  ryRatio             =     1.50
Leaving fnWndProc_OnCreate()


In that scenario everything gets scaled up 50%. I might point out that if these Control Panel changes are made and the call to make the program High Dpi Aware is commented out, Windows will do its best to scale things properly, but various visual 'artifacts' which negatively affects the app's visual appearance will occur. For example, everything will become slightly 'blurry' do to the pixel sampling Windows will do. Worse, text can get clipped, and in the worst case scenario an app may become completely unusable. This can occur for example if various buttons are 'virtualized' off the visible screen. I might add that I've had that happen to apps I've written. Its not a good thing.

continued...
Just some 'errata' now. I compiled and developed the code above with Visual Studio 2008 Pro which includes the VC++9 compiler. I usually use MinGW x64 for my C++ development work, but somehow or other that development environment doesn't contain that all important SetProcessDPIAware() call in its user32.lib. Using VC++9 the smallest I could get that program down to size wise was about 82 k. Bloated exes are one of my pet peeves and that is something which aggravates me about all modern C++ compilers. Actually, I mis-stated something above. I didn't really develop the code in Visual Studio, but just translated it to that from PowerBASIC version 10. That programming language compiles that exact code to a 12 k executable. Here is the PowerBASIC code I translated. You can see its the exact same Win32 app...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#Compile            Exe "DpiAware.exe
#Dim                All
%IDC_USERNAME       = 1500
%IDC_PASSWORD       = 1505
%IDC_SUBMIT         = 1510
%IDC_CANCEL         = 1515
%UNICODE            = 1
#If %Def(%UNICODE)
    Macro ZStr      = WStringz
    Macro BStr      = WString
#Else
    Macro ZStr      = Asciiz
    Macro BStr      = String
#EndIf
Macro SizX(x)       = x*rxRatio
Macro SizY(y)       = y*ryRatio
#Include            "Windows.inc"

Type WndEventArgs
  wParam            As Long
  lParam            As Long
  hWnd              As Dword
  hInst             As Dword
End Type

Type MessageHandler
  wMessage          As Long
  dwFnPtr           As Dword
End Type

Declare Function FnPtr(wea As WndEventArgs) As Long
Declare Function SetProcessDpiAware() As Long


Function SetMyProcessDpiAware() As Long
  Local hInstance,pFn,blnReturn As Dword
  
  hInstance=LoadLibrary("user32.dll")
  If hInstance Then
     pFn=GetProcAddress(hInstance,"SetProcessDPIAware")
     If pFn Then
        Call Dword pFn Using SetProcessDpiAware To blnReturn
     End If
  End If

  Function=blnReturn
End Function


Function fnWndProc_OnCreate(Wea As WndEventArgs) As Long
  Local dpiX,dpiY,iCalculatedFontHeight As Long
  Local pCreateStruct As CREATESTRUCT Ptr
  Local rxRatio,ryRatio,sngFont As Single
  Local hCtl,hDC,hFont,hTmp As Dword
  Local tm As TEXTMETRIC
  Local fp As Integer
  Local rc As RECT

  pCreateStruct=Wea.lParam : [email protected]
  sngFont = 8
  hDC=GetDC(%NULL)
  dpiX=GetDeviceCaps(hDC, %LOGPIXELSX)
  dpiY=GetDeviceCaps(hDC, %LOGPIXELSY)
  rxRatio = dpiX/96
  ryRatio = dpiY/96
  Call MoveWindow(Wea.hWnd, SizX(200), SizY(100), SizX(300), SizY(124), %FALSE)
  hFont=CreateFont(-MulDiv(sngFont,dpiY,72),0,0,0,%FW_NORMAL,0,0,0,%DEFAULT_CHARSET,0,0,0,0,"Tahoma")
  Call ReleaseDC(%NULL,hDC)
  Call SetWindowLong(Wea.hWnd,%GWL_USERDATA,hFont)
  hCtl=CreateWindowEx(0,"static","User Name",%WS_CHILD Or %WS_VISIBLE Or %SS_RIGHT,SizX(10),SizY(12),SizX(90),SizY(16),Wea.hWnd,-1,Wea.hInst,ByVal 0)
  Call SendMessage(hCtl,%WM_SETFONT,hFont,0)
  hCtl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",%WS_CHILD Or %WS_VISIBLE Or %WS_TABSTOP,SizX(110),SizY(10),SizX(160),SizY(20),Wea.hWnd,%IDC_USERNAME,Wea.hInst,ByVal 0)
  Call SendMessage(hCtl,%WM_SETFONT,hFont,0)
  hCtl=CreateWindowEx(0,"static","Password",%WS_CHILD Or %WS_VISIBLE Or %SS_RIGHT,SizX(10),SizY(36),SizX(90),SizY(16),Wea.hWnd,-1,Wea.hInst,ByVal 0)
  Call SendMessage(hCtl,%WM_SETFONT,hFont,0)
  hCtl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",%WS_CHILD Or %WS_VISIBLE Or %WS_TABSTOP,SizX(110),SizY(34),SizX(160),SizY(20),Wea.hWnd,%IDC_PASSWORD,Wea.hInst,ByVal 0)
  Call SendMessage(hCtl,%WM_SETFONT,hFont,0)
  hCtl=CreateWindowEx(0,"button","Submit",%WS_CHILD Or %WS_VISIBLE Or %WS_TABSTOP,SizX(120),SizY(60),SizX(60),SizY(20),Wea.hWnd,%IDC_SUBMIT,Wea.hInst,ByVal 0)
  Call SendMessage(hCtl,%WM_SETFONT,hFont,0)
  hCtl=CreateWindowEx(0,"button","Cancel",%WS_CHILD Or %WS_VISIBLE Or %WS_TABSTOP,SizX(200),SizY(60),SizX(60),SizY(20),Wea.hWnd,%IDC_CANCEL,Wea.hInst,ByVal 0)
  Call SendMessage(hCtl,%WM_SETFONT,hFont,0)

  fnWndProc_OnCreate=0
End Function


Function fnWndProc_OnCommand(Wea As WndEventArgs) As Long
  Select Case As Long Lowrd(Wea.wParam)
    Case %IDC_SUBMIT
      MessageBox(Wea.hWnd,"You Clicked The Submit Button!","Button Click Report!",%MB_OK)
    Case %IDC_CANCEL
      MessageBox(Wea.hWnd,"You Clicked The Cancel Button!","You Want Out!",%MB_OK)
      SendMessage(Wea.hWnd,%WM_CLOSE, 0, 0)
  End Select

  fnWndProc_OnCommand=0
End Function


Function fnWndProc_OnDestroy(Wea As WndEventArgs) As Long
  Call DeleteObject(GetWindowLong(Wea.hWnd,%GWL_USERDATA))
  Call PostQuitMessage(0)

  fnWndProc_OnDestroy=0
End Function


Sub AttachMessageHandlers()
  Dim MsgHdlr(2) As Global MessageHandler 'Associate Windows Message With Message Handlers
  MsgHdlr(0).wMessage=%WM_CREATE          : MsgHdlr(0).dwFnPtr=CodePtr(fnWndProc_OnCreate)
  MsgHdlr(1).wMessage=%WM_COMMAND         : MsgHdlr(1).dwFnPtr=CodePtr(fnWndProc_OnCommand)
  MsgHdlr(2).wMessage=%WM_DESTROY         : MsgHdlr(2).dwFnPtr=CodePtr(fnWndProc_OnDestroy)
End Sub


Function fnWndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
  Local Wea As WndEventArgs
  Register iReturn As Long
  Register i As Long

  For i=0 To 2
    If wMsg=MsgHdlr(i).wMessage Then
       Wea.hWnd=hWnd: Wea.wParam=wParam: Wea.lParam=lParam
       Call Dword MsgHdlr(i).dwFnPtr Using FnPtr(Wea) To iReturn
       fnWndProc=iReturn
       Exit Function
    End If
  Next i

  fnWndProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
End Function


Function WinMain(ByVal hInstance As Long, ByVal hPrevIns As Long, ByVal lpCmdLn As WStringz Ptr, ByVal iShowWnd As Long) As Long
  Local szAppName As ZStr*16
  Local wc As WNDCLASSEX
  Local hWnd As Dword
  Local Msg As tagMsg

  szAppName        = "DpiAware"                          : wc.lpszClassName = VarPtr(szAppName)
  wc.lpfnWndProc   = CodePtr(fnWndProc)                  : wc.hInstance     = hInstance
  wc.hCursor       = LoadCursor(%NULL, ByVal %IDC_ARROW) : wc.hbrBackground = %COLOR_BTNFACE+1
  wc.cbSize        = sizeof(WNDCLASSEX)
  Call RegisterClassEx(wc)
  Call SetMyProcessDpiAware()
  Call AttachMessageHandlers()
  hWnd=CreateWindowEx(0,szAppName,"DpiAware",%WS_OVERLAPPEDWINDOW,0,0,0,0,0,0,hInstance,ByVal 0)
  Call ShowWindow(hWnd,iShowWnd)
  While GetMessage(Msg,%NULL,0,0)
    If IsFalse(IsDialogMessage(hWnd,Msg)) Then
       Call TranslateMessage(Msg)
       Call DispatchMessage(Msg)
    End If
  Wend

  Function=msg.wParam
End Function 
That is some very pretty code!
Pairing the event messages with function pointers seems so much neater than having a long switch in the message callback function. Is that the standard way of doing it, or something you devised?

I spent the past day reading the article in your link, and reading most of the related topics referenced. You were right: it's not complicated, just...dense. A few things that I picked up on though:

If I understand correctly, there are three levels of DPI awareness: non-DPI aware, system DPI aware, and per-monitor/dynamic awareness. The last type applies only to Windows 8.1. I probably should have mentioned this in my first post, but I'm writing the app for Windows 7 using MinGW 4.9.2 w/ CodeBlocks IDE. That limits me to system DPI aware code.

For system DPI awareness, MSDN states that there are 2 options - SetProcessDPIAware() or the application manifest. For the function call, MSDN says that it is subject to a race condition and shouldn't be used. The remainder of the page is devoted to using the application manifest to provide the required awareness. I was worried that I would have to deal with creating a manifest resource to be able to add the awareness before I saw that you yourself used the function call in the sample code. Is it safe to assume that the conditions required for the race are fairly rare?

As you remarked in your last post, the MinGW compiler lacks the SetProcessDPIAware() function, although I don't understand why. :/ I'm pretty sure there are some computers at my college that have VS2012 installed , so I might be able to switch to that compiler for this assignment.

Thank you very much for all your help, and for waiting patiently while I stubbornly tried to reinvent the wheel.

EDIT: According to the Code::Blocks documentation, codeblocks supports resource files. Would using an application manifest be possible if I can't make the switch to the VS compiler, or would that just create more problems?
Last edited on
I'm glad you weren't 'put off' by my 'message cracker' scheme! That has been a source of concern for me in posting here because I do like to try to help people, but I realize the topic of function pointers is somewhat more advanced than some of the beginners here would like to deal with.

On the other hand, most of my example code that I have accumulated from many, many years of coding is in that form, so if I see a question here that I can easily answer through posting a simple example progam, well, that is what I have, so to answer a question I can take five minutes to post what I have, or take a half hour to rewrite a program using the more customary switch construct.

I don't believe that what I do is all that common but I'm not sure. I learned the technique from a Microsoft Press book on Windows CE by Douglas Boling. He states in his book that he does all his Win32 code that way, and he credits another author for the original idea, and that would be Ray Duncan who used to write for a programming magazine - possibly 'PC Techniques' or something like that.

But you are right; using the typical switch construct, and extrapolating to the level of absurdity, one can end up with a program with 10,000 lines of code all in two functions - WinMain() and the massive Window Procedure. My very first Win32 Api programs back in the 90s were like that until I started using this message cracker scheme. Using this scheme as above the Window Procedure will never exceed those 16 lines or so, and all the code will be modularized into message handling functions - as it should be. One other issue you run into using this architecture is that static declared variables in the Window Procedure to maintain 'state' across function calls won't work unless one passes them as parameters to the various message handling functions as needed.

I don't do that. One of my fundamental rules of Windows Programming is 'No Global Variables'. So what you've seen I did in fnWndProc_OnCreate() with the HFONT variable is what I do - attach variables which must retain state to the class instance itself through Window Properties or the WNDCLASSEX::cbWndExtra bytes (or GWLP_USERDATA). That is essentiall Object Oriented Programming done C style. But there is no bloat.

If it isn’t more widely adopted than it is my guess as to why not would be that it is considerably more complicated to look at than the switch logic, and most folks who write ‘unmanaged code’ nowadays likely use a class framework where the message routing code is hidden from view, such as MFC or something. But for me, I dislike any and all class frameworks equally as well, so I’ve stuch with bare bones Win32/64 SDK style code – with the exception of my message cracker scheme. I have a number of approx 100,000 line programs using this technique and it scales very well.

In terms of the DPI issue, none of my Mingw compilers, up to the tdm 4.8 series ones, seem to support the various DPI function calls. I really don’t have a clue either why not. All I can say is that if I include a manifest in an *.rc file it compiles with Code::Blocks but I don’t believe the calls work. Now that you press me on the topic, it has gotten me to thinking. I do recall running an exe compiled with Code::Blocks on my Windows 7 box, and checking the Output.txt log file to see if the function call IsProcessDPIAware() returned true, and I recall seeing that it didn’t. But what I’m wondering about now is how that call could have even succeeded at all in returning a false if the DPI functions weren’t present??? Hmmmm. Don’t know.

I’ve been just using the function calls as shown in my example app, simply because its less files and easier. I believe the race conditions were about code in dll’s making the calls weren’t they? In any case, I haven’t personally had any troubles with it. I’m fairly new to this stuff too. Jose Roca shammed me into doing this stuff only a few months back. His basic line was that having one’s apps look sh***y on some devices reflected poorly on the programmer coding those apps, so folks ought to get themselves in line and learn to do it right.

At first I fought the idea, simply because I figured it would add a lot of extra K to my compiled apps. What I first tried to do to save myself from having to code ‘DPI Aware’ was to just make text boxes, buttons, etc., big enough that if the user adjusted setting in Control Panel radically enough, text and what not would still fit. That’s actually mostly doable. Windows scales non DPI aware apps pretty well. But where things really go South is in that bluriness thing. At first I was prepared to just ‘live with it’, but upon comparing two instances of the same app on my screen – one running DPI Aware and the other not – I realized the non – DPI aware app really did look sh***y compared to the high Dpi enabled one.

I’m not sure of the effect of all this on Windows 8 and what will come next. Like you, I’ve read that article and several others, and am content to have everything working on Windows 7. I’ve never used a Windows 8 machine.

Changing the topic back to code. It occurred to me after my post that in fnWndProc_OnCreate() in my debug code I called IsProcessDPIAware(). And I didn’t wrap that call in a LoadLibrary()/GetProcAddress() sequence like I did with SetProcessDPIAware(). What happens if you run that with a Mingw exe? I’ll have to try that.

Actually, I mostly use the Code::Blocks editor because Visual Studio makes me nuts. I’m in the market for another IDE though because the Code::Blocks team is so aggressive at adding new features (that I don’t care about) that they are about to lose a user.

I’d recommend though that you at least download the SDK for Windows 7 from Microsoft to you can see for yourself the bluriness thing I’ve been talking about. It really doesn’t show up at normal resolutions, but makes all the difference in the world if the user tweaks control panel into the higher resolutions.
Last edited on
I just checked into whether or not I could make my GNU Code::Blocks created exe High DPI Aware using a manifest and my results are that one can't. I also wanted to check whether I could make a IsProcessDPIAware() call with CodeBlocks to determine whether its running High DPI Aware when compiled with a manifest, and that turned out negative too.

At this time (and for past 4 months) I've been converting one of my organizations mission critical apps from PowerBASIC to C++. I checked in my code and I see when I coded this a couple months back I created a conditional compilation flag named VSTUDIO which I turn on when compiling with VC9. So that answers my previous question as to how I have an IsProcessDPIAware() call in it. Anyway, I changed a couple minor things in the app I posted above to work with GNU Mingw CodeBlocks, but not too much ...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//Main.cpp                    // Program Shows How To Code
#ifndef UNICODE               // High DPI (Dots Per Inch)
    #define UNICODE           // Aware Apps
#endif
#ifndef _UNICODE
   #define _UNICODE
#endif
#include <windows.h>
#include <cstdio>
#include "DpiAware.h"
#define MyDebug
//#define VSTUDIO


void SetMyProcessDpiAware()   // 2000/XP Doesn't Have SetProcessDPIAware()
{                             // So Only Call Function If Its Present
 BOOL (__stdcall *pFn)(void);

 HINSTANCE hInstance=LoadLibrary(L"user32.dll");
 if(hInstance)
 {
    pFn=(BOOL (__stdcall*)(void))GetProcAddress(hInstance,"SetProcessDPIAware");
    if(pFn)
       pFn();
    FreeLibrary(hInstance);
 }
}


long fnWndProc_OnCreate(WndEventArgs& Wea)      // Constructor For Main Window.  Processes
{                                               // WM_CREATE Message
 float sngFont, dpiX, dpiY, rxRatio, ryRatio;
 HFONT hFont=NULL;
 HWND hCtl=NULL;
 FILE* fp=NULL;
 HDC hDC;

 #ifdef MyDebug
    fp=fopen("Output.txt","w");
    fprintf(fp,"Entering fnWndProc_OnCreate()\n");
    fprintf(fp,"  Wea.hWnd            = %p\n",Wea.hWnd);
    #ifdef VSTUDIO
    fprintf(fp,"  IsProcessDPIAware() = %d\n",IsProcessDPIAware());
    #endif
 #endif
 Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
 sngFont=8.0;                                   // Set Font Size To 8.0.  Get Handle To Device Context For Calls
 hDC = GetDC(NULL);                             // To GetDeviceCaps().  This Latter Function Obtains Pixel Resolution
 dpiX=(float)GetDeviceCaps(hDC, LOGPIXELSX);    // Of Current Display Setting.  Divide This By 96 To Obtain
 dpiY=(float)GetDeviceCaps(hDC, LOGPIXELSY);    // Scaling Factor.  Note SizX() and SizY() macros in DpiAware.h.
 rxRatio=(dpiX/96);                             // These Are Used To Scale ALL App Window/Control x, y, cx, and cy
 ryRatio=(dpiY/96);                             // Dimensions.  Note Use in MoveWindow() and CreateWindowEx() calls
 #ifdef MyDebug                                 // Below.
    fprintf(fp,"  dpiX                = %8.0f\n",dpiX);      // When the app is set up this way, it will respond
    fprintf(fp,"  dpiY                = %8.0f\n",dpiY);      // correctly to any system wide changes the user
    fprintf(fp,"  rxRatio             = %8.2f\n",rxRatio);   // makes to DPI/Resolution settings in Control Panel.
    fprintf(fp,"  ryRatio             = %8.2f\n",ryRatio);
 #endif
 MoveWindow(Wea.hWnd, SizX(200), SizY(100), SizX(300), SizY(124), FALSE);
 hFont=CreateFont(-MulDiv((int)sngFont,(int)dpiY,72),0,0,0,FW_NORMAL,0,0,0,DEFAULT_CHARSET,0,0,0,0,(wchar_t*)L"Tahoma");
 ReleaseDC(NULL,hDC);
 SetWindowLongPtr(Wea.hWnd,GWLP_USERDATA,(LONG_PTR)hFont);
 hCtl=CreateWindowEx(0,L"static",L"User Name",WS_CHILD|WS_VISIBLE|SS_RIGHT,SizX(10),SizY(12),SizX(90),SizY(16),Wea.hWnd,(HMENU)-1,Wea.hIns,0);
 SendMessage(hCtl,WM_SETFONT,(WPARAM)hFont,0);
 hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,L"edit",L"",WS_CHILD|WS_VISIBLE|WS_TABSTOP,SizX(110),SizY(10),SizX(160),SizY(20),Wea.hWnd,(HMENU)IDC_USERNAME,Wea.hIns,0);
 SendMessage(hCtl,WM_SETFONT,(WPARAM)hFont,0);
 hCtl=CreateWindowEx(0,L"static",L"Password",WS_CHILD|WS_VISIBLE|SS_RIGHT,SizX(10),SizY(36),SizX(90),SizY(16),Wea.hWnd,(HMENU)-1,Wea.hIns,0);
 SendMessage(hCtl,WM_SETFONT,(WPARAM)hFont,0);
 hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,L"edit",L"",WS_CHILD|WS_VISIBLE|WS_TABSTOP,SizX(110),SizY(34),SizX(160),SizY(20),Wea.hWnd,(HMENU)IDC_PASSWORD,Wea.hIns,0);
 SendMessage(hCtl,WM_SETFONT,(WPARAM)hFont,0);
 hCtl=CreateWindowEx(0,L"button",L"Submit",WS_CHILD|WS_VISIBLE|WS_TABSTOP,SizX(120),SizY(60),SizX(60),SizY(20),Wea.hWnd,(HMENU)IDC_SUBMIT,Wea.hIns,0);
 SendMessage(hCtl,WM_SETFONT,(WPARAM)hFont,0);
 hCtl=CreateWindowEx(0,L"button",L"Cancel",WS_CHILD|WS_VISIBLE|WS_TABSTOP,SizX(200),SizY(60),SizX(60),SizY(20),Wea.hWnd,(HMENU)IDC_CANCEL,Wea.hIns,0);
 SendMessage(hCtl,WM_SETFONT,(WPARAM)hFont,0);
 #ifdef MyDebug
    fprintf(fp,"Leaving fnWndProc_OnCreate()\n");
    fclose(fp);
 #endif

 return 0;
}


long fnWndProc_OnCommand(WndEventArgs& Wea)
{
 switch(LOWORD(Wea.wParam))
 {
   case IDC_SUBMIT:
     MessageBox(Wea.hWnd,L"You Clicked The Submit Button!",L"Button Click Report!",MB_OK);
     break;
   case IDC_CANCEL:
     MessageBox(Wea.hWnd,L"You Clicked The Cancel Button!",L"You Want Out!",MB_OK);
     SendMessage(Wea.hWnd,WM_CLOSE, 0, 0);
     break;
 }

 return 0;
}


long fnWndProc_OnDestroy(WndEventArgs& Wea)
{
 HFONT hFont=NULL;

 hFont=(HFONT)GetWindowLongPtr(Wea.hWnd,GWLP_USERDATA);
 if(hFont)
    DeleteObject(hFont);
 PostQuitMessage(0);
 return 0;
}


LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 WndEventArgs Wea;

 for(unsigned int i=0; i<dim(EventHandler); i++)
 {
     if(EventHandler[i].iMsg==msg)
     {
        Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*EventHandler[i].fnPtr)(Wea);
     }
 }

 return (DefWindowProc(hwnd, msg, wParam, lParam));
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 wchar_t szClassName[]=L"DpiAware";
 WNDCLASSEX wc;
 MSG messages;
 HWND hWnd;

 memset(&wc,0,sizeof(wc));
 //SetMyProcessDpiAware();
 wc.lpszClassName = szClassName;                wc.lpfnWndProc = fnWndProc;
 wc.cbSize        = sizeof(WNDCLASSEX);         wc.hInstance   = hInstance;
 wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;    wc.cbWndExtra  = sizeof(void*);
 RegisterClassEx(&wc);
 hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,0,0,0,0,0,0,hInstance,0);
 ShowWindow(hWnd,iShow);
 while(GetMessage(&messages,NULL,0,0))
 {
    if(!IsDialogMessage(hWnd,&messages))
    {
       TranslateMessage(&messages);
       DispatchMessage(&messages);
    }
 }

 return (int)messages.wParam;
}


Here is the DpiAware.rc file ...

 
1 24 "DpiAware.xml"


...and here the DpiAware.xml file ...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly
  xmlns="urn:schemas-microsoft-com:asm.v1"
  manifestVersion="1.0">
  <assemblyIdentity
  name="DpiAware"
  processorArchitecture="x86"
  version="5.1.0.0"
  type="win32"/>
  <description>Windows Shell</description>
  <asmv3:application>
    <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
      <dpiAware>true</dpiAware>
    </asmv3:windowsSettings>
  </asmv3:application>
</assembly>


If you comment out the VSTUDIO equate above it will compile with CodeBlocks but won't run DPI Aware. I tested it against a VC9 compiled exe and zapped my DPI Settings and Resolution way up and the GNU compiled version looked like crap - all blurry. The VC9 version was razor sharp.
Topic archived. No new replies allowed.