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

时间:2010-03-18 17:08:51

标签: wpf windows resize aspect-ratio

我有一个用户可调整大小的WPF窗口,我想约束调整大小,因此窗口的宽高比保持不变。

理想情况下,我想在调整窗口大小时通过将角落拖动到保持正确宽高比的位置来约束鼠标位置。如果使用鼠标调整边缘大小,则另一个维度应同时更改。

有没有一种简单的方法可以做到这一点或一个人人都知道的好的在线示例?

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

6 个答案:

答案 0 :(得分:19)

Nir ​​here找到了一个很好的答案。还有一些瑕疵,基本上在右上角调整大小,右下角和底边都会很好,其他的边角都没有。好的一面是,纵横比一直保持平稳。

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

XAML:

<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>

代码背后:

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

答案 1 :(得分:2)

这样做是否有效:

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) {

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

找到它here

答案 2 :(得分:2)

上面给出的答案有利于高度变化的宽度变化,所以如果你调整高度很多但是,由于鼠标定位,宽度也会稍微改变,用户仍会看到几乎相同的窗口。我有这个代码可以解决每个维度中的百分比变化,有利于用户最感兴趣的最大变化。

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

答案 3 :(得分:2)

虽然这并没有强制Window具有特定的比例(如OP所要求的那样),但我设法通过将内容包装在一个窗口中来扩展窗口的内容,同时保持原始的宽高比。 Viewbox并将拉伸比例设为Stretch="Uniform"。不需要代码隐藏。

WPF:

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

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

@Mike Fuchs 的回答并不完美。 如果您从左上角和左下角调整窗口大小,窗口会在调整大小时移动。

我找到了没有这些问题的更优雅的方法。

厦门:

<Window x:Class="WindowTop.UI.ResizeExample"
        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:WindowTop.UI"
        mc:Ignorable="d"
        Title="ResizeExample" Height="450" Width="800">
    <Grid>
        
    </Grid>
</Window>

C#

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

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


        IntPtr hWnd = IntPtr.Zero;
        double xRatio = 1;
        double yRatio = 1;
        int sizingEdge = 0;


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

        IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handeled)
        {
            const int WM_SIZE = 0x0005;
            const int WM_SIZING = 0x0214;
            const int WM_WINDOWPOSCHANGING = 0x0046;


            // https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-sizing
            const int WMSZ_BOTTOM = 6;
            const int WMSZ_BOTTOMLEFT = 7;
            const int WMSZ_BOTTOMRIGHT = 8;
            const int WMSZ_LEFT = 1;
            const int WMSZ_RIGHT = 2;
            const int WMSZ_TOP = 3;
            const int WMSZ_TOPLEFT = 4;
            const int WMSZ_TOPRIGHT = 5;

            switch (msg)
            {
                case WM_SIZING:
                    sizingEdge = wParam.ToInt32();
                    break;

                case WM_WINDOWPOSCHANGING:


                    var position =
                        (WINDOWPOS) Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));

                    if (position.cx == this.Width && position.cy == this.Height)
                        return IntPtr.Zero;

                    switch (sizingEdge)
                    {
                        case WMSZ_TOP: // Top edge
                        case WMSZ_BOTTOM: // Bottom edge
                        case WMSZ_TOPRIGHT: // Top-right corner
                            position.cx = (int) (position.cy * xRatio);
                            break;

                        case WMSZ_LEFT: // Left edge
                        case WMSZ_RIGHT: // Right edge
                        case WMSZ_BOTTOMRIGHT: // Bottom-right corner
                        case WMSZ_BOTTOMLEFT: // Bottom-left corner
                            position.cy = (int) (position.cx * yRatio);
                            break;


                        case WMSZ_TOPLEFT: // Top-left corner
                            position.cx = (int) (position.cy * xRatio);
                            position.x = (int) Left - (position.cx - (int) Width);
                            break;
                    }

                    Marshal.StructureToPtr(position, lParam, true);
                    break;
            }


            return IntPtr.Zero;
        }


        public void Show()
        {

            xRatio = Width / Height;
            yRatio = Height / Width;

            base.Show();

            if (hWnd == IntPtr.Zero)
            {
                var interopHelper = new WindowInteropHelper(this);

                hWnd = interopHelper.Handle;

                var source = HwndSource.FromHwnd(hWnd);
                source?.AddHook(DragHook);
            }
        }

    }
}