19

我想设置我的 WPF 窗口的初始客户端大小。我没有看到一个直接的方法来做到这一点。

具体来说,当我的窗口打开时,我希望它的大小刚好足以容纳其内容而无需滚动条。但是在显示之后,我希望窗口可以自由调整大小(更大或更小)。

如果我在 Window 元素上设置 Width 和 Height 属性,则会设置非客户端(外部)大小,这没有用。一旦标题栏和调整边框进入该空间,客户区将不再足以容纳其内容,并且我将拥有滚动条。我可以通过选择更大的尺寸来补偿,但标题栏高度和边框厚度都是用户可自定义的(以及默认值因操作系统版本而异),并且在不同的机器上不一定相同。

我可以在窗口的内容元素(<Grid>本例中为 a)上设置 Width 和 Height,然后将 Window 的 SizeToContent 属性设置为 WidthAndHeight。这让窗口的初始大小正好在我想要的地方。但是事情不再调整大小了——我可以调整窗口的大小,但它的内容不会随之调整大小,因为我指定了一个固定的大小。

有没有办法设置窗口的初始客户端大小,最好没有代码隐藏?(如果这是唯一的方法,我会采用代码隐藏,但如果有人有的话,我更喜欢仅 XAML 的方法。)

4

7 回答 7

22

您可以通过以下两种方式之一在 Load 事件处理程序的代码隐藏中执行此操作:

注意:两个示例中 LayoutRoot Grid 的内容相同,但 LayoutRoot 上的 Width 和 Height 仅在示例 A 中指定。

A) 窗口的 SizeToContent 和内容的宽度和高度上的 ClearValue:

using System.Windows;

namespace WpfWindowBorderTest
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            ClearValue(SizeToContentProperty);
            LayoutRoot.ClearValue(WidthProperty);
            LayoutRoot.ClearValue(HeightProperty);
        }
    }
}

假设页面布局如下(注意 Window 上的 SizeToContent 设置和 Loaded 事件处理程序以及 LayoutRoot 上指定的 Width 和 Height):

<Window x:Class="WpfWindowBorderTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" SizeToContent="WidthAndHeight" Loaded="Window_Loaded">
    <Grid x:Name="LayoutRoot" Width="300" Height="300" Background="Green">
        <Grid.RowDefinitions>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="3*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="3*"/>
        </Grid.ColumnDefinitions>
        <Rectangle Grid.Row="0" Grid.Column="0" Fill="Black" />
        <Rectangle Grid.Row="0" Grid.Column="0" Width="75" Height="75" Fill="Red" />
        <Rectangle Grid.Row="1" Grid.Column="1" Fill="Yellow" />
        <Rectangle Grid.Row="1" Grid.Column="1" Width="225" Height="225" Fill="Red" />
    </Grid>
</Window>

或者

B)设置窗口的宽度和高度占系统特定的客户端窗口框架大小:

使用 System.Windows;

namespace WpfWindowBorderTest
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            const int snugContentWidth = 300;
            const int snugContentHeight = 300;

            var horizontalBorderHeight = SystemParameters.ResizeFrameHorizontalBorderHeight;
            var verticalBorderWidth = SystemParameters.ResizeFrameVerticalBorderWidth;
            var captionHeight = SystemParameters.CaptionHeight;

            Width = snugContentWidth + 2 * verticalBorderWidth;
            Height = snugContentHeight + captionHeight + 2 * horizontalBorderHeight;
        }
    }
}

假设页面布局成比例(注意没有 SizeToContent 设置或 Window 上的 Loaded 事件处理程序或 LayoutRoot 上指定的宽度和高度):

<Window x:Class="WpfWindowBorderTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1">
    <Grid x:Name="LayoutRoot" Background="Green">
        <Grid.RowDefinitions>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="3*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="3*"/>
        </Grid.ColumnDefinitions>
        <Rectangle Grid.Row="0" Grid.Column="0" Fill="Black" />
        <Rectangle Grid.Row="0" Grid.Column="0" Width="75" Height="75" Fill="Red" />
        <Rectangle Grid.Row="1" Grid.Column="1" Fill="Yellow" />
        <Rectangle Grid.Row="1" Grid.Column="1" Width="225" Height="225" Fill="Red" />
    </Grid>
