Inaccessible Flex Button Icon Properties and Using Image Filters With Event Handlers
Discovered today an annoying thing: it doesn't seem possible to directly manipulate the icon of a button. I had grabbed some freely available icons as representative pieces for a project prototype, but in piecing together the mismatched set I wound up with varied sizes. The problem is that when the icon property of a button is set to the class of the respective embedded png, the image is put in at an unconstrained size. This creates a really unattractive inconsistent look. Sure, I could open up a handy image editor and re-size the images, but this is a prototype and I really couldn't justify spending a lot of time doing image manipulation.
[Aside: Ok, the last statement is a bit disingenuous as I had time to putz around looking for representative icons instead of just using any random set. And I had time to investigate this problem instead of just going for the quick obvious workaround. So ignore all that and assume no logical inconsistencies to my story. Thanks.]
While I had found sites telling me that directly modifying the icon was not possible, I did find an encouraging tidbit at stackoverflow. There was some code for grabbing the height and width of the embedded icon image by Christophe Herreman:
var icon:DisplayObject = button.getChildByName("upIcon");
trace("icon (width: " + icon.width + ", height: " + icon.height + ")");
I didn't experiment directly with the read aspect of the above code, as my goal was explicitly aimed at writing this information. Of course, note that the above code references the "upIcon" and my attempts centered around the "icon" style instead:
var icon:DisplayObject = button.getChildByName("icon");
icon.width = 18;
icon.height = 18;
The above did not generate a compile time error, nor an explicit run-time error, but it did prevent the button from being rendered. Nutz.
I hate to be a quitter, but I really don't see anywhere to go from here without making substantial modifications to some classes. And remember, I didn't want to waste any time. So I decided to use the <mx:Image> and add some simple modifications to get to my goal. Namely, I added some event listeners to add and remove filters on the image. It is a pretty straightforward process that I'll quickly cover.
My entry point to all this is a public setter on a custom component. The parent component sets a public setter with an array of strings representing the desired images to be placed. The setter takes the Array and iterates through each member, adding an image to correspond with each array item. I drop the each image into a target <mx:HBox>. Not to be terribly schizophrenic, but in some places I cleanly abstract the code into understandable chunks and in other places the aim is to be more concise. It makes sense to me, but, just the same, I hope that you can follow along.
[Embed(source="/assets/images/icons/myEmbeddedImage.png")]
private const IcoDemo:Class;
// My public setter for the Array of images
public function set myImages(value:Array):void {
for (var v:int=0;v<value.length;v++) {
hbTargetContainer.addChild( getIconFromView(value[v]) );
}
}
private function getIconFromView(view:String):Image {
var _returnImage:Image = new Image();
_returnImage.addEventListener(MouseEvent.MOUSE_DOWN,iconDown);
_returnImage.addEventListener(MouseEvent.MOUSE_UP,iconUp);
_returnImage.addEventListener(MouseEvent.ROLL_OVER,iconOver);
_returnImage.addEventListener(MouseEvent.ROLL_OUT,iconOut);
_returnImage.id = getIdFromView(view);
_returnImage.width = 18;
_returnImage.height = 18;
switch (view) {
case "Case1":
_returnImage.source = IcoDemo;
break;
// additional case statements
}
_returnImage.filters = [getIconShadow()];
return _returnImage;
}
The above contains what I talked about, but also includes a bunch of event listeners, and, if you are paying attention, you'll notice that I am setting the filters property of the _returnImage to an array literal populated by a function call. I'll cover these in just a moment.
As you might imagine, as this is a snippet from a prototype, there is some evolution going on. Since I couldn't get the button icon manipulation going, I needed to give the user some sense that my images actually do something. Playing with the filters seems reasonable, and I associate a filter, or the lack thereof to mouse events associated with the image. However, there is another snag. Flash Player will apply all of the filters in the array in order of their index. In a simple scenario of ROLL_OVER|ROLL_OUT, it is not a problem. On ROLL_OVER, write the array. On ROLL_OUT, clear the array. Or whatever behavior suits your purpose. However, when you throw additional events into the mix, like the MOUSE_UP and MOUSE_DOWN events, I can't just remove the filters.
For instance, in my case, I have a DropShadowFilter effect I want to be active except when for the MOUSE_DOWN and I want to apply a GlowFilter on ROLL_OVER. If I ROLL_OUT of the image, I don't want to remove the DropShadowFilter. To solve my problem, I created functions that return the desired filter types, and the abstract the actual setting of the filters property to a separate function. In the event handlers, I just set Boolean values so that the function for setting the filters can intelligently decide what to add. Luckily, in this case, the order of filter application is not important.
So, the event handlers and helper functions:
private var _iconDown:Boolean = false;;
private var _iconOver:Boolean = false;
private function iconDown(e:MouseEvent):void {
_iconDown = true;
setIconFilters(e);
}
private function iconUp(e:MouseEvent):void {
_iconDown = false;
setIconFilters(e);
}
private function iconOver(e:MouseEvent):void {
_iconOver = true;
setIconFilters(e);
}
private function iconOut(e:MouseEvent):void {
_iconOver = false;
setIconFilters(e);
}
private function setIconFilters(e:Event):void {
(e.target as UIComponent).filters = getIconFilters();
}
private function getIconFilters():Array {
var _filters:Array = []
if (!_iconDown) {
_filters.push(getIconShadow());
}
if (_iconOver) {
_filters.push(getIconGlow());
}
return _filters;
}
private function getIconShadow():DropShadowFilter {
var filter : DropShadowFilter = new DropShadowFilter();
filter.blurX = 4;
filter.blurY = 4;
filter.quality = 2;
filter.alpha = 0.5;
filter.angle = 45;
filter.color = 0x202020;
filter.distance = 6;
filter.inner = false;
return filter;
}
private function getIconGlow():GlowFilter {
var filter:GlowFilter = new GlowFilter();
filter.blurX = 10;
filter.blurY = 10;
filter.quality = 2;
filter.alpha = 0.7;
filter.color = 0xFF9900;
filter.inner = false;
return filter;
}
As mentioned, I have two Boolean values to keep track of the image state. Because of the exclusive nature of the events, I can keep a single global reference for all of my images. That is, it would be difficult in my application for the mouse to be over two images simultaneously. If you have overlapping images, you might have to do some extra logic to keep track of things.
Each of the event handlers sets the corresponding Boolean and then calls the setIconFilters function. I just pass along the event to keep everything really generic and keep the code reuse going.
At setIconFilters, I treat the e.target as a UIComponent. This is another case of me being lazy as I was experimenting with Buttons and Images and found it easier to just use UIComponent as a catchall. The UIComponent is an ancestor class for all Flex display objects. Notice that I didn't add the UIComponent import statement; (import mx.core.UIComponent;). This function calls the getIconFilters function.
getIconFilters is the controller for this scheme. It looks at the state of the event Booleans and kicks back an Array populated with the appropriate filters.
Lastly, the two filter functions instantiate and return configured filters for the desired effects. I'm not going to go into filters in this post, but they are really powerful tools.
I don't have an immediate live example of this yet. My prototype worked well enough but it is very likely going to be scraped for broader UX requirements. If someone is interested in seeing this, let me know with a comment and I will whip up a little demo app.
Here is a full demo with source of flex images with event driven filters.


There are no comments for this entry.
[Add Comment]