艺虎动画 > Flex数据绑定陷阱:常见的误用和错误

Flex数据绑定陷阱:常见的误用和错误

翼虎动漫   2010-7-20

 

 

 

Using the wrong bindable event name
使用错误的绑定事件


Using the wrong event name in the [Bindable] tag can cause your application to not bind your property,
and you will not even know why. When you use the [Bindable] tag with a custom name, the example below looks like a good idea:
在[Bindable]标签中使用错误的事件名,将会导致你的程序绑定不成功,而且不知道错在哪。当你使用[Bindable]标签,并定义一个自定义的事件名,
如同下面这个例子一样,看上去像是个好主意:

public static const EVENT_CHANGED_CONST:String = "eventChangedConst"; 



private var _number:Number = 0; 



[Bindable(event=EVENT_CHANGED_CONST)] 

public function get number():Number  

{                          

   return _number;

}    



public function set number(value:Number) : void 

{                          

   _number = value;

   dispatchEvent(new Event(EVENT_CHANGED_CONST));

}
The code above assigns a static property to the event name,
and then uses the same assignment to dispatch the event.
However, when the value changes, the binding does not appear to work.  
The reason is that the event name will be EVENT_CHANGED_CONST and not the value of the variable.

上面的代码将一个静态属性值赋给[Bindable]标签的事件名,并使用同样的值赋给派发的事件。
尽管如此,当值改变时,绑定同样不会工作。
原因是这个事件的名将会是EVENT_CHANGED_CONST而不是这个变量的值。

The code should have been written as follows:

正确的使用方法如下:
public static const EVENT_CHANGED_CONST:String = "eventChangedConst";   



private var _number:Number = 0;   



[Bindable(event="eventChangedConst")] 

public function get number():Number   

{                       

   return

   _number;

}



public function set number(value:Number) : void 

{                       

   _number = value;

   dispatchEvent(new Event(EVENT_CHANGED_CONST));

}
 
 
 
 
 
 
 
不改变默认的 PropertyChange事件常量

When you use the Bindable tag without adding an event setting, propertyChange is the default event type that will be dispatched.
So a [Bindable] tag is equivalent to Bindable(event="propertyChange").  
The compiler creates additional code for the setter and getter when you do not specify the event string,
so it is recommended that you add your own name constant to avoid this extra overhead.

当你使用Bindable标签时,不添加任何事件设置,propertyChange事件将作为默认的事件类型被触发。
因此[Bindable]标签等价于Bindable(event="propertyChange")。
当你没有指定特别的事件类型时,编译器将创建额外的getter和setter方法代码
因此建议你添加命名的事件类型常量,以避免这种额外的开销。

Consider the following binding statement:
看下下面的绑定声明
[Bindable] 

public var testData:String;
Because no event name was set, the compiler will create the following:
因为没有事件名被指定,编译器将创建以下这些代码:
[Bindable(event="propertyChange")]          

public function get testData():String  

{  

   return this._1147269284testData;  

}  



public function set testData(value:String):void  

{  

   var oldValue:Object = this._1147269284testData; 

   if (oldValue !== value) {  

      this._1147269284testData = value; 

      if (this.hasEventListener("propertyChange"))                              

         this.dispatchEvent(mx.events.PropertyChangeEvent.createUpdateEvent 

         (this, "testData", oldValue, value));  

      }  

}

As you can see the mxmlc creates a generated setter which contains code that dispatches the PropertyChangeEvent.
When you change the default constant, the Flex compiler will NOT generate code and you are responsible for dispatching the event yourself.
This allows you to optimize the listening code.
If you don't change the default constant,
every [Bindable] property will dispatch the propertyChange event and the listening code must interrogate the event to determine exactly which property changed.
This is especially costly process if a class has a large number of [Bindable] properties.

