WinForms v2 - How to scale a form manually?

G

Guest

I have a form that contains various UserControls (these are rendered simply
as boxes on the screen in different colors). I am trying to figure out how
to make the Controls within this form "scale" depending upon how large the
user has made the form.

I have found that by changing the fonts and using the AutoScaleMode property
(set to Font) that I can achieve this "scaling behavior" but I don't want to
change the entire application ... only the contents of this specific form.
Not to mention that I don't want to go into Control Panel on the OS and
change the DPI/Font settings. I need a different way.

The idea is if a user had the form set to a size of 200 x 300, and then
changes the form to size of 400 x 600 the contents of this form would "scale
up" by the same percentage. It's simple to change the Width and Height of
each Control and adjust its size by the percentage in (in this case 100%) the
form's size change. But, my problem is making sure the Controls all maintain
their original relative positions to each other and that the white space in
between controls (the blank space) remains to scale and position too.

Does anyone have a way they can recommend that will scale a form's contents
as the hosting form get's larger (or shrink it as the host get's smaller)?

Thanks for any and all advice.
 
G

Guest

Thanks for your reply Kevin.

I've tried working with the Anchor property but the problem I'm running into
is that I need for the screen to scale the contents in a "proportional"
manor. Allow me to explain.

First off, it's entirely possible (read ... probable) that I'm doing this
wrong. But, using the Anchor property I notice the following behavior:
let's say I have a form that is 100 pixels wide with a Control on the form
that is Located such that Control.Location.X = 15 pixels, and Control.Width =
25 pixels. Lastly, let's say I've set the Anchor to Left | Top | Right
because I want the Control to "scale" as I widen the form.

When I drag the form's right boarder to a width of 200 pixels the Control
will stretch such that right edge of the Control will equal 140, which is not
proportional. Simply, the original setting had the right edge of the Control
at 40 (Control.Location.X = 15 + Control.Width = 25 equals 40), leaving 60
pixels between the Control's right edge and the right edge of the form
(original value was Form.Width = 100).

Based on what I've observed so far the "scaling" is in absolute pixels and
not based on percentage of change. What I'm trying to achieve is for the
control to double in width (location is still 15, but the right edge should
be 65), because the form doubled when we change it to 200 pixels. If we
changed the form's width from the original 100 to 150 (a 50% increase) then
the Control should only increase its width by 50% yielding a right edge value
of 38 (actually 37.5, but rounded because of the Integral value type).

What I get instead is no matter what I change the width of the form I will
always have 60 pixels between the right edge of the Control and the right
edge of the form .... this is why I call it "absolute" scaling instead of
proportional or "percentage of change."

The point of all this is I need for my forms contents to stay to scale as
the form increases or decreases in size - each Control of the form needs to
consume the same ratio of screen real estate as when it was setup in the
designer (by the way I use VS 2005) - analagous to taking a 3x5 picture and
"blowing" it up to a 6x10. Everything stays to scale.

I'm new to Forms development, but I was hoping their might be a settings
that would allow the form to scale things this way - can't find it if it
exists. Instead, the only thing I've stumbled across is changing the
accessibility settings via the operating system .... this won't work. I need
for this type of scaling only on a single form out of the entire application.

Sorry for the overly descriptive explanation but I'm hopeful that the extra
detail will help somehow.

Any thoughts or suggestions? Again, thanks for you help.
 
G

Guest

A quick correction of my second example:

If we changed the form's width from the original 100 to 150 (a 50% increase)
then
the Control should only increase its width by 50% yielding a right edge value
of 53 (not the 38 I said perviously).

Control.Location.X = 15 + (original Control.Width = 25 + a 50% increase
yielding 12.5 pixels = 38, rounded) .... giving a right edge X value fo 53.
 
S

Sebastian Rettig

Hi,

If you use .NET 2.0, you might want to have a look at TableLayoutPanel.
You simply put a table onto your Form and set the Dock property to Fill.
Then you can add some cells and put your custom controls into them. Set
the Dock-property of these controls to Fill too. To change the space
between the controls change their Margin-properties. If you want to set
the relative width and height of the cells modify the Columns property.

You can also put another TableLayoutPanel into one of the cells of the
main table, if you want a more complex layout. Do the layout of your
entire Form with TableLayoutPanels.

This will not scale the aspect ratio of the controls proportionally. If
the width and height of your form in the Designer is, say 200 x 500 and
you runtime scale the Form to 400 x 750, a control that was designed
with 100 x 100 will be 200 x 150.
If you want a fixed aspect ratio, you might achieve this if you override
the OnSizeChanged method of your form and make sure that your Form
always has the correct aspect ratio.

Sebastian
 
G

Guest

Well Kevin ... it didn't work entirely the way I needed it to, but thank you
for pointing me in the right direction. Bravo and thanks! Couldn't have
done it without your suggestion.

