Archive for Flex Components
Dotted Underline LinkButton (Flex)
Posted by: | CommentsI needed a link control that would display a dotted-underline on hover. To achieve this I overrode the rollOverHandler and the rollOutHandler of a standard LinkButton. Inside the rollOverHandler I used the graphics API to draw a dotted underline beneath the control’s textField.
Here’s an example:
There are some magic numbers, but tweaking them to get the look you want shouldn’t be too difficult.
And here’s the source: DottedLinkButton.as
Simple Flex ShortcutManager
Posted by: | CommentsJust for fun I wrote a simple ShortcutManager this evening, it turned out to be an interesting little project and taught me a few things about Functions. It’s by no means finished, I’ll detail a few planned improvements after I go over it’s usage.
My goals were to have a static registry that the application could register a keyCode, along with a qualifying key combination (I refer to this as “pair” in the code) like CTRL, or CTRL-ALT. I left out ALT, as Windows seems to have issues with giving up control of the ALT key when used alone. Along with the keyCode, I needed to be able to register a function that would be called when the key combination was pressed. I also wanted to be able to remove a key combination at runtime.
My data structure was simple, I used a separate Dictionary for each of the key combinations I was supporting.
private static var ctrlFunctionMap : Dictionary = new Dictionary(); private static var ctrlAltFunctionMap : Dictionary = new Dictionary();
I wrote an addShortcut method that accepted the keyCode integer, the function to be called, and a string declaring the combination key(s). In it I simply associated the keycode and the function in the appropriate Dictionary. Here is the addShortcut():
public static function addShortcut(keycode : uint, func : Function, pair : String = "ctrl") : void {
if (pair == "ctrl") {
ctrlFunctionMap[keycode] = func;
}
else if (pair == "ctrlAlt") {
ctrlAltFunctionMap[keycode] = func;
}
else {
trace("Key combination not supported.");
}
}
The removeShortcut() method has a similar structure, with one less parameter since we don’t need to know the function. We simply set the appropriate function for the appropriate keycode and combination key to null. Here is the removeShortcut() function:
public static function removeShortcut(keycode : uint, pair : String = "ctrl") : void {
if (pair == "ctrl") {
ctrlFunctionMap[keycode] = null;
}
else if (pair == "ctrlAlt") {
ctrlAltFunctionMap[keycode] = null;
}
else {
trace("Key combination not supported.");
}
}
Finally we need a method that accepts a KeyboardEvent and determines if one of the functions needs to be executed, and then call it. Here is the shortcutHandler() function that does this:
public static function shortcutHandler(event : KeyboardEvent) : void {
if ( event.ctrlKey && !event.altKey && (ctrlFunctionMap[event.keyCode] != null) ) {
(ctrlFunctionMap[event.keyCode] as Function).apply();
}
else if (event.ctrlKey && event.altKey && (ctrlAltFunctionMap[event.keyCode] != null) ) {
(ctrlAltFunctionMap[event.keyCode] as Function).apply();
}
}
I learned an important lesson when I first tried to implement this method. I tried casting the value from the map as a Function, like this:
Function(ctrlFunctionMap[event.keyCode]).apply();
which didn’t work, and spat the error:
EvalError: Error #1066: The form function('function body') is not supported.
Fortunately, one of ActionScript’s more descriptive error messages (I could tell by the parentheses what was wrong) and was able to correct it using the as operator. I didn’t try it, but I might have been able to not bother casting it at all.
Here’s my ShortcutManagerDemo application, demonstrating how it can be used with a variety of functions and function literals.
import com.googolflex.gflib.managers.ShortcutManager;
private function onCreationComplete() : void {
ShortcutManager.addShortcut(89, onCtrlY, "ctrl");
ShortcutManager.addShortcut(89, onAltY, "ctrlAlt");
var f : Function = function():void { trace("Ctrl-U pressed."); }
ShortcutManager.addShortcut(85, f, "ctrl");
ShortcutManager.addShortcut(85,
function():void { trace("Ctrl-Alt-U pressed."); },
"ctrlAlt");
}
private function onAddedToStage() : void {
stage.addEventListener(KeyboardEvent.KEY_DOWN, globalShortcutHandler);
}
private function globalShortcutHandler(event : KeyboardEvent) : void {
ShortcutManager.shortcutHandler(event);
}
private function onCtrlY() : void {
trace("Control-Y pressed.");
}
private function onAltY() : void {
trace("Ctrl-Alt-Y pressed.");
}
I’ll post the code for the manager, and the sample application at the end of the post. Anyway, some improvements I’m planning…
I’d like to associate a context with each command, so that the same keyCodes could be used on different screens. Organizing the manager for such functionality doesn’t strike me as being that hard. I would have to make some changes to my logic to keep down the “conditional explosion”. The daunting part is where to get the context state… I’ll probably end up creating an interface for it, and then make my models implement it.
Of course I’d like to add more combination keys: SHIFT, SHIFT-ALT, CTRL-SHIFT-ALT, etc. The challenging part for that will be simplifying the conditional logic in shortcutHandler() to keep from having to check for a billion cases. I think I would start by pushing the (ctrlFunctionMap[event.keyCode] != null) check down a level, and using my first level of logic only to check the combination keys pressed.
Anyway, if this is useful for anyone I’m glad I could help. Here’s the source:
ShortcutManagerDemo.mxml
ShortcutManager.zip
Update (8/18/09): I added a few things to ShortcutManagerDemo.mxml, namely an interface that allows the user to add their own key commands at runtime. This has limited usefulness in it’s present form, as the functions themselves are still fixed at compile time. Here are some keycodes you can try it out on, anyhow.
a-65; b-66; c-67… 0-48; 1-49; 2-50…
Extending the SkinnableComponent (well, sort of)
Posted by: | CommentsI won’t exactly be extending a SkinnableComponent in this post, I’ll be extending the Button (which extends SkinnableComponent) and creating a few new skins for it. Extending the SkinnableComponent is almost identical to extending the SkinnableContainer (see my previous post). You won’t need the contentGroup, of course.
I wanted to illustrate the power of Flex’s new skinnable architecture when combined with states.
Specifically I will show:
1. How easy it is to create multiple skins for the same class.
2. How easy it is to change skins at runtime via states.
For my collapsible panel, I need a button. All in all the button has 8 possible states: for each of the two expanded or collapsed states of the container, there are the 4 possible button states (up, down, over, disabled). Rather than suffer the madness of creating a skin with 8 possible states and the button class to manage them, I’m going to create two skins (one for expanded, one for collapsed), each which use the 4 standard button states.
I’ll include the full code for the CollapseButtonSkin, (which extends Skin). The code for ExpandButtonSkin is almost identical, except for the image names. The most important thing to note, is how I change the source of the Image using the new state syntax. I’ll be using that same syntax later in my panel.
<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/halo"
minWidth="12" minHeight="12" alpha.disabled="0.5">
<!-- host component -->
<fx:Metadata>
[HostComponent("com.googolflex.gflib.controls.ExpandCollapseButton")]
</fx:Metadata>
<!-- states -->
<s:states>
<s:State name="up" />
<s:State name="over" />
<s:State name="down" />
<s:State name="disabled" />
</s:states>
<mx:Image
source.down="@Embed('../assets/collapse_panel/collapse_down.png')"
source.over="@Embed('../assets/collapse_panel/collapse_over.png')"
source.up="@Embed('../assets/collapse_panel/collapse.png')"
source.disabled="@Embed('../assets/collapse_panel/collapse_over.png')" />
</s:Skin>
I didn’t do anything special in the ExpandCollapseButton class, I just extended Button (which contains the code to change between the up, down, over, and disabled states).
When I add my button to the QuickCollapsePanelSkin class, I specify a skinClass for each of my two panel states. When the state of the component changes, the button skins (which in turn have their own states) will be updated automatically. Here is the code:
<controls:ExpandCollapseButton
width="12" height="12"
top="{(SimpleQuickCollapsePanel(hostComponent).headerHeight - 12) / 2}"
left="4"
skinClass.collapsed="com.googolflex.gflib.skins.ExpandButtonSkin"
skinClass.expanded="com.googolflex.gflib.skins.CollapseButtonSkin"
click="SimpleQuickCollapsePanel(hostComponent).toggleExpanded()" />
You can download the full source for this example here: simple_collapsible_panel.zip
Extending the SkinnableContainer (attempt 1)
Posted by: | CommentsI’ve just created my first Flex 4 component, and loved doing it. It’s not entirely finished, but I wanted to share what I learned for those who may follow after me.
In the component proper, some things to remember:
1. Extend SkinnableContainer.
2. Declare the skin states using metadata brackets. In my case:
[SkinState("collapsed")][SkinState("expanded")]
3. Override getCurrentSkinState() to return one of the strings declared in #2, based on component state.
override protected function getCurrentSkinState() : String { if (expanded) return "expanded"; else return "collapsed";}
4. Call invalidateSkinState(), if necessary. Possibly done in setters or in commitProperties()
In the skin class, some things to remember:
1. Extend the SkinnableContainerSkin.
2. Declare the host component, the component you are skinning. Done as follows, using metadata tags:
<fx:Metadata>
[HostComponent("com.googolflex.gflib.containers.SimpleQuickCollapsePanel")]
</fx:Metadata>
If you need to access any properties of the extended class, ie SkinnableContainer you can do so by using the hostComponent variable. When I wanted to access members specific to my HostComponent, I had to cast hostComponent to a SimpleQuickCollapsePanel.
3. Declare the states of your skin. This may be done as follows:
<default:states> <mx:State name="collapsed"> <mx:State name="expanded"> </default:states>
4. Draw your layers or add your images to the skin as necessary. Reference your states, or hostComponent as necessary.
5. In the case of the SkinnableContainer you’ll want to make sure you add the container for its children. You can supply whatever layout you need, and can even change it based on state. It needs to be called contentGroup, however.
Here’s an example of how I did it:
<s:Group id="contentGroup"
left="3" right="3"
top="{SimpleQuickCollapsePanel(hostComponent).headerHeight+3}"
bottom="3"
visible="{SimpleQuickCollapsePanel(hostComponent).expanded}">
<s:layout>
<s:VerticalLayout />
</s:layout>
</s:Group>
I’ll be including the full source for my QuickCollapsePanel in my next post, where I describe extending the SkinnableComponent.
Update: In the most recent SDK release, the spark.skins.default package has been changed to the spark.skins.spark package. This affects the SkinnableContainerSkin class.
Stay tuned for the next update, when Adobe changes it to the spark.skins.spark.skins package…