正如你所看到的,mxmlc(编译器)创建了一个包含派发PropertyChangeEvent属性变化事件的setter方法。
当你改变默认的常量时,Flex编译器将不会生成代码,而你需要自己去派发这个事件。
这可以使你优化监听的代码。
如果你不改变默认的事件类型常量,
每一个被[Bindable]标签绑定的属性都会派发propertyChange属性改变事件,并且监听代码必须判断这个事件是由那个属性改变引起的。
这在某个类有大量被绑定的属性时,代价是非常大的。

Once you set the event name yourself, such as in the example below, the compiler just copies the code over.
一旦你设置了自己定义的事件名,就如下面的例子,编译器将仅仅复制这些代码。

Private var _number:Number = 0;      

[Bindable(event="changeNumberEvent")]  

public function get number():Number    

{                        

   return _number; 

}              

           

public function set number(value:Number) : void  

{                        

   _number = value; 

   dispatchEvent(new Event("changeNumberEvent")); 

}
 
 
 
 
 
 
 
 
 
Using binding in place of direct assignment
在直接赋值的地方使用绑定


有一种情况,就是你可以不使用绑定,使用直接赋值就可以成功实现同样效果的话,你最好不要使用绑定。我已经看到过很多形式的这类型错误了。

下面的代码就可以说明:
<?xml version="1.0" encoding="utf-8"?> 

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 

   xmlns:s="library://ns.adobe.com/flex/spark" 

   minWidth="1024" minHeight="768">                         



   <fx:Script> 

      <![CDATA[ 

         private var text:String; 

      ]]> 

   </fx:Script>                         



   <s:layout> 

      <s:VerticalLayout/> 

   </s:layout> 

                            

   <s:TextInput id="textInput2" text="{text}"  /> 



</s:Application>

这段代码中定义了一个文本输入框,将私有成员变量绑定到这个文本输入框的text属性上。它看起来无害,对不对?我经常看到Flex应用程序中使用这些类型的标签。Flex的编译器会自动生成代码来实现绑定。

你会发现,尽管你不需要绑定text字符串,因为它是一次性赋值的,但是编译器仍然会生成代码,来适应属性绑定的条件。另外,有些情况下你需要在赋值后解除绑定,或是移出绑定代码来减少开销,但是你在MXML中使用<mx:Binding> 标签后将不能这样做。

作为一个经验法则,应避免使用绑定到一个私有变量。

在上面的例子中,你可以直接给输入文本框设置值。

<s:TextInput id="textInput2" text="some text goes here" />

当你使用直接赋值时,你会大大减少你的开销,因为编译器不再会生成你不需要的绑定代码。作为一个通用的经验法则,不要使用数据绑定,除非你绑定的值可能或肯定会改变。

 

 

 

尝试绑定一个没有包含IPropertyChangeNotifier接口的类

实现IPropertyChangeNotifier接口的类,必须在类改变属性时派发事件并且任何嵌套类必须作为公共属性被曝光。因此,你能发现类里的属性何时改变了。例如,让我们来看一下UIComponent类的签名(见图3)。UIComponent类确实实现了发布属性改变事件,这个一旦某个属性被改变就会派发一个事件的接口。


图3.UIComponent类的签名


现在看下下面这个控制用户信息的类:

package vo 



   public final class UserInfo 

   { 

      public var userName:String; 

      public var password:String; 



      public function UserInfo() 

      { 

      } 

   } 

}

如果你尝试绑定用户信息类的某个属性刀Label的text属性上,按照下面的说明这么写的话,这个绑定将不会成功:

<?xml version="1.0" encoding="utf-8"?> 

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"     

   xmlns:s="library://ns.adobe.com/flex/spark"  

   xmlns:mx="library://ns.adobe.com/flex/mx"  

   minWidth="1024" minHeight="768"  

   creationComplete="creationCompleteHandler()"> 



   <fx:Script> 

      <![CDATA[   

         import vo.UserInfo;  

         [b][Bindable][/b] 

         private var userInfo:UserInfo = new UserInfo();            

                         

         protected function creationCompleteHandler():void 

         {  

            userInfo.userName = "EladElrom"; 

         }  

      ]]> 

   </fx:Script> 



   <s:Label id="lbl" text="{userInfo.userName}" /> 



