WPF : screen resolution independenceWPF : screen resolution independence
The unit of 2D WPF coordinate system is the Device Independent Pixel (DIP). It is a logical pixel, independent of the resolution of the display device : 96 DIP correspond to 1 inch (2.54 cm).
It is therefore easy to determine dimensions programmatically in physical units of measure, particularly in inches. For fans of the metric system, the Perspective library (from version 0.5) provides conversion functions in the RenderingHelper class : MmToDip(), CmToDip(), InchToDip(), DipToCm() and DipToMm().
It works fine for printing. But the on-screen display is much less obvious.
Indeed, the system does not know the size of the screen (his diagonal) and therefore can't know its actual points density.
Windows provides options for manually setting the points density. But its role is ambiguous, as it is presented to the user as a way to change the font size (and indirectly to apply a scaling factor to the whole system). This is made via the desktop context menu :
- under Windows Vista : Properties – Adjust font size (DPI).
- Sous Windows XP : Properties – Settings – Advanced.

By default, Windows considers that the screen has a resolution of 96 dpi. This is true for a 17-inch flat screen with a 1280x1024 matrix, but wrong for a 19-inch with the same definition, and in many other cases... So generally, 96 DIP do not correspond to one inch!
Unless if you tell Windows the actual density of the screen, using the custom DPI setting.

The adjustment is done by dragging the displayed graduation until it corresponds to a ruler (in inches) put on the screen.
But an overall scaling is applied to the system, and some applications may encounter problems.
A solution is to make a scaling on the same principle but only at the WPF application level. The scaling factor to be applied may be determined in 2 ways with tools provided by the Perspective library :
- RenderingHelper.GetScreenIndependentScaleFactor() determines that factor from the size of the diagonal of the screen, expressed in inches. This assumes that this size is known, and most importantly that it fits well the displayed surface (which is not always the case with cathodic screens).
- The Ruler class displays a scale in centimeters ;-) (or in inches if you prefer). It can be used the same way as the Windows configuration, applying a scaling until its dimensions correspond to a ruler put on the screen. The obtained factor can then be used for a general scaling of the application (except for the setting screen, of course).
These 2 techniques are illustrated in the PerspectiveDemo.exe application (page DpiScaling.xaml, here showed for a 100 dpi screen) :

The ScreenSizeToScaleFactorConverter class (which uses RenderingHelper.GetScreenIndependentScaleFactor()) allows the binding of the scale factor to the input box of the screen diagonal size (itself linked to a Slider). Extract from DpiScaling.xaml :
<Page x:Class="PerspectiveDemo.Pages.DpiScaling"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:p="http://www.codeplex.com/perspective"
xmlns:appConv="clr-namespace:PerspectiveDemo.Converters"
Loaded="Page_Loaded"
Title="DpiScaling" FontSize="14">
<Page.Resources>
<appConv:ScreenSizeToScaleFactorConverter x:Key="Converter"/>
</Page.Resources>
<StackPanel Orientation="Vertical" Margin="25.0, 25.0, 25.0, 25.0">
<StackPanel Orientation="Vertical" >
<Slider x:Name="sScreenSize" Minimum="10.0" Maximum="25.0" />
<StackPanel Orientation="Horizontal">
<TextBlock Name="tbDiagonalScreenSizeText" Text="Screen diagonal size (inches) : "/>
<TextBox Name="tbDiagonalScreenSizeInput" Text="{Binding ElementName=sScreenSize, Path=Value, Mode=TwoWay}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Name="tbScaleFactorText" Text="Screen independent scale factor to apply : "/>
<TextBlock Name="tbScaleFactorValueText" Text="{Binding ElementName=tbDiagonalScreenSizeInput, Path=Text, Converter={StaticResource Converter}, ConverterParameter=ScaleX}"/>
</StackPanel>
</StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Auto" Margin="0.0, 5.0, 0.0, 0.0">
<p:Ruler Length="15.0" >
<p:Ruler.LayoutTransform>
<ScaleTransform
x:Name="ScreenIndependentScaleTransform"
ScaleX="{Binding ElementName=tbDiagonalScreenSizeInput, Path=Text, Converter={StaticResource Converter}, ConverterParameter=ScaleX}"
ScaleY="{Binding ElementName=tbDiagonalScreenSizeInput, Path=Text, Converter={StaticResource Converter}, ConverterParameter=ScaleY}"/>
</p:Ruler.LayoutTransform>
</p:Ruler>
</ScrollViewer>
</StackPanel>
</Page>This application also illustrates the storage of scaling parameters in .NET Isolated Storage via AssemblyConfigManager class (which uses IsolatedStorageHelper).
private void bSaveScale_Click(object sender, RoutedEventArgs e)
{
RenderingHelper.ScreenIndependentScaleX = ScreenIndependentScaleTransform.ScaleX;
RenderingHelper.ScreenIndependentScaleY = ScreenIndependentScaleTransform.ScaleY;
RenderingHelper.ScreenSize = sScreenSize.Value;
AssemblyConfigManager.SaveMachineStoreForAssembly();
}About this article
Author: Olivier Dewit.
History:
- January 6, 2008 : 1st publication (Perspective version 0.5).