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.