calculating Spark TextArea width - apache-flex

I am treating a spark TextArea as text input(by setting heightInLines="1"). The TextArea is part of an mxml component and I want to resize the component when the text is changed.
I haven't been able to use textArea.measureaText(textArea.text) to get line metrics and use it. I get this error "Parameter antiAliasType must be non-null."
Is there any way to get the width of a TextArea which it is going to consume at runtime for a particular string or a particular TextFlow?

A little ugly but works for me:
var uiText:UItextField = new UITextField();
// if you have custom text style on the TextArea then set it the same of this uitextfield
uiText.setTextFormat("bla bla bla");
uiText.text = "This is the text that I want the size for";
var textW:Number = uiText.textWidth;
var textH:Number = uiText.textHeight;
// then we need to consider also the border of the text area and the paddings
// try read the padding values and border sizes using getStyle(...)
myTextArea.width = textW+2*borderW+paddingL+paddingR;
myTextArea.height = textH+2*borderH+paddingT+paddingB;
That should be all

Related

How can I reduce the amount of vertical space between lines of text using CSS?

The "obvious" answer is to use the line-height CSS rule, but this code (where I do use that):
private void GenerateSection7()
{
var headerFirstPart = new Label
{
CssClass = "finaff-webform-field-label",
Text = "<h2><strong>Section 7: Submit Information </strong></h2>"
};
headerFirstPart.Style.Add("display", "inline-block");
headerFirstPart.Style.Add("white-space", "pre");
headerFirstPart.Style.Add("line-height", "20%");
var headerSecondPart = new Label
{
CssClass = "finaff-webform-field-label",
Text = " (This payment is subject to post audit review by Financial Affairs)"
};
headerSecondPart.Style.Add("display", "inline-block");
headerFirstPart.Style.Add("line-height", "20%");
this.Controls.Add(headerFirstPart);
this.Controls.Add(headerSecondPart);
var submissionInstructions = new Label
{
CssClass = "finaff-webform-field-label",
Text = "<h4>Submit completed and approved form to Mail stop: FAST/AP Office</h4>"
};
this.Controls.Add(submissionInstructions);
}
...produces this result:
As you can see, the Cumberland gap could fit between these two lines. I started with 80% and kept reducing the percentage, but it makes no difference - 20% is no better than 50% (which was no better than 80%).
How can I cinch up the "play" between the lines?
UPDATE
Spurred on by oriol and quinnuendo, I changed my code to this:
private void GenerateSection7()
{
var headerFirstPart = new Label
{
CssClass = "finaff-webform-field-label",
Text = "<h2><strong>Section 7: Submit Information </strong></h2>"
};
headerFirstPart.Style.Add("display", "inline-block");
headerFirstPart.Style.Add("white-space", "pre");
//headerFirstPart.Style.Add("line-height", "20%");
headerFirstPart.Style.Add("margin", "0");
var headerSecondPart = new Label
{
CssClass = "finaff-webform-field-label",
Text = " (This payment is subject to post audit review by Financial Affairs)"
};
headerSecondPart.Style.Add("display", "inline-block");
//headerSecondPart.Style.Add("line-height", "20%");
headerFirstPart.Style.Add("margin", "0");
this.Controls.Add(headerFirstPart);
this.Controls.Add(headerSecondPart);
var submissionInstructions = new Label
{
CssClass = "finaff-webform-field-label",
Text = "<h4>Submit completed and approved form to Mail stop: FAST/AP Office</h4>"
};
submissionInstructions.Style.Add("margin", "0");
this.Controls.Add(submissionInstructions);
}
...but it still generates exactly the same page (with the Grand Canyon between the last two lines preventing Evel Kneivel from safely traversing the expanse).
Note: I tried both "0" (as shown above) and "0px" as the margin values, and the results are identical either way.
Line height is not the problem here. The problem is the space between the elements, which is controlled by the margin properties.
These tend to be preset to some values that allow for "normal" text flow, so you will have space before and after a heading and around paragraphs for instance.
Headings have various amounts of margin-top and margin-bottom depending on the level of the heading (h2 will have more than an h4). For instance it is "1em" sometimes (the width of a capital M in the current font).
So you will want to reduce these, or set them to 0 directly and experiment from there. The margins need to be set for the actual headings (h4, h2, or whatever) not for the containing element, they will not inherit it. For initial experiments you can try to just apply everything directly in the code, something like <h4 style='margin:0px'> ..... For a proper solution you would want to define actual CSS sheets for the particular case with introducing classes into the heading or in some surrounding div or an analogue.
In some cases you may also need to check out the padding property to fully control the spacings.
The problem is not the line-height but the vertical margins between elements. Also the fact that the headerSecondPart is untag.
See example for the solution.

FlashBuilder 4.5 :: Render Text without lifecycle for upsampling

