Scale windows forms window - c#

It is possible to prepare the windows forms window to resize/reposition all elements depending on the window size, but I am trying to do something different.
Is it possible in some way to actually scale the window along with all elements inside regardless of their positions, properties, etc?
Basically the way you would scale a picture in some graphics editor - you can just stretch or shrink it, but it doesn't matter what is on that picture.
So, is it possible to do something similar with the form? Being able to scale its size regardless of what's inside the form.

Windows form does not provide any feature to do this. But, you can write your own code and make your form resolution independent.
This is not a complete example to make windows form resolution independent but, you can get logic from here. The following code creates problem when you resize the window quickly.
CODE:
private Size oldSize;
private void Form1_Load(object sender, EventArgs e) => oldSize = base.Size;
protected override void OnResize(System.EventArgs e)
{
base.OnResize(e);
foreach (Control cnt in this.Controls)
ResizeAll(cnt, base.Size);
oldSize = base.Size;
}
private void ResizeAll(Control control, Size newSize)
{
int width = newSize.Width - oldSize.Width;
control.Left += (control.Left * width) / oldSize.Width;
control.Width += (control.Width * width) / oldSize.Width;
int height = newSize.Height - oldSize.Height;
control.Top += (control.Top * height) / oldSize.Height;
control.Height += (control.Height * height) / oldSize.Height;
}
Otherwise you can use any third party control like DevExpress Tool. There is LayoutControl which is providing same facility. you can show and hide any control at runtime without leaving blank space.

Your form has a Scale property. You can directly set this property and it will simultaneously affect every control on the form.
float scaleX = ((float)formNewWidth / formBaseWidth);
float scaleY = ((float)formNewHeight / formBaseHeight);
this.Scale(new SizeF(scaleX, scaleY));
put this in your resize event.

Check out the Control.Scale method available since .NET 2.0.

Related

Dynamically resize WinForms controls when Form is resized

I have a WinForms project on which I would like all of the controls to grow proportionally along with the form as the form is resized. This is what the form looks like in normal state: Normal State Form
I have tried setting the Anchor properties to their appropriate values given the location of each control on the form, and while it does move the controls, they remain the same size. I tried using the AutoSize property, but also to no avail. Here is what the form looks like after being maximized with the Anchor properties set: Maximized Form
I also tried using a formula from Shaun Halverson to dynamically resize everything but it does not relocate the control properly, and I can't seem to figure out why. Here is the code I used to try and resize dynamically:
private void Main_Load(object sender, EventArgs e)
{
originalFormSize = new Rectangle(this.Location.X, this.Location.Y, this.Size.Width, this.Size.Height);
submitBtnOriginal = new Rectangle(submitButton.Location.X, submitButton.Location.Y, submitButton.Width, submitButton.Height);
}
private void Main_Resize(object sender, EventArgs e)
{
resizeControl(submitBtnOriginal, submitButton);
}
private void resizeControl(Rectangle r, Control c)
{
float xRatio = (float)(this.Width) / (float)(originalFormSize.Width);
float yRatio = (float)(this.Height) / (float)(originalFormSize.Height);
int newWidth = (int)(r.Width * xRatio);
int newHeight = (int)(r.Height * yRatio);
int newX = (int)(r.Width * xRatio);
int newY = (int)(r.Height * yRatio);
c.Location = new Point(newX, newY);
c.Size = new Size(newWidth, newHeight);
}
When I run this code, it moves the button to the opposite corner of the form, but it resizes it properly.
This would obviously be quite redundant given that I have to get an original size for every control I want to resize, but I would be fine with that if I could get dynamic resizing to work. I am surprised that this is not a more common problem, and I couldn't find hardly anything on this specific topic other than to use the Anchor and Dock properties. Is there an easy way to do this that I am missing? Is this a more difficult problem than it seems?
Put TextBox anchor property values as Top, Bottom, Left, Right and resize the form. That should work.

