如何创建随窗口高度缩放且不受系统DPI影响的WPF工业监控应用?
WPF工业监控应用多分辨率与DPI适配方案
核心需求梳理
针对1024x768基准分辨率设计UI,实现:
- 全屏运行,适配1024x768/1920x1080/1920x1200等分辨率
- 所有元素随窗口高度等比例缩放
- 部分区域宽度自适应窗口宽度
- 忽略系统DPI缩放(100%/125%/150%)
- 展示固定信息量,暂不做分宽高比的多布局
一、禁用系统DPI缩放
要忽略系统DPI设置,需让WPF直接使用设备像素而非设备独立像素(DIP),有两种可靠实现方式:
1. 应用清单配置
在项目的app.manifest中添加以下配置,强制应用不参与系统DPI缩放:
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">false</dpiAware> </windowsSettings> </application>
2. 代码级强制
在App.xaml.cs的OnStartup方法中重置DPI转换矩阵,直接映射设备像素:
protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var mainWindow = new MainWindow(); var source = PresentationSource.FromVisual(mainWindow); if (source != null) { source.CompositionTarget.TransformToDevice = Matrix.Identity; source.CompositionTarget.TransformFromDevice = Matrix.Identity; } mainWindow.Show(); }
二、布局实现方案
方案1:自定义缩放容器(推荐)
自定义ScaledPanel继承自Panel,自动按窗口高度比例缩放子元素,同时支持标记元素宽度自适应:
容器代码
using System.Windows; using System.Windows.Controls; public class ScaledPanel : Panel { // 基准高度(1024x768中的768) public static readonly DependencyProperty BaseHeightProperty = DependencyProperty.Register("BaseHeight", typeof(double), typeof(ScaledPanel), new PropertyMetadata(768.0, (s, e) => ((ScaledPanel)s).InvalidateArrange())); public double BaseHeight { get => (double)GetValue(BaseHeightProperty); set => SetValue(BaseHeightProperty, value); } // 标记元素是否宽度自适应 public static readonly DependencyProperty IsWidthAdaptiveProperty = DependencyProperty.RegisterAttached("IsWidthAdaptive", typeof(bool), typeof(ScaledPanel), new PropertyMetadata(false)); public static void SetIsWidthAdaptive(UIElement element, bool value) => element.SetValue(IsWidthAdaptiveProperty, value); public static bool GetIsWidthAdaptive(UIElement element) => (bool)element.GetValue(IsWidthAdaptiveProperty); protected override Size MeasureOverride(Size availableSize) { var scale = availableSize.Height / BaseHeight; var scaledSize = new Size(availableSize.Width / scale, availableSize.Height / scale); foreach (UIElement child in Children) { if (GetIsWidthAdaptive(child)) child.Measure(new Size(double.PositiveInfinity, scaledSize.Height)); else child.Measure(scaledSize); } return availableSize; } protected override Size ArrangeOverride(Size finalSize) { var scale = finalSize.Height / BaseHeight; foreach (UIElement child in Children) { if (GetIsWidthAdaptive(child)) { var scaledHeight = child.DesiredSize.Height * scale; child.Arrange(new Rect(0, 0, finalSize.Width, scaledHeight)); } else { var scaledWidth = child.DesiredSize.Width * scale; var scaledHeight = child.DesiredSize.Height * scale; child.Arrange(new Rect(0, 0, scaledWidth, scaledHeight)); } } return finalSize; } }
XAML使用示例
<Window x:Class="IndustrialMonitor.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:IndustrialMonitor" WindowState="Maximized" WindowStyle="None"> <local:ScaledPanel BaseHeight="768"> <!-- 固定缩放元素:按1024x768基准等比例缩放 --> <Grid Width="1024"> <TextBlock Text="工业监控系统" FontSize="24" Margin="10"/> <!-- 其他固定布局控件(如仪表盘、状态灯) --> </Grid> <!-- 宽度自适应元素:占满窗口宽度,高度按比例缩放 --> <Grid local:ScaledPanel.IsWidthAdaptive="True" Margin="10,0"> <TextBlock Text="实时生产数据" HorizontalAlignment="Center"/> <!-- 自适应宽度的表格、趋势图等 --> </Grid> </local:ScaledPanel> </Window>
方案2:绑定缩放比例(灵活度高)
如果需要更精细的控制,可在窗口中定义高度缩放比例,手动绑定到元素的高度、字体大小、边距等属性:
窗口代码
using System.ComponentModel; using System.Windows; using System.Windows.Data; public partial class MainWindow : Window, INotifyPropertyChanged { private double _heightScale; public double HeightScale { get => _heightScale; set { _heightScale = value; OnPropertyChanged(); } } public MainWindow() { InitializeComponent(); DataContext = this; HeightScale = ActualHeight / 768.0; } private void Window_SizeChanged(object sender, SizeChangedEventArgs e) => HeightScale = e.NewSize.Height / 768.0; public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); } // 缩放转换器 public class ScaleConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (value is double scale && double.TryParse(parameter.ToString(), out double baseValue)) return scale * baseValue; return baseValue; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) => throw new NotImplementedException(); }
XAML使用示例
<Window x:Class="IndustrialMonitor.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:IndustrialMonitor" WindowState="Maximized" WindowStyle="None" SizeChanged="Window_SizeChanged"> <Window.Resources> <local:ScaleConverter x:Key="ScaleConverter"/> </Window.Resources> <Grid> <!-- 固定缩放区域 --> <Grid x:Name="FixedGrid" Width="1024" HorizontalAlignment="Left"> <TextBlock Text="监控标题" FontSize="{Binding HeightScale, Converter={StaticResource ScaleConverter}, ConverterParameter=24}" Margin="{Binding HeightScale, Converter={StaticResource ScaleConverter}, ConverterParameter=10}"/> </Grid> <!-- 宽度自适应区域 --> <Grid Margin="{Binding ElementName=FixedGrid, Path=Width}" HorizontalAlignment="Stretch"> <TextBlock Text="自适应数据区" FontSize="{Binding HeightScale, Converter={StaticResource ScaleConverter}, ConverterParameter=18}"/> </Grid> </Grid> </Window>
三、关键注意事项
- 禁用DPI缩放后,所有WPF控件的尺寸直接映射屏幕物理像素,无需额外处理系统DPI设置
- 两种方案均保证元素高度按窗口高度比例缩放,固定信息量的展示不会因分辨率变化丢失内容
- 若后续需要支持宽高比适配,可在
ScaledPanel中添加宽高比判断逻辑,切换不同基准布局
内容的提问来源于stack exchange,提问作者sloopy
相关产品推荐
相关产品推荐

