I've created a AS3 class which extends UIComponent so that I can easily add it to my MXML layout. It works fine, but I'm trying to figure out how to update the UIComponent width and height dynamically. I want my super UIComponent to draw a border around the content, so I need get the specific 'content' dimensions in order to do this.
I understand the idea of UIComponent is that you are supposed to specifically set the width and height... but I am literally only extending it so that I am able to use my custom component with MXML easily.
Is there a method in which I can get the width and height of a UIComponents 'content'?
Thanks
Like Robusto described in his comment, the Canvas is probably a better choice if you're trying to add children to it. The Canvas instantiates a CanvasLayout, which handles all the complex logic for sizing itself and children.
The width and height of a UIComponent's content is determined in the measure() method, unless the UIComponent has explicit or percent sizes defined. Measure is one of those methods that aren't well documented. I suggest Diving Deep into the Flex Component Lifecycle with EffectiveUI. They go into the lifecycle methods and really explain how to use them.
The measure() method is called if the parent (your component) doesn't have any sizing defined. Then it goes through all of its children, calculates their sizes, and sums them up into measuredWidth and measuredHeight. The parent's sizes are then set to those values.
If you extend UIComponent, you have to do a lot in the measure method to handle all the edge cases: explicit, percent, constraints (top, left...), padding, whether or not includeInLayout = true, etc. Canvas does all that. Same with the other container components: Box, HBox, VBox, List, etc.
Hope that helps,
Lance
Not sure what you mean by the "content" of your UIComponent. Do you mean the UIComponent's dimensions, or a child container's?
One place you can look for the size of any component is in its updateDisplayList method, which is a protected method you would have to override, as so:
override protected function updateDisplayList(width:Number, height:Number) {
super.updateDisplaylist(width, height);
// your statements here
}
You say you are extending UIComponent only so that you are able to use your custom component "with MXML easily." But you still need to extend it as much as you need to in order to do what you need to do. For that you should learn about the lifecycle of a component, when to overrwrite the createChildren(), commitProperties(), measure() and other methods, and so on. It's really not hard, and you've already taken the first step. Good luck!
Related
I am a little confused about the setActualSize method. It appears from what I've read, that if it is not called on a component by its parent, the component will not be rendered.
So it appears that setActualSize is a critical method that is directly bound to rendering the UIComponent. It also appears that the width and height properties of UIComponent override the functionality of the width and height properties of flash.display.DisplayObject, in that they are not directly bound to the rendering of the object but are virtual values that are mainly used by the getExplicitOrMeasured when the parent of the component calls the component's setActualSize method.
So the question are:
1) Why isn't the default behavior of every component to just call setActualSize(getExplicitOrMeasuredWidth(),getExplicitOrMeasuredHeight()) on itself?
2) I guess this question stems from the above question and the behavior as I understand it as described above: does setActualSize change the visibility of the component?
It appears that that the behavior is that a component is not rendered until setActualSize is called, but if it contains display object children itself (expected behavior as it can calculate measure on itself) and is added to the display list, the only reason why flash isn't rendering it, is because its not visible.
The answers to your questions are in the way the Flex component life cycle works, consider these two phases:
measurement:
The Flex framework will call the measure() method of your component. You can override this method to set a default and/or minimum size for your component.
Flex components first measure themselves to provide a default and/or minimum size suggestion to the layout/container classes. Flex does this from a bottom up approach, so that the lowest level objects are measured first. Thus when each parent object measures itself, the preferred sizes of it's child objects has been established.
rendering:
Later Flex calls the updateDisplayList() method of your component. You can override this to size/position your component's child objects. This is where setActualSize() is intended to be used: the parent calls setActualSize() on it's child objects, not on itself.
Note the method signature of updateDisplayList():
protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
Measurement was done from the bottom up. However, rendering is done from the top down. So at render time, updateDisplayList(unscaledWidth, unscaledHeight) gets executed on your component. Flex is telling your component the space it has been allotted to render itself, and your component must size/position it's child objects accordingly and/or do programtic drawing.
The sizes passed in to updateDisplayList() are based on various factors:
how/if you override the measure() method (measure is not called when
your component has a fixed width/height)
the types of sizing
parameters (fixed, percent, constraint) and layouts that you use
An old but good resource on this topic
setActualSize() is one of the crutial and most interesting methods in Flex layout process:
1) Notice that setActualSize() is an entry point for parent's layout to set the component size, and it has to be called by parent (container) almost exclusively!
This is because only the parent knows the amount of space available for each child (this method is being called after all children are measured and the container knows it's own given size).
(note: the example of not calling it by layout posted below)
This method exists because if parent would set 'width' and 'height' on children directly, they would immediately turn into fixed size children, and they won't be measured anymore.
Using this method, only the rendering size is being changed - not the (explicit) width and height but _width and _height - meaning if for some reason the container resizes again, the children will be resized by given rules (percentage of the parent, expanding to child component's children size etc.)
2) Yes, because if this method isn't called at all, the component has a (rendering) size of (0, 0), so this is the reason of it's invisibility (not setting 'visible' to false)! ^_^
Note that THERE HAS TO BE A LAYOUT (even a non attractive one) to trigger this method call. By 'non attractive' I consider the layout that isn't supposed to do anything smart, like CHANGE THE WIDTH AND HEIGHT of children at all (like absolute layout)!
Now, look at the PopUpManagerImpl's class addPopUp() method: there is an interesting case of calling setActualSize():
IUIComponent(window).setActualSize(
IUIComponent(window).getExplicitOrMeasuredWidth(),
IUIComponent(window).getExplicitOrMeasuredHeight());
Explanation: PopUpManager does stuff that layout should normally do, because it WANTS TO KNOW THE POPUP DIMENSIONS IMMEDIATELY, so it could center the popup on stage. It has no time to wait for the layout pass!
If you comment those 3 lines in the framework code, you'll see that popup is being centered with it's top left corner - just like it's size is (0, 0). Anyway, it is rendered with proper width and height because at rendering time the dimensions are known.
Hope this makes things a bit clearer...
Cheers! ^_^
Danko Kozar
I am writting a QStyle Here I am changing the QProgressBar to a Slim single line, no text. So Height will also be reduced to 5px. However Widget Width will be determined by layout. what should I do in My Style's drawControl to change widget height ?
I've never actually written a QStyle but I would consider it odd if you were supposed to resize anything inside drawControl. I could be wrong on this by a quick review of the documentation would seem to suggest that you would override subElementRect and return a rect based on current width and your preferred height. I assume this would be called by layout activities and would be sorted out by the time drawControl gets called.
Do you need the height for the widget to be fixed? Or just the drawn height to never go past 5 pixels? For the first, set the height and resize policy on the polish function. For the second, override the drawing in the QStyle to only use 5 pixels. The functions that do the drawing generally take rects; you can call the base class's draw with a modified rect if you properly override the appropriate functions. Unfortunately, it's been years since I've done any QStyle work, so I'm not sure exactly what those functions are.
I have custom components which must adjust their text content based on space constraints. For example a component adds labels until there is no space, and then the content of the last label becomes "(x more)"
I do not have access to size of child controls before adding them. When in updateDisplayList, I make changes to the layout of the component, but for labels, lblInstance.text property fires events, which lead to updateDisplayList being called again. I know that updateDisplayList may be called more than once, but if there is a way to modify text without triggering events, that'd be really useful. For example, setActualSize method in UIComponent allows this kind of modification.
Anything similar for text controls? Or do you have best practices for laying out and managing text content, in the context of custom Flex components?
Best Regards
Seref
Use the measure() method to set your text component sizes.
You will probably have to call validateNow() on them so that they will be forced to figure out the actual textWidth and textHeight and return the actual number of lines of text in the textField if they are long enough to wrap. Investigate the TextMetrics class for more ways to measure.
I want to create an MXML container component that has some of its own chrome -- a standard query display, et al -- and that supports the addition of child components to it. Something a lot like the existing mx:Panel class, which includes a title label, but acts like a plain mx:Box with regards to adding children.
What's the easiest way to do this?
Edit:
To be clear, I want to be able to extend the container using MXML, so the "Multiple visual children" problem is relevant.
Extend a container and add a title label. Probably the <mx:Canvas/> will work here. Make the title a public var and include a var for the styleName of the label.
Then override the addChild() method so that any child that is added is added instead to the that is your container.
Leave enough space for your title when you position your Box element (i.e., give its y property enough space. If there is no title you may want to reclaim that space.
That's the basics. Customize to your heart's content.
EDITED TO ADD: I do this creating an ActionScript class first, extending the container I am targeting, and I add the "furniture" — items the class will always use, like title in your case — by overriding createChildren and calling super.addChild(item) for those items. Calling addChild from then on, even in MXML markup, adds items to the inner container.
We do this with states.
We put the chrome for the base container in a state (in mx:AddChild elements) and then use the initialize event to switch to that state when the control is created. All the chrome is then added to the container.
That gets round the multiple sets of visual children problem.
The downsides to this approach are:
You don't see the chrome when editing descendents of the base.
You can't directly access the parent chrome controls from descendent components as they are not there at compile time (instead, you need to define properties, methods or events on the base that the descendents can access)
However, it works well for us.
I have 1 class (that is extending a UIcomponent) that is representing a component. In that component I create different instances of another class that is also a UIComponent but this class doesn't show up in de first class. I see that it is running and that it has the correct hights but I got the feeling that it doesn't show up with this hights. If I trace I see the width and height is 200 X 200 but there is still nothing visible in my component
I bet there is a simple solution for this problem?
If extending UIComponent directly, you have to make sure to override all the right methods, including createChildren, updateDisplayList, and measure. If you miss any of those it won't work.
Remember to addChild in createChildren, to set x, y, width, and height in updateDisplayList, and to set your own measured properties in measure.
"Fixing" it by "setting everything in one UIComponent" is probably not the right idea.
That said, it's usually A LOT easier to just put stuff into a canvas instead of extending UIComponent. Everything will show up by default, and you don't have to override anything. I only extend UIComponent when I'm doing something really fancy or I want custom control