</Window>

我还没有想出一种在 XAML 中以声明方式执行此操作的方法。

于 2009-07-15T23:04:51.207 回答
17

您可以在 XAML 中删除窗口的 Width 和 Height 属性,并添加 SizeToContent="WidthAndHeight"。这会将窗口的初始尺寸设置为其内容,但仍允许您调整其大小。

<Window x:Class="WpfApplication2.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" SizeToContent="WidthAndHeight">
    <Grid>
        <TextBlock Text="How to set WPF window’s startup ClientSize?"/>
    </Grid>
</Window>

启动时,它看起来像这样:

在此处输入图像描述

然而你仍然可以用鼠标拉伸它:

在此处输入图像描述

于 2009-07-04T05:44:56.107 回答
6

我也花了很多时间来弄清楚整个故事。在网上找到这个问题的纯 XAML(零代码)答案非常困难,所以这是我的。

当我们在 Visual Studio WPF 设计器中设计 Window 时,标准(默认情况下)方法是在 上定义WidthHeight属性Window,就像在 XAML 中这样:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="75" Width="190">
    <Grid>
        <Button Content="Right" HorizontalAlignment="Right" Margin="0,0,10,10" VerticalAlignment="Bottom" Width="75" />
        <Button Content="Left" HorizontalAlignment="Left" Margin="10,0,0,10" VerticalAlignment="Bottom" Width="75"/>
    </Grid>
</Window>

设计器预览如下所示:

在此处输入图像描述

一切看起来都很酷,但是当我们运行应用程序时,根据当前的 Windows 版本、主题和所有显示设置 jazz,运行时窗口有 99% 的可能性不会看起来像设计的窗口。这是它在我的 Windows 8.1 上的样子:

在此处输入图像描述

最初的解决方案是,就像在奥伦的回答中使用该SizeTocontent属性一样。它基本上告诉 WPF 从它的内容定义窗口大小(又名“客户端大小”),而不是窗口本身(又名“客户端大小 + 所有非客户端/chrome/边框完全无法控制的东西”)。

因此,我们可以将 Content 定义为固定大小,如下所示:

<Window x:Class="WpfApplication1.MainWindowSizeToContentCentered"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" SizeToContent="WidthAndHeight">
    <Grid Height="40" Width="180">
        <Button Content="Right" HorizontalAlignment="Right" Margin="0,0,10,10" VerticalAlignment="Bottom" Width="75" />
        <Button Content="Left" HorizontalAlignment="Left" Margin="10,0,0,10" VerticalAlignment="Bottom" Width="75"/>
    </Grid>
</Window>

现在,运行时窗口看起来正是我们想要的(请注意,网格的高度和宽度与原始窗口一 - 40/180 vs 75/190 - 的值并不完全相同,这很好,就像在设计模式下一样,你现在可以永远忘记窗口的高度和宽度属性):

在此处输入图像描述

调整窗口大小时它的行为如何?像这样,网格居中,如果您想要这种行为,这很好:

在此处输入图像描述

但是,如果我们想要问题中的行为(“我可以调整窗口大小,但它的内容不会随之调整大小,因为我指定了固定大小。”),这也是原始行为,而不是指定 Width和高度,我们可以使用MinWidthMinHeight,像这样:

<Window x:Class="WpfApplication1.MainWindowSizeToContentResizable"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" SizeToContent="WidthAndHeight">
    <Grid MinHeight="40" MinWidth="180">
        <Button Content="Right" HorizontalAlignment="Right" Margin="0,0,10,10" VerticalAlignment="Bottom" Width="75" />
        <Button Content="Left" HorizontalAlignment="Left" Margin="10,0,0,10" VerticalAlignment="Bottom" Width="75"/>
    </Grid>
</Window>

运行时窗口看起来相同,但调整大小的体验现在与原始默认窗口布局相当:

在此处输入图像描述

那应该是默认的 WPF 设计器布局恕我直言。

于 2014-06-13T08:31:16.263 回答
2

