How to keep the aspect ratio of a UserControl? - c#

Does anyone have an idea how to keep the Height/Width Ratio 1:1 of a UserControl?
E.g. if Height > Width, Width & Height will have the same size and vice versa.

I'm not sure this will work, but if you register a handler for the SizeChanged event and in there put in your code keep the aspect ratio 1:1.
The SizeChangedEventArgs argument has the old size and the new size so you can check which has changed and update the other accordingly.
You might need to introduce a guard variable so that you don't get a cascade of SizeChanged events as a result of updating the Height or Width.

Another alternative:
<local:MyControl Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"/>

Try using a ViewBox and setting its Stretch property to Uniform

i used this code for keeping aspect ratio
inside usercontrol globally define org_width, org_height, org_ratio :
private static double org_width = 77.6;//desired width
private static double org_height = 81.4;//desired height
private static double org_ratio = org_width / org_height;
use this code inside usercontrol in SizeChanged event:
FrameworkElement UCborder = this;
UCborder.Width = UCborder.Height*org_ratio;
and finally your user control code should looks like this:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace yournamespace
{
public partial class YourUserControl : UserControl
{
private static double org_width = 77.6;//desired width
private static double org_height = 81.4;//desired height
private static double org_ratio = org_width / org_height; // width/height
public YourUserControl()
{
InitializeComponent();
}
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
FrameworkElement UCborder = this;
UCborder.Width = UCborder.Height*org_ratio;
}
}
}
good luck

private bool isSizeChangeDefered;
private void uiElement_SizeChanged(object sender, SizeChangedEventArgs e)
{
//Keep Acpect Ratio
const double factor = 1.8;
if(isSizeChangeDefered)
return;
isSizeChangeDefered = true;
try
{
if (e.WidthChanged)
{
driverPan.Height = e.NewSize.Width * factor;
}
if (e.HeightChanged)
{
driverPan.Height = e.NewSize.Width / factor;
}
}
finally
{
// e.Handled = true;
isSizeChangeDefered = false;
}
}
maybe this helps... cheers

Related

Xamarin Create square layout using screen size

I have a problem. I want to create a StackLayout that has the width and height of the screen size, so it will be a square. I already tried this:
public Page1()
{
InitializeComponent();
imageLayout.HeightRequest = Application.Current.MainPage.Width;
imageLayout.WidthRequest = Application.Current.MainPage.Width;
}
But that gave me the error that Application.Current.MainPage is null.
How can I fix this?
I think I would use the SizeChanged Event from your page:
public Page1()
{
InitializeComponent();
SizeChanged += (s,a) =>
{
imageLayout.HeightRequest = this.Width;
imageLayout.WidthRequest = this.Width;
};
}
You should override OnSizeAllocated for these purposes.
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
imageLayout.WidthRequest= width;
imageLayout.HeightRequest = width;
}
Much cleaner way

How to enable both scrolling and zooming using pinch in WPF?

