Adobe Flex/AIR: Scrolling a sub-component, not the whole window - apache-flex

I'm developing an application with Adobe Flex and AIR, and I've been banging my head against the wall trying to figure out how to solve a scrolling issue.
The basic structure of my main application window (simplified greatly) is this:
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
paddingTop="0" paddingRight="0" paddingBottom="0" paddingLeft="0"
width="800" height="600" layout="vertical" verticalAlign="top"
>
<mx:VBox id="MainContainer" width="100%" height="100%">
<mx:Panel id="Toolbars" width="100%" height="25" />
<mx:HDividedBox width="100%" height="100%" >
<mx:Panel id="Navigation" minWidth="200" height="100%" />
<mx:VBox id="MainContent" width="100%">
<mx:Panel width="100%" height="200" />
<mx:Panel width="100%" height="200" />
<mx:Panel width="100%" height="200" />
<mx:Panel width="100%" height="200" />
<mx:Panel width="100%" height="200" />
</mx:VBox>
<mx:Panel id="HelpContent" minWidth="200" height="100%" />
</mx:HDividedBox>
<mx:Panel id="FooterContent" width="100%" height="25" />
</mx:VBox>
</mx:WindowedApplication>
The trouble is that the "MainContent" box might contain a huge list of subcomponents, and the presence of that long list causes a vertical scrollbar to appear at the highest level of the GUI, surrounding the "MainContainer" vbox.
It looks really silly, having scrollbars around the entire application window.
What I'm looking for instead is a solution where the scrollbar is only applied to the "MainContent" vbox (as well as the Navigation and HelpContent panels, if their content stretches past the window bounds).
I found a related question on StackOverflow, where the problem's solution was to use "autoLayout" and "verticalScrollPolicy" attributes on parent containers.
So I tried adding autoLayout="false" and verticalScrollPolicy="off" attributes to all of the parent containers, as well as verticalScrollPolicy="on" to the "MainContent" vbox. But the end-result of that experiment was that the content was simply clipped from the main container (and a useless scrollbar with no thumb was added to the MainContent vbox).
Anyone know how to solve this?

An HBox or a VBox will try as hard as possible to display its content without scroll bars. This forces a parent container (often all the way up to the main Application) to be the one that must scroll if the content is too large to fit in the available bounds.
Behind the scenes, the HBox or VBox is setting the measuredMinWidth and measuredMinHeight properties in its measure() function to match the dimensions required by its children. The parent container will honor that recommendation and the task of scrolling moves up the display list.
hasseg's solution works in many cases because it stops the container from measuring, but it's kind of hacky. Here's what you can do without building replacement subclasses for your containers. On the container instance that you want to scroll, set minWidth or minHeight to 0. This will take precedence over the measuredMinWidth or measuredMinHeight properties of that container, allowing the parent to set the actual size to something more manageable.

Found a solution.
Looks like the only way to prevent the VBox from aggressively extending its vertical space (and forcing its parents to grow scrollbars) is to wrap the VBox in a Canvas.
There's a handy little component here, called ScrollableVBox, which performs the workaround while taking care of a few bookkeping issues (like adding and removing children to the VBox, passing them around the Cavas wrapper).

Instead of wrapping the VBox in a Canvas, set minHeight property of the VBox you want to have scrollbar to 0.

Your problem seems like a lot like one that I struggled with some time ago. I found my answer from this discussion: just disable the measure() implementation of the Box.
This was a pretty easy fix which worked perfectly in my case and hasn't caused any "collateral damage". Your results may vary.
package whatever
{
import mx.containers.Box;
/**
* A Box that has no measure() implementation.
*
* <p>
* See http://old.nabble.com/-flex_india%3A3318--Size-layout-issues-with-respect-to-parent-containers-to12882767.html
* for more info.
* </p>
*/
public class NonMeasuredBox extends Box
{
/**
* Constructor
*/
public function NonMeasuredBox():void
{
super();
}
override protected function measure():void { /* disabled */ }
}
}

Related

Placing Flex-component in the middle of the screen