Overlapping panels with "Dock" resize functionality

I've been struggling for a couple of days with this issue, which seems like it would have an obvious solution.
How could I place a panel, which maintains position and resizes as if Docked, in the middle of the form, over other panels populating a TableLayoutPanel without messing Column/Row spans and misplacing other containers and controls?
I would like to avoid developing custom functionality and make due with the basic visual studio toolbox (extensions are welcome).
In an effort to better explain what I'm hoping to achieve, I've provided a link to an image of the goal.
Blue is the dynamically resizable, centered pop-up panel.
Green is an Image/BackgroundImage starting on cell [1,1] of the TableLayoutPanel.
Orange is a drop-down panel for a side menu, also starting on cell [1,1].
So... I found a solution.
I guess I needed to admit that I have to write the resizing function myself.
I'm new here, so I don't know if answering oneself question is canon, but here goes.
For the Orange panel, given it's Anchored Top,Left:
private double panel1WidthRatio, panel1HeightRatio; // Global variables for maintaining ratio.
private void ParentForm_ResizeBegin(object sender, EventArgs e)
{
double p1w = this.side_panel.Size.Width; // Grab the panels' dimensions
double p1h = this.side_panel.Size.Height; // as soon as the user begins to resize
double fw = this.Size.Width; // in order to store
double fh = this.Size.Height; // the Panel to Form
panel1WidthRatio = p1w / fw; // dimension ratios.
panel1HeightRatio = p1h / fh;
}
private void ParentForm_SizeChanged(object sender, EventArgs e)
{
double formWidth = this.Size.Width; // As soon as a new size is set
double formHeight = this.Size.Height; // resize the panel using the earlier ratio.
this.side_panel.Size = new Size((int)(formWidth * panel1WidthRatio), (int)(formHeight * panel1HeightRatio));
}
For the Blue panel, given no Anchors, simply replace:
this.side_panel.Size = new Size((int)(formWidth * panel1WidthRatio), (int)(formHeight * panel1HeightRatio));
with:
this.middle_panel.Size = new Size((int)(formWidth * panel2WidthRatio), (int)(formHeight * panel2HeightRatio));
this.middle_panel.Location = new System.Drawing.Point(this.Size.Width/4-9, this.Size.Height/4-24);
The constants (9,24) take into account the Form's frame and borders, so as to correctly reposition the panel in the center.

How to automatically snap a WPF window to an edge of the screen while retaining its size?

