我有一个用户可调整大小的 WPF 窗口,我想限制调整大小,使窗口的纵横比保持不变。
理想情况下,我想在调整窗口大小时通过将角拖动到保持适当纵横比的位置来限制鼠标位置。如果使用鼠标调整边的大小,则其他尺寸应同时更改。
有没有一种简单的方法可以做到这一点或任何人都知道的好的在线示例?
如果没有更好的解决方案出现,我会在稍微改进后发布我所做的。
我有一个用户可调整大小的 WPF 窗口,我想限制调整大小,使窗口的纵横比保持不变。
理想情况下,我想在调整窗口大小时通过将角拖动到保持适当纵横比的位置来限制鼠标位置。如果使用鼠标调整边的大小,则其他尺寸应同时更改。
有没有一种简单的方法可以做到这一点或任何人都知道的好的在线示例?
如果没有更好的解决方案出现,我会在稍微改进后发布我所做的。
我在这里找到了 Nir 的一个很好的答案。还是有一些瑕疵,基本上右上角、右下角和底边都可以,其他边和角都没有。好的一面是,纵横比一直保持平稳。
编辑:我找到了解决大部分问题的方法。开始调整大小时,将通过定位鼠标相对于窗口的位置来确定将人为调整以保持纵横比的尺寸。我发现的唯一剩下的缺陷是从角落调整大小时窗口的位置可能会改变(右下角除外)。
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>
后面的代码:
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;
}
}
}
尽管这并不强制窗口具有特定的比例(正如 OP 所要求的那样),但我已经设法通过将内容包装在 a 中Viewbox
并设置来缩放窗口的内容,同时保持原始纵横比拉伸属性为Stretch="Uniform"
。不需要代码隐藏。
WPF:
<Viewbox Name="MainViewbox" Stretch="Uniform">
... your content here
</Viewbox>
上面给出的答案有利于宽度变化而不是高度变化,因此如果您调整高度很多,但是由于鼠标定位,宽度也会发生一些变化,用户仍然会看到几乎相同的窗口。我有这段代码可以处理每个维度的百分比变化,有利于最大的变化作为用户最感兴趣的变化。
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);
}
这样做的伎俩:
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) {
if (sizeInfo.WidthChanged) this.Width = sizeInfo.NewSize.Height * aspect;
else this.Height = sizeInfo.NewSize.Width / aspect;
}
在这里找到它。
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
@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);
}
}
}
}