调整 WPF 窗口的大小,但保持比例?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2471867/
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-09-08 21:35:09  来源:igfitidea点击:

Resize a WPF window, but maintain proportions?

wpfwindowsresizeaspect-ratio

提问by Chasler

I have a user resizable WPF Window that I want to constrain the resizing so the aspect ratio of the window stays constant.

我有一个用户可调整大小的 WPF 窗口,我想限制调整大小,以便窗口的纵横比保持不变。

Ideally I would like to constrain mouse location when the window is being resized by dragging a corner to positions that maintain the proper aspect ration. If an edge is resized with the mouse, the other dimension should change at the same time.

理想情况下,我想在调整窗口大小时通过将角拖动到保持适当纵横比的位置来限制鼠标位置。如果使用鼠标调整边缘的大小,则另一个维度应同时更改。

Is there a simple way to do this or a good on-line example that anyone knows of?

有没有一种简单的方法可以做到这一点,或者有谁知道的一个很好的在线例子?

If no better solutions come up, I'll post what I've done after I've refined it a bit.

如果没有更好的解决方案,我会在我稍微改进后发布我所做的。

回答by Mike Fuchs

I've found a good answer by Nir here. There are still some flaws, basically resizing in top right corner, bottom right corner and bottom side will be fine, other sides and corners are not. The bright side is, the aspect ratio is smoothly kept all the time.

我已经发现了尼尔一个很好的答案在这里。还是有一些瑕疵,基本上是在右上角、右下角和底边调整大小就可以了,其他边和角就不行了。好的一面是,纵横比一直保持平稳。

EDIT: I found a way to remove most of the problems. When sizing starts, the dimension that will be artificially adjusted to keep the aspect ratio is determined by locating the mouse position relative to the window. The only remaining imperfections I found are that the position of the window may change when resizing from the corners (except bottom right).

编辑:我找到了一种方法来消除大部分问题。调整大小开始时,将通过定位鼠标相对于窗口的位置来确定将被人为调整以保持纵横比的尺寸。我发现的唯一剩余缺陷是从角落调整大小时窗口的位置可能会改变(右下角除外)。

xaml:

xml:

<Window x:Class="WpfApplication1.ConstantAspectRatioWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ConstantAspectRatioWindow" MinHeight="100" MinWidth="150" SizeToContent="WidthAndHeight">
    <Grid>
        <Border Width="300" Height="200" Background="Navy"/>
        <Border Width="150" Height="100" Background="Yellow" />
    </Grid>
</Window>

Code behind:

后面的代码:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;

namespace WpfApplication1
{
    public partial class ConstantAspectRatioWindow : Window
    {
        private double _aspectRatio;
        private bool? _adjustingHeight = null;

        internal enum SWP
        {
            NOMOVE = 0x0002
        }
        internal enum WM
        {
            WINDOWPOSCHANGING = 0x0046,
            EXITSIZEMOVE = 0x0232,
        }

        public ConstantAspectRatioWindow()
        {
            InitializeComponent();
            this.SourceInitialized += Window_SourceInitialized;
        }

        [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;
        }

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool GetCursorPos(ref Win32Point pt);

        [StructLayout(LayoutKind.Sequential)]
        internal struct Win32Point
        {
            public Int32 X;
            public Int32 Y;
        };

        public static Point GetMousePosition() // mouse position relative to screen
        {
            Win32Point w32Mouse = new Win32Point();
            GetCursorPos(ref w32Mouse);
            return new Point(w32Mouse.X, w32Mouse.Y);
        }


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

            _aspectRatio = this.Width / this.Height;
        }

        private IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            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;

                        // determine what dimension is changed by detecting the mouse position relative to the 
                        // window bounds. if gripped in the corner, either will work.
                        if (!_adjustingHeight.HasValue)
                        {
                            Point p = GetMousePosition();

                            double diffWidth = Math.Min(Math.Abs(p.X - pos.x), Math.Abs(p.X - pos.x - pos.cx));
                            double diffHeight = Math.Min(Math.Abs(p.Y - pos.y), Math.Abs(p.Y - pos.y - pos.cy));

                            _adjustingHeight = diffHeight > diffWidth;
                        }

