Sunday, April 24, 2011

Custom skinning mx tabnavigator tabbars in Flex 4.1 (pre-4.5 mx style)

For anyone who doesn't care about my explanation and just wants code, please see the bottom of the post.

The annoying part about this post is that it probably won't matter for anyone using Flex 4.5 or later, but if you are using Flex SDK 4.0 or 4.1, this might just help you.  I've been working on skinning a tab navigator, and in the process, kept running into roadblocks, especially when I wanted the skin to respect a new state.  Long story short, skinstates in MX components were not behaving correctly, and skinning a Flex 3 component with a flex 4 mxml skin, expecting states to help me change views, wasn't working.

I tried to invalidateSkinState, but that's not a feature of Flex 3, and I tried to overwrite the Tabbar and Tab, but that become a bloody mess because of all the needed inheritance.  The best answer to solve the riddle came here from Ivan Latysh, who succinctly says, "doc's are lying".  Hellz yes they are.  The docs only tell me to use the css (it doesn't mention that the framework actually comes with two).  The biggest problem I was having was just getting the skinning rectangles to respect their selected/non-selected states.  They wouldn't.  The couldn't. because when I traced down to it, tab navigator doesn't fire off a StateChangeEvent for anything other than "up" (*disclaimer: myriad reasons might be at fault, including my own customizations of my setup, but for sake of argument, I couldn't skin a tabbar as part of a tabnavigator [separate components wouldn't work because by the time I was brought in on the project, the logic was heavily embedded for the tab navigator]).

Because I can't have the tabnavigator separated, I needed to figure out how to skin it appropriately to recognize the events and the actions.  Justin, aka Saturnboy has an amazing post on pushing component events into skins here.  So did WebDevGirl and James Whitakker.  And this stackoverflow post helped, but still, I still find the right info.  The tabbar refused to accept anything for mouse over events, and the only thing I could eventually figure out was that the component wasn't applying state events to the tabbar.

If this is the case, then it's a bug in 4.1.

But here's my solution, because I wanted to set everything in my library to respect the classes, and then styles could be overwritten in later CSS files (this tabnavigator was actually part of my core library, and I needed it to have the default skins to be adjusted by later users.

up-skin: ClassReference("");
over-skin: ClassReference("");
down-skin: ClassReference("");
selected-over-skin: ClassReference("");
selected-down-skin: ClassReference("");
selected-up-skin:   ClassReference("");

However, if I want the original skins from the mx component, and have those skins and their faculties recognized, use this to override the default skins that I was trying to set styles to:

skin: ClassReference("mx.skins.halo.TabSkin");
border-skin: ClassReference("mx.skins.halo.HaloBorder");
border-side: "left, top, bottom";

The main purpose of this blog post is to point out that even though the styling that I wanted to use in Flex 4.1 on an MX component should have allowed me to create a custom skin (albeit in AS3 or MXML), and apply to the class, I couldn't do it because the component was reverting everything back to 1 class and not passing state events into the component (the only event I could capture was "UP", which was not very helpful.

Anyways, I do believe the tabnavigator is now a Spark component, and this headhache should evaporate with Flex 4.5, however, if you are stuck on a 4.0 or 4.1 project, and need to apply skins to the tabbar, this should at least show you that the skins themselves aren't very good for listening for events, so either override them completely, or use the original skin, and use original stylings for mx components (and just override in the css - apparently doing so at the component creation point doesn't seem to help ;p )