WPF 視窗大小自適應
在設定桌面不同解析度以及較大DPI下,視窗如何顯示的問題。
方案一 設定視窗最大值和最小值顯示
通過對比當前螢幕的可顯示區域,將視窗高寬最大值和最小值,設定為視窗的實際高寬(此例中僅設定高度)
介面設定
- 設定視窗內容自適應SizeToContent= " WidthAndHeight "
- 新增ViewBox -- 設定預設不拉伸Stretch= " None " ,當DPI超大時如超過1920*1080p的175%(即win10預設不支援的比例顯示),開啟ViewBox縮放
- 頂層佈局容器 RootGrid 新增高寬最大值和最小值。
1 <Window x:Class="WindowHeightChangedForDpi.MainWindow" 2xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6xmlns:local="clr-namespace:WindowHeightChangedForDpi" 7mc:Ignorable="d" 8Title="MainWindow" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen"> 9<Viewbox x:Name="RootViewbox" Stretch="None"> 10<Grid x:Name="RootGrid" Width="1000" MaxHeight="680" MinHeight="520" ClipToBounds="True"> 11 12</Grid> 13</Viewbox> 14 </Window>
後臺設定
獲取工作列的高度
var taskbarHeight = SystemParameters.PrimaryScreenHeight - SystemParameters.WorkArea.Height;
當工作列隱藏不顯示時,此高度為0
視窗大小自適應設定
- 新增對Loaded事件的監聽,並在之後登出。視窗只需要首次初始其高度即可。
- 獲取螢幕的高度和工作列的高度 -- 具體可以參考 ofollow,noindex" target="_blank">C# 獲取當前螢幕的寬高和位置
- 比較當前可顯示高度(螢幕高度-工作列高度)與視窗的最大/最小高度,然後設定當前視窗的實際高度。
- 如果可顯示高度比最小值還小,則開啟ViewBox內容縮放。ViewBox的高度為當前可顯示高度。
- 如果當前視窗有陰影,可設定陰影高度大小。保證視窗在可顯示區域內正常顯示。
1public partial class MainWindow : Window 2{ 3public MainWindow() 4{ 5InitializeComponent(); 6Loaded += InitWindowActualHeight_OnLoaded; 7} 8 9#region 設定視窗對螢幕高度的自適應 10 11private void InitWindowActualHeight_OnLoaded(object sender, RoutedEventArgs e) 12{ 13Loaded -= InitWindowActualHeight_OnLoaded; 14InitWindowActualHeight(); 15} 16 17private const double WindowShadowHeight = 0; 18 19private void InitWindowActualHeight() 20{ 21//獲取窗體所在螢幕的高度 22var visibleAreaHeight = GetScreenHeight(); 23 24//可顯示高度 > 視窗最大高度 25if (visibleAreaHeight > RootGrid.MaxHeight + WindowShadowHeight) 26{ 27//設定高度等於最大高度 28RootGrid.Height = RootGrid.MaxHeight; 29} 30//可顯示高度 < 視窗最小高度 31else if (visibleAreaHeight < RootGrid.MinHeight + WindowShadowHeight) 32{ 33//設定Viewbox高度=可視高度-陰影高度(此處通過綻放顯示視窗,所以不能通過設定視窗或者設定內容的高度來實現) 34RootViewbox.Height = visibleAreaHeight - WindowShadowHeight; 35//等比例縮小 36RootViewbox.Stretch = Stretch.Uniform; 37} 38else 39{ 40//設定高度等於最小高度 41RootGrid.Height = RootGrid.MinHeight; 42} 43} 44const double DpiPercent = 96; 45private double GetScreenHeight() 46{ 47var intPtr = new WindowInteropHelper(this).Handle;//獲取當前視窗的控制代碼 48var screen = Screen.FromHandle(intPtr);//獲取當前螢幕 49 50double height = 0; 51using (Graphics currentGraphics = Graphics.FromHwnd(intPtr)) 52{ 53double dpiXRatio = currentGraphics.DpiX / DpiPercent; 54double dpiYRatio = currentGraphics.DpiY / DpiPercent; 55height = screen.WorkingArea.Height / dpiYRatio; 56//var width = screen.WorkingArea.Width / dpiXRatio; 57//var left = screen.WorkingArea.Left / dpiXRatio; 58//var top = screen.WorkingArea.Top / dpiYRatio; 59} 60return height; 61} 62#endregion 63}
注:獲取的螢幕高度為螢幕畫素,需要轉換為WPF單位。
以上只是設定了高度的最大值最值,如果需要,可以對高度設定多個梯度,對應不同解析度下的顯示。
下載 Demo
方案二 設定視窗為螢幕的百分比(如60%)顯示
視窗設定為螢幕的百分比大小(如60%高寬)顯示,在這基礎上新增限制(最大值、最小值)。
如此,對多種解析度、DPI比例,我們開發時就不需要考慮其它因素,簡單明瞭且所有視窗大小能統一。
比如主視窗A設定為螢幕可顯示區域的60%大小,二級子視窗設定為可顯示區域的40%大小,三級子視窗設定為可顯示區域的30%大小。
實現方案與案例
通過新增附加屬性,設定當前視窗寬為可顯示區域的80%大小,高為可顯示區域高的75%大小。
1 <Window x:Class="WindowSizeToScreenRatioDisplay.MainWindow" 2xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6xmlns:local="clr-namespace:WindowSizeToScreenRatioDisplay" 7mc:Ignorable="d" 8Title="MainWindow" 9local:WindowAdaptation.WidthByScreenRatio="0.8" MaxWidth="1200" MinWidth="800" 10local:WindowAdaptation.HeightByScreenRatio="0.75" MaxHeight="800" MinHeight="520"> 11<Grid Background="CornflowerBlue"> 12 13</Grid> 14 </Window>
新增附加屬性 WidthByScreenRatio、HeightByScreenRatio。
控制視窗大小:
- 預設設定為當前螢幕工作區域的顯示比例大小
- 如果超過視窗最大高度/寬高,則顯示為視窗最大高度/寬高
- 如果小於視窗最小高度/寬高,則顯示為當前可顯示區域的最大高度/寬高
1/// <summary> 2/// 為視窗<see cref="Window"/>新增附加屬性的輔助類 3/// </summary> 4public class WindowAdaptation 5{ 6#region 視窗寬度比例 7/// <summary> 8/// 視窗寬度比例 單位:小數(0 - 1.0] 9/// <para>視窗實際寬度=使用螢幕可顯示區域(螢幕高度-工作列高度)* 視窗寬度比例</para> 10/// </summary> 11public static readonly DependencyProperty WidthByScreenRatioProperty = DependencyProperty.RegisterAttached( 12"WidthByScreenRatio", typeof(double), typeof(WindowAdaptation), new PropertyMetadata(1.0, OnWidthByScreenRatioPropertyChanged)); 13 14private static void OnWidthByScreenRatioPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 15{ 16if (d is Window window && e.NewValue is double widthByScreenRatio) 17{ 18if (widthByScreenRatio <= 0 || widthByScreenRatio > 1) 19{ 20throw new ArgumentException($"螢幕比例不支援{widthByScreenRatio}"); 21} 22 23var screenDisplayArea = GetScreenSize(window); 24var screenRatioWidth = screenDisplayArea.Width * widthByScreenRatio; 25 26if (!double.IsNaN(window.MaxWidth) && screenRatioWidth > window.MaxWidth) 27{ 28window.Width = window.MaxWidth; 29} 30else if (!double.IsNaN(window.MinWidth) && screenRatioWidth < window.MinWidth) 31{ 32window.Width = screenDisplayArea.Width; 33} 34else 35{ 36window.Width = screenRatioWidth; 37} 38} 39} 40 41public static void SetWidthByScreenRatio(DependencyObject element, double value) 42{ 43element.SetValue(WidthByScreenRatioProperty, value); 44} 45 46public static double GetWidthByScreenRatio(DependencyObject element) 47{ 48return (double)element.GetValue(WidthByScreenRatioProperty); 49} 50#endregion 51 52#region 視窗高度比例 53/// <summary> 54/// 視窗寬度比例 單位:小數(0 - 1.0] 55/// <para>視窗實際寬度=使用螢幕可顯示區域(螢幕高度-工作列高度)* 視窗寬度比例</para> 56/// </summary> 57public static readonly DependencyProperty HeightByScreenRatioProperty = DependencyProperty.RegisterAttached( 58"HeightByScreenRatio", typeof(double), typeof(WindowAdaptation), new PropertyMetadata(1.0, OnHeightByScreenRatioPropertyChanged)); 59 60private static void OnHeightByScreenRatioPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 61{ 62if (d is Window window && e.NewValue is double heightByScreenRatio) 63{ 64if (heightByScreenRatio <= 0 || heightByScreenRatio > 1) 65{ 66throw new ArgumentException($"螢幕比例不支援{heightByScreenRatio}"); 67} 68 69var screenDisplayArea = GetScreenSize(window); 70var screenRatioHeight = screenDisplayArea.Height * heightByScreenRatio; 71 72if (!double.IsNaN(window.MaxHeight) && screenRatioHeight > window.MaxHeight) 73{ 74window.Height = window.MaxHeight; 75} 76else if (!double.IsNaN(window.MinHeight) && screenRatioHeight < window.MinHeight) 77{ 78window.Height = screenDisplayArea.Height; 79} 80else 81{ 82window.Height = screenRatioHeight; 83} 84} 85} 86 87public static void SetHeightByScreenRatio(DependencyObject element, double value) 88{ 89element.SetValue(HeightByScreenRatioProperty, value); 90} 91 92public static double GetHeightByScreenRatio(DependencyObject element) 93{ 94return (double)element.GetValue(HeightByScreenRatioProperty); 95} 96#endregion 97 98const int DpiPercent = 96; 99private static dynamic GetScreenSize(Window window) 100{ 101var intPtr = new WindowInteropHelper(window).Handle;//獲取當前視窗的控制代碼 102var screen = Screen.FromHandle(intPtr);//獲取當前螢幕 103using (Graphics currentGraphics = Graphics.FromHwnd(intPtr)) 104{ 105//分別獲取當前螢幕X/Y方向的DPI 106double dpiXRatio = currentGraphics.DpiX / DpiPercent; 107double dpiYRatio = currentGraphics.DpiY / DpiPercent; 108 109var width = screen.WorkingArea.Width / dpiXRatio; 110var height = screen.WorkingArea.Height / dpiYRatio; 111 112return new { Width = width, Height = height }; 113} 114} 115}
下圖為1920*1080p的175%DPI顯示:
下圖為1366*768的125%DPI下顯示: