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 { }
}
}
}
Related
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.
I'm porting a large iOS codebase to a Xamarin.Forms app. We have a lot of custom views which perform their layout logic by making calculations in -layoutSubviews. The codebase is too large for me to port in time if I need to reinterpret these calculations in terms of Stack or Grid layouts. What I really want is a direct equivalent, where I can add the equivalent subviews to our views without worrying about where they go, and then a method which is called when the view's bounds change inside which I can set the new bounds of the subviews. Then I can directly port our existing iOS code.
Is there some equivalent in Xamarin.Forms for -layoutSubviews?
You can create your own Layout by deriving from Xamarin.Forms.Layout class.
public class CustomLayout : Layout<View>
{
public CustomLayout ()
{
}
}
The layout must override the LayoutChildren method. This method is responsible for positioning children on screen.
Children can be measured by using the GetSizeRequest method, which will return both the desired size and the minimum size the child desires.
protected override void LayoutChildren (double x, double y, double width, double height)
{
for (int i = 0; i < Children.Count; i++) {
var child = (View) Children[i];
// skip invisible children
if(!child.IsVisible)
continue;
var childSizeRequest = child.GetSizeRequest (double.PositiveInfinity, height);
var childWidth = childSizeRequest.Request.Width;
LayoutChildIntoBoundingRegion (child, new Rectangle (x, y, childWidth, height));
x += childWidth;
}
}
This method will automatically be called whenever the layout needs to be recomputed. If your layout consists of hardcoded or fixed size elements, hard code their sizes into this algorithm instead of measuring. GetSizeRequest calls are some of the most expensive calls that can be made, and are not predictable in their runtime as the subtree may be arbitrary complex. Fixing their size is a great way to get a performance boost if dynamic sizing is not required.
Implementing OnSizeRequest is required to make sure the new layout is sized correctly when placed inside other layouts. During layout cycles this method may be called many times depending on the layout above it and how many layout exceptions are required to resolve the current layout hierarchy.
protected override SizeRequest OnSizeRequest (double widthConstraint, double heightConstraint)
{
var height = 0;
var minHeight = 0;
var width = 0;
var minWidth = 0;
for (int i = 0; i < Children.Count; i++) {
var child = (View) Children[i];
// skip invisible children
if(!child.IsVisible)
continue;
var childSizeRequest = child.GetSizeRequest (double.PositiveInfinity, height);
height = Math.Max (height, childSizeRequest.Minimum.Height);
minHeight = Math.Max (minHeight, childSizeRequest.Minimum.Height);
width += childSizeRequest.Request.Width;
minWidth += childSizeRequest.Minimum.Width;
}
return new SizeRequest (new Size (width, height), new Size (minWidth, minHeight));
}
You can read the whole tutorial of how to create a Custom layout here.
I am not sure if there is a an equivalent in forms for layoutSubviews but the calculations that you are talking about can be done inside a method called:
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
}
You need to inherit from a ContentPage or Any Page to override this method.
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.
/I'm working with and testing on a computer that is built with the following:
{1 GB RAM (now 1.5 GB), 1.7 GHz Intel Pentium Processor, ATI Mobility Radeon X600 GFX}
I need scale / transform controls and make it flow smoothly. Currently I'm manipulating the size and location of a control every 24-33ms (30fps), ±3px. When I add a 'fade' effect to an image, it fades in and out smoothly, but it is only 25x25 px in size. The control is 450x75 px to 450x250 px in size. In 2D games such as Bejeweled 3, the sprites animate with no choppy animation.
So as the title would suggest: which is easier/faster on the processor: animating a bitmap (rendering it to the parent control during animation) or animating the control it's self?
EDIT:
Hey, I thought this was a helpful community, not one that down-rates questions that don't seem challenging! (And I've seen more ridiculous questions here with better ratings too!) Please drop me a line first before negatively rating my questions!
I managed to find some free-time in my heck-tick scheduled, to quickly whip up a new project. I'm sure my time could have been better spent else where but hopefully someone else in my shoes may find this of use out there...
The answer is: a Picture over a Control. When rendering a bitmap onto the canvas, there are very little events that will fire, if any. As for the control, it is filled with events - some chained, some looped, and the addition of recursion, so a simple 'LocationChanged' event wouldn't even cover the half of what actually is taking place under the hood.
What I would do for controls that have lots of dynamic animations applied to them during runtime, is to develop a two piece set: a control [rendering] template or active interface (for when the control is at a stand-still or before the play of an animation), and a the animating structure with basic defining properties such as the display image [the rendered control], the rectangle bounds, and any animation algorithms that may be applied latter.
Edit: As Requested, here are the before and after code examples:
// This is the triggering event of the translating animation
private void object_Click(object sender, EventArgs e)
{
// the starting point is at (75,75)
element.Transform(new Point(500, 250));
}
Before:
public class ControlElement : UserControl
{
private Timer tick;
private Point pT0;
public ControlElement() : base()
{
tick = new Timer();
tick.Interval = 30; // about 30fps
tick.Tick += new EventHandler(tick_Tick);
}
void tick_Tick(object sender, EventArgs e)
{
// get the new point from distance and current location/destination
this.Location = Utils.Transform(this.Location, pT0, 3);
if ((pT0.X - this.Location.X)+(pT0.Y - this.Location.Y) <= 0)
{
this.Location = pT0;
tick.Stop();
//this.Visible = true;
}
}
public void Transform(Point destination)
{
pT0 = destination;
//this.Visible = false;
tick.Start();
}
}
After: I create a class that holds a picture of what the control would look like using the DrawToBitmap feature. It still contains the same animation methods as above. I had to add the Location and LocationChanged elements since this class was no longer a control. If and when the actual control needed to be accessed, I would stop rendering and display an instance of the control it's self.
Here is the rendering call:
void element_LocationChanged(object sender, EventArgs e)
{
canvas.Invalidate();
}
void canvas_Paint(object sender, PaintEventArgs e)
{
if (element != null)
{
Bitmap bmp = new Bitmap(element.Display);
Pen p = new Pen(Color.FromArgb(128, 128, 128), 1);
e.Graphics.DrawImage(bmp, element.Location);
e.Graphics.DrawRectangle(p,
element.Location.X, element.Location.Y,
bmp.Width, bmp.Height);
}
}
I am trying to extend the System.Windows.Forms.Label class to support vertically drawn text. I do this by creating a new property called MyLabelOrientation that the user can set to Horizontal or Vertical. When the user changes this setting, the values for width and height are swapped to resize the control to its new orientation. Finally, I override the OnPaint function to draw my Label.
I would like to extend the AutoSize property for this control as well so that my Label will auto-size to the text it contains. For the horizontal orientation, the base functionality implements this for me. For the vertical orientation, I create a Graphics object and set the height of the control to the width of the SizeF object returned from Graphics.MeasureString(Text, Font). You can see an example of the code I'm using below.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel.Design;
using System.Windows.Forms.Design;
public class MyLabel : Label
{
public enum MyLabelOrientation {Horizontal, Vertical};
protected MyLabelOrientation m_orientation = MyLabelOrientation.Horizontal;
[Category("Appearance")]
public virtual MyLabelOrientation Orientation
{
get { return m_orientation; }
set
{
m_orientation = value;
int temp = Height;
Width = Height;
Height = temp;
Refresh();
}
}
private Size ResizeLabel()
{
Graphics g = Graphics.FromHwnd(this.Handle);
SizeF newSize = g.MeasureString(Text, Font);
if (m_orientation == MyLabelOrientation.Horizontal)
Width = (int)newSize.Width;
else
Height = (int)newSize.Width;
}
protected override void OnPaint(PaintEventArgs e)
{
Brush textBrush = new SolidBrush(this.ForeColor);
if (m_orientation == LabelOrientation.Vertical)
{
e.Graphics.TranslateTransform(Width, 0);
e.Graphics.RotateTransform(90);
e.Graphics.DrawString(Text, Font, textBrush, Padding.Left, Padding.Top);
}
else
{
base.OnPaint(e);
}
}
}
However, setting AutoSize to true seems to prevent and/or override any changes to the size of the control. This means that I can't change the width or height when I want to change the Label's orientation. I'm wondering if this behavior can be overridden, so that I can test whether AutoSize is set, and then adjust the size of the control according to it's orientation.
I know this a a pretty old question, but i stumbled across it today and was wondering how to do the same thing.
My solution to the problem was overriding the GetPreferredSize(Size proposedSize) method. I used a button class that houses an arrow in addition to the text which, of course, was not taken into account using the AutoSize property so i added additional space and it works fine for me.
Given the problem of changing orientation or switching width and height, you could completely change the way the preferred size is calculated.
public override Size GetPreferredSize(Size proposedSize)
{
Size s = base.GetPreferredSize(proposedSize);
if (AutoSize)
{
s.Width += 15;
}
return s;
}
I have not done this before, I believe you can theoretically override a property declaration (via the new keyword) and check the orientation before proceeding:
override public bool AutoSize
{
set
{
if( /* orientation is horizontal */ )
{
base.AutoSize = value;
}
else
{
// do what you need to do
}
}
}
If think a solution is to override OnResize itself :
protected override void OnResize(EventArgs e)
{
if (AutoSize)
{
// Perform your own resizing logic
}
else
OnResize(e);
}