[ACCEPTED]-How Can I Only Allow Uniform Resizing in a WPF Window?-aspect-ratio
You can reserve aspect ratio of contents 5 using WPF's ViewBox with control with fixed 4 width and height inside.
Let's give this 3 a try. You can change "Stretch" attribute 2 of ViewBox to experience different results.
Here 1 is my screeen shot:
<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>
You can always handle the WM_WINDOWPOSCHANGING 7 message, this let's you control the window 6 size and position during the resizing process 5 (as opposed to fixing things after the user 4 finished resizing).
Here is how you do it 3 in WPF, I combined this code from several 2 sources, so there could be some syntax errors 1 in it.
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;
}
This is what my solution was.
You will need 7 to add this to your control/window tag:
Loaded="Window_Loaded"
And 6 you will need to place this in your code 5 behind:
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;
}
}
I tried the Viewbox trick and I did 4 not like it. I wanted to lock the window 3 border to a specific size. This was tested 2 on a window control but I assume it would 1 work on a border as well.
You could try replicating an effect that 11 I often see on Flash Video websites. They 10 allow you to expand the browser window any 9 way you like, but only stretch the presentation 8 area so that it fits the smallest of the 7 height or width.
For example, if you stretch 6 the window vertically, your application 5 would not resize. It would simple add black 4 bars to the top and bottom of the display 3 area and remain vertically centered.
This 2 may or may not be possible with WPF; I don't 1 know.
I had expected that you could two-way bind 12 the width to the height using a value converter 11 to maintain aspect ratio. Passing the aspect 10 ratio as the converter parameter would make 9 it more general purpose.
So, I tried this 8 - the binding with no converter first:
<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>
Strangely, the 7 binding is behaving as if it is one-way 6 and the reported width of the window (as 5 shown in the TextBlock) is not consistent 4 with it's size on screen!
The idea might 3 be worth pursuing, but this strange behavior 2 would need to be sorted out first.
Hope that 1 helps!
This may be bit late but you can simply 1 put it in your code behind....
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
In the code sample:
if (sizeInfo.WidthChanged)
{
this.Width = sizeInfo.NewSize.Height * aspectRatio;
}
else
{
this.Height = sizeInfo.NewSize.Width * aspectRatio;
}
I believe the second 10 computation should be:
this.Height = sizeInfo.NewSize.Width * (1/aspectRatio);
I made a variation 9 of this work in a "SizeChanged" event handler. Since 8 I wanted the width to be the controlling 7 dimension, I simply forced the height to 6 match to it with a computation of the form:
if (aspectRatio > 0)
// enforce aspect ratio by restricting height to stay in sync with width.
this.Height = this.ActualWidth * (1 / aspectRatio);
You 5 may note the check for an aspectRatio > 0, ... I 4 did this because I found that it was tending 3 to call my handlers that did the resizing 2 before the "Load" method had even assigned 1 the aspectRatio.
Maybe too late, but i found a solution from 2 Mike O'Brien blog, and it work really good. http://www.mikeobrien.net/blog/maintaining-aspect-ratio-when-resizing/ Below 1 is code from his blog:
<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;
}
}
I got a way that doesn't depend on Windows 5 platform-specific API also with acceptable 4 user experience (not shake while dragging 3 the window). It uses a timer to adjust the 2 window size after 0.1 seconds so user won't 1 see it shakes.
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);
}
}
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.