I need to find a way to "upsample" text from 72dpi (screen) to 300dpi (print) for rendered client generated text. This is a true WYSIWYG application and we're expecting a ton of traffic so client side rendering is a requirement. Our application has several fonts, font sizes, colors, alignments the user can modify in a textarea. The question is how to convert 72dpi to 300dpi. We have the editior complete, we just need to make 300dpi versions of the textarea.
MY IDEA
1) Get textarea and increase the height, width, and font size by 300/72. (if ints are needed on font size I may need to increase the font then down-sample to the height/width)
2) use BitmapUtil.getSnapshot on the textarea to get a rendered version of the text
THE QUESTION
How can I render text inside of a textarea without the component lifecycle? Imagine:
var textArea:TextArea = new TextArea();
textArea.text = "This is a test";
var bmd:BitmapData = textArea.render();
Like Flextras said, width/height has nothing to do with DPI, unless you actually zoom into the application by 4.16X. If your application all has vector based graphics, it shouldn't be a problem. Plus, the concept of DPI is lost in any web application until you're trying to save/print a bitmap.
It's definitely possible, but you'll have to figure it on your own.
To ask a question another way, it is possible to create a TextArea in
memory which I can use the BitmapUtil.getSnapshot() function to
generate a BitmapData object
Technically, all components are in memory. What you want to do, I believe, is render a component without adding it to a container.
We do exactly this for the watermark on Flextras components. Conceptually we created a method to render the instance; like this:
public function render(argInheritingStyles : Object):void{
this.createChildren();
this.childrenCreated();
this.initializationComplete();
this.inheritingStyles = argInheritingStyles;
this.commitProperties();
this.measure();
this.height = this.measuredHeight;
this.width = this.measuredWidth;
this.updateDisplayList(this.unscaledWidth,this.unscaledHeight);
}
The method must be explicitly called. Then you can use the 'standard' procedure for turning the component into a bitmap. I think we use a Label; but the same approach should work on any given component.
Here is the final method I used to solve the problem of creating a printable version of the text and style of a Spark TextArea component. I ended up placing the custom component TextAreaRenderer (see below) in the MXML and setting the visibility to false. Then using the reference to this component to process any text field (renderObject) and get back a BitmapData object.
public class TextAreaRenderer extends TextArea implements IAssetRenderer
{
public function render(renderObject:Object, dpi:int = 300):BitmapData{
// CAST THE OBJECT
//.................
var userTextArea:TextArea = TextArea(renderObject);
// SCALE IS THE DIVISION OF THE NEW DPI OVER THE SCREEN DPI 72
//............................................................
var scale:Number = dpi / 72;
// COPY THE USER'S TEXT AREA INTO THE OFFSCREEN TEXT AREA
//.......................................................
this.text = userTextArea.text; // the actual text
this.height = Math.floor(userTextArea.height * scale); // scaled height
this.width = Math.floor(userTextArea.width * scale); // scaled width
// GET THE LAYOUT FORMATS AND COPY TO OFFSCREEN
// - the user's format = userTextAreaLayoutFormat
// - the hidden format = thisLayoutFormat
//...............................................
var editableLayoutProperties:Array = ['fontSize', 'fontFamily', 'fontWeight', 'fontStyle', 'textAlign', 'textDecoration', 'color']
userTextArea.selectAll();
var userTextAreaLayoutFormat:TextLayoutFormat = userTextArea.getFormatOfRange();
this.selectAll();
var thisLayoutFormat:TextLayoutFormat = this.getFormatOfRange();
for each(var prop:String in editableLayoutProperties){
thisLayoutFormat[prop] = userTextAreaLayoutFormat[prop];
}
// SCALE THE FONT SIZE
//....................
thisLayoutFormat.fontSize = thisLayoutFormat.fontSize * scale;
// SET THE FORMAT BACK IN THE TEXT BOX
//...................................
this.setFormatOfRange(thisLayoutFormat);
// REDRAW THE OFFSCREEN
// RETURN THE BITMAP DATA
//.......................
this.validateNow();
return BitmapUtil.getSnapshot(this);
}
}
Then calling the TextAreaRenderer after the text area is changed to get a scaled up bitmap.
// COPY THE DATA INTO THE OFFSCREEN COMPONENT
//............................................
var renderableComponent:IAssetRenderer = view.offScreenTextArea;
return renderableComponent.render(userTextArea, 300);
Thanks to the advice from www.Flextras.com for working through the issue with me.

How do I enable a Text class to accept html text and will I be able to calculate height and width?

When showing the html I would need to know the height of the text box in order to align things below it properly.
In order to pass it html and have it rendered as such you would set the htmlText property instead of the text property. Once the text box has been added to the display list you can get it's height just like normal.
If you are trying to get the value of the height and width immediately after setting insance.htmlText, you are not going to get the calculated size of the container. You can add an event listener for FlexEvent.UPDATE_COMPLETE or force validation to do it synchronously and use getExplicitOrMeasuredHeight(). Be careful when forcing validation, it is expensive and does not necessarily need to be done in most cases. I've included a right and wrong way of making it synchronous that was not entirely obvious to me at first.
Wrong:
var container : Box = new Box;
var text : Text = new Text;
container.addChild(text);
text.htmlText = "<b>THIS IS A TEST</b>";
trace( text.getExplicitOrMeasuredHeight() > 0 ) // False
Right:
var container : Box = new Box;
var text : Text = new Text;
container.addChild(text);
text.htmlText = "<b>THIS IS A TEST</b>";
text.validateNow(); // Forces the validation methods to run and update the component
trace( text.getExplicitOrMeasuredHeight() > 0 ) // True

How to Auto-resize font size to fit text box height / width?

I am trying to have text automatically size its font to fill an entire text component.
My current approach is to set font size as a function of the number of text characters and the text components height and width but I can't find the right coefficients to make this work nicely.
Is there a simpler or more elegant way?
Does truncateToFit work on Text? I read somewhere that it doesn't work well.
Edit: I forgot to mention that I would like it to scale beyond the max font size (which is 127 i believe). How is this done? scaleX?
AS3 sample function. You should call it anytime your TextField's content changes
function Autosize(txt:TextField):void
{
//You set this according to your TextField's dimensions
var maxTextWidth:int = 145;
var maxTextHeight:int = 30;
var f:TextFormat = txt.getTextFormat();
//decrease font size until the text fits
while (txt.textWidth > maxTextWidth || txt.textHeight > maxTextHeight) {
f.size = int(f.size) - 1;
txt.setTextFormat(f);
}
}

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