February 21, 2009

You open, You see :: Label

An English translation is available for this post...
בהמשך ל"פותחים רואים" הפעם אני אתעכב על class שכולנו משתמשים בה תכופות ויכולה לעיתים למרוט את העצבים שאנו שומרים רק להפסדים של נבחרת הכדוריד של טורקיה: Label.
למי שלא מכיר, הקדמה קלה – Label זו מחלקת UI שבאה להציג טקסט בשורה אחת עם היכולת הנהדרת של חיתוך הטקסט במקרה ואין מספיק מקום עבור כולו, שירשור עם "..." ויצירת tooltip עם הטקסט המלא. זו פונקציונאליות לא רעה, בייחוד אם רוצים להציג מידע ב- DataGrid למשל, ומעוניינים בהתנהגות שמגדירה כי ברגע שמצמצמים רוחב של עמודה המציגה טקסט, הטקסט יחתך עם "..." ו- tooltip יופיע עבורו.
אז הנה שני דברים מעניינים שגיליתי תוך כדי נבירה במחלקה:
הראשון, אם נביט במתודת updateDisplayList נגלה שיד נסתרת מירקרה (ניטרלה את הקוד כהערה) האחראי ליצירת צבע רקע עבור ה Label. מדוע? לא ממש ברור, ואני יודע שיש מספיק חנאנות שבנו מגדלים רבים בכדי לספק ל- Label שלהם רקע, אז סאחבק הרים את הכפפה המעופשת והרחיב את המחלקה. לא עשיתי הרבה, שכן הקוד היה כבר שם. אני אשמח לדעת אם יש סיבה מסויימת לכך שהוא מורקר. את הקוד תוכלו למצוא בסוף הפוסט.
הדבר השני, שהטריד אותי הרבה יותר, הוא באג תמוה ומכעיס שיש ל- Label ושנוגע בדיוק בפיצ'ר המגניב שהזכרתי לעיל. אני אסביר: נניח שיש לנו container ברוחב מסויים ותחתיו יש לנו Label עם רוחב של 100 אחוזים. הדבר יתבטא בכך שה- Label יקבל את הרוחב של ה container. נקטין את ה container נקטין גם את ה Label, אתם מבינים את הרעיון. באופן מוזר, ברגע שלא מוגדר ל Label רוחב פיקסלי מוצהר, הטקסט שבתוכה מרחיב אותה בלי להתחשב ב- container בו היא נמצאת. רציתי לברר את הסיבה למחדל הזה שגורם לנו לפעמים ליצור כל מיני workarounds מטורפים (שגם שעולים בביצועים) ולמצוא פתרון יותר אלגנטי עד שהחבר'ה שם למעלה יתעוררו.
הסיבה למחדל הזה נעוצה בחישוב ה- explicitMinWidth של Label. כפי שאולי ידוע לכם, את המאפיין explicitMinWidth אנחנו לא מגדירים ישירות אלא ע"י השמת ערך למאפיין minWidth, מה שאומר שאם לא מגדירים minWidth אזי ה explicit min width לא מוגדר. תוסיפו למשוואה את העובדה ש- explicitMinWidth נדרש עבור חישוב גודל "הילדים" של container ויש לכם כבר שלושת-רבעי התשובה.
Label מחזיקה את UITextField בקומפוזיציה וזו אחראית (היא וכל אבותיה) על כל חישוביי הרוחב של תיבת הטקסט, חיתוך הטקסט וכיו"ב. כפי הנראה, ב- measure נעשה חישוב של רוחב תיבת הטקסט נכון לטקסט שבתוכה וגם ה minWidth מוגדר עפ"י אותו הגודל, משמע, היה לנו טקסט שרוחבו הסתכם ב 480 פיקסלים לצורך העניין, גם ה- minWidth של תיבת הטקסט יוגדר כ 480 פיקסלים. ואם ה minWidth מוגדר כך, אזי גם explicitMinWidth מוגדר כך. אין סיכוי שתיבת הטקסט תצטמצם נכון לcontainer שמחזיק אותה שהרי יש לה הגבלת מינימום של 480 פיקסלים. מרגש, לא?
בד"כ זהו המקום להוציא שלל דימויים עבור מי שקודד את האיוולת, אך במקום זאת אני אפנה לפתרון המיידי והפשוט. ברגע שאתם רוצים שה- Label יסונכרן ברוחבו ל container שמגדיר אותו, יש להגדיר את ה minWidth שלו ל-0. שכן, ברגע שמוגדר כבר ערך עבור explicitMinWidth פעולת ה set של minWidth פשוט לא עושה דבר. זה הכל.
אני מקווה שהשכלתם ומעתה תדעו יותר על מה שקורה בתוך Label. עד הפעם הבאה, עם מערכת חיסון חזקה יותר מכל הבחינות.
תיסלם.


