C# 如何只允许在 WPF 窗口中统一调整大小?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/386484/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 01:29:13  来源:igfitidea点击:

How Can I Only Allow Uniform Resizing in a WPF Window?

c#.netwpfresizeaspect-ratio

提问by Mark Carpenter

I don't want my window to be resized either "only horizontally" or "only vertically." Is there a property I can set on my window that can enforce this, or is there a nifty code-behind trick I can use?

我不希望“仅水平”或“仅垂直”调整窗口大小。是否有我可以在我的窗口上设置的属性可以强制执行此操作,或者是否有我可以使用的漂亮的代码隐藏技巧?

回答by EndangeredMassa

You could try replicating an effect that I often see on Flash Video websites. They allow you to expand the browser window any way you like, but only stretch the presentation area so that it fits the smallest of the height or width.

您可以尝试复制我经常在 Flash Video 网站上看到的效果。它们允许您以您喜欢的任何方式扩展浏览器窗口,但只能拉伸演示区域,使其适合最小的高度或宽度。

For example, if you stretch the window vertically, your application would not resize. It would simple add black bars to the top and bottom of the display area and remain vertically centered.

例如,如果您垂直拉伸窗口,您的应用程序将不会调整大小。它会简单地在显示区域的顶部和底部添加黑条并保持垂直居中。

This may or may not be possible with WPF; I don't know.

WPF 可能会也可能不会;我不知道。

回答by Gant

You can reserve aspect ratio of contents using WPF's ViewBox with control with fixed width and height inside.

您可以使用 WPF 的 ViewBox 和内部具有固定宽度和高度的控件来保留内容的纵横比。

Let's give this a try. You can change "Stretch" attribute of ViewBox to experience different results.

让我们试一试。您可以更改 ViewBox 的“Stretch”属性以体验不同的结果。

Here is my screeen shot: enter image description here

这是我的屏幕截图: 在此处输入图片说明

<Window x:Class="TestWPF.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">

    <Viewbox Stretch="Uniform">
        <StackPanel Background="Azure" Height="400" Width="300" Name="stackPanel1" VerticalAlignment="Top">
            <Button Name="testBtn" Width="200" Height="50">
                <TextBlock>Test</TextBlock>
            </Button>
        </StackPanel>
    </Viewbox>

</Window>

回答by Daniel Paull

I had expected that you could two-way bind the width to the height using a value converter to maintain aspect ratio. Passing the aspect ratio as the converter parameter would make it more general purpose.

我曾期望您可以使用值转换器将宽度双向绑定到高度以保持纵横比。将纵横比作为转换器参数传递将使其更通用。

So, I tried this - the binding with no converter first:

所以,我尝试了这个 - 首先没有转换器的绑定:

<Window 
    ...
    Title="Window1" Name="Win" Height="500" 
    Width="{Binding RelativeSource={RelativeSource self}, 
                    Path=Height, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <StackPanel>
        <TextBlock>Width:</TextBlock>
        <TextBlock Text="{Binding ElementName=Win, Path=Width}" />
        <TextBlock>Height:</TextBlock>
        <TextBlock Text="{Binding ElementName=Win, Path=Height}" />
    </StackPanel>    
</Window>

Strangely, the binding is behaving as if it is one-way and the reported width of the window (as shown in the TextBlock) is not consistent with it's size on screen!

奇怪的是,绑定的行为好像是单向的,并且报告的窗口宽度(如 TextBlock 中所示)与其在屏幕上的大小不一致!

The idea might be worth pursuing, but this strange behavior would need to be sorted out first.

这个想法可能值得追求,但首先需要解决这种奇怪的行为。

Hope that helps!

希望有帮助!

回答by Ben Doerr

This is what my solution was.

这就是我的解决方案。

You will need to add this to your control/window tag:

您需要将其添加到您的控件/窗口标签中:

Loaded="Window_Loaded"

And you will need to place this in your code behind:

你需要把它放在你的代码后面:

private double aspectRatio = 0.0;

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    aspectRatio = this.ActualWidth / this.ActualHeight;
}

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
    if (sizeInfo.WidthChanged)
    {
        this.Width = sizeInfo.NewSize.Height * aspectRatio;
    }
    else
    {
        this.Height = sizeInfo.NewSize.Width * aspectRatio;
    }
}

I tried the Viewbox trick and I did not like it. I wanted to lock the window border to a specific size. This was tested on a window control but I assume it would work on a border as well.

我尝试了 Viewbox 技巧,但我不喜欢它。我想将窗口边框锁定为特定大小。这是在窗口控件上测试的,但我认为它也适用于边框。

回答by Nir

You can always handle the WM_WINDOWPOSCHANGING message, this let's you control the window size and position during the resizing process (as opposed to fixing things after the user finished resizing).

您始终可以处理 WM_WINDOWPOSCHANGING 消息,这让您可以在调整大小的过程中控制窗口大小和位置(而不是在用户完成调整大小后进行修复)。

Here is how you do it in WPF, I combined this code from several sources, so there could be some syntax errors in it.

