Thursday, September 18, 2008

Vertical Scrollbars and 100% width content

I've often wished Flex would simply shrink the available width of my containers when their contents grow too tall, but alas, often you'll find yourself with a horizontal scrollbar that only exists to show you the content that's just been hidden by the vertical scrollbar that wasn't there a minute ago!

It's a common question, and the Adobe guys had their reasons for making it how it is. Not much consolation for the rest of us though! So, a (perhaps not the, but a) solution:

package info.joshmcdonald.barra.components
{

Shameless self-promotion: I've created a google code project for code examples and utils from my blog.

    import flash.display.DisplayObject;

    import mx.containers.Canvas;
    import mx.core.UIComponent;

    public class SmartVerticalScroll extends Canvas
    {
        override public function addChild(child : DisplayObject) : DisplayObject
        {
            return addChildAt(child, numChildren);
        }

        override public function addChildAt(child : DisplayObject, index : int) : DisplayObject
        {
            if (numChildren > 0)
                throw new Error("Only one child, stick a canvas or something in here");

            return super.addChildAt(child, index);
        }

Just to keep thing simple, and this example small, we're limiting this to a single child. Not ideal, but we can get away with it by simply using it as a container for a Canvas or a VBox or similar.

        override protected function updateDisplayList(unscaledWidth : Number, unscaledHeight : Number) : void
        {
            var innerWidth : Number = unscaledWidth - borderMetrics.left - borderMetrics.right;

            super.updateDisplayList(unscaledWidth, unscaledHeight);

            if (numChildren == 1)
            {
                var child : DisplayObject = getChildAt(0);
                var uic : UIComponent = child as UIComponent;

                if (uic)
                {
                    if (verticalScrollBar)
                    {
                        var newWidth : Number = Math.max(Math.min(uic.width + uic.x , innerWidth - verticalScrollBar.width), uic.measuredMinWidth);
                        uic.setActualSize(newWidth, uic.height);
                    }
                }
                else
                {
                    child.x = 0;
                    child.y = 0;
                }
            }
        }

Here's where half the magic lies. If we have a vertical scrollbar, we'll shrink our child component to fit within it, unless that would violate its minWidth requirement.

        override public function validateDisplayList() : void
        {
            var before : Boolean = verticalScrollBar == null;
            super.validateDisplayList();
            var after : Boolean = verticalScrollBar == null;

            if (after != before)
            {
                //Scrollbar added or removed - repaint and resize inner component
                invalidateDisplayList();
            }
        }
    }
}

Here's the other half of the magic. When we determine that Flex's built in layout code has decided to add a vertical scrollbar, we make sure to schedule another call to our updateDisplayList() so we can shrink the content down to get out of the way.

6 comments:

Amy B said...

Thanks, Josh ;-)

Влад said...

Could you give an example with usage of your class?

Josh McDonald said...

It's pretty simple, just put another container in it (like a VBox for example), with a percentage width, or left and right widths. When the box becomes too tall to fit, a scrollbar will appear and the box will become slightly narrower to accomodate it.

Hintington Beach Greg said...

Apr 30 2009

Hi...

Fyi the link in this seciton of your blog is stale:

" Here's the other half of the magic. When we determine that Flex's built in layout code has decided to add a vertical scrollbar, we make sure to schedule another call to our updateDisplayList() so we can shrink the content down to get out of the way.

Besides the Google Code repo, source for this class can be viewed here."

Cheers GHudd

geoff said...

Here is a much easier way to correct this issue...

[code]
package
{
import mx.containers.Canvas;
import mx.events.ResizeEvent;

public class FixedCanvas extends Canvas
{
public function FixedCanvas()
{
super();
}

override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number): void {
if (verticalScrollBar ) unscaledWidth -= verticalScrollBar .measuredWidth;
if (horizontalScrollBar ) unscaledHeight -= horizontalScrollBar.measuredHeight;
super.updateDisplayList(unscaledWidth, unscaledHeight);
}
}
}
[/code]

This fixes the issue for both vertical and horizontal scrolling

Josh McDonald said...

That's a much better solution Geoff, cheers! I might switch to using that and see how it goes. I can't see anything in Container that would stop it working in edge cases.

This is

  • Tales of Flex
  • From Brisbane, Australia
  • Opinions on Flex development
  • Tips and FAQs
  • Shameless self-promotion

I am

  • Twitterer
  • Flexcoder
  • Maroon
  • Designer
  • Java lover
  • That loud-mouthed Aussie yob
  • Blogger
  • Problem solver
  • Contributor
  • Cricket Fan
  • Lousy photographer
  • Great cook

I read



Subscribe via RSS to receive updates!