我在构造函数中执行以下操作并在 xaml 中添加 ResizeMode="CanResizeWithGrip" ,但这有点取决于您的内容在启动时占用了多少空间

public Window1()
{
    this.Height = SystemParameters.WorkArea.Height;
    this.Width = SystemParameters.WorkArea.Width;
}
于 2009-07-04T12:29:01.007 回答
1

Simon Mourier发布了一个很好的答案,但我需要一个控件的大小来服从其他控件。所以用属性反转窗口大小的行为SizeToContent并不是我需要的。我最终遵循[Tim's] [计算非客户端大小的方法]从 MainWindow 的动态宽度和高度中减去非客户端区域;在 XAML<MultiBinding>元素中。这是通过读取SystemParameters.WindowCaptionHeightSystemParameters.ResizeFramVerticalBorderWidth属性来完成的(参见IMultiValueConverter下面的代码)。

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition x:Name="_mwR0" Height="Auto"/>
    <RowDefinition x:Name="_mwR1" Height="4*"/>
    <RowDefinition x:Name="_mwR2" Height="Auto"/>
    <RowDefinition x:Name="_mwR3">
      <RowDefinition.Height>
        <MultiBinding Converter="{StaticResource SizeToRemainderConverter}" Mode="TwoWay">
          <Binding ElementName="_LogWindow" Path="Height" Mode="TwoWay"/>
          <Binding ElementName="_MainWindow" Path="Height" Mode="OneWay"/>
          <Binding ElementName="_mwR0" Path="Height" Mode="OneWay"/>
          <Binding ElementName="_mwR1" Path="Height" Mode="OneWay"/>
          <Binding ElementName="_mwR2" Path="Height" Mode="OneWay"/>
        </MultiBinding>
      </RowDefinition.Height>
    </RowDefinition>
  </Grid.RowDefinitions>
  <Menu IsMainMenu="True" Grid.Row="0">...</Menu>
  <ListView Grid.Row="1">...</ListView>
  <GridSplitter Grid.Row="2" ShowsPreview="True" HorizontalAlignment="Stretch" Height="6" VerticalAlignment="Center" Margin="0"/>
  <RichTextBox x:Name="_LogWindow" Grid.Row="3"/>
</Grid>

_LogWindow是网格最后一行的内部控件。随着 MainWindow 的大小调整,我需要缩小此控件以尊重其他控件。

System.Double由于必须同时处理和System.Windows.GridLength对象类型,转换器变得复杂。我还保留了应用程序会话之间的布局,因此我需要转换器是双向的(为密集代码道歉)。

public class SizeToRemainderConverter : IMultiValueConverter
{
  public object Convert(object[] values, Type targetType, object parm, System.Globalization.CultureInfo culture)
  {
    double ret = 0.0;
    if (values != null && values.Length > 0)
    {
      if (values[0].GetType().Name.Equals("String")) double.TryParse((values[0] as string), out ret);
      else if (values[0].GetType().Name.Equals("GridLength")) ret = ((GridLength)values[0]).Value;
      else ret = (double)System.Convert.ChangeType(values[0], typeof(double));
    }

    double available = 0.0;
    if (values != null && values.Length > 1)
    {
      if (values[1].GetType().Name.Equals("String")) double.TryParse((values[1] as string), out available);
      else if (values[1].GetType().Name.Equals("GridLength")) available = ((GridLength)values[1]).Value;
      else available = (double)System.Convert.ChangeType(values[1], typeof(double));

      available -= SystemParameters.WindowCaptionHeight;
      available -= SystemParameters.ResizeFrameVerticalBorderWidth;
      available -= SystemParameters.ResizeFrameVerticalBorderWidth;
    }

    for (int i = 2; i < (values?.Length ?? 0); ++i)
    {
      double delta = 0.0;

      if (values[i].GetType().Name.Equals("String")) double.TryParse((values[i] as string), out delta);
      else if (values[i].GetType().Name.Equals("GridLength")) delta = ((GridLength)values[i]).Value;
      else delta = (double)System.Convert.ChangeType(values[i], typeof(double));
      available -= delta;
    }

    if (available < ret) ret = 0.0;

    if (targetType.Name.Equals("GridLength")) return new GridLength(ret);
    return System.Convert.ChangeType(ret, targetType);
  }

  public object[] ConvertBack(object v, Type[] t, object p, System.Globalization.CultureInfo c)
  {
    object[] ret = new object[t.Length];
    switch (v.GetType().Name)
    {
      case "Double":
        for (int i = 0; i < t.Length; ++i)
        {
          if (t[i].Name.Equals("String")) ret[i] = v.ToString();
          else if (t[i].Name.Equals("GridLength")) ret[i] = new GridLength((v as double?) ?? 0.0);
          else ret[i] = System.Convert.ChangeType(v, t[i]);
        }
        break;

      case "GridLength":
        GridLength gl = (v as GridLength?) ?? new GridLength();
        for (int i = 0; i < t.Length; ++i)
        {
          if (t[i].Name.Equals("String")) ret[i] = gl.Value.ToString();
          else if (t[i].Name.Equals("GridLength")) ret[i] = gl;
          else ret[i] = System.Convert.ChangeType(gl.Value, t[i]);
        }
        break;

      case "String":
      default:
        double d = 0.0;
        double.TryParse(v as string, out d);
        for (int i = 0; i < t.Length; ++i)
        {
          if (t[i].Name.Equals("String")) ret[i] = v.ToString();
          else if (t[i].Name.Equals("GridLength")) ret[i] = new GridLength(d);
          else ret[i] = System.Convert.ChangeType(v, t[i]);
        }
        break;
    }

    return ret;
  }
}
于 2017-05-05T21:55:26.477 回答
1

我不知道,为什么你需要复杂的代码。这对我来说总是很好:

<Window x:Class="Loadsheet_2._0.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"
    xmlns:local="clr-namespace:Loadsheet_2._0"
    mc:Ignorable="d"
    Title="MainWindow" MinHeight="635" MinWidth="1200" SizeToContent="WidthAndHeight" ResizeMode="CanResize">
于 2020-05-15T20:09:39.973 回答
0

这是一个完全原生的方法,不需要任何 XAML,并且在显示窗口之前就可以工作。

public unsafe override void SetSize(int width, int height, WindowSizeType type)
        {
            if (type == WindowSizeType.WorkingArea)// aka client area
            {
                // get native HWND handle
                IntPtr handle = new WindowInteropHelper(window).EnsureHandle();

                // get window rect and size
                RECT rect = new RECT();
                int result = GetWindowRect(handle, ref rect);
                if (result == 0) throw new Exception("GetWindowRect failed");
                int rectWidth = rect.right - rect.left;
                int rectHeight = rect.bottom - rect.top;

                // get client rect and size
                RECT clientRect = new RECT();
                result = GetClientRect(handle, ref clientRect);
                if (result == 0) throw new Exception("GetClientRect failed");
                int clientRectWidth = clientRect.right - clientRect.left;
                int clientRectHeight = clientRect.bottom - clientRect.top;

                // increase size based on client side decoration delta
                width = width + (rectWidth - clientRectWidth);
                height = height + (rectHeight - clientRectHeight);

                // apply new adjusted window size
                result = SetWindowPos(handle, IntPtr.Zero, 0, 0, width, height, SWP_NOMOVE);
                if (result == 0) throw new Exception("SetWindowPos failed");
            }
            else
            {
                window.Width = width;
                window.Height = height;
            }
        }

        #region SetSize native Helpers
        [StructLayout(LayoutKind.Sequential)]
        struct RECT
        {
            public int left, top, right, bottom;
        }

        private const string lib = "User32.dll";

        [DllImport(lib, EntryPoint = "GetWindowRect")]
        private extern static int GetWindowRect(IntPtr hWnd, ref RECT lpRect);

        [DllImport(lib, EntryPoint = "GetClientRect")]
        private extern static int GetClientRect(IntPtr hWnd, ref RECT lpRect);

        [DllImport(lib, EntryPoint = "SetWindowPos")]
        private extern static int SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
        private const int SWP_NOMOVE = 0x0002;
        #endregion
于 2019-10-29T06:37:21.733 回答