Is there a multiline text workaround for Flex - apache-flex

Is there a workaround for displaying multiline text in Flex 3? The two controls I have tried so far are mx:Text, and mx:TextArea. Each control has its own bug associated with it. For reference: mx:Text bug - http://bugs.adobe.com/jira/browse/SDK-9819 mx:TextArea bug - http://bugs.adobe.com/jira/browse/SDK-12616. Basically, neither control handles scrolling correctly if you do not specify a height and the text wraps onto the next line (height is determined dynamically by Flex, based on the wrapping). Does anybody have a workaround that might be helpful?
Thanks.
Update: One of the methods I have tried in the past has been to manually calculate the height of a mx:Text element. I can do this by using the following:
var textItem:Text = new Text();
var len:int = value.length;
var lines:int = int(len/115) + 1;
var height:int = lines * 20;
textItem.height = height;
While this seems to get around the problem in mx:Text, there is one big fault. The calculation relies heavily on font-size, letter-spacing, and the width of textItem. I can use this method, and move on with my project. However, maintenance on this is inevitable, and with code like this, it will a gigantic PITA.

I've had to deal with this a few times myself. The best way I've found to get dynamic height sizing of <mx:Text> is to leave the height out of the text and then specify a percent height of 100% on the enclosing VBox, HBox, etc. Something like the following should work for you:
<mx:VBox width="100%" height="100%">
<mx:Text text="Your really long text goes here." width="100%"/>
</mx:VBox>
As this is a bit of a hack itself, your milage may vary.
Edit
If you want to extend your above example so that maintenance on the code is easier, you should look into the TextLineMetrics class. This will allow you to measure the width and height of your text, taking into account font, size, etc. The docs for TextLineMetrics can be found here. To use your above example, you'd want to do something like the following:
var textItem:Text = new Text();
var metrics:TextLineMetrics = textItem.measureText( value );
var len:int = metrics.width;
var lines:int = int(len/textItem.width) + 1;
var height:int = lines * metrics.height;
textItem.height = height;

I use a variable height text area class that works very well for me:
package
{
import mx.controls.TextArea;
/**
* TextArea that xpands to the height of the content contained
* within.
* #author joel
*
*/
public class VariableHeightTextArea extends TextArea
{
public function VariableHeightTextArea()
{
super();
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number) : void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if(this.height != int(this.textField.measuredHeight) + 5 )
{
this.height = this.textField.measuredHeight + 5;
}
}
}
}

Related

Scroll inside an item renderer result in an empty item

