1. 程式人生 > >WPF 視窗大小自適應

WPF 視窗大小自適應

在設定桌面不同解析度以及較大DPI下,視窗如何顯示的問題。

方案一 設定視窗最大值和最小值顯示

通過對比當前螢幕的可顯示區域,將視窗高寬最大值和最小值,設定為視窗的實際高寬(此例中僅設定高度)

介面設定

  1. 設定視窗內容自適應SizeToContent="WidthAndHeight"
  2. 新增ViewBox -- 設定預設不拉伸Stretch="None",當DPI超大時如超過1920*1080p的175%(即win10預設不支援的比例顯示),開啟ViewBox縮放
  3. 頂層佈局容器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>

後臺設定 - 視窗大小自適應設定

  1. 新增對Loaded事件的監聽,並在之後登出。視窗只需要首次初始其高度即可。
  2. 獲取螢幕的高度和工作列的高度 -- 具體可以參考C# 獲取當前螢幕的寬高和位置
  3. 比較當前可顯示高度(螢幕高度-工作列高度)與視窗的最大/最小高度,然後設定當前視窗的實際高度。
  4. 如果可顯示高度比最小值還小,則開啟ViewBox內容縮放。ViewBox的高度為當前可顯示高度。
  5. 如果當前視窗有陰影,可設定陰影高度大小。保證視窗在可顯示區域內正常顯示。
 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. 預設設定為當前螢幕工作區域的顯示比例大小
  2. 如果超過視窗最大高度/寬高,則顯示為視窗最大高度/寬高
  3. 如果小於視窗最小高度/寬高,則顯示為當前可顯示區域的最大高度/寬高
  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下顯示: