Is Your App DPI-Aware?

When you tell Windows Vista (and 7 & 8) that your app is DPI-aware, Windows will not use DPI virtualization for your app and will no longer “lie” when you ask about the DPI settings.

Please note that by telling Windows that your app is DPI-aware, it is completely up to your app (you) to resize your UI in order to match user expectations based on their settings.

There are 2 different ways to tell Windows Vista, 7 and 8 that your app is DPI-aware:

  • a Windows API function, or
  • adding a section to your apps manifest file.

Using a Windows API Function

Windows Vista, 7 and 8 contain an API function named SetProcessDPIAware. The function has no parameters. It returns a zero if the function fails, and a non-zero result if the function succeeds.

Using it from within Visual FoxPro is easy:

DECLARE INTEGER SetProcessDPIAware IN WIN32API
SetProcessDPIAware()

The function simply tells Windows, “This app (process) is DPI Aware, so please do not lie to me. I will handle all of the resizing myself, so my users will see things the way they want to.”

Easy, right? Well, kind of. If you run the code listing above from the VFP Command Window, and if your Windows DPI setting has been set to 144dpi with DPI Virtualization turned on, you’ll get the correct (truthful) font factor from Windows (when you follow the API call with the code listing from a previous post).

BUT, remember this function has told Windows that this process is DPI-aware. And since you’re running the code from within the VFP IDE, you’re essentially telling Windows that Visual FoxPro is DPI-aware.

Unfortunately, Visual FoxPro is not DPI-aware, and has already been “DPI virtualized” by Windows. Causing Windows to suddenly accept that Visual FoxPro is DPI-aware can wreak havoc with the IDE.

If you don’t trust me on this, try it. It’s u-g-l-y!

So, one way we can force Windows to accept the fact that our app is DPI-aware is to simply call the SetProcessDPIAware function – preferably as early in your app as possible. I have seen several forum postings which report calling the function too late can cause Windows to continue to “lie” and use DPI virtualization anyway, though I have not recreated this problem myself.

 

Using Your App Manifest File

Another way to tell Windows Vista (and 7) that the app is DPI-aware is to add the following section to your app manifest file:

<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
   <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
      <dpiAware>true</dpiAware>
   </asmv3:windowsSettings>
</asmv3:application>

If you are not sure about manifest files and adding manifest resources to your Visual FoxPro applications, I recommend the following articles (I’ve been using Markus’ Manifest Tools in my work):

Calvin Hsia: Add a Manifest to Control Your Application Vista UAC Behavior

Craig Boyd: Apply Application Manifest At Compile Time With ProjectHook

Markus Winhard: ManifestTools.zip (based on code from Calvin’s blog above)

If you are not comfortable with messing around with manifest files, the SetProcessDPIAware API function is the way to go. Just be sure it only runs on Vista/Win7/Win8 (or call it in a TRY-CATCH-ENDTRY block) to prevent an error in pre-Vista versions of Windows!

Additional thoughts and (better) explanations of DPI-awareness in Vista can be found in the following article: http://www.rw-designer.com/DPI-aware

Windows Is Lying To You… With DPI Virtualization

DPI Virtualization in Windows

Windows Vista (and subsequently, Windows 7 & 8) introduced a new feature called DPI Virtualization.

DPI Virtualization means Windows will automatically “resize” your forms and form objects for you, with no coding or resizing required on your part.

Sounds great, doesn’t it? Well, not quite. Remember my “ugly” form in an earlier post, running at 144dpi? It was DPI Virtualized by Windows 7, and the result was a blurry window.

DPI Virtualization also requires the code listing from a previous post to fail, every time. Why? Because when the code asks Windows, “Hey, what’s the current setting?”, Windows Vista, 7 and 8 will always answer “your font factor is 1” (no resizing required) if the user has selected to use DPI Virtualization.

“Whoa, whoa, whoa, Kevin! If the user has selected DPI Virtualization?”

Yes. Windows Vista, 7 and 8 have two different modes of DPI scaling: XP Style, and DPI Virtualization.

Setting the DPI scale mode in Windows 7

Setting the DPI scale mode in Windows 7

If the user changes her DPI setting, and checks the “Use Windows XP style DPI scaling” checkbox, Windows Vista, 7 and 8 will return the expected (truthful) result from the previous code listing – just like XP did.

So, a DPI of 144dpi (150%) with this box unchecked, results in the following:

The correct font factor with XP style scaling turned on

The correct font factor with XP style scaling turned on

And, just like I would do with the XP result from earlier, I would want to resize my forms and form objects, making them 50% larger than “normal.”

But if the user has not checked the box, here’s what Windows tells me about the “font factor” (notice the messagebox is also a bit blurry):

Thanks, Windows, for lying and making my forms blurry

Thanks, Windows, for lying to me and making my forms blurry

Great. The user has selected 144dpi, but my app does not know this because Windows has said, “Don’t worry, no need to resize anything. All is good.”

