Mastering the Tao of Personal Computing

Inspirational stuff!!! RT @GuyKawasaki: 15 weird but useful inventions http://om.ly/?jwJ via http://om.ly/?jwK NC 7 hrs ago

Flex 3 Designer ScrollBar - Fixed Size ScrollThumb

May 20th 2008
20 Comments
respond
trackback

Recently Grant Skinner wrote about Designer ScrollBars in Flex 3 - this post is very useful in helping you amputate important usability features of the ScrollBars, but it omits another further disabling requirement which is also very common when implementing designer scrollbars. I’m talking about scrollbars with fixed size ScrollThumbs. One of the scrollbar features is to set the size of the ScrollThumb according to the size of the content that needs to be scrolled. Take a look at the sample application - the scrollbars are skinned and the ScrollThumb is sized nicely (or not so nicely) according to the content:

Well, I find this feature very useful - the size of the ScrollThumb is giving the user a hint about the amount of content that needs scrolling. But vanity has almost never been in favor of usability, so I’ll have to share with you how to accomplish this further amputation of the scrollbar features.

To make a fixed size ScrollThumb you need to do roughly two things:
1. To extend the ScrollThumb class by overriding the setActualSize() method.
2. To replace the ScrollThumb on the existing ScrollBar instance.

I’m providing a sample application with a View Source option, so you can check how I’ve implemented the FixedSizeScrollThumb and how the scrollbar is skinned:

I prefer to specify the size of the FixedSizeScrollThumb with styles. In case of a very tiny thumb it’s useful to make the hitArea of the thumb a bit larger. I use styles again to specify this additional padding for the hitArea.

Now, we’re ready to replace the traditional ScrollThumb with the new FixedSizeScrollThumb, and this is the most tricky and controversial part of the task. I use a simple utility class ScrollBarUtil, which provides the static replaceScrollThumb method:

public static function replaceScrollThumb(scrollBar:ScrollBar):void
{
    if (scrollBar != null && !(scrollBar.scrollThumb is FixedSizeScrollThumb))
    {
	scrollBar.removeChild(scrollBar.scrollThumb);

	var fixedSizeScrollThumb:FixedSizeScrollThumb = new FixedSizeScrollThumb();
	fixedSizeScrollThumb.x = scrollBar.scrollThumb.x;
	fixedSizeScrollThumb.y = scrollBar.scrollThumb.y;

	fixedSizeScrollThumb.styleName = new StyleProxy(scrollBar, null);
        fixedSizeScrollThumb.upSkinName = "thumbUpSkin";
	fixedSizeScrollThumb.overSkinName = "thumbOverSkin";
        fixedSizeScrollThumb.downSkinName = "thumbDownSkin";
	fixedSizeScrollThumb.iconName = "thumbIcon";
	fixedSizeScrollThumb.skinName = "thumbSkin";

	scrollBar.scrollThumb = fixedSizeScrollThumb;
	scrollBar.addChildAt(fixedSizeScrollThumb, scrollBar.getChildIndex(scrollBar.downArrow));
    }
}

This method needs an initialized ScrollBar instance, which you can get usually like this:

ScrollBarUtil.replaceScrollThumb(
    ScrollBar(someFlexContainer.rawChildren.getChildByName("verticalScrollBar")));

By now, we know how to replace the ScrollThumb, but still we don’t know when to do it. Which is the moment when the container’s scrollbar is available and needs to change its thumb? In the sample above I’m using the updateComplete event handler of the Flex Container.

I know this ScrollBar looks more like a VSlider or HSlider, and maybe you will try using a skinned slider instead of a scrollbar, but you should be aware that this approach will need custom code to detect whether the content needs scrolling, which is already implemented in the Container class from the Flex Framework.


This post is tagged , , , , , ,

