How Can I Only Allow Uniform Resizing in a WPF Window?
Asked Answered
Y

9

18

I don't want my window to be resized either "only horizontally" or "only vertically." Is there a property I can set on my window that can enforce this, or is there a nifty code-behind trick I can use?

Yezd answered 22/12, 2008 at 15:17 Comment(1)
I have answered a similar question, how to keep the aspect ratio of a WPF window when resizing. See my post here, it is based on Nir's answer.Kilauea
C
13

You can reserve aspect ratio of contents using WPF's ViewBox with control with fixed width and height inside.

Let's give this a try. You can change "Stretch" attribute of ViewBox to experience different results.

Here 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>
Chair answered 22/12, 2008 at 19:32 Comment(6)
"Let's give it a try" - a perfect description of WPF - will it work?, no one knows! Let's try. I'll stick to Win32 where I just tell the operating system: here's my size, deal with it.Judson
@Frank Krueger: it's not that simple in Win32, the only difference is that you already know the quirks and the expected behavior. Once you know those in WPF it's just as easy to do anything you want.Mattins
Oh no no, Frank. I actually test the code above and it works perfectly well. Maybe it's a miscommunication :) Forgive my bad English.Chair
I think I will include some screenshots.Chair
This approach works somewhat (and I used it for a while), but I don't want my buttons/checkboxes/text to actually appear larger or smaller when resized.Yezd
This will cause the content of the window to be resized uniformly, not the window itself...Flagstone
S
12

You can always handle the WM_WINDOWPOSCHANGING message, this let's you control the window size and position during the resizing process (as opposed to fixing things after the user finished resizing).

Here is how you do it in WPF, I combined this code from several sources, so there could be some syntax errors 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;
}
Scherzando answered 30/3, 2009 at 7:11 Comment(3)
This is a very slick way of doing this.Absorb
Interesting but we don't know if user is changing width, height (or both. We need to know it to see if we must adjust the height or the width.Rommel
Working very well, gives much smoother results than adjustments in SizeChanged/RenderSizeChanged.Kilauea
A
8

This is what my solution was.

You will need to add this to your control/window tag:

Loaded="Window_Loaded"

And you will need to place this in your code 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 not like it. I wanted to lock the window border to a specific size. This was tested on a window control but I assume it would work on a border as well.

Absorb answered 30/3, 2009 at 2:52 Comment(1)
This approach works, but has the downside that the window border is blinking (for lack of a better word) during the resize operation - the size is first set to whatever the user is freely resizing it to, then the border is adjusted to the overridden size.Unreasonable
E
1

You could try replicating an effect that I often see on Flash Video websites. They allow you to expand the browser window any way you like, but only stretch the presentation area so that it fits the smallest of the height or width.

For example, if you stretch the window vertically, your application would not resize. It would simple add black bars to the top and bottom of the display area and remain vertically centered.

This may or may not be possible with WPF; I don't know.

Emmy answered 22/12, 2008 at 18:37 Comment(0)
F
1

I had expected that you could two-way bind the width to the height using a value converter to maintain aspect ratio. Passing the aspect ratio as the converter parameter would make it more general purpose.

So, I tried this - 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 binding is behaving as if it is one-way and the reported width of the window (as shown in the TextBlock) is not consistent with it's size on screen!

The idea might be worth pursuing, but this strange behavior would need to be sorted out first.

Hope that helps!

Flashgun answered 23/12, 2008 at 1:0 Comment(0)
E
1

This may be bit late but you can simply 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
Evy answered 15/9, 2010 at 22:17 Comment(0)
C
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 computation should be:

this.Height = sizeInfo.NewSize.Width * (1/aspectRatio);  

I made a variation of this work in a "SizeChanged" event handler. Since I wanted the width to be the controlling dimension, I simply forced the height to 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 may note the check for an aspectRatio > 0, ... I did this because I found that it was tending to call my handlers that did the resizing before the "Load" method had even assigned the aspectRatio.

Cofield answered 21/12, 2010 at 13:38 Comment(0)
T
0

Maybe too late, but i found a solution from Mike O'Brien blog, and it work really good. http://www.mikeobrien.net/blog/maintaining-aspect-ratio-when-resizing/ Below 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;
    }
}
Thoroughbred answered 4/7, 2013 at 2:46 Comment(0)
L
0

I got a way that doesn't depend on Windows platform-specific API also with acceptable user experience (not shake while dragging the window). It uses a timer to adjust the window size after 0.1 seconds so user won't 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);
    }
}
Larch answered 14/1, 2022 at 13:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.