I am struggling with making both touch events and manipulation work properly in a WPF project. I have a ScrollViewer which contains a picture and I would like to scroll both horizontally and vertically using a swipe gestures. Additionally, I would like to zoom in/out in the center of the pinch gesture. The code below achieves what I wish, but it has the following problems:
Sometimes the scrolling is laggy;
The scrolling does not work on the first try, only when attempting the same gesture a second time;
The zoom in/out does not work on the first try, only when attempting the same gesture a second time.
I enabled the IsManipulationEnabled and I implemented the code for zoom in/out functionality. However, I was not able to combine it with the scrolling functionality (by setting the PanningMode in the ScrollViewer only). Therefore, I created a custom control which inherits from Image control and I overwritten the OnTouchDown and OnTouchUp event handlers. Basically, what I am doing in these overwritten handlers is counting the number of touches on the screen and enabling/disabling manipulation. I also tried setting the PanningMode for the ScrollViewer, but it did not do the trick.
Below is the XAML:
<Grid>
<ScrollViewer
x:Name="ScrollViewerParent"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
PanningMode="Both">
<local:CustomImage
x:Name="MainImage"
Source="{Binding Source={x:Static local:Constants.ImagePath}}"
IsManipulationEnabled="True"
ManipulationStarting="MainImage_ManipulationStarting"
ManipulationDelta="MainImage_ManipulationDelta">
</local:CustomImage>
</ScrollViewer>
</Grid>
Here is the code-behind:
public partial class MainWindow : Window
{
private void MainImage_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
{
e.ManipulationContainer = ScrollViewerParent;
e.Handled = true;
}
private void MainImage_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
var matrix = MainImage.LayoutTransform.Value;
Point? centerOfPinch = (e.ManipulationContainer as FrameworkElement)?.TranslatePoint(e.ManipulationOrigin, ScrollViewerParent);
if (centerOfPinch == null)
{
return;
}
var deltaManipulation = e.DeltaManipulation;
matrix.ScaleAt(deltaManipulation.Scale.X, deltaManipulation.Scale.Y, centerOfPinch.Value.X, centerOfPinch.Value.Y);
MainImage.LayoutTransform = new MatrixTransform(matrix);
Point? originOfManipulation = (e.ManipulationContainer as FrameworkElement)?.TranslatePoint(e.ManipulationOrigin, MainImage);
double scrollViewerOffsetX = ScrollViewerParent.HorizontalOffset;
double scrollViewerOffsetY = ScrollViewerParent.VerticalOffset;
double pointMovedOnXOffset = originOfManipulation.Value.X - originOfManipulation.Value.X * deltaManipulation.Scale.X;
double pointMovedOnYOffset = originOfManipulation.Value.Y - originOfManipulation.Value.Y * deltaManipulation.Scale.Y;
double multiplicatorX = ScrollViewerParent.ExtentWidth / MainImage.ActualWidth;
double multiplicatorY = ScrollViewerParent.ExtentHeight / MainImage.ActualHeight;
ScrollViewerParent.ScrollToHorizontalOffset(scrollViewerOffsetX - pointMovedOnXOffset * multiplicatorX);
ScrollViewerParent.ScrollToVerticalOffset(scrollViewerOffsetY - pointMovedOnYOffset * multiplicatorY);
e.Handled = true;
}
}
The XAML for the custom control:
<Style TargetType="{x:Type local:CustomImage}" />
Here is where I override the OnTouchDown and OnTouchUp event handlers:
public class CustomImage : Image
{
private volatile int nrOfTouchPoints;
private volatile bool isManipulationReset;
private object mutex = new object();
static CustomImage()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomImage), new FrameworkPropertyMetadata(typeof(CustomImage)));
}
protected override void OnTouchDown(TouchEventArgs e)
{
lock (mutex)
{
nrOfTouchPoints++;
if (nrOfTouchPoints >= 2)
{
IsManipulationEnabled = true;
isManipulationReset = false;
}
}
base.OnTouchDown(e);
}
protected override void OnTouchUp(TouchEventArgs e)
{
lock (mutex)
{
if (!isManipulationReset)
{
IsManipulationEnabled = false;
isManipulationReset = true;
nrOfTouchPoints = 0;
}
}
base.OnTouchUp(e);
}
}
What I expect from this code is the following:
When using one finger to swipe horizontally or vertically across the touchscreen, the image should be scrolled accordingly;
When I use a pinch gesture on the touch screen, the image should be zoomed in/out in the center of the pinch.
Fortunately, I managed to find the perfect solution. Therefore, I am going to post the answer in the case that someone is working on a similar problem and needs some help.
What I did:
Got rid of the custom control as it was not necessary;
Create a field which counts the number of the touch points;
Implemented the TouchDown event handler, which increases the number of touch points by 1 (this method is called each time there is a touch down gesture on the device);
Implemented the TouchUp event handler, which decreases the number of touch points by 1 (this method is called each time there is a touch up gesture on the device);
In the Image_ManipulationDelta event handler, I check the number of touch points:
if the number of touch points < 2, then the translation value is added to the current offset of the scrollbars, thus achieving scrolling;
otherwise, the center of the pinch is calculated and a scale gesture is applied.
Here is the full XAML:
<Grid
x:Name="GridParent">
<ScrollViewer
x:Name="ScrollViewerParent"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
PanningMode="Both">
<Image
x:Name="MainImage"
Source="{Binding Source={x:Static local:Constants.ImagePath}}"
IsManipulationEnabled="True"
TouchDown="MainImage_TouchDown"
TouchUp="MainImage_TouchUp"
ManipulationDelta="Image_ManipulationDelta"
ManipulationStarting="Image_ManipulationStarting"/>
</ScrollViewer>
</Grid>
Here is the entire code discussed above:
public partial class MainWindow : Window
{
private volatile int nrOfTouchPoints;
private object mutex = new object();
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Image_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
{
e.ManipulationContainer = ScrollViewerParent;
e.Handled = true;
}
private void Image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
int nrOfPoints = 0;
lock (mutex)
{
nrOfPoints = nrOfTouchPoints;
}
if (nrOfPoints >= 2)
{
DataLogger.LogActionDescription($"Executed {nameof(Image_ManipulationDelta)}");
var matrix = MainImage.LayoutTransform.Value;
Point? centerOfPinch = (e.ManipulationContainer as FrameworkElement)?.TranslatePoint(e.ManipulationOrigin, ScrollViewerParent);
if (centerOfPinch == null)
{
return;
}
var deltaManipulation = e.DeltaManipulation;
matrix.ScaleAt(deltaManipulation.Scale.X, deltaManipulation.Scale.Y, centerOfPinch.Value.X, centerOfPinch.Value.Y);
MainImage.LayoutTransform = new MatrixTransform(matrix);
Point? originOfManipulation = (e.ManipulationContainer as FrameworkElement)?.TranslatePoint(e.ManipulationOrigin, MainImage);
double scrollViewerOffsetX = ScrollViewerParent.HorizontalOffset;
double scrollViewerOffsetY = ScrollViewerParent.VerticalOffset;
double pointMovedOnXOffset = originOfManipulation.Value.X - originOfManipulation.Value.X * deltaManipulation.Scale.X;
double pointMovedOnYOffset = originOfManipulation.Value.Y - originOfManipulation.Value.Y * deltaManipulation.Scale.Y;
double multiplicatorX = ScrollViewerParent.ExtentWidth / MainImage.ActualWidth;
double multiplicatorY = ScrollViewerParent.ExtentHeight / MainImage.ActualHeight;
ScrollViewerParent.ScrollToHorizontalOffset(scrollViewerOffsetX - pointMovedOnXOffset * multiplicatorX);
ScrollViewerParent.ScrollToVerticalOffset(scrollViewerOffsetY - pointMovedOnYOffset * multiplicatorY);
e.Handled = true;
}
else
{
ScrollViewerParent.ScrollToHorizontalOffset(ScrollViewerParent.HorizontalOffset - e.DeltaManipulation.Translation.X);
ScrollViewerParent.ScrollToVerticalOffset(ScrollViewerParent.VerticalOffset - e.DeltaManipulation.Translation.Y);
}
}
private void MainImage_TouchDown(object sender, TouchEventArgs e)
{
lock (mutex)
{
nrOfTouchPoints++;
}
}
private void MainImage_TouchUp(object sender, TouchEventArgs e)
{
lock (mutex)
{
nrOfTouchPoints--;
}
}
}
}

