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