I'm developing a mobile app. I have a view with an horizontal list, each item has a long description so I would need a vertical scroll for that independent information. I'm trying to add an scroll as a Child of an itemRenderer. And I dont get it. Anyone knows what I'm doing bad?
I have a class with inheritance of ItemRenderer (I tried BaseRenderer from AsFusion too, that it seems to have more performance for mobile apps, with the same result).
And here is the part of the scroll from my code:
override protected function createChildren():void
{
super.createChildren();
scroller = new VScrollBar();
scroller.percentHeight = 100;
scroller.setStyle('right', 0);
scrollGroup = new Group();
scrollGroup.percentHeight = 100;
scrollGroup.percentWidth = 100;
super.addElement(scroller);
super.addElement(scrollGroup);
scroller.viewport = scrollGroup;
}
I also tried
override protected function createChildren():void
{
super.createChildren();
scroller = new Scroller();
scroller.percentHeight = 100;
scroller.percentWidth = 100;
scrollGroup = new Group();
scrollGroup.percentHeight = 100;
scrollGroup.percentWidth = 100;
super.addElement(scroller);
super.addElement(scrollGroup);
scroller.viewport = scrollGroup;
}
And the result is the same. An empty item in the list. I can change page (pagesnapping of the list horizontal scroll) and the next item is also empty. If I delete addElement(scroller) I can see the items perfectly, but without the vertical scroll that I really need. So the problem is in the scroller. Any idea what I'm doing so bad?? Please? I need the solution in actionscript, I have more itemrenderers done and I will make inheritance, and the performance for the mobile is better in actionscript. Thank you in advance.
I've never used scroll bars in an item renderer... But you might check out the Scroller component? Something like this:
<s:ItemRenderer>
<s:Scroller width="100%" height="100%">
<s:Group>
<Your_components_here />
</s:Group>
</s:Scroller>
</s:ItemRenderer>
Not sure if it would behave any different though.
In order to work, scrolls need an actual width and height. It seems that the groups you're passing are actually empty, although they do have a set percentWidth. Add content into them.
If you're scrolling text, it might be more viable to use the built in scroll of the TextArea.
I solved, with the guidance of Grigorash Vasilij I noticed that the content of the scroller was not showing because in the group the content size was 0 and private visible variable was false. So the percent sizes of the scroller was not working, I updated it in the method updateDisplayList.
override protected function updateDisplayList( unscaledWidth:Number, unscaledHeight:Number ):void
{
super.updateDisplayList( unscaledWidth, unscaledHeight );
scroller.width = unscaledWidth;
scroller.height = unscaledHeight;
...
}
After that, the scroller was horizontal, then the horizontal scroll wasn't work, I wanted a verticalScroll if needed in the item Renderer, so after the constructor of Scroller I set the horizontalPolicy of the scroll equal to off. The result is the next:
override protected function createChildren():void
{
super.createChildren();
scroller = new Scroller();
scroller.percentHeight = 100;
scroller.percentWidth = 100;
scroller.setStyle("horizontalScrollPolicy", "off");
scrollGroup = new Group();
scrollGroup.percentHeight = 100;
scrollGroup.percentWidth = 100;
addChild(scrollGroup);
scroller.viewport = scrollGroup;
addChild(scroller);
}
My class is inheriting of BaseRenderer from Asfusion If you inherit of itemrenderer use addElement instead of addChild.

Scrolling through list element causes text elements to scroll as well

I'm using a list element with variableRowHeight and word-wrap set to true like in the example below:
http://blog.flexexamples.com/2007/10/27/creating-multi-line-list-rows-with-variable-row-heights/
When I scroll through the list with a mouse scrollwheel the text in the listItems also scroll. I know that this is to do with the height of the textField... but I am unsure how to best resolve this issue. Any thoughts are appreciated!
The issue that I am experiencing also occurs in the example above.
OK, solved it after a bit of work. Thought I would put it here for others:
This can be simply solved by extending the List element, and the ListItemRenderer and modifying a couple of lines:
First up, extend the List element:
package au.com.keeghan.controls {
import mx.controls.List;
import mx.core.ClassFactory;
public class ExtendedList extends List{
public function ExtendedList(){
super();
itemRenderer = new ClassFactory(ExtendedListItemRenderer);
}
}
}
Now we want to extend the newly created item renderer (ExtendedListItemRenderer). Because there isn't actually that much code required, we can just put it in the same .as file. We do this by declaring it as a internal class, and locating it outside of the package above... just below the closing bracket:
import mx.controls.listClasses.ListItemRenderer;
internal class AsOneListItemRenderer extends ListItemRenderer{
override protected function measure():void{
super.measure();
var w:Number = 0;
if (icon)
w = icon.measuredWidth;
// Guarantee that label width isn't zero
// because it messes up ability to measure.
if (label.width < 4 || label.height < 4)
{
label.width = 4;
label.height = 16;
}
if (isNaN(explicitWidth))
{
w += label.getExplicitOrMeasuredWidth();
measuredWidth = w;
measuredHeight = label.getExplicitOrMeasuredHeight();
}
else
{
measuredWidth = explicitWidth;
label.setActualSize(Math.max(explicitWidth - w, 4), label.measuredHeight + 3);
label.validateNow();
label.height = label.textHeight + 5;
measuredHeight = label.getExplicitOrMeasuredHeight() + 3;
if (icon && icon.measuredHeight > measuredHeight){
measuredHeight = icon.measuredHeight;
}
}
}
}
Now, the majority of the above code is actually just copied from the ListItemRenderer, the magic occurs down the bottom... specifically these lines:
label.setActualSize(Math.max(explicitWidth - w, 4), label.measuredHeight + 3);
label.validateNow();
label.height = label.textHeight + 5;
measuredHeight = label.getExplicitOrMeasuredHeight() + 3;
All I do here is add some height to both the label, and the overall measuredHeight which in the end is the thing that is causing this issue.
The only downside to this solution is that you will get a larger amount of padding below the listItem element, however you can still make this look good by playing around with the verticalAlign and padding css properties.

Trying to draw a Rectangle to a Custom Container in Flex4/AS3

So below is the code I have so far. For now I simply want to make it draw a square and have it show up. Right now when I click the area defined in MXML as <components:PaintArea width="100%" height="100%" id="paint-a"></PaintArea> it shows nothing; however, the actionlistener is getting triggered and an element is being added to the group. Not sure exactly what is going on... perhaps for some reason it doesn't think the element is drawable? Anyways thanks for the help!
public class PaintArea extends SkinnableContainer
{
private var canvas:Group;
public function PaintArea()
{
super();
canvas = new Group();
canvas.clipAndEnableScrolling = true;
canvas.percentHeight = 100;
canvas.percentWidth = 100;
canvas.addEventListener(MouseEvent.MOUSE_UP,drawRectangle);
this.addElement(canvas);
}
private function drawRectangle(e:MouseEvent):void{
var r:Rect = new Rect();
r.fill = new SolidColor(0x00ff00,.5);
canvas.addElement(r);
}
}
You should probably set the width and height of the rectangle r.
You could also use a BorderContainer (http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/spark/components/BorderContainer.html) - its a SkinnableContainer with a styleable border and fill

Styling UITextField

I've been playing around with different methods of determining at runtime the width of a "label" so that I can resize the "label" because I don't want it to truncate. I've finally found an easy solution through UITextField which allows me to set the .autoSize which is great! However, now I'm trying to "style" (simply adjust font and font size) of the UITextField but it seems that I have to do it manually with '.htmlText' (which I'll gladly accept if that is the ONLY way).
I'm using the .text to set the value of the label.
My test case involves a HBox (I'm actually using a Grid but they should be the same and I've done testing on both):
I style the HBox and the style carries through to the UITextField. I don't believe this will work for me because I have other components inside that I need to style differently.
I've tried: UITextFormat and TextFormat (I see that the .htmlText being updated accordingly but the output doesn't update. Then I noticed that whenever I called hbox.addChild(myUITextField) it would override the .htmlText
I've tried setting the style with myUITextField.setStyle("fontSize", 20) before and/or after the call to addChild neither of which made an impact on the display as per what I noted above.
Changes are being made but they seem to be overrided when I add it to the display.
So what do I need to do in order to style the UITextField aside from manually setting it along with my contents in .htmlText? Solutions not using UITextField is fine as long as there is some easy way of not truncating the text.
EDIT: I want to just do textField.setStyle('fontSize', 20) and expect that every time I change the text, I wouldn't need to use HTML to go with it (so I can just do textField.text = 'something else' and expect that it will still have a font size of 20). This is what I meant by not using .htmlText (sorry if I wasn't clear before).
2nd EDIT: I guess I should present the whole issue and maybe that'll clarify what I did wrong or couldn't achieve.
My intent is to have a Grid and add text into it. I do not want it to wrap or scroll so I add it to the next row in the Grid when the current row's children total width exceeds some number. In order to add it to the next row, I need to be able to calculate the width of the text. I would like to be able to style that text individually based on cases and there might be other components (like a TextInput). Essentially what I'm trying to accomplish is "Fill in the Blank".
I've included code to show what I'm currently doing and it works somewhat. It might be un-related to the original issue of styling but I can't figure out how to adjust the distance between each UITextField but aside from that this fits what I would like to accomplish. Relevant to the question is: I would like to change the way I style each UITextField (currently setting .htmlText) into something a bit straightforward though like I previously mentioned I'll gladly accept using .htmlText if that's the only solution.
So I have a Grid with x Rows in it and in each row, I have exactly one GridItem. Based on the input, I add UITextField and TextInput into the GridItem going on to the next GridItem when necessary. If you have a better way of doing so then that would be better but I guess what I really want is to find a different way of styling.
Also another problem, I'm not sure of the exact way to add a TextField into the display. I tried:
var t : TextField = new TextField();
t.text = "I'm a TextField";
hBox.addChild(t); // doesn't work
//this.addChild(t); // doesn't work either
But I get the following error:
TypeError: Error #1034: Type Coercion failed: cannot convert flash.text::TextField#172c8f9 to mx.core.IUIComponent.
Here's what I have that's working.
private function styleQuestionString(str : String) : String {
return '<FONT leading="1" face="verdana" size="20">' + str + '</FONT>';
}
private function loadQuestion(str : String) : void {
/* Split the string */
var tmp : Array = str.split("_");
/* Track the current width of the GridItem */
var curWidth : int = 0;
/* Display components that we will add */
var txtField : UITextField = null;
var txtInput : TextInput = null;
/* Track the current GridItem */
var curGridItem : GridItem = null;
/* Track the GridItem we can use */
var gridItemAC : ArrayCollection = new ArrayCollection();
var i : int = 0;
/* Grab the first GridItem from each GridRow of Grid */
var tmpChildArray : Array = questionGrid.getChildren();
for (i = 0; i < tmpChildArray.length; i++) {
gridItemAC.addItem((tmpChildArray[i] as GridRow).getChildAt(0));
}
curGridItem = gridItemAC[0];
gridItemAC.removeItemAt(0);
/* Used to set the tab index of the TextInput */
var txtInputCounter : int = 1;
var txtFieldFormat : UITextFormat = new UITextFormat(this.systemManager);
txtFieldFormat.leading = "1";
//var txtFieldFormat : TextFormat = new TextFormat();
//txtFieldFormat.size = 20;
/* Proper Order
txtField = new UITextField();
txtField.text = tmp[curItem];
txtField.autoSize = TextFieldAutoSize.LEFT;
txtField.setTextFormat(txtFieldFormat);
*/
var txtLineMetrics : TextLineMetrics = null;
var tmpArray : Array = null;
curGridItem.setStyle("leading", "1");
var displayObj : DisplayObject = null;
for (var curItem : int= 0; curItem < tmp.length; curItem++) {
/* Using UITextField because it can be auto-sized! */
/** CORRECT BLOCK (ver 1)
txtField = new UITextField();
txtField.text = tmp[curItem];
txtField.autoSize = TextFieldAutoSize.LEFT;
txtField.setTextFormat(txtFieldFormat);
***/
tmpArray = (tmp[curItem] as String).split(" ");
for (i = 0; i < tmpArray.length; i++) {
if (tmpArray[i] as String != "") {
txtField = new UITextField();
txtField.htmlText = styleQuestionString(tmpArray[i] as String);
//txtField.setTextFormat(txtFieldFormat); // No impact on output
txtLineMetrics = curGridItem.measureHTMLText(txtField.htmlText);
curWidth += txtLineMetrics.width + 2;
if (curWidth >= 670) {
curGridItem = gridItemAC[0];
curGridItem.setStyle("leading", "1");
if (gridItemAC.length != 1) {
gridItemAC.removeItemAt(0);
}
// TODO Configure the proper gap distance
curWidth = txtLineMetrics.width + 2;
}
displayObj = curGridItem.addChild(txtField);
}
}
//txtField.setColor(0xFF0000); // WORKS
if (curItem != tmp.length - 1) {
txtInput = new TextInput();
txtInput.tabIndex = txtInputCounter;
txtInput.setStyle("fontSize", 12);
txtInputCounter++;
txtInput.setStyle("textAlign", "center");
txtInput.width = TEXT_INPUT_WIDTH;
curWidth += TEXT_INPUT_WIDTH;
if (curWidth >= 670) {
curGridItem = gridItemAC[0];
if (gridItemAC.length != 1) {
gridItemAC.removeItemAt(0);
}
// TODO Decide if we need to add a buffer
curWidth = TEXT_INPUT_WIDTH + 2;
}
curGridItem.addChild(txtInput);
txtInputAC.addItem(txtInput);
/* Adds event listener so that we can perform dragging into the TextInput */
txtInput.addEventListener(DragEvent.DRAG_ENTER, dragEnterHandler);
txtInput.addEventListener(DragEvent.DRAG_DROP, dragDropHandler);
txtInput.addEventListener(DragEvent.DRAG_EXIT, dragExitHandler);
}
/* Add event so that this label can be dragged */
//txtField.addEventListener(MouseEvent.MOUSE_MOVE, dragThisLabel(event, txtField.text));
}
}
After about 8 hours of searching for a solution to what would seem to be such a simple issue I FINALLY stumbled on your posts here... Thankyou!!!
I have been stumbling around trying to get TextField to work and had no Joy, Label was fine, but limited formatting, and I need to be able to use embedded fonts and rotate. After reading the above this finally worked for me:
var myFormat:TextFormat = new TextFormat();
myFormat.align = "center";
myFormat.font = "myFont";
myFormat.size = 14;
myFormat.color = 0xFFFFFF;
var newTxt:UITextField = new UITextField();
newTxt.text = "HELLO";
addChild(newTxt);
newTxt.validateNow();
newTxt.setTextFormat(myFormat);
The order of addChild before the final 2 steps was critical! (myFont is an embedded font I am using).
One again... a thousand thankyou's...
John
EDIT BASED ON THE ASKERS FEEDBACK:
I didn't realize you wanted to just apply one style to the whole textfield, I thought you wanted to style individual parts. This is even simpler for you, won't give you any trouble at all :)
var textFormat: TextFormat = new TextFormat("Arial", 12, 0xFF0000);
myText.setTextFormat(textFormat);
Be aware that this sets the style to the text that is in the TextField, not necessarily future text you put in. So have your text in the field before you call setTextFormat, and set it again every time you change it just to be sure it stays.
It's probably best if you use a normal TextField as opposed to the component. If you still want the component you may need to call textArea.validateNow() to get it to update with the new style (not 100% sure on that one though) Adobe components are notoriously bad, and should be avoided. :(
To see all available options on the TextFormat object see here
END EDIT ---------
This is easy enough to just do with CSS in a normal old TextField.
var myCSS: String = "Have some CSS here, probably from a loaded file";
var myHTML: String = "Have your HTML text here, and have it use the CSS styles";
// assuming your textfield's name is myText
var styleSheet: StyleSheet = new StyleSheet();
styleSheet.parseCSS(myCSS);
myText.autoSize = TextFieldAutoSize.LEFT;
myText.styleSheet = styleSheet;
myText.htmlText = myHTML;
Supported HTML tags can be found here
Supported CSS can be found here
The reason you have a problem adding Textfield to containers is that it doesn't implement the IUIComponent interface. You need to use UITextField if you want to add it. However, that's presenting me with my own styling issues that brought me to this question.
A few things I know:
TextField is styled using the TextFormat definition, and applying it to the textfield. As Bryan said, order matters.
setStyle does nothing on IUITextField, and the TextFormat method doesn't seem to work the same as in normal TextFields. (Edit #2: Ahah. You need to override the "validateNow" function on UITextFields to use the setTextFormat function)
To autosize a TextArea, you need to do something like this (inheriting from TextArea):
import mx.core.mx_internal;
use namespace mx_internal;
...
super.mx_internal::getTextField().autoSize = TextFieldAutoSize.LEFT;
this.height = super.mx_internal::getTextField().height;
Found this code on, I think, on StackOverflow a while back. Apologies to the original author. But the idea is that you need to access the "mx_internal" raw textfield.
Text and TextArea have wrapping options. (Label does not). So if you set the explicit width of a Text object, you might be able to size using the measuredHeight option and avoid truncation.
(edit: That was #4, but stackoverflow parsed it into a 1...)

Flex: Custom Item Renderer For Combobox controls truncates text

I've implemented a custom item renderer that I'm using with a combobox on a flex project I'm working on. It displays and icon and some text for each item. The only problem is that when the text is long the width of the menu is not being adjusted properly and the text is being truncated when displayed. I've tried tweaking all of the obvious properties to alleviate this problem but have not had any success. Does anyone know how to make the combobox menu width scale appropriately to whatever data it's rendering?
My custom item renderer implementation is:
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml"
styleName="plain" horizontalScrollPolicy="off">
<mx:Image source="{data.icon}" />
<mx:Label text="{data.label}" fontSize="11" fontWeight="bold" truncateToFit="false"/>
</mx:HBox>
And my combobox uses it like so:
<mx:ComboBox id="quicklinksMenu" change="quicklinkHandler(quicklinksMenu.selectedItem.data);" click="event.stopImmediatePropagation();" itemRenderer="renderers.QuickLinkItemRenderer" width="100%"/>
EDIT:
I should clarify on thing: I can set the dropdownWidth property on the combobox to some arbitrarily large value - this will make everything fit, but it will be too wide. Since the data being displayed in this combobox is generic, I want it to automatically size itself to the largest element in the dataprovider (the flex documentation says it will do this, but I have the feeling my custom item renderer is somehow breaking that behavior)
Just a random thought (no clue if this will help):
Try setting the parent HBox and the Label's widths to 100%. That's generally fixed any problems I've run into that were similar.
Have you tried using the calculatePreferredSizeFromData() method?
protected override function calculatePreferredSizeFromData(count:int):Object
This answer is probably too late, but I had a very similar problem with the DataGrid's column widths.
After much noodling, I decided to pre-render my text in a private TextField, get the width of the rendered text from that, and explicitly set the width of the column on all of the appropriate resize type events. A little hack-y but works well enough if you haven't got a lot of changing data.
You would need to do two things:
for the text, use mx.controls.Text (that supports text wrapping) instead of mx.controls.Label
set comboBox's dropdownFactory.variableRowHeight=true -- this dropdownFactory is normally a subclass of List, and the itemRenderer you are setting on ComboBox is what will be used to render each item in the list
And, do not explicitly set comboBox.dropdownWidth -- let the default value of comboBox.width be used as dropdown width.
If you look at the measure method of mx.controls.ComboBase, you'll see that the the comboBox calculates it's measuredMinWidth as a sum of the width of the text and the width of the comboBox button.
// Text fields have 4 pixels of white space added to each side
// by the player, so fudge this amount.
// If we don't have any data, measure a single space char for defaults
if (collection && collection.length > 0)
{
var prefSize:Object = calculatePreferredSizeFromData(collection.length);
var bm:EdgeMetrics = borderMetrics;
var textWidth:Number = prefSize.width + bm.left + bm.right + 8;
var textHeight:Number = prefSize.height + bm.top + bm.bottom
+ UITextField.TEXT_HEIGHT_PADDING;
measuredMinWidth = measuredWidth = textWidth + buttonWidth;
measuredMinHeight = measuredHeight = Math.max(textHeight, buttonHeight);
}
The calculatePreferredSizeFromData method mentioned by #defmeta (implemented in mx.controls.ComboBox) assumes that the data renderer is just a text field, and uses flash.text.lineMetrics to calculate the text width from label field in the data object. If you want to add an additional visual element to the item renderer and have the ComboBox take it's size into account when calculating it's own size, you will have to extend the mx.controls.ComboBox class and override the calculatePreferredSizeFromData method like so:
override protected function calculatePreferredSizeFromData(count:int):Object
{
var prefSize:Object = super.calculatePrefferedSizeFromData(count);
var maxW:Number = 0;
var maxH:Number = 0;
var bookmark:CursorBookmark = iterator ? iterator.bookmark : null;
var more:Boolean = iterator != null;
for ( var i:int = 0 ; i < count ; i++)
{
var data:Object;
if (more) data = iterator ? iterator.current : null;
else data = null;
if(data)
{
var imgH:Number;
var imgW:Number;
//calculate the image height and width using the data object here
maxH = Math.max(maxH, prefSize.height + imgH);
maxW = Math.max(maxW, prefSize.width + imgW);
}
if(iterator) iterator.moveNext();
}
if(iterator) iterator.seek(bookmark, 0);
return {width: maxW, height: maxH};
}
If possible store the image dimensions in the data object and use those values as imgH and imgW, that will make sizing much easier.
EDIT:
If you are adding elements to the render besides an image, like a label, you will also have to calculate their size as well when you iterate through the data elements and take those dimensions into account when calculating maxH and maxW.

Resources