Here's what I ended up going through: first off, I added a call to the
Control.Scale() method in my SizeChanged() event handler. The problem was
the Scale() method takes a SizeF parameter, and as I continued to drag the
boarder of the TabPage ... my container for the UserControls ... I would
apply my percentage change, which is a floating point value, to an integral
value type (ultimately the Bounds for the UserControl). The reason my SizeF
parameter is a floating point type is because when I take the current size of
the Tab and divide that into the previous size you end up with a percentage.
All my UserControls need to "scale" the same percentage amount so that they
consume the same relative amount of real estate at the new size as they did
at the previous size. The TabPage's new size get's stored as the value that
we'll compute against when the size changes again (it will become the divisor
when we run this calculation again).

My problem came from the fact that I would use the previous size as the
number I would divide into with the TabPage's current client size (this is
the new size created by the user dragging the TabPage's boarder) that gets
handled in the SizeChanged event handler. The actual problem is taking a
value of 0.9722345 and applying it to a a number like 835, then taking a
value of 0.9888457 to 800 .......... by the time the TabPage goes from a
width of 850 to 425 (1/2 the original size, which means the UserControls
should be scaled to 50%) the relative sizes and positions between the
UserControls gets "slightly out of wack" because of the systematic rounding
errors that I have introduced by applying a float to an integral value
repeatedly.

The Fix --- store the original design-time values as a readonly field, and
divide into that figure the current container size (this will give you the
percentage change you need to use in the Control.Scale() method). This way
rounding errors do not "build up" on each other as the user changes the size
of the form over and over again. A cool logic flaw, though, because it makes
the UserControls "walk away" from each other, or "climb on top" of each other
after repeated invokations.

But this is only half the problem --- this only works if you can take the
UserControl as "reset" it back to its design values. Otherwise, you will
keep taking a percentage of a percentage of a percentage ..... you get the
idea. For this to be accurate you need to apply the Scale value to the
design values each time. Here's were it gets fun. Calling a "reset" method
before calling the Control.Scale() method ... to set the Size & Location back
to the values that were used in the designer and then applying the float
percentage ... causes A LOT of screen flicker. Put it this way: you're
taking a size & location, changing it back to the original values used at
design time, then changing them again to the new values that were calculated
in the SizeChanged() event handler ......... lots of flicker.

The Fix ---- my UserControl now implements a "DoScale" method (which gets
called from the SizeChanged() event handler for each Control on the TabPage)
passing in the newly calculated scale value. My Control.DoScale() method
then calls the Control.ScaleControl() method (according to the documentation
when you call Control.Scale() the Scale() method calls ScaleControl()
internally). ScaleControl() handles the actual scaling logic. ScaleControl
internally calls an Overridden helper method of Control.GetScaledBounds()
whereby I apply my passed-in scale value against the design size & location
from the SizeChanged() event handler.

The point?? I can now do the appropriate math in the GetScaledBounds()
method (new percentage x origial design values) without actually "resetting"
the size & location, and thus eliminating the flicker. Not to mention the
performance is MUCH better because you're not invoking properties and methods
unneccessarily.

The answer ---- implement a "DoScale(float widthRatio, float heightRatio)"
method in the UserControl that needs to scale itself. Pass in the relative
percentage values - both horizontal and vertical ratio values - to this
method. Call "ScaleControl()" from within DoScale(). ScaleControl
internally will call the GetScaledBounds() helper method. Override
"GetScaledBounds()" and have it do the multiplication and return the new
Rectangle (widthRatio x Deisgn Width .... heightRatio x Design Height) .....
don't forget to do this for both Location and Size.

You're done.

One last note .... you will create a bug (sort of) by overriding the
GetScaledBounds() method. This happens in the designer. When you add the
UserControl to your form, but after you build it, you will notice the Control
disappears (still there at runtime, though). This is obviously getting
called in the design process, so your overridden code needs a flag of when it
should run - set this before you make the call to ScaleControl() in the
DoScale() method, and unset it after the call. In the GetScaledBounds()
method check to see if the flag is set? If it is, run your code, otherwise
call "base.GetScaledBounds( parameter list )" instead.

A llllooooonnnnnnnggggggggg winded explanation, but I thought I would pass
this along because I have to admit it works really well. Thought others
might use this code if they need controls that scale proportionately when
their container changes size.

Thanks for pointing me in the right direction Kevin.
 
G

Guest

Thanks for the idea Sebastian. Unfortunately for me my form has a very
complicated series of relationships between controls that would have multiple
cells within the table having to overlap each other.

The only viable way to do this is if the controls are smart enough to change
their own location and sizes appropriately. Which, by the way, I think we
successfully were able to do. Please check the other response to "Kevin" on
how I implemented this functionality.

Thank you for your ideas.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top