Keep Buttons location with proportion to Resizing Image

I am trying to create a Winforms App and in one of screens, I have an image and some buttons generated at runtime, Now What i want is these buttons should always stay in proportion to the Image
for example Hand1 Button should always remain where Image's Hand1 is
This is my initial Image
but while resizing the form, it becomes like this
What I want is these buttons should not change their size and always stay in proportion to the Image.
The ImageSizeMode of the Image is StretchImage and buttons are Not Anchored (otherwise they don't move or start stretch/shrink.
How can i achieve this behavior. ?
Here is a simple, complete solution for a single button:
Note: The scale is calculated relative to the upper, left corner of the control (button). If the scaling needs to be modified such as to the middle or lower/right, then the scaling calculation has to be adjusted. Otherwise the location will not look quite precise and the button will "wander" as the image is resized.
using System;
using System.Drawing;
using System.Windows.Forms;
public partial class Form1 : Form
{
// X, Y scaling variables for btn1
private float _btn1xScale;
private float _btn1yScale;
public Form1()
{
InitializeComponent();
// The scale is really the % of btn X & Y along image width and height:
// Calculate X and Y scale from initial location and position in image
// Has to happen AFTER InitializeComponent is called!
_btn1xScale = btn1.Location.X / (float)pictureBox1.Width;
_btn1yScale = btn1.Location.Y / (float)pictureBox1.Height;
}
private void pictureBoxResize(object sender, EventArgs e)
{
// adjust position based on
btn1.Location = new Point(
(int)(pictureBox1.Width * _btn1xScale),
(int)(pictureBox1.Height * _btn1yScale));
}
}
Fancier version:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
public partial class Form1 : Form
{
private List<ControlScaler> _buttonsToScale = new List<ControlScaler>();
public Form1()
{
InitializeComponent();
// Has to happen AFTER InitializeComponent is called!
_buttonsToScale.Add(new ControlScaler(btn1, pictureBox1));
_buttonsToScale.Add(new ControlScaler(btn2, pictureBox1));
}
private void pictureBoxResize(object sender, EventArgs e)
{
foreach (var control in _buttonsToScale)
control.AdjustPositionToScale();
}
}
public class ControlScaler
{
// X, Y scaling variables
private float _btn1xScale;
private float _btn1yScale;
private Control _scaledControl;
private readonly Control _scaleTo;
public ControlScaler(Control scaledControl, Control scaleTo)
{
_scaledControl = scaledControl;
_scaleTo = scaleTo;
_btn1xScale = scaledControl.Location.X / (float)scaleTo.Width;
_btn1yScale = scaledControl.Location.Y / (float)scaleTo.Height;
}
public void AdjustPositionToScale()
{
var newLocation = new Point(
(int)(_scaleTo.Width * _btn1xScale),
(int)(_scaleTo.Height * _btn1yScale));
_scaledControl.Location = newLocation;
}
}
This solution is for hand1 button. The same logic applies to the other buttons too.
You want to keep distances c and d constant. First measure a, b, c, d and:
double dblHand1X, dblHand1Y;
private void Form1_Load(object sender, EventArgs e)
{
dblHand1X= (double)b / (double)pictureBox1.Width;
dblHand1Y= (double)a / (double)pictureBox1.Height;
}
private void pictureBox1_SizeChanged(object sender, EventArgs e)
{
int x, y;
x = (int)(dblHand1X* (double)pictureBox1.Width) + pictureBox1.Location.X;
y = (int)(dblHand1Y* (double)pictureBox1.Height) + pictureBox1.Location.Y;
x -= d;
y -= c;
Hand1.Location = new Point(x, y);
}
valter
You'll have to move buttons manually.
Choose origins for all buttons. For example, origin the "Hand 1" button is probably (button.Width / 2; button.Height) and origin of the "Leg 2" button is (0; button.Height).
Calculate and save positions of button origins: (OriginalX; OriginalY).
When the form is resized, multiply original positions by scale: (OriginalX * ScaleX; OriginalY * ScaleY). Then set button's position based on new positions of origins: (OriginalX * ScaleX - OriginX; OriginalY * ScaleY - OriginY).
Buttons should be anchored to top left.

Why when resizing the user control graph it's not really resized?

I have a user control chart in my Form1 designer and this is the code to resize it:
private void graphChart1_MouseEnter(object sender, EventArgs e)
{
graphChart1.Size = new Size(600, 600);
}
When I move the mouse to the control area it's not resizing it make it bigger but deleting some other controls.
This is an image before I move the mouse over the control:
And this is an image when I moved the mouse over the control:
This is the code of the user control where the chart is:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Web;
using System.Windows.Forms.DataVisualization.Charting;
namespace GatherLinks
{
public partial class GraphChart : UserControl
{
public GraphChart()
{
InitializeComponent();
}
private double f(int i)
{
var f1 = 59894 - (8128 * i) + (262 * i * i) - (1.6 * i * i * i);
return f1;
}
private void GraphChart_Load(object sender, EventArgs e)
{
chart1.Series.Clear();
var series1 = new System.Windows.Forms.DataVisualization.Charting.Series
{
Name = "Series1",
Color = System.Drawing.Color.Green,
IsVisibleInLegend = false,
IsXValueIndexed = true,
ChartType = SeriesChartType.Line
};
this.chart1.Series.Add(series1);
for (int i = 0; i < 100; i++)
{
series1.Points.AddXY(i, f(i));
}
chart1.Invalidate();
}
}
}
EDIT:
I did this in the user control class code:
public void ChangeChartSize(int width, int height)
{
chart1.Size = new Size(width, height);
chart1.Invalidate();
}
I had to add chart1.Invalidate(); to make it to take effect but then it sized the chart it self inside the user control. The user control was not changed.
So in the Form1 mouse enter I also changing the graphChart1 the control size:
private void graphChart1_MouseEnter(object sender, EventArgs e)
{
graphChart1.ChangeChartSize(600, 600);
graphChart1.Size = new Size(600, 600);
}
The problem is that now it's taking a lot of time almost 20 seconds or so until it take effect when I'm moving the mouse over the control. If I will remove the second line:
graphChart1.Size = new Size(600, 600);
it will work fast but then it will change the chart only inside the control but it won't change the control size.
Tried also with invalidate:
private void graphChart1_MouseEnter(object sender, EventArgs e)
{
graphChart1.ChangeChartSize(600, 600);
graphChart1.Size = new Size(600, 600);
graphChart1.Invalidate();
}
But still very slow. Maybe I need to change the control it self size also in the user control class code and not in Form1 ?
The problem is that you are resizing the GraphicChart (your user control) but not the Chart itself. You could add the method in your GraphChart class in order to do that. This is the method that will change the chart size:
public void ChangeChartSize(int width, int height)
{
chart1.Size = new Size(width, height);
}
And in your mouse enter event handler you could call something like this:
void graphicChart1_MouseEnter(object sender, EventArgs e)
{
graphChart1.ChangeChartSize(600, 600);
}
With graphChart1.Size = you are resizing your container but not the chart within it.
The easiest work-around is probably to make chart1 public in the control and do graphChart1.chart1.Size = instead.
In the user control class code I did:
public void ChangeChartSize(int width, int height)
{
this.Size = new Size(width, height);
chart1.Size = new Size(width, height);
chart1.Invalidate();
}
In Form1 I did:
private void graphChart1_MouseEnter(object sender, EventArgs e)
{
graphChart1.ChangeChartSize(600, 600);
}
Working smooth.

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

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?
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;
}
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:
<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>
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.
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.
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
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!
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.
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;
}
}
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);
}
}

Categories