[ACCEPTED]-How Can I Only Allow Uniform Resizing in a WPF Window?-aspect-ratio

Accepted answer
Score: 13

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: enter image description here

<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>
Score: 13

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;
}
Score: 8

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.

Score: 1

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.

Score: 1

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!

Score: 1

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
Score: 1

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.

Score: 0

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;
    }
}
Score: 0

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