以下是您在 WPF 中的操作方法,我结合了来自多个来源的这段代码,因此其中可能存在一些语法错误。

internal enum WM
{
   WINDOWPOSCHANGING = 0x0046,
}

[StructLayout(LayoutKind.Sequential)]
internal struct WINDOWPOS
{
   public IntPtr hwnd;
   public IntPtr hwndInsertAfter;
   public int x;
   public int y;
   public int cx;
   public int cy;
   public int flags;
}

private void Window_SourceInitialized(object sender, EventArgs ea)
{
   HwndSource hwndSource = (HwndSource)HwndSource.FromVisual((Window)sender);
   hwndSource.AddHook(DragHook);
}

private static IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handeled)
{
   switch ((WM)msg)
   {
      case WM.WINDOWPOSCHANGING:
      {
          WINDOWPOS pos = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
          if ((pos.flags & (int)SWP.NOMOVE) != 0)
          {
              return IntPtr.Zero;
          }

          Window wnd = (Window)HwndSource.FromHwnd(hwnd).RootVisual;
          if (wnd == null)
          {
             return IntPtr.Zero;
          }

          bool changedPos = false;

          // ***********************
          // Here you check the values inside the pos structure
          // if you want to override tehm just change the pos
          // structure and set changedPos to true
          // ***********************

          if (!changedPos)
          {
             return IntPtr.Zero;
          }

          Marshal.StructureToPtr(pos, lParam, true);
          handeled = true;
       }
       break;
   }

   return IntPtr.Zero;
}

回答by Brian

This may be bit late but you can simply put it in your code behind....

这可能有点晚了,但你可以简单地把它放在你的代码后面......

Private Sub UserControl1_SizeChanged(ByVal sender As Object, ByVal e As System.Windows.SizeChangedEventArgs) Handles Me.SizeChanged
    If e.HeightChanged Then
        Me.Width = Me.Height
    Else
        Me.Height = Me.Width
    End If
End Sub

回答by Code-an the Barbarian

In the code sample:

在代码示例中:

if (sizeInfo.WidthChanged)     
{         
    this.Width = sizeInfo.NewSize.Height * aspectRatio;    
}     
else     
{         
    this.Height = sizeInfo.NewSize.Width * aspectRatio; 
} 

I believe the second computation should be:

我相信第二次计算应该是:

this.Height = sizeInfo.NewSize.Width * (1/aspectRatio);  

I made a variation of this work in a "SizeChanged" event handler. Since I wanted the width to be the controlling dimension, I simply forced the height to match to it with a computation of the form:

我在“SizeChanged”事件处理程序中做了这项工作的变体。由于我希望宽度作为控制尺寸,我只是通过计算形式强制高度与其匹配:

if (aspectRatio > 0)
// enforce aspect ratio by restricting height to stay in sync with width.  
this.Height = this.ActualWidth * (1 / aspectRatio);

You may note the check for an aspectRatio > 0, ... I did this because I found that it was tending to call my handlers that did the resizing before the "Load" method had even assigned the aspectRatio.

您可能会注意到对 aspectRatio > 0 的检查,...我这样做是因为我发现它倾向于调用我的处理程序,这些处理程序在“Load”方法甚至分配了 aspectRatio 之前进行了调整。

回答by Nguyen Kien

Maybe too late, but i found a solution from Mike O'Brien blog, and it work really good. http://www.mikeobrien.net/blog/maintaining-aspect-ratio-when-resizing/Below is code from his blog:

也许为时已晚,但我从 Mike O'Brien 博客中找到了一个解决方案,而且效果非常好。 http://www.mikeobrien.net/blog/maintaining-aspect-ratio-when-resizing/下面是他博客中的代码:

<Window ... SourceInitialized="Window_SourceInitialized" ... >
    ...
Window>

public partial class Main : Window
{
    private void Window_SourceInitialized(object sender, EventArgs ea)
    {
        WindowAspectRatio.Register((Window)sender);
    }
    ...
}


internal class WindowAspectRatio
{
    private double _ratio;

    private WindowAspectRatio(Window window)
    {
        _ratio = window.Width / window.Height;
        ((HwndSource)HwndSource.FromVisual(window)).AddHook(DragHook);
    }

    public static void Register(Window window)
    {
        new WindowAspectRatio(window);
    }

    internal enum WM
    {
        WINDOWPOSCHANGING = 0x0046,
    }

    [Flags()]
    public enum SWP
    {
        NoMove = 0x2,
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct WINDOWPOS
    {
        public IntPtr hwnd;
        public IntPtr hwndInsertAfter;
        public int x;
        public int y;
        public int cx;
        public int cy;
        public int flags;
    }

    private IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handeled)
    {
        if ((WM)msg == WM.WINDOWPOSCHANGING)
        {
            WINDOWPOS position = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));

            if ((position.flags & (int)SWP.NoMove) != 0 || 
                HwndSource.FromHwnd(hwnd).RootVisual == null) return IntPtr.Zero;

            position.cx = (int)(position.cy * _ratio);

            Marshal.StructureToPtr(position, lParam, true);
            handeled = true;
        }

        return IntPtr.Zero;
    }
}