Я не хочу, чтобы размер моего окна менялся «только по горизонтали» или «только по вертикали». Есть ли свойство, которое я могу установить в своем окне, которое может обеспечить это, или есть изящный трюк с выделенным кодом, который я могу использовать?
Как я могу разрешить только равномерное изменение размера в окне WPF?
Ответы (8)
Вы можете зарезервировать соотношение сторон содержимого с помощью WPF ViewBox с элементом управления с фиксированной шириной и высотой внутри.
Давай попробуем. Вы можете изменить атрибут «Растянуть» ViewBox, чтобы получить другие результаты.
Вот мой снимок экрана:
<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;
}
Вот каким было мое решение.
Вам нужно будет добавить это в свой тег control / 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, и он мне не понравился. Я хотел зафиксировать границу окна определенного размера. Это было протестировано на элементе управления окном, но я предполагаю, что это будет работать и на границе.
Вы можете попробовать воспроизвести эффект, который я часто вижу на сайтах 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) не соответствует его размеру на экране!
Возможно, эту идею стоит продолжить, но сначала нужно разобраться с этим странным поведением.
Надеюсь, это поможет!
В примере кода:
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);
Вы можете отметить проверку на аспект
Может быть, уже слишком поздно, но я нашел решение в блоге Майка О'Брайена, и оно действительно хорошо работает. http://www.mikeobrien.net/blog/mainpting-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;
}
}