And my user gets to see “somewhat” resized, yet blurry, forms in my app.

What we need is a “Windows Lie Detector” to make sure Windows stops lying to us.

How do we do this?

We need to tell Windows that the app is “DPI Aware”.

And that’s exactly what we’ll look at in tomorrow’s post.

 

Related Links (With much better explanations)

High DPI Settings In Windows
Fixing Windows Programming

 

Respecting The User’s Font Settings

Updated December 16, 2013: Source code from this post is available here.

All versions of Windows have a “system font”, which is used for menus, title bars, messageboxes, etc.

The Windows XP system font (except for a couple of Windows Classic themes) is Tahoma.

The system font for Windows Vista, Windows 7, and Windows 8 is Segoe UI.

I’ve seen apps which use a variety of fonts within the application. Some may use Arial for labels, but Courier New (or some other font) for all data-bound controls. I’ve seen arguments online about the pros and cons of using multiple fonts. For myself, though, I just don’t like seeing a mix-and-match approach to fonts in an app. The app menu and form captions will have the Windows system font, and the form itself may have two or three different fonts. This is simply too much for my old eyes (and slow brain) to take in.

This is, of course, a personal preference. I’m not making any “right” or “wrong” statements here — just sharing my opinion.

Since I want my apps to have a level of consistency and a professional Windows look and feel, I like for my forms and form objects to use the same font that Windows is using.

In an effort to “define the lowest common denominator”, I’ve decided to use Tahoma exclusively in Windows XP, and Segoe UI in Windows Vista and later.

In Doug’s Windows 7 session, he showed how he added code in the Init event of classes to change the font to Segoe UI if the user is running Windows Vista or later. I do the opposite: I check the OS and change the font to Tahoma if the user is running a pre-Vista version of Windows. This is again a personal preference; since I do my development work in Windows 7, I like to see the Segoe UI font while I’m doing my dev work.

Doug has posted a LOT of his whitepapers, articles and sample code on his web site. Comments about Segoe and Windows 7 can be found in this whitepaper.

It was mentioned during one of my sessions that if I am working on a team, and any members of the team are using Windows XP (or earlier), I’d want to develop in Tahoma exclusively and follow Doug’s method of switching to Segoe UI at runtime. An excellent suggestion!

 

Does The User Want Large Fonts?

I now have a way of ensuring my apps use one particular font. Now we need to see if the user has told Windows that she would like to use large fonts. I try to determine if the user wants to use large fonts for one simple reason: if the user wants large fonts, she usually has a reason (large monitor with high resolution, personal comfort, or perhaps she has a disability and needs to use large fonts). This way, my app can resize the forms and form controls automatically, to match the users needs.

With a couple of Windows API calls, I know right away if the user has selected large fonts in Windows. I’ve used the code listed below in the past for determining the font size setting, slightly modified from an article Doug Hennig wrote several years ago:

LOCAL liHDC, liPixelsPerInchX, ;
      liPixelsPerInchY, lnFontFactor

DECLARE INTEGER GetDC IN Win32API ;
   INTEGER iHDC

DECLARE INTEGER GetDeviceCaps IN WIN32API ;
   INTEGER iHDC, INTEGER iIndex

#DEFINE cnLOG_PIXELS_X 88
#DEFINE cnLOG_PIXELS_Y 90

liHDC = GetDC(ThisForm.HWnd)
liPixelsPerInchX = GetDeviceCaps(liHDC, cnLOG_PIXELS_X)
liPixelsPerInchY = GetDeviceCaps(liHDC, cnLOG_PIXELS_Y)

IF liPixelsPerInchX <= 96
   ** The user has selected "normal" font size
   lnFontFactor = 1
ELSE
   ** The user wants to use large fonts. The
   ** calculation will determine how large
   ** (by percentage) the fonts should be.
   lnFontFactor = 1 + ((liPixelsPerInchX - 96)/96)
ENDIF

MESSAGEBOX("The DPI 'font factor' is: " + ; 
           ALLTRIM(TRANSFORM(lnFontFactor)),0,"Font Factor")

This code simply calls Windows and says, “Hey Windows, what’s the current DPI setting?” and displays the percentage I should bump up my visual controls by in order to match the users settings.When I run the code in Windows XP, with DPI set to the “normal” 96dpi, I get the following:

Font Factor for 96dpi in Windows XP

Font Factor for 96dpi in Windows XP

A “font factor” of 1 means I don’t need to do any resizing of any forms or form objects.

Setting the DPI in Windows XP to 120dpi results in this:

Font Factor for 120dpi in Windows XP

Font Factor for 120dpi in Windows XP

A “Font Factor” of 1.25 means the user wants to see things larger than the “normal” 96dpi, so I need to resize all of my visual controls in the app to be 25% larger.

For good measure, here’s the result when the DPI is set to 144dpi:

Font Factor for 144dpi in Windows XP

Font Factor for 144dpi in Windows XP

In this case, I’ll want to resize all of the visual controls by increasing their size by 50%.

So now it’s just a matter of resizing all controls on my form by multiplying the design time font size by the “font factor”.

Of course, I’ll also need to resize the forms height and width by the same percentage, and “move” each control accordingly.

I’ll be adding a class (cusResizer) for this series which automatically performs the calculations and resizing of forms and form objects. It will be available for download soon.

And, we’re done!

Well, not quite yet.

The code above works fine in XP.

But, how does it do in Vista and Windows 7 & 8?

That depends

 

 

 

Respecting The User’s Color Settings

Before we talk about color settings, it’s important to know upfront where Visual FoxPro gets the default values for forms and form objects.

Have you ever wondered where the VFP IDE comes up with its default colors for forms (and other objects)? They come right from the Windows API.

According to the VFP Help File: “The color settings, or Themes, of the operating system set the default color settings for the BackColor and ForeColor properties.”

This explains why, on my machine, when I create a new form in Windows 7 the properties sheet contains the following (BackColor = 240,240,240):

Default form BackColor on my Windows 7 machine

The default BackColor for a new form on my Windows 7 machine is 240,240,240

The BackColor property depends completely on the current color scheme (theme) I’m using in Windows at design time. If I am set to Windows Classic in Windows 7, the BackColor is different. If I decide to punish myself and create a new form while the High Contrast (Black) theme is running, the default BackColor will be 0,0,0.

When I open this form in Windows XP, I get a different BackColor:

Default form BackColor on my Windows XP machine

The default BackColor of a new form in XP (depends on your theme)

What this means is if I do not change the BackColor of a new form, even if I created the form in Windows 7 with an Aero theme, my form BackColor will match whatever theme I’m using in other versions of Windows (and Windows themes) at design time and runtime – without me having to do anything!

In short, “Your Default BackColor = the Windows API System Color”.

This is pretty powerful stuff. In Windows 7, I’ve created the form pictured below. I left the BackColor property as the Default. When I run it in Windows 7, I see this:

Same BackColor as my default

Just as I expected. The exact same BackColor as the default in my design environment.

When I run the same form on Windows XP (with the Windows XP Blue Theme), I get this:

A bit different

It’s different, but the same

It’s different, but it’s exactly what Windows wants the BackColor to be, based on the theme.

When I change the XP theme to Windows Classic Spruce, I get this:

Perfect!

Perfect! And I didn’t have to change a thing!

And once again, the BackColor matches the Windows theme at runtime. All without a single property change or a single line of code!

I wonder what happens when I make a change to the BackColor?

I flip back to my Windows 7 dev machine, change the BackColor to 192,192,192, save and run the form – and here’s what I see:

Big deal. It's exactly what I told it to be, right?

Big deal. It’s exactly what I told it to be, right?

And running the same form on XP (again with the Blue Theme):

Same BackColor as Windows 7, because I told it to change

Same BackColor as Windows 7, because I told it to change

Whew! I’ve set my form BackColor to 192,192,192, and it looks relatively the same on my Windows 7 and Windows XP. So, I’m done, right?

Not quite. I changed my theme in Windows XP back to “Spruce”:

That's different. REAL different. And, dare I say it? Ugly!

That’s different. REAL different. And, dare I say it? Ugly!

OK, I don’t like that. So, I’ll just jump back into VFP and change the color back to what Windows 7 said the default should be: 240,240,240.

Let’s see it in Windows 7:

And we're back to the default! Or, are we?

And we’re back to the default! Or, are we?

So far, so good. Let’s see how it looks in Windows XP “Spruce”:

What happened? I changed it back to the default!

What happened? I changed it back to the default!

I expected my form BackColor to pick up the Windows theme again. But, it didn’t. Let’s take a look at the property sheet:

Hmm... It's the Default, but it's not the "Default"

Hmm… It’s the Default, but it’s not the “Default”

When a property is shown in Bold in the VFP Properties window, it’s not the Default – even if the value is the same as the Default value you expected.

So, how can we get it back to the way it was originally? Reset to Default. Right-click the property in the Properties window, and select “Reset to Default” from the context menu:

Fixed. Finally!

Fixed. Finally!

 

So, what’s the big tip for forms and form objects?

Don’t touch the default values for “color” properties if you want your forms (and form objects) to be consistent with Windows themes.

If you do change them, then later decide to go back to the “Default”, make sure you “Reset to Default”!

Just in case you need more evidence of just how ugly the form is when the BackColor is not set to the Default (i.e. Windows System Color), here’s the same form in High Contrast with the BackColor set to 192,192,192:

At least the label and the button used the correct colors.

At least the label and the button used the correct colors.

After a “Reset to Default”, the form looks like this:

Whew, that's better!

Whew, that’s better!

As you can see from the previous images, the BackColor, ForeColor (in fact, all of the color properties) are tied to the Windows System Colors at runtime – but only if their values are set to their Default properties!