As the application starts, I'd like my WPF window to automatically snap to the right edge of the screen. Is there a way to do that? I also want to be able to retain its dimensions. So, unlike the snapping behavior that happens when you drag a window to the edge of the screen, causing the window to resize to either a portion of the screen or full screen, I want my window to simply snap to the edge at a certain location by default or if dragged by the user to a specific location afterwards, without resizing. I still want to retain the ability of the user to drag the window away from the edge.
Is there anything like that already implemented or would I have to create my own behavior schema? I tried numerous search keyword combinations, but couldn't find anything similar to what I'm doing. Some of the searches included disabling snapping behavior or providing snapping behavior, but nothing in the way I described above.
EDIT:
I haven't been able to find a ready solution, so I wrote my own. This solution is based on BenVlodgi's suggestions, so I thank him for helping me out. This is a very rough implementation and still requires a lot of polishing and better code techniques, but it works and it's a good base for anyone wanting to try this. It's incredibly simple and works very well with WPF. The only limitation of this implementation is that I haven't tried getting it to work with two screens yet, but it's incredibly simple (I'm just not going to have time for it and I don't need that functionality at this point). So, here's the code and I hope that it helps someone out there:
public partial class MainWindow : Window
{
// Get the working area of the screen. It excludes any dockings or toolbars, which
// is exactly what we want.
private System.Drawing.Rectangle screen =
System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
// This will be the flag for the automatic positioning.
private bool dragging = false;
// The usual initialization routine
public MainWindow()
{
InitializeComponent();
}
// Wait until window is lodaded, but prior to being rendered to set position. This
// is done because prior to being loaded you'll get NaN for this.Height and 0 for
// this.ActualHeight.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Sets the initial position.
SetInitialWindowPosition();
// Sets the monitoring timer loop.
InitializeWindowPositionMonitoring();
}
// Allows the window to be dragged where the are no other controls present.
// PreviewMouseButton could be used, but then you have to do more work to ensure that if
// you're pressing down on a button, it executes its routine if dragging was not performed.
private void Window_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// Set the dragging flag to true, so that position would not be reset automatically.
if (e.ChangedButton == System.Windows.Input.MouseButton.Left)
{
dragging = true;
this.DragMove();
}
}
// Similar to MouseDown. We're setting dragging flag to false to allow automatic
// positioning.
private void Window_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (e.ChangedButton == System.Windows.Input.MouseButton.Left)
{
dragging = false;
}
}
// Sets the initial position of the window. I made mine static for now, but later it can
// be modified to be whatever the user chooses in the settings.
private void SetInitialWindowPosition()
{
this.Left = screen.Width - this.Width;
this.Top = screen.Height / 2 - this.Height / 2;
}
// Setup the monitoring routine that automatically positions the window based on its location
// relative to the working area.
private void InitializeWindowPositionMonitoring()
{
var timer = new System.Windows.Threading.DispatcherTimer();
timer.Tick += delegate
{
// Check if window is being dragged (assuming that mouse down on any portion of the
// window is connected to dragging). This is a fairly safe assumption and held
// true thus far. Even if you're not performing any dragging, then the position
// doesn't change and nothing gets reset. You can add an extra check to see if
// position has changed, but there is no significant performance gain.
// Correct me if I'm wrong, but this is just O(n) execution, where n is the number of
// ticks the mouse has been held down on that window.
if (!dragging)
{
// Checking the left side of the window.
if (this.Left > screen.Width - this.Width)
{
this.Left = screen.Width - this.Width;
}
else if (this.Left < 0)
{
this.Left = 0;
}
// Checking the top of the window.
if (this.Top > screen.Height - this.Height)
{
this.Top = screen.Height - this.Height;
}
else if (this.Top < 0)
{
this.Top = 0;
}
}
};
// Adjust this based on performance and preference. I set mine to 10 milliseconds.
timer.Interval = new TimeSpan(0, 0, 0, 0, 10);
timer.Start();
}
}
Make sure that your window has the following:
MouseDown="Window_MouseDown"
MouseUp="Window_MouseUp"
WindowStartupLocation="Manual"
Loaded="Window_Loaded"
Also, this doesn't work well with Windows native components of the window, such as the top bar, so I disable the style and create my own (which is actually good for me, since I don't want the windows style for this):
WindowStyle="None"
I don't like the polling approach, but it's been excessively difficult to find a better solution that is still simple in WPF, so I'm gonna post my own.
The solution that I found is actually quite simple, in that it reimplements the behaviour of the DragMove() method of the window, which gives you the option to change the window position while it's being dragged. The following code reimplements DragMove() by storing the distance between the top left corner of the window and the mouse cursor.
public partial class MainWindow : Window
{
// this is the offset of the mouse cursor from the top left corner of the window
private Point offset = new Point();
public MainWindow()
{
InitializeComponent();
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point cursorPos = PointToScreen(Mouse.GetPosition(this));
Point windowPos = new Point(this.Left, this.Top);
offset = (Point)(cursorPos - windowPos);
// capturing the mouse here will redirect all events to this window, even if
// the mouse cursor should leave the window area
Mouse.Capture(this, CaptureMode.Element);
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Mouse.Capture(null);
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (Mouse.Captured == this && Mouse.LeftButton == MouseButtonState.Pressed)
{
Point cursorPos = PointToScreen(Mouse.GetPosition(this));
double newLeft = cursorPos.X - offset.X;
double newTop = cursorPos.Y - offset.Y;
// here you can change the window position and implement
// the snapping behaviour that you need
this.Left = newLeft;
this.Top = newTop;
}
}
}
Now you could implement the snapping / sticky window behaviour like this: The window will stick to the edge of the screen if it's within a range of 25 pixels (plus or minus).
int snappingMargin = 25;
if (Math.Abs(SystemParameters.WorkArea.Left - newLeft) < snappingMargin)
newLeft = SystemParameters.WorkArea.Left;
else if (Math.Abs(newLeft + this.ActualWidth - SystemParameters.WorkArea.Left - SystemParameters.WorkArea.Width) < snappingMargin)
newLeft = SystemParameters.WorkArea.Left + SystemParameters.WorkArea.Width - this.ActualWidth;
if (Math.Abs(SystemParameters.WorkArea.Top - newTop) < snappingMargin)
newTop = SystemParameters.WorkArea.Top;
else if (Math.Abs(newTop + this.ActualHeight - SystemParameters.WorkArea.Top - SystemParameters.WorkArea.Height) < snappingMargin)
newTop = SystemParameters.WorkArea.Top + SystemParameters.WorkArea.Height - this.ActualHeight;
The downside of this approach is that the snapping will not work, if the window is being dragged on the title bar, because that doesn't fire the OnMouseLeftButtonDown event (which I don't need, because my window is borderless). Maybe it will still help someone.
There is no API calls you can make (as far as I've seen) to use the Windows snapping features, however you could just get the System.Windows.Forms.Screen.PrimaryScreen.WorkingArea of the screen and set your Top, Left, Height and Width Properties of your Window accordingly.
Edit: The above suggestion does require Forms, which you probably don't want. I believe the WPF equivalent is System.Windows.SystemParameters.WorkArea

Setting form's location when calling Form.Show()

I'm trying to set the location of a form when calling it by .Show(). The problem is that because I'm using .Show instead of .ShowDialog the StartPosition value does not work. I can't use the .Showdialog since I want the program to do work in the background while showing the form.
When I'm creating the form I set it's location to a fixed value:
using (ConnectingForm CF = new ConnectingForm())
{
CF.Show();
CF.Location = new Point(this.ClientSize.Width / 2, this.ClientSize.Height / 2);
}
But when I run the Code the form places itself on different positions each time I start it.
Any solutions? (The location is never set anywhere else by my code)
StartPosition should work fine with Form.Show. Try:
ConnectingForm CF = new ConnectingForm();
CF.StartPosition = FormStartPosition.CenterParent;
CF.Show(this);
If you want to manually place the form, as you've shown, that can be done as well, but still requires setting the StartPosition property to Manual:
ConnectingForm CF = new ConnectingForm();
CF.StartPosition = FormStartPosition.Manual;
CF.Location = new Point(this.ClientSize.Width / 2, this.ClientSize.Height / 2);
CF.Show();
On a side note, you shouldn't use a using statement with Form.Show. using will call Dispose on the form, which isn't desired, since the form's lifetime is longer than this block of code.
With some help from other threads I found a working solution:
using (ConnectingForm CF = new ConnectingForm())
{
CF.StartPosition = FormStartPosition.Manual;
CF.Show(this);
......
}
On the new form's load event:
private void ConnectingForm_Load(object sender, EventArgs e)
{
this.Location = this.Owner.Location;
this.Left += this.Owner.ClientSize.Width / 2 - this.Width / 2;
this.Top += this.Owner.ClientSize.Height / 2 - this.Height / 2;
}
(I'm no expert so please correct me if I'm wrong)
Here is how I interpret the problem and the solution:
The problem from the beginning was that the first form's (MainForm) Startup Position was set to Windows Default Location which varies when you start up the form. When I then called the new form (Connecting form), it's location was not relative to it's parent's location, but the location (0, 0) (top lef corner of the screen). So what I was seeing was the MainForms position changing, which made it look like the Connecting Form's position was moving. So the solution to this problem was basically to first set the new form's location to the Main Form's location. After that I was able to set the location to be the center of the MainForm.
TL;DR the new form's location was not relative to the parent form's location, but to a fixed position that I'm guessing is (0, 0)
I changed the MainForm's Startup Position to a fixed one for my own convenience. I also added an event to make sure that the new forms position always was the center of the MainForm.
private void Location_Changed(object sender, EventArgs e)
{
this.Location = this.Owner.Location;
this.Left += this.Owner.ClientSize.Width / 2 - this.Width / 2;
this.Top += this.Owner.ClientSize.Height / 2 - this.Height / 2;
}
private void ConnectingForm_Load(object sender, EventArgs e)
{
this.Owner.LocationChanged += new EventHandler(this.Location_Changed);
this.Location = this.Owner.Location;
this.Left += this.Owner.ClientSize.Width / 2 - this.Width / 2;
this.Top += this.Owner.ClientSize.Height / 2 - this.Height / 2;
}
Hopefully this will help others with the same problem!

How to make compact framework custom controls AutoScale aware

Windows Forms (including Windows Forms for Compact Framwork, which is what I am using) has an AutoScale feature. By setting the AutoScaleMode property to AutoScaleMode.Dpi, your application designed for, say, 320x200 automatically scales to the larger display of, for example, a VGA device.
This works great, but I have a few self-made custom controls that do their own OnPaint stuff, and I'd like them to scale as well. Unfortunately, I've not found good documentation or an example on how to do that.
Currently, I'm doing this:
protected SizeF zoom = new SizeF(1.0, 1.0);
protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
{
base.ScaleControl(factor, specified);
zoom = factor; // remember the zoom factor
}
protected override void OnPaint(PaintEventArgs e)
{
// scale everything by zoom.Width and zoom.Height
...
e.Graphics.DrawImage(...);
...
}
It works, but I'm wondering if this is "the right way" to do it. Since (according to ILSpy) none of the other CF controls have an internal field to store the scale factor, I'm wondering if there's an easier or better way to do this.
We generally handle all of the scaling in OnResizein our controls and forms. We also have to support a lot of different devices with crazy dimensions and DPI (some paltforms don't even report the correct DPI!). We found with AutoScaleMode off you can proportionaly use a helper like this to scale a form's children in OnResize. You simply add a Size _initalSize member set to the form size in the constructor. However I've generally found on most forms I have to write custom layout code to appropriate deal with portrait and landscape displays.
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
// Scale the control
ScaleChildren(this, ref _initialFormSize);
}
public static void ScaleChildren(Control control, ref Size initialSize, float fontFactor)
{
if (control == null || control.Size == initialSize)
return;
SizeF scaleFactor = new SizeF((float)control.Width / (float)initialSize.Width, (float)control.Height / (float)initialSize.Height);
initialSize = control.Size;
if (!float.IsInfinity(scaleFactor.Width) || !float.IsInfinity(scaleFactor.Height))
{
foreach (Control child in control.Controls)
{
child.Scale(scaleFactor);
if (child is Panel)
continue;
try
{
// scale the font
float scaledFontSize = (float)(int)(child.Font.Size * scaleFactor.Height * fontFactor + 0.5f);
System.Diagnostics.Debug.WriteLine(
string.Format("ScaleChildren(): scaleFactor = ({0}, {1}); fontFactor = {2}; scaledFontSize = {3}; \"{4}\"",
scaleFactor.Width, scaleFactor.Height, fontFactor, scaledFontSize, child.Text));
child.Font = new Font(child.Font.Name, scaledFontSize, child.Font.Style);
}
catch { }
}
}
}

Categories

Resources