我不希望我的窗口“仅水平”或“仅垂直”调整大小。我可以在我的窗口上设置一个可以强制执行此操作的属性,还是我可以使用一个漂亮的代码隐藏技巧?
9 回答
您可以使用 WPF 的 ViewBox 保留内容的纵横比,其中包含固定宽度和高度的控件。
让我们试一试。您可以更改 ViewBox 的“Stretch”属性来体验不同的效果。
这是我的屏幕截图:
<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>
您始终可以处理 WM_WINDOWPOSCHANGING 消息,这让您可以在调整大小的过程中控制窗口大小和位置(而不是在用户完成调整大小后进行修复)。
这是您在 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;
}
这就是我的解决方案。
您需要将其添加到您的控件/窗口标签:
Loaded="Window_Loaded"
你需要把它放在你的代码后面:
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;
}
}
我尝试了 Viewbox 技巧,但我不喜欢它。我想将窗口边框锁定为特定大小。这是在窗口控件上测试的,但我认为它也可以在边框上工作。
在代码示例中:
if (sizeInfo.WidthChanged)
{
this.Width = sizeInfo.NewSize.Height * aspectRatio;
}
else
{
this.Height = sizeInfo.NewSize.Width * aspectRatio;
}
我相信第二个计算应该是:
this.Height = sizeInfo.NewSize.Width * (1/aspectRatio);
我在“SizeChanged”事件处理程序中对这项工作进行了修改。因为我希望宽度成为控制维度,所以我只是通过计算表单来强制高度与之匹配:
if (aspectRatio > 0)
// enforce aspect ratio by restricting height to stay in sync with width.
this.Height = this.ActualWidth * (1 / aspectRatio);
您可能会注意到检查 aspectRatio > 0,... 我这样做是因为我发现它倾向于在“Load”方法甚至分配 aspectRatio 之前调用我的处理程序来调整大小。
您可以尝试复制我经常在 Flash Video 网站上看到的效果。它们允许您以任何您喜欢的方式扩展浏览器窗口,但仅拉伸演示区域以使其适合最小的高度或宽度。
例如,如果您垂直拉伸窗口,您的应用程序将不会调整大小。它会简单地在显示区域的顶部和底部添加黑条并保持垂直居中。
WPF 可能会也可能不会;我不知道。
这可能有点晚了,但你可以简单地将它放在你的代码后面......
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
我曾期望您可以使用值转换器将宽度与高度双向绑定以保持纵横比。将纵横比作为转换器参数传递将使其更通用。
所以,我尝试了这个 - 首先没有转换器的绑定:
<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>
奇怪的是,绑定的行为好像是单向的,并且报告的窗口宽度(如 TextBlock 中所示)与它在屏幕上的大小不一致!
这个想法可能值得追求,但这种奇怪的行为需要首先解决。
希望有帮助!
也许为时已晚,但我从 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;
}
}
我得到了一种不依赖于 Windows 平台特定 API 的方法,也具有可接受的用户体验(拖动窗口时不会晃动)。它使用计时器在 0.1 秒后调整窗口大小,因此用户不会看到它抖动。
public partial class MainWindow : Window
{
private DispatcherTimer resizeTimer;
private double _aspectRatio;
private SizeChangedInfo? _sizeInfo;
public MainWindow()
{
InitializeComponent();
_aspectRatio = Width / Height;
resizeTimer = new DispatcherTimer();
resizeTimer.Interval = new TimeSpan(100*10000); // 0.1 seconds
resizeTimer.Tick += ResizeTimer_Tick;
}
private void ResizeTimer_Tick(object? sender, EventArgs e)
{
resizeTimer.Stop();
if (_sizeInfo == null) return;
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;
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
_sizeInfo = sizeInfo;
resizeTimer.Stop();
resizeTimer.Start();
base.OnRenderSizeChanged(sizeInfo);
}
}