Tuesday 2 November 2010

Style Glitches when Loading Modules

We have just finished converting (where necessary) our Flex 3 project to be compilable with Flex 4 in Flash Builder 4. One of the biggest lumps in this otherwise pleasantly smooth process was styling. So we left it till last.

One of the last tasks with this styling — or at least we reckoned it was with styling — was addressing a peculiar flashing effect that occurred whenever particular modules were loaded. Upon closer scrutiny we realised something was triggering all the background colours and all the button skins to be redrawn — very perceptively — when some modules were first displayed.

On the off chance someone else is puzzling over this problem and cannot find any documentation on the matter, I lay down my findings here.

After systematically breaking down and comparing components that were being added at runtime (as children of the loaded modules) I found mx.controls.ButtonBar to be the common culprit. If this wasn't loaded, the flashing didn't happen.

I then systematically broke down the ButtonBar component and found that the problem was indeed relating to styling: witness the following code.

var typeSelector:CSSStyleDeclaration = styleManager.getMergedStyleDeclaration("mx.controls.ButtonBar");

if (typeSelector)
{
var borderSkin:* =
styleManager.getMergedStyleDeclaration("global").getStyle("borderSkin");

if (typeSelector.getStyle("borderSkin") !== borderSkin)
{
// Setting a merged style is not supported so get a local style.
typeSelector = styleManager.getStyleDeclaration("mx.controls.ButtonBar");

// Our style manager may not have a local definition of the
// button bar style so add a local one so we can modify it.
if (!typeSelector)
{
var selector:CSSSelector = new CSSSelector("mx.controls.ButtonBar", null, null);
typeSelector = new CSSStyleDeclaration(selector, styleManager);
}

typeSelector.setStyle("borderSkin", borderSkin);
}
}
The above snippet was extracted from the overridden setter function moduleFactory (line 497 of ButtonBar.as) and shows a comparison being made between the merged ButtonBar's borderSkin and the global borderSkin styles. In Flex 4 this equality comparison always returns false the first time it is encountered, causing a style to be set, and somehow manages to affect the global style; in the Flex 3 SDK this code doesn't exist.

There is no way to forcefully assign a borderSkin to the component, since the class defines the style as excluded, and — in our framework anyway — the component fails to inherit the style by this point from the halo theme's global style.

As any good workaround ever is, the solution was simple.

I added a child ButtonBar to the top-level application. Since by itself it's invisible and sizeless in the halo theme, adding <mx:buttonbar/> somewhere discreetly was fairly inoffensive.

Debugging the code again, we find that the borderSkin comparison still fails, but it doesn't matter since the application defines the style before first rendering; and any subsequent appearance of the component from within a module or anywhere else in the application inherits directly, and properly, from this definition.

Now the two components we needed to load — RichTextEditor and TabNavigator — in our modules are loaded and displayed without changing any application styles, hence no confounded flashing.