In continue to “you open, you see” series, this time I’m going to talk about a class that we all know and use very often, yet it has the potential for killing us slowly as a result of a nerves breakdown: the Label.
For those of you who have no idea of what I’m talking about, here’s a quick brief – Label is a UI class that represents a text in a single line with the great ability to truncate that text if the space doesn’t allow all of it, adding a “…” suffix to it and a nice tooltip with the full text in it. This functionality is not bad at all, especially when you want to display a textual data in a DataGrid, and have a behavior that when the column is reduced in width, then the text within it will be truncated with a “…” suffix and a nice tooltip.
So here are two interesting things that I found out digging in that class:
The first, if we look at the updateDisplayList we shall find that a mysterious hand has removed the code that was assigned to handle coloring the background of the Label component. Why? Heavens knows. I do know that many geeks have built many extensions to overcome not being able to color the background of Labels, so I’ve decided to take this small opportunity and extend the Label class. Actually, I didn’t do that much. The code was already there, but commented out. I’ll be glad to know if there is a reason for commenting this out in the first place. You can find the code at the end of this post.
The second thing that bothered me even more, is the weird and annoying bug that Label has right where the nice feature I mentioned above is. I’ll explain: Say we have a Container bearing a certain width and residing in it we have a Label with the width of 100%. This means that if we reduce the width of the Container then the Label’s width will act accordingly and be reduced as well. Strangely, when you don’t set a specific pixel width to the Label, the text inside it stretches it without caring about the width defined on the parent Container. I wanted to realize why this happens, this which cause us sometimes to come up with weird workarounds (that are also expensive performance wise), and find a more elegant solution to it until the guys upstairs wake up (maybe they did, in Gumbo?).
The reason for this bug is due to the calculation of the explicitMinWidth member. As you might know, we can’t directly set the explicitMinWidth member, but instead we define it by defining the minWidth member. This means that if you don’t define the minWidth then you’re not defining the explicitMinWidth as well. Adding to this equation the fact that a Container relies on explicitMinWidth to measure its children and you already have three-fourths of the solution.
Labels holds UITextField in composition, which is responsible (with its ancestors) for all the text field sizing calculations, text truncations and the rest. As it seems, the width calculation of the Label’s text field is done within the measure method of the Label class. Inside that method the minWidth is set for the component according to the text measurement I’ve mentioned before, so if our text width is 480 pixels, the minWidth of the Label component is set to 480. If the minWidth is set to 480 pixels so is the explicitMinWidth. There is no way that the parent Container of the Label will be able to cast it’s width on it, since we have a 480 pixels minimum width constraint on it. Exciting, isn’t it?... oh man.
Usually this is the place I reach for the sickest metaphors I have in order to describe to ones who coded this monstrosity, but let me spare you with that and go directly to the simple solution. When you want the Label to be in synch with its parent Container width, simply put 0 as it’s minWidth because, when there is a value set to the explicitMinWidth the minWidth setter does nothing. That’s all.
I hope this helps you and you now know a bit more about what’s going on inside the Label class. Until next time, take care, guys.

package com.flashmattic.underthehood.labelex {
import flash.display.Graphics;

import mx.controls.Label;
import mx.styles.StyleManager;


/**
* Color of the Label object's opaque background.
* The default value is undefined,
* which means that the background is transparent.
*/
[Style(name="backgroundColor", type="uint", format="Color", inherit="no")]

public class LabelEx extends Label {
public function LabelEx() {
super();
}

override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
super.updateDisplayList(unscaledWidth, unscaledHeight);

var g:Graphics = graphics;
g.clear();
var backgroundColor:* = getStyle("backgroundColor");
if (StyleManager.isValidStyleValue(backgroundColor))
{
g.beginFill(getStyle("backgroundColor"));
g.drawRect(0, 0, unscaledWidth, unscaledHeight);
g.endFill();
}
}
}
}

No comments: