我不希望我的窗口被 "仅水平" 或 "仅垂直" 调整大小。是否有属性可以在我的窗口上设置以强制执行此操作,或者是否有巧妙的代码后台技巧可以使用?
我不希望我的窗口被 "仅水平" 或 "仅垂直" 调整大小。是否有属性可以在我的窗口上设置以强制执行此操作,或者是否有巧妙的代码后台技巧可以使用?
你可以始终处理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;
}
使用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>
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技巧,但是并不喜欢它。我希望将窗口边框锁定在特定大小上。这在窗口控件上进行了测试,但我认为对边框也适用。
可能有点晚了,但您可以将它简单地放在您的代码后面...
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
if (sizeInfo.WidthChanged)
{
this.Width = sizeInfo.NewSize.Height * aspectRatio;
}
else
{
this.Height = sizeInfo.NewSize.Width * aspectRatio;
}
我认为第二个计算应该是:
this.Height = sizeInfo.NewSize.Width * (1/aspectRatio);
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 之前,它往往会调用我的调整大小处理程序。
我原本以为你可以使用值转换器将宽度与高度进行双向绑定,以保持纵横比。将纵横比作为转换器参数传递会使其更具通用性。
因此,我首先尝试了没有转换器的绑定:
<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所示)与屏幕上的大小不一致!
这个想法可能值得追求,但首先需要解决这种奇怪的行为。
希望这能帮到你!
我有一种方法,不依赖于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);
}
}
或许有点晚了,但我在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;
}
}