20 Comments

  1. kevin

    Awesome! This helped big time.

  2. anna

    Is it possible to make this work for a TextArea? It doesn’t seem to have public access to rawChildren or verticalScrollBar…

  3. admin

    Hi, Anna,
    If you read carefully, you’ll see that the sample above is suitable only for components inheriting the Flex Container component. The TextArea component inherits from the ScrollControlBase component, which does not have rawChildren, but has verticalScrollBar and horizontalScrollBar properties. The problem you need to solve is how to access these properties when they are declared as protected. It’s easy - you just need to extend the Flex TextArea in the following way:

    package
    {
        import mx.controls.TextArea;
        import mx.controls.scrollClasses.ScrollBar;
    
        public class TextArea extends mx.controls.TextArea
        {
            public function getVerticalScrollBar() : ScrollBar
            {
                return verticalScrollBar;
            }
    
            public function getHorizontalScrollBar() : ScrollBar
            {
                return horizontalScrollBar;
            }
    
            public function TextArea()
            {
                super();
            }
        }
    }
    

    Now these protected properties are exposed through public methods and you can use them just like in the samples above.

    Cheers

  4. anna

    You’re absolutely right - work like a charm now!!! Thank you for this excellent extension.

  5. Alberto

    Hi Vladimir,

    I need to have the vertical scrollbar of a container, let’s say a Canvas, outside the Canvas. What Im doing right now is hidind the verticalScrollBar of the Canvas and using an independent VScrollBar component.

    On creationcomplete of the container, I set the values for the scrollbar just like:

    scrollbar.maxScrollPosition = container.verticalScrollBar.maxScrollPosition;
    scrollbar.invalidateDisplayList();
    

    It seems to work fine BUT the thumbSize of the independent scrollbar is not sized correctly; the min and max values are correct but the thumbSize is smaller than the canvas’ vertical scroll bar.

    Do you know how can I fix this?

    Thank you in advance.

    Alberto

  6. admin

    Hi Alberto,

    If I’m getting it right - you have a Canvas, which has no scrolling (scroll policies of the Canvas are set to “off”), and you want to control the scrolling position of that Canvas with a separate scroll-bar.

    The solution to the problem depends on where the independent scroll-bar is placed.

    Variant 1 - The Canvas and the scroll-bar are placed inside another Container

    The solution is easy - you gotta keep the scroll policies of the contained Canvas to “off”, but you also need to add clipContent=”false” and make sure you haven’t switched off the scrolling policies of the Canvas Container. Thus you won’t need anymore an independent scroll-bar - the scroll-bar will show automatically inside the Canvas Container.

    Variant 2 - The Canvas and the scroll-bar are placed in different containers, or the layout is so complicated so you need an independent scroll-bar to fulfill layout requirements

    Every scroll-bar has a ScrollThumb, which is basically a Button - its dimensions vary depending on the size of the scrolled content. Simply setting minScrollPosition and maxScrollPosition is not enough - these positions are setting the range within we scroll, but in order to change the size of the ScrollThumb, you need to supply some more information like pageSize.

    On creation completion try using the setScrollProperties()-method of the ScrollBar. Check its signature in the reference documentation.

    Cheers

  7. Alberto

    Hi Vladimir,

    First of all, thank you for your fast and good response.

    Yesterday, browsing the code of the ScrollBar component, I found the same solution you point out: using setScrollProperties() you can set all the scrollbar properties of an independent scrollbar.

    So the problem was: I have one container with things and I dont want this container to show its internal scrollbar but I want this scrollbar outside the container. The way to do this is using an independent scrollbar and copy all the settings of the container scrollbar to this new scrollbar. But, what if I dont have access to the container scrollbar( scrollpolicies are set to “off”? What If I cant access container.verticalScrollbar.pageSize or maxScrollposition, etc? How can I calculate the needed parameters for the setScrollProperties method?

    Thanks.

    Alberto

  8. Alberto

    Hi again Vladimir,

    I have finally found a working solution. I have created a public method on my container class to calculate everything as the Container class does in its private createScrollbarsIfNeeded method. Then, I have created a public method, getScrollProperties, to return all these calculated properties in a custom class, ScrollProperties.

    Now I can have the container scrollbar “off” and access the expected scroll properties if I had the scrollbars of the container ON.

    I dont know if this is the best way to do it but it works :)

    Cheers.

    Alberto

  9. admin

    Nice hearing from you, Alberto,

    Basically you’ve done a custom component - when you do custom components you should always ask yourself the question: “Do I really need a custom component?”
    Very often after nice thorough discussion with a designer you’ll find ways how to fit most of the things with nesting components, using constraints, styles etc. Doing this will prevent numerous hours of coding, testing and fixing bugs - after all the guys from Adobe have done lots of stuff with and inside the Flex Framework - try first utilizing the most of it.
    When you have a solution based on off- the-shelf Flex Framework components, you can start analyzing is it OK in terms of performance and construction complexity. The conclusions from such analysis will lead you to the decision whether you need to code brand new components.
    Nevertheless, you can always base your custom implementation on top of the Flex Framework implementation - the Flex components are priceless source of ideas and code that you can use - sometimes by just copying useful fragments, like I suppose you’ve done in your solution after careful reading of the Container’s code.

    Best Regards,
    Vladimir

  10. Mano

    Thanks for the nice tutorial, but it’s difficult for a Designer.
    I tried to go with the following. Works fine as long as the height is not fixed,
    but when I draw rectangle with fixed height e.g. 20, the total scroll-bar turns into a half.
    How can I handle that?

    Please Help!

    package classes
    {
        import mx.controls.scrollClasses.ScrollThumb;
        import mx.skins.ProgrammaticSkin;	
    
        public class myScrollBar extends ProgrammaticSkin
        {
            private var scrollH:Number;
    
            public function myScrollBar()
            {
                super();
            }
    
            override protected function updateDisplayList(w:Number, h:Number):void
            {
                if(parent is ScrollThumb)
                {
                    var myScrollThumb:ScrollThumb = ScrollThumb(parent);
                    scrollH = myScrollThumb.height;
                }
    
                super.updateDisplayList(w,h);
                graphics.clear();
    
                graphics.beginFill(0x000000);
                graphics.drawRect(0,0,15,scrollH);
                graphics.endFill();
            }
    
        }
    }
    
  11. admin

    Hi, Mano,
    I’m not sure what is supposed to do the code fragment you have put in your comment, and to which component you’re trying to apply this programmatic skin.

    The article is definitely not written as a tutorial - it is more like a programmers guide for how to make a fixed sized scroll thumb. And it’s a bit hard to read and understand if you’re a designer, involved only with skinning and styling, but not in writing custom Flex components. So, I’ll try to dumb down my article in a set of steps, that are easy to follow from a designer:

    1. Define the skin for the scroll-bar:

    VScrollBar
    {
        down-arrow-skin: ClassReference("VScrollBar_downArrowSkin");
        up-arrow-skin: ClassReference("VScrollBar_upArrowSkin");
        track-skin: ClassReference("VScrollBar_trackSkin");
        thumb-skin: ClassReference("VScrollBar_thumbSkin");
        thumb-icon: ClassReference("VScrollBar_thumbIcon");
    }
    

    This skin defines how your scroll-bar is going to look like. But this step is not enough to have a scroll-bar with a fix sized scroll thumb.

    2. Download the source code from the samples in the article from here.
    Copy-paste into your project the component and util packages from the code you’ve just downloaded.

    3. Define the skin for the fix sized scroll thumb:

    FixedSizeScrollThumb
    {
        thumbWidth: 4;
        thumbHeight: 4;
        hitAreaPadding : 10;
    }
    

    The styling in the fragment above defines the fixed dimensions of the scroll thumb. In cases of really tiny scroll thumb you can set a padding area with a particular size that, so you can grab the thumb everywhere inside this area, not just on the visible dimensions.

    4. Replacing the scroll bar scroll thumb with a fixed size scroll thumb. This is the step where you need a programmer to get involved, but you can try it yourself.

    First, you need to identify which is the container that will have a scroll bar. Let’s imagine this Canvas is that container:

    <mx:Canvas id="longTextContainer" width="240" height="200" clipContent="true" x="0" y="0"
        updateComplete="ScrollBarUtil.replaceScrollThumb(longTextContainer.verticalScrollBar);">
        <mx:Text width="90%" text="{loremIpsumLong}"/>
    </mx:Canvas>
    

    All you need to do is to add this attribute to the Canvas MXML:

    updateComplete="ScrollBarUtil.replaceScrollThumb(longTextContainer.verticalScrollBar);"
    

    The direction you’ve chosen for your solution has the advantage of declaratively skin all scroll thumbs in an application, instead of using programming logic to selectively skin just the scroll thumbs of the containers you’re interested in.

    Good luck!

  12. Mano

    Thanks for the sudden response :)
    Sorry, I couldn’t add the total detail before!
    Here is the complete code which will describe my intentions!

    I have 1 more question! I saw in the reference of ScrollThumb that it has a maxHeigh and minHeight properties! How can I change these if I have a TextArea?

    Do you have any Flex Forum which you use to help guys like me?

    This is the MXML file with the style declarations:

    <mx:Canvas>
    
        <mx:Style>
            .myscroll {
                background-alpha: 1;
                up-arrow-skin: ClassReference( null );
                down-arrow-skin: ClassReference( null );
                track-skin: ClassReference( null );
                thumb-skin: ClassReference("classes.myScrollBar");
            }
        </mx:Style>
    
        <mx:TextArea
            verticalScrollBarStyleName=”myscroll”
            width=”110″ height=”110″ text="{a_long_paragraph}"/>
    
    </mx:Canvas>
    

    classes/myScrollBar.as

    package classes
    {
        import flash.geom.Rectangle;
    
        import mx.controls.scrollClasses.ScrollThumb;
        import mx.skins.ProgrammaticSkin;	
    
        public class myScrollBar extends ProgrammaticSkin
        {
            private var scrollH:Number;	
    
            public function myScrollBar()
            {
                super();
    	}
    
           override protected function updateDisplayList(w:Number, h:Number):void
           {
                if(parent is ScrollThumb)
                {
                     var myScrollThumb:ScrollThumb = ScrollThumb(parent);
                     scrollH = myScrollThumb.height;
                }
                //super.updateDisplayList(w,h);
                graphics.clear();
                graphics.beginFill(0x000000);
                graphics.drawRect(0,0,15,scrollH);
                graphics.endFill();
                parent.parent.height = 110;
           }
    }
    

    My approach (if parent is ScrollThumb) might be wrong as I’m a designer and trying to write a little code!
    I want it to accomplish this feature using as simple technique as I can digest it ;)
    If there is not a simple way then please help me by setting maxHeigh and minHeight properties.

    Thanks once again

  13. admin

    Mano, I have tried your approach with the programmatic skin, but I had no luck with it - the visual appearance of the ScrollThumb is like it has a fixed size, but it actual dimensions are not. I’ve tried to override setActualSize, but again no luck - something else is setting the actual dimensions.

    So, you have to stick with the steps I’ve described you in my previous response.

    First, you should do steps 1, 2, 3.

    Then, check out my MXML file:

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application layout="absolute"
        xmlns:mx="http://www.adobe.com/2006/mxml">
    
        <mx:Script>
            <![CDATA[
                import mx.core.mx_internal;
                import util.ScrollBarUtil;
    
                use namespace mx_internal;
            ]]>
        </mx:Script>
    
        <mx:Style>
    	.fixedThumbScrollBar
            {
                down-arrow-skin: ClassReference(null);
                up-arrow-skin: ClassReference(null);
                track-skin: ClassReference(null);
                thumb-skin: ClassReference(”skin.ScrollThumbSkin”);
            }
    
            FixedSizeScrollThumb
            {
                thumbWidth: 15;
                thumbHeight: 15;
                hitAreaPadding : 10;
            }
        </mx:Style>
    
        <mx:TextArea id=”textArea” verticalScrollBarStyleName=”fixedThumbScrollBar” x=”20″ y=”20″ width=”200″ height=”90″
            updateComplete=”ScrollBarUtil.replaceScrollThumb(textArea.scroll_verticalScrollBar)”/>
    
    </mx:Application>
    

    In this sample I use a programmatic skin very similar to yours:

    skin/ScrollThumbSkin.as

    package skin
    {
        import mx.controls.scrollClasses.ScrollThumb;
        import mx.skins.ProgrammaticSkin;
    
        public class ScrollThumbSkin extends ProgrammaticSkin
        {
            override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
            {
                var scrollThumb : ScrollThumb = ScrollThumb(parent);
    
                var thumbWidth : Number = scrollThumb.getStyle("thumbWidth");
                var thumbHeight : Number = scrollThumb.getStyle("thumbHeight");
    
                if (!isNaN(thumbWidth) && !isNaN(thumbHeight))
                {
                    graphics.clear();
                    graphics.beginFill(0x000000);
                    graphics.drawRect(thumbWidth/2, 0, thumbWidth, thumbHeight);
                    graphics.endFill();
                }
            }
    
        }
    }
    

    Please, follow the steps carefully and try it like this!

  14. Mano

    Thanks, Works Perfect :)
    Looking forward to see some more tutorials and unique topics from you
    Thanks once again.

  15. Damian

    Hi,
    I’ve been trying to apply your code to the TileList. Seems like it can’t find the rawChildren property at all. Any idea on how to go about it??

    Thanks,
    Damian

  16. admin

    Hi, Damian,
    It’s a bitch, but there is still no universal structure for all Flex components - some of them have directly the properties verticalScrollBar and horizontalScrollBar (e.g. Canvas), some of them are extending the ScrollControlBase component (e.g. TextArea, TileList). While the ScrollControlBase has the getters scroll_verticalScrollBar and scroll_horizontalScrollBar, they are marked with the mx_internal access modifier instead of being provided with public access. You can still access these properties, but you need to use the following imports:

    import mx.core.mx_internal;
    use namespace mx_internal;

    The sample code below should show you how you can fix your problem:

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application layout="absolute"
        xmlns:mx="http://www.adobe.com/2006/mxml">
    
        <mx:Script>
            <![CDATA[
                import mx.core.mx_internal;
                import util.ScrollBarUtil;
    
                use namespace mx_internal;
            ]]>
        </mx:Script>
    
        <mx:Style>
    	.fixedThumbScrollBar
            {
                down-arrow-skin: ClassReference(null);
                up-arrow-skin: ClassReference(null);
                track-skin: ClassReference(null);
                thumb-skin: ClassReference(”skin.ScrollThumbSkin”);
            }
    
            FixedSizeScrollThumb
            {
                thumbWidth: 15;
                thumbHeight: 15;
                hitAreaPadding : 10;
            }
        </mx:Style>
    
        <mx:TileList id=”tileList” verticalScrollBarStyleName=”fixedThumbScrollBar”
            updateComplete=”ScrollBarUtil.replaceScrollThumb(tileList.scroll_verticalScrollBar)”/>
    
    </mx:Application>
    

  17. Alexandre

    Hi there, i was trying to use your scrollbar, i wonder how do you do the glow effect in your ScrollThumb,

    Thanks
    Alex

  18. admin

    Hi, Alex,
    I don’t have to do anything to have a glow effect in my scroll thumb - well, that’s the programmer’s perspective in case you have someone specialized in skinning and styling components - this guy or girl (a.k.a. deseloper) usually uses the Flash IDE to prepare the symbols composing the skin and then compile them into SWC. The deseloper can put in those symbols whatever he wants - transparency, glow, blur, etc.

    Another approach (which I don’t recommend) is to make a programmatic skin, where instead of using the cool drawing facilities of the Flash IDE, you code your ass off in attempts to achieve the nice glow. Programmatic skins have its application in some scenarios, but I doubt this simple scroll thumb needs so much effort.

    Good luck, Alex

  19. I spent two days looking how to do this. I tried to override the setHeight on the scrollThumb, but it won’t work. You really made my day today.

Leave a Reply