I have a very general question and have prepared a simple test case.
When using BasicLayout (i.e. absolute positioning) - what is the best approach to place a Flex component in the center of the application?
In the test case below I'm using x="{width/2}" but this gives me at least 2 problems:
How do I account for the component dimensions (the ProgressBar in the test case)?
Is the binding {width/2} a good idea to use? Wouldn't it send unnecessary DATA_CHANGE events in some cases?
And finally I wonder, how this all applies to full screen applications using StageScaleMode.SHOW_ALL - because currently my application is aligned to the top-left in the full screen mode and there is a white dead zone on the right of it. (Which is at least a change from pure Flash/AS3 behaviour, where the full screen content is displayed in the center of the screen and 2 dead zones on the left and right).
<?xml version="1.0" encoding="utf-8"?>
<s:Application
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="700" height="525"
backgroundColor="#CCFFCC"
initialize="systemManager.stage.scaleMode=StageScaleMode.SHOW_ALL">
<fx:Script>
<![CDATA[
private function fullScreen(event:MouseEvent):void {
stage.displayState =
stage.displayState == StageDisplayState.NORMAL ?
StageDisplayState.FULL_SCREEN :
StageDisplayState.NORMAL;
}
]]>
</fx:Script>
<s:states>
<s:State name="normal" />
<s:State name="connected" />
</s:states>
<s:CheckBox right="10" bottom="10"
label="Full screen"
click="fullScreen(event)" />
<mx:ProgressBar indeterminate="true"
x="{width/2}" y="{height/2}"
label="Connecting..." labelPlacement="center"
includeIn="normal" />
</s:Application>
Way easier than that: just use horizontalCenter and verticalCenter properties of UIComponent. When used inside a BasicLayout the component will always be centered. Like this:
<mx:ProgressBar indeterminate="true"
horizontalCenter="0" verticalCenter="0"
label="Connecting..." labelPlacement="center" />
If you give these properties a value other than '0' the component will be offset from the middle by the amount of pixels you specify as a value. (e.g. horizontalCenter="50" will offset the component 50 pixels to the right of the center of the parent component)
That white space is probably due to your usage of systemManager.stage.scaleMode=StageScaleMode.SHOW_ALL. Just remove that line. Why are you doing that anyway?
edit
Just noticed you're using a fixed 'width' and 'height' on your Application: you'll have to make those '100%' if you want your app to really fill the screen. This is another possible cause for that white space.
To get your application centered (rather than left/right aligned) when using systemManager.stage.scaleMode=StageScaleMode.SHOW_ALL, set systemManager.stage.align to the empty string - i.e.:
// in your Application creationComplete handler
systemManager.stage.scaleMode = StageScaleMode.SHOW_ALL;
systemManager.stage.align = "";
I typically do 2 things,
I place objects inside VGroups/HGroups with alignment, I also use relative widths eg percentages.
Think like in the old days of using HTML tables. It works very well especially if your canvas is resized allot.
All of my canvases have to be resizeable as I like to only write my apps once, for pc, mobile etc, so I just make everything scalable by default.

Resizing an image in Flex whilst keeping it central

I am using a mx:Resize effect to change the width of an image in Flex (leaving the height to look after itself) but I want the image to remain vertically centered on the page. Whilst I could calculate the aspect ratio of the photo, and work out the new y value to keep it centred, I would have to include a mx:Move effect too, and I am hopful that Flex has a simpler way.
This is for a photo gallery where images are loaded at run-time, and swapped about, so I really would like to avoid recalculating the aspect ratio and vertical position every time the updateComplete event fires. It seems so messy.
I tried putting my image inside a mx:VBox with verticalAlign="middle", thinking that the box would do all the centering for me, but this only seems to happen when the app first starts - i need it to gradually adjust the position during the period where the effect plays (to keep it central even when part-way through).
Here's a stripped-down example (which doesn't work):
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="600" height="600">
<mx:Resize id="fxResize" target="{imgPhotoA}" widthTo="350" duration="500" />
<mx:Button x="10" y="10" label="Resize" click="fxResize.play()" />
<mx:VBox id="boxPhotoA" x="100" y="20" height="500" width="350" verticalAlign="middle" borderStyle="solid" verticalScrollPolicy="off" horizontalScrollPolicy="off">
<mx:Image id="imgPhotoA" width="60" maintainAspectRatio="true" source="http://sstatic.net/so/img/logo.png"/>
</mx:VBox>
</mx:Application>
I am a newcomer to Flex, so apologies if I am doing something daft. Looking at the source of the Adobe example photo gallery hasn't helped me realise a solution - I don't think they use effects, but create their own routine to alter the size and position. The code looks horrid!
You could try creating an event handler for the effectEnd event of the Resize control, something that calls invalidateDisplayList on the VBox, something like:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="600" height="600">
<mx:Script>
<![CDATA[
private function resizeFinishedHandler(Event:Event) : void {
boxPhotoA.invalidateDisplayList();
}
]]>
</mx:Script>
<mx:Resize id="fxResize" target="{imgPhotoA}" widthTo="350" duration="500"
effectEnd="resizeFinishedHandler(event)" />
<mx:Button x="10" y="10" label="Resize" click="fxResize.play()" />
<mx:VBox id="boxPhotoA" x="100" y="20" height="500" width="350" verticalAlign="middle" borderStyle="solid" verticalScrollPolicy="off" horizontalScrollPolicy="off">
<mx:Image id="imgPhotoA" width="60" maintainAspectRatio="true" source="http://sstatic.net/so/img/logo.png"/>
</mx:VBox>
</mx:Application>
Hope this helps.
Since the boss wanted this ready for tomorrow, I had to give up on the idea of Flex doing the work for me, and calculate the values myself.
Note that I knew that all my JPGs were 120px wide, but the heights could vary. This made horizontal positioning easy, so its only the vertical positioning I will refer to below...
PART ONE
Every time the complete event fired on an image object (i.e. a JPG was loaded into it) I set its position based on its height. Assuming the centreline was a line 300 px down from the top of the screen, I used:
oImage.y = 300 - (oImage.contentHeight / 2)
I avoided the updateComplete event as it seemed to fire off hundreds of times when an effect moved/resized the image. I also avoided the height property (in favour of contentHeight) as I found a strange issue with the image tag whereby I had to set its height to a value (I only wanted to set the width really) in order for the resize effect to actually do anything. I used height="999" and let the image take care of its own aspect ratio.
PART TWO
Just before I called the play() method on my effects, I had to set the yTo value for each move effect, to ensure the image would move to the correct vertical position to take into account it being resized at the same time (e.g. if image shrinks, it needs to be moved down slightly).
For each image, I needed to work out the aspect ratio and then I could calculate what the new height of the image would be after resizing.
private function calculate_yto_value(oImage:Image, iWidthAfterTheEffect:int):int {
var iAspectRatio:Number = oImage.contentHeight / oImage.width
var iHeightAfterTheEffect:Number = iAspectRatio * iWidthAfterTheEffect
return 300 - (iHeightAfterTheEffect / 2)
}
fxMovePhoto.yTo = calculate_yto_value(imgPhotoA, 60)

Flex 3 scroll bars not added/enabled when they should be

I'm currently learning Flex, and am having the hardest time getting scroll bars to work. In short, I'm making a giant form for users to fill out, contained within a viewstack component. The user will type up information in one view, and it will be displayed in the other. But right now in the first canvas I have components that run off the screen and flex doesn't automatically add a scroll bar, so I added 'verticalScrollPolicy="on"' to my canvas. Now, while it gives me a scroll bar, it gives me an empty scroll bar. I still cannot move it up or down, meaning components are still trapped off the bottom of my screen. Am I missing something simple?
Edit - I'm using Adobe Flex Builder 3, and the components it lets you drag in. http://img12.imageshack.us/img12/218/problem1f.jpg This is a picture of the problem, and i guess relavent code would be.
<mx:Application xmlns:mx="adobe.com/2006/mxml" layout="absolute" width="830" height="835">
<mx:ViewStack x="10" y="72" id="viewstack1" width="790" height="751" >
<mx:Canvas label="Design Mode" width="100%" height="100%" verticalScrollPolicy="on" horizontalScrollPolicy="on" >
(Components inside)
</mx:Canvas>
I think the problem is in the way the content in your canvas determines it's height.
If I do this:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:ViewStack width="500" height="500">
<mx:Canvas width="100%" height="100%">
<mx:Canvas width="100" height="1000" backgroundColor="#ededed" />
</mx:Canvas>
</mx:ViewStack>
</mx:Application>
I get scrollbars - Canvas has a default horizontalScrollPolicy and verticalScrollPolicy of auto, meaning it'll show the scrollbars as needed. I think, for whatever reason, the outer canvas isn't detecting that the content is taller than it should be.
if it can help some one : some related problem