</s:Application>

因为这段代码尝试去绑定一个没有实现IPropertyChangeNotifier接口的类,绑定机制将不会工作。在这个例子中,你将看到问题窗口下的以下提示信息(见图4):数据绑定将不能实现侦测userName的任务。


图4.问题面板显示的警告信息



要让前面的数据绑定能够工作,你可以将[Bindable]标签加到类声明前面。这将使得这个类的所有公共属性能够被绑定。Flex的编译器将会为你生成公共的getter和setter方法,这些将会包含使绑定工作的所有必要代码。或者如果你不希望使类的所有属性都可以绑定,你可以将[Bindable]标签加到特定的属性前面。

package vo 



   [Bindable] 

   public final class UserInfo 

   { 

      public var userName:String; 

      public var password:String; 

                              

      public function UserInfo()  

      { 

      } 

   } 

}

ObjectProxy类

数据绑定要求被绑定的类实现IPropertyChangeNotifier接口,否则该对象将不能被绑定。

尽管如此,类/属性或变量,如原始变量,没有标记[Bindable]标签不能实现这个接口。
如果这个类是你自己写的类,你所需要做的就是添加[Bindable]元数据标签。
如果不是你自己的类,你又希望绑定它,或你只是希望运行时添加绑定功能,你可以使用ObjectProxy类。
ObjectProxy封装一个没有绑定的类并在这个被绑定类的任何属性改变时派发属性改变事件,
使得你能监听你程序里的该对象的属性改变。
你可以在Adobe Flex 4语言参考里找到更多关于ObjectProxy类的更多信息。

下面的例子使用了ObjectProxy类。我创建了一个ObjectProxy的实例并把我希望看到的对象传给它,在这个例子里是UserInfo。接着我添加了一个事件监听,来跟踪UserInfo各项的改变。

<?xml version="1.0" encoding="utf-8"?>           

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"             

   xmlns:s="library://ns.adobe.com/flex/spark" 

   xmlns:mx="library://ns.adobe.com/flex/mx" 

   minWidth="1024" minHeight="768"                                     

   creationComplete="creationCompleteHandler()"> 

    

   <fx:Script>         

      <![CDATA[  



         import mx.events.PropertyChangeEvent; 

         import mx.utils.ObjectProxy; 



         import vo.UserInfo; 



         private var userInfo:UserInfo = new UserInfo(); 

         private var objectProxy:ObjectProxy; 



         protected function creationCompleteHandler():void 

         { 

            objectProxy = new ObjectProxy( userInfo ); 

            objectProxy.addEventListener( PropertyChangeEvent.PROPERTY_CHANGE, onPropertyChange ); 

            objectProxy.userName = "EladElrom"; 

            objectProxy.password = "123"; 

         }  



         private function onPropertyChange( event:PropertyChangeEvent ):void   

         {     

            lbl.text = event.newValue.toString(); 

         }     

      ]]>        

   </fx:Script>   

              

   <s:Label id="lbl" />  

    

</s:Application>
 

一定要注意的一件事情是我们使用ObjectProxy类是为了方便转让消息,注册的监听器会在每次改变目标对象时被调用。这会在无形中引入一个重要开销,事实上在我们的例子中因为属性改变了两次所以onPropertyChange方法被调用了两次。

 

 

 

 

当构建Flex或者Adobe AIR程序时,将一个对象的值自动的传递给另一个对象这种处理是数据绑定最常用并最有用的特征之一。尽管如此,同时数据绑定会减缓程序的初始化,并且当开发者不是完全理解数据绑定的机制是如何工作的话,会引起一些开发者意想不到的问题。最好的解决方法及是确保你正确的使用了它并且在只有需要它的时候才使用它。在这篇文章中,我整理了十条开发者在构建应用使用数据绑定时常见的陷阱和错误。