                        if (_adjustingHeight.Value)
                            pos.cy = (int)(pos.cx / _aspectRatio); // adjusting height to width change
                        else
                            pos.cx = (int)(pos.cy * _aspectRatio); // adjusting width to heigth change

                        Marshal.StructureToPtr(pos, lParam, true);
                        handled = true;
                    }
                    break;
                case WM.EXITSIZEMOVE:
                    _adjustingHeight = null; // reset adjustment dimension and detect again next time window is resized
                    break;
            }

            return IntPtr.Zero;
        }
    }
}

回答by 742

Does that do the trick:

这样做是否可行:

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) {

    if (sizeInfo.WidthChanged) this.Width = sizeInfo.NewSize.Height * aspect;
    else this.Height = sizeInfo.NewSize.Width / aspect;
}

Found it here.

在这里找到

回答by Mark

The answer given above favors width change over height change so if you adjust the height a lot but, because of mouse positioning, the width also changes a bit, the user will still see pretty much the same window. I have this code that works off percentage changes in each dimension favoring the largest change as the one the user is most interested in.

上面给出的答案有利于宽度变化而不是高度变化,所以如果你调整高度很多,但是由于鼠标定位,宽度也会改变一点,用户仍然会看到几乎相同的窗口。我有这段代码可以处理每个维度的百分比变化,有利于最大的变化作为用户最感兴趣的那个。

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
    {
        var percentWidthChange = Math.Abs(sizeInfo.NewSize.Width - sizeInfo.PreviousSize.Width) / sizeInfo.PreviousSize.Width;
        var percentHeightChange = Math.Abs(sizeInfo.NewSize.Height - sizeInfo.PreviousSize.Height) / sizeInfo.PreviousSize.Height;

        if (percentWidthChange > percentHeightChange)
            this.Height = sizeInfo.NewSize.Width / _aspectRatio;
        else
            this.Width = sizeInfo.NewSize.Height * _aspectRatio;

        base.OnRenderSizeChanged(sizeInfo);
    }

回答by gbmhunter

Although this doesn't force the Window to be of a specific ratio (as the OP asked), I have managed to get the CONTENT of a window to scale, while maintaining the original aspect ratio, by wrapping the contents in a Viewboxand setting the stretch propety as Stretch="Uniform". No code-behind is needed.

虽然这不会强制窗口具有特定的比例(如 OP 所要求的那样),但我已经设法通过将内容包装在 a 中Viewbox并设置拉伸属性为Stretch="Uniform". 不需要代码隐藏。

WPF:

WPF:

<Viewbox Name="MainViewbox" Stretch="Uniform">
    ... your content here
</Viewbox>

回答by yossharel

On Window - you can listen to message of Win32 API simply:

在 Window 上 - 您可以简单地收听 Win32 API 的消息:

 private double ratio = 1.33; // retio of 3:4

        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);
            HwndSource source = HwndSource.FromVisual(this) as HwndSource;
            if (source != null)
            {
                source.AddHook(new HwndSourceHook(WinProc));
            }
        }

        public const Int32 WM_EXITSIZEMOVE = 0x0232;
        private IntPtr WinProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, ref Boolean handled)
        {
            IntPtr result = IntPtr.Zero;
            switch (msg)
            {
                case WM_EXITSIZEMOVE:
                    {
                        if (Width < Height)
                        {
                            Width = Height * ratio;
                        }
                        else
                        {
                            Height = Width / ratio;
                        }
                    }
                    break;
            }

            return result;
        }

On this code you always take the shorter side and set it to be equal to the longer. You can always take the opposite approach and set the longer to be equal to the shorter. I found the solution here: http://social.msdn.microsoft.com/forums/en-US/wpf/thread/b0df3d1f-e211-4f54-a079-09af0096410e

在此代码中,您总是取较短的一侧并将其设置为等于较长的一侧。您始终可以采用相反的方法,将较长的设置为与较短的相等。我在这里找到了解决方案:http: //social.msdn.microsoft.com/forums/en-US/wpf/thread/b0df3d1f-e211-4f54-a079-09af0096410e