Flex full screen mode

I'm new in flex.
I faced problem to make full screen in flex 3.My layout container type like below.
<mx:Application >
<mx:Panel>
<mx:ViewStack>
<mx:VBox >
<mx:VBox >
<mx:Canvas >
<mx:HBox width="300" height="300">
</mx:HBox>
</mx:Canvas>
</mx:VBox>
</mx:VBox>
</mx:ViewStack>
</mx:Panel>
</mx:Application>
the container width and height will be fixed like above.Then when i click a button full screen it will be full screen mode. Please help me with example and tutorial.
best regard.
Mahedi
Assuming you are referring to full screen as in 'use the whole monitor':
Calling stage.displayState = StageDisplayState.FULL_SCREEN; will set your flash movie to full screen mode (provided that the allowFullScreen parameter has been set to true in the object/embed tags of the embedding html page). Scale up the hbox (change its width/height) so that it occupies the whole space available.
Just set
<mx:HBox width="100%" height="100%">
//Other Codes
</mx:HBox>
From what I understand what you want is to change the width / height of the component at run-time.
In this case you need to give an id to the HBOx, add a script block, and call the 'width' / 'height' setter on your block.
Is that what you want to do ?
Thanks
PH

Fluid layout of text in Flex3

In my Flex3 app I have some floating windows which contain variable amounts of text. The windows are meant to be user-resizable. Currently, while I can resize the windows OK, I can't get the text in a TextArea in the window to re-flow when the window is resized. I've come across blog postings that there's a size bug in TextArea that means that setting the text content does not re-size the TextArea properly, together with suggested workarounds. In my case, the content stays the same but the geometry of the container changes. What seems to be happening is that the TextArea adopts a fixed size when it is first rendered, and no amount of resizing the container changes that. Can anyone suggest a means of creating a fluid text area in Flex?
Ian
By binding the textArea's width to its container's with, you can manage to keep the margins and borders and don't have to deal with percentages.
Also, your textArea will be resized each time its parent changes in size.
i'm thinking of something like this:
<fx:Script>
<![CDATA[
import spark.layouts.supportClasses.LayoutBase;
protected function onButtonClicked(event:MouseEvent):void
{
currentState = (event.target.selected ? 'largeState' : 'smallState');
}
]]>
</fx:Script>
<s:TitleWindow id="window" width="300" height="200">
<s:TextArea width="{window.width - 10}" height="{window.height - 60}"
text="{IMyConsts.LOREMIPSUM}" borderVisible="false" />
<s:ToggleButton id="btnEffect" bottom="5" click="onButtonClicked(event)"
label="{btnEffect.selected ? 'Go smaller' : 'Go larger'}" />
</s:TitleWindow>
<s:states>
<s:State name="smallState" />
<s:State name="largeState" />
</s:states>
<s:transitions>
<s:Transition fromState="smallState" toState="*">
<s:Resize target="{window}" widthTo="400" heightTo="300" />
</s:Transition>
<s:Transition fromState="largeState" toState="*">
<s:Resize target="{window}" widthTo="300" heightTo="250" />
</s:Transition>
</s:transitions>
I found a great example of fluid windows please do visit it. They provide source code also so may be you would get better idea.
http://examples.adobe.com/flex3/devnet/dashboard/main.html
You could try Text Layout Framework from Adobe:
http://labs.adobe.com/technologies/textlayout/
I can take a whack at this... but don't laugh..
I am not sure if you need to use a particular container but in any event, set the width and height to 100% and use Grid > GridRow and GridItem to size and resize Text Control you can specify the width of the table or just use it just like an HTML table in Flex to manipulate the size of controls and containers....
hope that helps...

Resources