WPF 高 DPI 下的 SizeToContent
这两天解决了一个 WPF 界面方面的问题:某个窗体运行在 1366*768
分辨率的屏幕上,并且窗口设置了 SizeToContent="WidthAndHeight"
属性,即窗体的尺寸将随内容而变化。当系统的 DPI 为默认的 96 时,一切呈现正常;但是当设置系统的 DPI 为 144 时,界面右侧被截断。既然窗体的尺寸随内容而变化,DPI 的修改怎会导致显示不全呢?究其原因,此问题是由 DPI 和 SizeToContent 共同造成的。
WPF 尺寸单位与 DPI 的关系
为了弄清楚显示不全的原因,我们需要先明确计算机系统和 WPF 程序中的一些尺寸概念:
- 屏幕分辨率:即我们通常所说的屏幕尺寸,比如
1366*768
或1920*1080
,其单位为:像素 - 系统 DPI:每英寸的点数(像素个数),通常情况下系统的默认 DPI 为: 96
- WPF 尺寸单位:设备无关单位,其值为:[1/96] 英寸
通过如上定义可以推算,当系统的 DPI 为 96 时,则有
WPF 尺寸单位 = [1/96] 英寸 * 96 像素/英寸 = 1 像素
。同理,如果系统的 DPI 设置为 144 ,则有WPF 尺寸单位 = 1.5 像素
。
SizeToContent 将限定 Window 的最大尺寸
在 WPF 程序中,当设置了 Window 的 SizeToContent="WidthAndHeight"
属性时,窗体的大小会随内容的尺寸而变化。但是,此时窗体的最大尺寸也将受限于屏幕的尺寸,例如在分辨率为 1366*768(单位:像素)
的屏幕上时,该窗体的最大宽度将为 1380 像素
左右(之所以比 1366
大一点,是因为系统设置了窗体边框)。
注意,此处 1380 的单位是 “像素”,在 144 DPI 下换算为 “WPF 尺寸单位”,则为 920。即此窗体的最大尺寸将为 920 个 WPF尺寸单位。
问题原因
先来看一下界面的相关代码:
<Window x:Class="Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Title="MainWindow" WindowStartupLocation="CenterScreen"
SizeToContent="WidthAndHeight">
<Grid Width="1200" Background="LightCoral" Height="300">
<!--内部代码省略-->
</Grid>
</Window>
结合前面介绍的 WPF 尺寸单位
和 SizeToContent 对窗体最大尺寸的影响
,我们可以轻松的分析出显示不全的原因:在 1366*768
分辨率下,SizeToContent="WidthAndHeight"
将窗体的最大尺寸限定为了 1380 像素
,在 144 DPI
下换算为 WPF 尺寸单位
为 920
,而窗口的内容尺寸被设定为了 1200(WPF 尺寸单位)
,超出了窗体的最大尺寸,所以内容被截断。而在 96 DPI
下,1380像素
换算为 WPF 尺寸单位为 1380(WPF 尺寸单位)
,内容尺寸在窗体的最大尺寸范围内,窗口的大小将随内容尺寸而定。
解决方案
当窗体的最大尺寸小于内容的尺寸时,我们可以使用 Viewbox
来实现内容的整体缩小。即在界面的根部放置一个 Viewbox
控件,默认情况下 Stretch="None"
,即不缩放内容。当窗口的尺寸小于内容的尺寸时,说明窗体的大小受限于最大尺寸,SizeToContent
不生效了,此时启用 Viewbox
的缩放功能。
<Viewbox x:Name="RootViewbox" Stretch="None">
<Grid x:Name="RootGrid" Width="1200" Background="LightCoral" Height="300">
<!--内部代码省略-->
</Grid>
</Viewbox>
protected override void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
// 窗体尺寸和内容尺寸不一致,说明已经受限于最大尺寸,此时缩放内容
if (ActualWidth < RootGrid.ActualWidth)
{
RootViewbox.Stretch = Stretch.Uniform;
}
}
在 WPF 应用程序的开发中,一定要考虑 DPI 对界面呈现的影响,不然会出现显示不全或尺寸不协调等问题。