原文地址:http://www.adobe.com/devnet/flex/articles/databinding_pitfalls_03.html

第二条

第一条:
难以发现的无声错误

这有些绑定操作似乎已经工作,但是你最后却沮丧的发现没有并且无从下手修改的例子。

在绑定表达式或者在绑定框架里调用绑定方法时抛出的异常和错误,只会默默的捕获。因此你将在Flash Player调试版本中看不到任何运行时错误。不仅绑定没有工作,而且没有任何错误显示。为什么这些错误被无声捕获?代码实现绑定机制,在绑定发生前需要几个先决条件。绑定机制会吞噬掉任何错误以防止在运行时抛出运行时异常。如果你不希望在你的程序中看到这些可能出现的意外错误,这是一个很好的事情。

思考下下面的简单绑定例子:

<?xml version="1.0" encoding="utf-8"?> 

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 

   xmlns:s="library://ns.adobe.com/flex/spark" 

   xmlns:mx="library://ns.adobe.com/flex/mx" 

   minWidth="1024" minHeight="768"                                     

   preinitialize="handlePreinitialize()">                         



   <fx:Script> 

      <![CDATA[ 

         [Bindable] 

         private var xml:XML =  

         <users> 

            <user> 

               <name>EladElrom</name> 

               <address>1 Wall Street</address> 

            </user> 

         </users>;    

                                

         protected function handlePreinitialize():void 

         { 

            xml = null; 

            //BindingManager.debugBinding("label.text"); 

         } 

      ]]> 

   </fx:Script> 

                         

   <s:Label id="label" text="{xml.user.name}"/> 



</s:Application>
我已经添加了xml变量绑定到Label组件。这些代码会运行的很好;但是,我在组件预初始化时将xml变量设置为null。这个事件将在组件初始化序列开始之前被派发,此时Label组件还没有被设置。这个xml变量被设置为null,因此没有name属性在xml对象上。如果你运行这个程序,你会发现绑定没有发生也没有提示任何错误。

调试绑定

虽然错误被无声的捕获,你仍然有办法可以找出是怎么回事。使用BindingManager.as和Binding.as来进行调试并不容易,因为如果你没有下载完整的Flex sdk的话,绑定类是没有的。相反,你可以设置一个断点并跟踪相关的绑定对象来找出问题所在。在这种情况下,你会发现xml对象的值被设置为null,这就是为什么绑定为什么失败的原因(看图1)。


图1. xml对象的值被设置为null

另一种方法更加直观,是使用BindingManager类的debugBinding方法。你设置你想观察的组件和属性,然后你就可以看到被捕获的错误。在上面的示例代码中,我给下面的这行代码加了注释:

BindingManager.debugBinding("label.text");

把这行的注释去掉并在debug模式下运行它;你将在控制台看到绑定错误(见图2)


图2.控制台输出的绑定错误

看一下Binding.as和BindingManager.as类的代码。代码中含有许多的try...catch表达式来确保形成有效绑定的条件。以下有一些在绑定时可能抛出的错误类型:

    * Error #1006: Call attempted on an object that is not a function.
    * Error #1009: Null has no properties.       
    * Error #1010: Undefined has no properties.       
    * Error #1055: Has no properties.
    * Error #1069: Property - not found on - and there is no default value
       
    *错误#1006:试图调用的对象不是函数
    *错误#1009:空没有属性。
    *错误#1010:未定义没有属性。
    *错误#1055:没有属性。
    *错误#1069:属性 - 找不到 - 并且没有默认值

如果任何这些错误发生,绑定对象将默默捕获他们并不再实现绑定。你可以使用绑定管理的调试选项来看到这些错误。绑定管理除了捕获这些错误之外还捕获其他的错误。