Подтвердить что ты не робот

Измените размер окна WPF, но сохраните пропорции?

У меня есть изменяемое пользователем окно WPF, которое я хочу ограничить изменение размера, поэтому соотношение сторон окна остается постоянным.

В идеале я хотел бы ограничить местоположение мыши, когда размер окна изменяется, перетаскивая угол в позиции, поддерживающие правильный аспектный рацион. Если ребро изменено с помощью мыши, другое измерение должно измениться в одно и то же время.

Есть ли простой способ сделать это или хороший онлайн-пример, о котором все знают?

Если лучшие решения не появятся, я опубликую то, что я сделал после того, как немного уточнил.

4b9b3361

Ответ 1

Я нашел хороший ответ от Nir здесь. Есть еще некоторые недостатки, в основном изменение размеров в верхнем правом углу, нижний правый угол и нижняя сторона будут в порядке, другие стороны и углы - нет. Светлая сторона, соотношение сторон плавно сохраняется все время.

EDIT: Я нашел способ удалить большинство проблем. Когда начинается калибровка, размер, который будет искусственно изменен для сохранения пропорции, определяется путем размещения положения мыши относительно окна. Единственные оставшиеся дефекты, которые я обнаружил, это то, что положение окна может измениться при изменении размера из углов (кроме нижнего правого).

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

Ответ 2

Это делает трюк:

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) {

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

Нашел здесь.

Ответ 3

Ответ, приведенный выше, способствует изменению ширины по изменению высоты, поэтому, если вы много настраиваете высоту, но из-за расположения мышью ширина также немного меняется, пользователь все равно будет видеть почти то же окно. У меня есть этот код, который работает с процентными изменениями в каждом измерении в пользу наибольшего изменения, как тот, который больше всего интересует пользователь.

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

Ответ 4

Хотя это не приводит к тому, что окно имеет конкретное отношение (как задал ОП), мне удалось получить СОДЕРЖАНИЕ окна для масштабирования, сохраняя при этом исходное соотношение сторон, путем обертывания содержимого в Viewbox и установив простейшую растяжку как Stretch="Uniform". Никакой код не нужен.

WPF:

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

Ответ 5

В окне - вы можете просто слушать сообщение 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