Traditional Windows applications are bound by certain
assumptions about resolution. Developers usually assume a standard
monitor resolution (such as 1024 by 768 pixels), design their windows
with that in mind, and try to ensure reasonable resizing behavior for
smaller and larger dimensions.
The problem is that the user
interface in traditional Windows applications isn't scalable. As a
result, if you use a high monitor resolution that crams pixels in more
densely, your application windows become smaller and more difficult to
read. This is particularly a problem with newer monitors that have high
pixel densities and run at correspondingly high resolutions. For
example, it's common to find consumer monitors (particularly on laptops)
that have pixel densities of 120 dpi or 144 dpi (dots per inch), rather
than the more traditional 96 dpi. At their native resolution, these
displays pack the pixels in much more tightly, creating eye-squintingly
small controls and text.
Ideally, applications
would use higher pixel densities to show more detail. For example, a
high-resolution monitor could display similarly sized toolbar icons but
use the extra pixels to render sharper graphics. That way, you could
keep the same basic layout but offer increased clarity and detail. For a
variety of reasons, this solution hasn't been possible in the past.
Although you can resize graphical content that's drawn with GDI/GDI+,
User32 (which generates the visuals for common controls) doesn't support
true scaling.
WPF doesn't suffer from
this problem because it renders all user interface elements itself,
from simple shapes to common controls such as buttons. As a result, if
you create a button that's 1 inch wide on your computer monitor, it can
remain 1 inch wide on a high-resolution monitor—WPF will simply render
it in greater detail and with more pixels.
This is the big picture, but it
glosses over a few details. Most importantly, you need to realize that
WPF bases its scaling on the system
DPI setting, not the DPI of your physical display device. This makes
perfect sense—after all, if you're displaying your application on a
100-inch projector, you're probably standing several feet back and
expecting to see a jumbo-size version of your windows. You don't want
WPF to suddenly scale down your application to "normal" size. Similarly,
if you're using a laptop with a high-resolution display, you probably
expect to have slightly smaller windows—it's the price you pay to fit
all your information onto a smaller screen. Furthermore, different users
have different preferences. Some want richer detail, while others
prefer to cram in more content.
So, how does WPF determine how big an application window should
be? The short answer is that WPF uses the system DPI setting when it
calculates sizes. But to understand how this really works, it helps to
take a closer look at the WPF measurement system.
1. WPF Units
A WPF window and all the elements inside it are measured using device-independent units.
A single device-independent unit is defined as 1/96 of an inch. To
understand what this means in practice, you'll need to consider an
example.
Imagine that you create a
small button in WPF that's 96 by 96 units in size. If you're using the
standard Windows DPI setting (96 dpi), each device-independent unit
corresponds to one real, physical pixel. That's because WPF uses this
calculation:
[Physical Unit Size] = [Device-Independent Unit Size] × [System DPI]
= 1/96 inch × 96 dpi
= 1 pixel
Essentially, WPF assumes it
takes 96 pixels to make an inch because Windows tells it that through
the system DPI setting. However, the reality depends on your display
device.
For example, consider a
19-inch LCD monitor with a maximum resolution of 1600 by 1200 pixels.
Using a dash of Pythagoras, you can calculate the pixel density for this
monitor, as shown here:
In this case, the pixel
density works out to 100 dpi, which is slightly higher than what Windows
assumes. As a result, on this monitor a 96-by-96-pixel button will be
slightly smaller than 1 inch.
On the other hand, consider a
15-inch LCD monitor with a resolution of 1024 by 768. Here, the pixel
density drops to about 85 dpi, so the 96-by-96 pixel button appears
slightly larger than 1 inch.
In both these cases, if you
reduce the screen size (say, by switching to 800 by 600 resolution), the
button (and every other screen element) will appear proportionately
larger. That's because the system DPI setting remains at 96 dpi. In
other words, Windows continues to assume it takes 96 pixels to make an
inch, even though at a lower resolution it takes far fewer pixels.
As you no doubt know, LCD monitors are designed to work best at a specific resolution, which is called the native resolution.
If you lower the resolution, the monitor must use interpolation to fill
in the extra pixels, which can cause blurriness. To get the best
display, it's always best to use the native resolution. If you want
larger windows, buttons, and text, consider modifying the system DPI
setting instead (as described next).
|
|
2. System DPI
So far, the WPF button
example works exactly the same as any other user interface element in
any other type of Windows application. The difference is the result if
you change the system DPI setting. In the previous generation of
Windows, this feature was sometimes called large fonts. That's because the system DPI affects the system font size but often leaves other details unchanged.
NOTE
Many Windows
applications don't fully support higher DPI settings. At worst,
increasing the system DPI can result in windows that have some content
that's scaled up and other content that isn't, which can lead to
obscured content and even unusable windows.
This is where WPF is different.
WPF respects the system DPI setting natively and effortlessly. For
example, if you change the system DPI setting to 120 dpi (a common
choice for users of large high-resolution screens), WPF assumes that it
needs 120 pixels to fill an inch of space. WPF uses the following
calculation to figure out how it should translate its logical units to
physical device pixels:
[Physical Unit Size] = [Device-Independent Unit Size] × [System DPI]
= 1/96 inch × 120 dpi
= 1.25 pixels
In other words, when you set
the system DPI to 120 dpi, the WPF rendering engine assumes one
device-independent unit equals 1.25 pixels. If you show a 96-by-96
button, the physical size will actually be 120 by 120 pixels (because 96
× 1.25 = 120). This is the result you expect—a button that's 1 inch on a
standard monitor remains 1 inch in size on a monitor with a higher
pixel density.
This automatic scaling wouldn't
help much if it applied only to buttons. But WPF uses
device-independent units for everything it displays, including shapes,
controls, text, and any other ingredient you put in a window. As a
result, you can change the system DPI to whatever you want, and WPF will
adjust the size of your application seamlessly.
NOTE
Depending on the
system DPI, the calculated pixel size may be a fractional value. You
might assume that WPF simply rounds off your measurements to the nearest
pixel. However, by default, WPF does something different. If an edge of
an element falls between pixels, it uses anti-aliasing to blend that
edge into the adjacent pixels. This might seem like an odd choice, but
it actually makes a fair bit of sense. Your controls won't necessarily
have straight, clearly defined edges if you use custom-drawn graphics to
skin them; so some level of anti-aliasing is already necessary.
The steps for adjusting the
system DPI depend on the operating system. The following sections
explain what to do, depending on your operating system.
2.1. Windows XP
Right-click your desktop and choose Display.
Choose the Settings tab and click Advanced.
On
the General tab, choose Normal Size (96 dpi) or Large Size (120 dpi).
These are the two recommended options for Windows XP, because custom DPI
settings are less likely to be supported by older programs. To try a
custom DPI setting, choose Custom Setting. You can then specify a
specific percentage value. (For example, 175% scales the standard 96 dpi
to 168 dpi.)
2.2. Windows Vista
Right-click your desktop and choose Personalize.
In the list of links on the left, choose Adjust Font Size (DPI).
Choose
between 96 or 120 dpi. Or click Custom DPI to use a custom DPI setting.
You can then specify a percentage value, as shown in Figure 1.
(For example, 175% scales the standard 96 dpi to 168 dpi.) In addition,
when using a custom DPI setting, you have an option named Use Windows
XP Style DPI Scaling, which is described in the sidebar "DPI Scaling with Windows Vista and Windows 7."
2.3. Windows 7
Right-click your desktop and choose Personalize.
In the list of links at the bottom-left of the window, choose Display.
Choose
between Smaller (the default option), Medium, or Larger. Although these
options are described by scaling percentages (100%, 125%, or 150%),
they actually correspond to the DPI values 96, 120, and 144. You'll
notice that the first two are the same standards found in Windows Vista
and Windows XP, while the third one is larger still. Alternatively, you
can click Set Custom Text Size to use a custom DPI percentage, as shown
in Figure 1-1.
(For example, 175% scales the standard 96 dpi to 168 dpi.) When using a
custom DPI setting, you have an option named Use Windows XP Style DPI
Scaling, which is described in the sidebar "DPI Scaling with Windows Vista and Windows 7."
Because older
applications are notoriously lacking in their support for high DPI
settings, Windows Vista introduced a new technique called bitmap scaling. Windows 7 also supports this feature.
With bitmap scaling, when you
run an application that doesn't appear to support high DPI settings,
Windows resizes it as though it were an image. The advantage of this
approach is that the application still believes it's running at the
standard 96 dpi. Windows seamlessly translates input (such as mouse
clicks) and routes them to the right place in the application's "real"
coordinate system.
The scaling algorithm that
Windows uses is a fairly good one—it respects pixel boundaries to avoid
blurry edges and uses the video card hardware where possible to increase
speed—but it inevitably leads to a fuzzier display. It also has a
serious limitation in that Windows can't recognize older applications
that do
support high DPI settings. That's because applications need to include a
manifest or call SetProcessDPIAware (in User32) to advertise their high
DPI support. Although WPF applications handle this step correctly,
applications created prior to Windows Vista won't use either approach
and will be stuck with bitmap scaling even when they support higher
DPIs.
There are two
possible solutions. If you have a few specific applications that support
high DPI settings but don't indicate it, you can configure that detail
manually. To do so, right-click the shortcut that starts the application
(in the Start menu) and choose Properties. On the Compatibility tab,
enable the option named Disable Display Scaling on High DPI Settings. If
you have a lot of applications to configure, this gets tiring fast.
The other possible solution
is to disable bitmap scaling altogether. To do so, choose the Use
Windows XP Style DPI Scaling option in the Custom DPI Setting dialog box
shown in Figure 1-1.
The only limitation of this approach is that there may be some
applications that won't display properly (and possibly won't be usable)
at high DPI settings. By default, Use Windows XP Style DPI Scaling is
checked for DPI sizes of 120 or less but unchecked for DPI sizes that
are greater.
|
3. Bitmap and Vector Graphics
When you work with
ordinary controls, you can take WPF's resolution independence for
granted. WPF takes care of making sure that everything has the right
size automatically. However, if you plan to incorporate images into your
application, you can't be quite as casual. For example, in traditional
Windows applications, developers use tiny bitmaps for toolbar commands.
In a WPF application, this approach is not ideal because the bitmap may
display artifacts (becoming blurry) as it's scaled up or down according
to the system DPI. Instead, when designing a WPF user interface, even
the smallest icon is generally implemented as a vector graphic. Vector graphics are defined as a set of shapes, and as such they can be easily scaled to any size.
NOTE
Of course, drawing a
vector graphic takes more time than painting a basic bitmap, but WPF
includes optimizations that are designed to lessen the overhead to
ensure that drawing performance is always reasonable.
It's difficult to
overestimate the importance of resolution independence. At first glance,
it seems like a straightforward, elegant solution to a time-honored
problem (which it is). However, in order to design interfaces that are
fully scalable, developers need to embrace a new way of thinking.