(十)自定义事件并利用事件对象传递数据以及集合元素变化能派发的事件

创建和分派事件
1.使用事件可以降低程序的耦合度
2.子类自定义事件,事件可以冒泡,父类声明事件,捕获并处理事件
------------------------------------------------------
要让一个组件广播事件,需要使用dispatchEvent()方法
该方法在flash.events.EventDispatcher类中定义
Flex中的UIComponent类就直接继承自EventDispatcher类
dispatchEvent()接受一个参数,即需要分派的事件对象
在分派事件后,任何监听该事件的对象都会收到通知,相应的事件监听器/处理程序就会执行

如果事件目标(分派事件的对象)不是可见对象,Flash Player会简单将事件对象分派给指定的目标
如:Flash Player会直接将result事件分派给HTTPService组件

如果事件目标是可见的,Flash Player在分派事件时会从最外层容器(Application容器)开始,逐层分派,直至目标组件
如果事件可以冒泡,则还会再次返回Application容器

创建自定义事件
1.创建事件
      为自定义事件添加属性或者方法,必须重写clone()
      该方法负责在原始事件的基础上创建一个新的事件
2.声明事件
3.派发事件
4.监听自定义事件

为集合添加事件监听器,当集合中的元素发生变化时,发生一个事件,触发某种操作
如:shoppingCart中商品数量的变化需要重新计算商品总价
------------------------------------------------------------------
package cart {
	import mx.collections.ArrayCollection;
	import mx.collections.IViewCursor;
	import mx.collections.Sort;
	import mx.collections.SortField;
	import mx.events.CollectionEvent;

	public class ShoppingCart {
		[Bindable]
		public var items:ArrayCollection = new ArrayCollection();//所有商品都将放入此购物车

		[Bindable]
		public var total:Number = 0;//购物车中所有物品的总金额
		/**
		 * 创建购物车的时候就指定排序规则 
		 * 排序字段按product进行排序
		 */		
		public function ShoppingCart() {
			var prodSort:Sort = new Sort();
			var sortField:SortField = new SortField( "product" );
			prodSort.fields = [ sortField ];
			items.sort = prodSort;
			items.refresh();
			
			//为ArrayCollection添加事件监听函数
			//这样做的好处:不用分别在添加或删除购物车中的商品时计算总价
			//而是更全面的考虑到:只要items集合中的数据由变化,就重新计算购物车中的商品总价
			//通过ArrayCollection具备的change事件来实现更好!!
			items.addEventListener(CollectionEvent.COLLECTION_CHANGE,handleItemsChange);
		}
		/**
		 * 添加商品到购物车
		 * 如果已经存在,则更新数量
		 * 否则,直接加入到购物车,默认一次添加1个 
		 * @param item
		 * 
		 */		
		public function addItem( item:ShoppingCartItem ):void {
			if ( isItemInCart( item ) ) {
				updateItem( item );
			} else {
				items.addItem( item );
			}

			//calculateTotal();
		}
		/**
		 * 从购物车中删除某类商品
		 * 使用游标进行删除 
		 * @param item
		 * 
		 */		
		public function removeItem( item:ShoppingCartItem ):void {
			var cursor:IViewCursor = items.createCursor();

			if ( cursor.findFirst( item ) ) {
				cursor.remove();
			}

			//calculateTotal();
		}
		
		/**
		 * 购物车中已经存在该类商品
		 * 通过cursor寻找到该商品并返回 
		 * @param item
		 * @return 如果存在,返回存在的哪个product,否则返回null
		 * 
		 */		
		private function getItemInCart( item:ShoppingCartItem ):ShoppingCartItem {
			var existingItem:ShoppingCartItem;
			var cursor:IViewCursor = items.createCursor();

			var found:Boolean = cursor.findFirst( item );

			if ( found ){
				existingItem = cursor.current as ShoppingCartItem;
			}

			return existingItem; 
		}
		
		/**
		 * 判断购物车中是否已经添加该类商品
		 * @param item
		 * @return 
		 * 
		 */		
		private function isItemInCart( item:ShoppingCartItem ):Boolean {
			var sci:ShoppingCartItem = getItemInCart( item );

			return ( sci != null );
		}
		
		/**
		 * 当购物车中已经添加过此商品,则该方法会通过逻辑判断进而被调用
		 * 更新该商品的数量 
		 * @param item
		 * 
		 */		
		private function updateItem( item:ShoppingCartItem ):void {
			var existingItem:ShoppingCartItem = getItemInCart( item );
			existingItem.quantity += item.quantity;
		}
		
		/**
		 * 统计购物车中所有商品的总价
		 */		
		private function calculateTotal():void{
			var newTotal:Number = 0;
			var existingItem:ShoppingCartItem;

			for ( var i:uint=0; i<items.length; i++ ) {
				existingItem = items[ i ] as ShoppingCartItem;
				newTotal += existingItem.subtotal;
			}
			
			this.total = newTotal;
		}
		
		/**
		 * CollectionEvent是ArrayCollection等集合广播的一个特殊类型的事件
		 * 表示集合中某项发生了变化
		 * 只要ShoppingCart中的商品有变化,都会重新计算商品的总价
		 * 这样就能准确的跟踪购物车中的总金额
		 * @param event
		 * 
		 */		
		private function handleItemsChange(event:CollectionEvent):void {
			calculateTotal();
		}
	}
}



package cart {
	import valueObjects.Product;

	[Bindable]
	public class ShoppingCartItem {
		public var product:Product;//代表某类商品
		private var _quantity:uint;//添加的商品数量
		public var subtotal:Number;//小计该类商品的金额

		public function ShoppingCartItem( product:Product, quantity:uint=1 ){
			this.product = product;
			this._quantity = quantity;
			calculateSubtotal();//这里不能注释掉,第一次添加的时候需要在这里计算金额
		}
		
		public function get quantity():uint
		{
			return _quantity;
		}

		public function set quantity(value:uint):void
		{
			_quantity = value;
			calculateSubtotal();//每次添加商品都会引发数量的改变,set 方法都会被调用,在这里重新计算总价
		}

		private function calculateSubtotal():void{
			this.subtotal = product.listPrice * quantity;
		}
		
		/**
		 * cartGroup中的List中,dataProvider="{shoppingCart.items}"
		 * 将会以toString返回的字符串作为列表呈现
		 * @return 
		 * 
		 */		
		public function toString():String {
			return "[ShoppingCartItem] " + product.prodName + ":" + quantity;
		}
	}
}


ProductItem.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:DataRenderer xmlns:fx="http://ns.adobe.com/mxml/2009" 
		 xmlns:s="library://ns.adobe.com/flex/spark" 
		 xmlns:mx="library://ns.adobe.com/flex/mx" 
		 width="100%">

	<s:states>
		<s:State name="State1"/>
		<s:State name="expanded"/>
	</s:states>
	
	<!-- 声明自定义事件 -->
	<fx:Metadata>
		[Event(name="addProduct", type="events.ProductEvent")]
		[Event(name="removeProduct", type="events.ProductEvent")]
	</fx:Metadata>
	
	
	<fx:Script>
		<![CDATA[
			import cart.ShoppingCart;
			import cart.ShoppingCartItem;
			
			import events.ProductEvent;
			
			import valueObjects.Product;
			[Bindable]
			public var product:Product;//由flex在为data赋值的时候,将value赋值给product
			
			//public var shoppingCart:ShoppingCart;//购物车从哪里传入呢?通过事件派发添加/删除事件,在ShoppingView中完成,而不是在ProductItem中完成!
			
			//添加某类商品到购物车,添加商品会派发一个addProduct事件
			private function addToCart(product:Product):void {
				var event:Event = new ProductEvent("addProduct",product);//将product放到事件对象event中
				this.dispatchEvent(event);//派发事件
			}
			
			//从购物车中删除该类商品,派发一个removeProduct事件
			private function removeFromCart( product:Product ):void {
				var event:Event = new ProductEvent("removeProduct",product);//将product放到事件对象event中
				this.dispatchEvent(event);//派发事件
			}
			
			//当指定了dataProvider之后,flex会针对数据集中每个数据项创建一个呈现器实例
			//然后将取得的数据放到data中保存
			//这里覆盖data的set方法,利用上述原理,在赋值时直接将数据赋值到product对象中,而不是赋值给data
			public override function set data(value:Object) :void {
				this.product = value as Product; //注意进行类型转换
			}
		]]>
	</fx:Script>
	
	<fx:Declarations>
		<!-- 将非可视元素(例如服务、值对象)放在此处 -->
	</fx:Declarations>
	
	<!-- 商品 -->
	<s:VGroup  id="products">
		<s:Label text="{product.prodName}" id="prodName"/>
		<mx:Image source="assets/{product.imageName}" scaleContent="true" 
				  mouseOver="this.currentState='expanded'"
				  mouseOut="this.currentState='State1'"/>
		<s:Label text="${product.listPrice}" id="price"/>
		<s:Button label="AddToCart" id="add"
				  click="addToCart(product )"/>
		<s:Button label="Remove From Cart" id="remove"
				  click="removeFromCart(product )"/>			
	</s:VGroup>
	
	<!-- 商品详细信息 -->
	<s:VGroup includeIn="expanded" x="200" width="100%">
		<s:RichText text="{product.description}"
					width="50%"/>
		<s:Label text="Certified Organic"
				 visible="{product.isOrganic}"/>
		<s:Label text="Low Fat"
				 visible="{product.isLowFat}"/>
	</s:VGroup>
</s:DataRenderer>


ProductEvent.as
package events
{
	import flash.events.Event;
	
	import valueObjects.Product;
	
	public class ProductEvent extends Event
	{
		public var product:Product;
		
		public function ProductEvent(type:String, product:Product)
		{
			super(type, true);//指定事件可以冒泡
			this.product = product;//将数据作为事件对象的一个属性绑定到对象上进行传递
		}
		
		public override function clone():Event {
			return new ProductEvent(type,product);
		}
		
	}
}


ShoppingView.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009" 
		 xmlns:s="library://ns.adobe.com/flex/spark" 
		 xmlns:mx="library://ns.adobe.com/flex/mx" width="0" height="0" xmlns:components="components.*">
	<s:layout>
		<s:HorizontalLayout/>
	</s:layout>
	<s:states>
		<s:State name="State1"/>
		<s:State name="cartView"/>
	</s:states>
	

	<fx:Script>
		<![CDATA[
			import cart.ShoppingCart;
			import cart.ShoppingCartItem;
			
			import components.ProductItem;
			
			import events.ProductEvent;
			
			import mx.collections.ArrayCollection;
			
			import valueObjects.Product;
			[Bindable]
			public var shoppingCart:ShoppingCart = new ShoppingCart();//创建一个购物车,每种商品都使用同一个购物车!
			[Bindable]
			public var groceryInventory:ArrayCollection;//用于存放HTTPService返回的各种商品信息
			
			//查看当前购物车中的商品
			private function handleViewCartClick( event:MouseEvent ):void {
				this.currentState="cartView";
			}
			
			//该方法返回一个字符串,用来作为数据集的呈现方式,由labelFunction函数指定
			//当List中使用labelFunction时,会自动以正确的方式从dataProvider中获取数据并传递到该方法中
			//返回的字符串将作为被呈现的内容
			private function renderProductName(item:ShoppingCartItem):String {
				var product:Product = item.product;
				return "("+item.quantity+")" + product.prodName + " $" + item.subtotal;
			}
			
			
			//对ProductItem派发的addProduct事件进行监听并处理
			public function addProductHandler(event:ProductEvent):void {
				var sci:ShoppingCartItem = new ShoppingCartItem(event.product);//从自定义事件中获取属性
				shoppingCart.addItem(sci);
			}
			
			//对ProductItem派发的removeProduct事件进行监听并处理
			public function removeProductHandler(event:ProductEvent):void {
				var sci:ShoppingCartItem = new ShoppingCartItem(event.product);//从自定义事件中获取属性
				shoppingCart.removeItem(sci);
			}
		]]>
	</fx:Script>
	
	<fx:Declarations>
		<!-- 将非可视元素(例如服务、值对象)放在此处 -->
	</fx:Declarations>
	
	<!-- 该DataGroup被下面的ProductList取代  
	            通过事件冒泡实现商品的添加与删除
	            因为原来在ProductItem中直接对购物车添加或删除商品的操作无法进行
	  	  原因是无法对每个ProductItem都传入同一个购物车
		 [虽然this.parent.parent.shoppingCart可以访问到购物车,但是依赖性太强,不推荐]
		<s:DataGroup width="100%" height="100%" 
					 width.cartView="0" height.cartView="0" visible.cartView="false"
					 dataProvider="{groceryInventory}"
					 itemRenderer="components.ProductItem">
			<s:layout>
				<s:VerticalLayout/>
			</s:layout>
		</s:DataGroup>
	-->
	
	<!-- 使用具有事件监听出派发功能的组件替代原来的DataGroup-->
	<!-- 实现添加/删除商品的步骤:
	     ProductList作为一个组件,通过dataProvider获取到数据
		  在ProductList中又指名了itemRenderer为ProductItem
		  这样,ProductItem中的product属性就可以被赋予值
		  当添加商品的时候,会触发addProduct事件,而且事件可以冒泡
		  在ProductList中对该事件进行了声明,则可以对该事件继续向上冒泡
		  这样,在ShoppingView中就可以对这个事件进行捕获并处理!!!
	           然后,在ProductList组件中就可以指定事件发生后的处理函数了!!!
	-->
	<components:ProductList width="100%" height="100%"
							width.cartView="0" height.cartView="0" visible.cartView="false"
							dataProvider="{groceryInventory}"
							addProduct="addProductHandler(event)"
							removeProduct="removeProductHandler(event)"/>
	
	<!-- 购物车组件 -->
	<s:VGroup id="cartGroup" height="100%"  width.cartView="100%">
		<s:List id="cartList"
				dataProvider="{shoppingCart.items}" includeIn="State1"
				labelFunction="renderProductName"/>			
		<s:Label text="Your Cart Total: ${shoppingCart.total}"/>
		<s:Button label="View Cart" click="handleViewCartClick( event )" includeIn="State1"/>
		<mx:DataGrid includeIn="cartView" id="dgCart" width="100%">
			<mx:columns>
				<mx:DataGridColumn headerText="Column 1" dataField="col1"/>
				<mx:DataGridColumn headerText="Column 2" dataField="col2"/>
				<mx:DataGridColumn headerText="Column 3" dataField="col3"/>
			</mx:columns>
		</mx:DataGrid>
		<s:Button includeIn="cartView" label="Continue Shopping" click="this.currentState=''"/>
	</s:VGroup>
</s:Group>


ProductList.mxml
<?xml version="1.0" encoding="utf-8"?>
<!-- 由于ProductItem已经自定义并声明了事件,所以父类也必须声明那些事件,这样才能对其进行派发与监听 -->
<s:DataGroup xmlns:fx="http://ns.adobe.com/mxml/2009" 
			 xmlns:s="library://ns.adobe.com/flex/spark" 
			 xmlns:mx="library://ns.adobe.com/flex/mx"
			 itemRenderer="components.ProductItem"> <!-- 将itemRenderer指定为ProductItem -->
	<s:layout>
		<s:VerticalLayout/>
	</s:layout>
	
	<!-- 声明ProductItem中的自定义事件 -->
	<fx:Metadata>
		[Event(name="addProduct", type="events.ProductEvent")]
		[Event(name="removeProduct", type="events.ProductEvent")]
	</fx:Metadata>
	
	<fx:Declarations>
		<!-- 将非可视元素(例如服务、值对象)放在此处 -->
	</fx:Declarations>
	
</s:DataGroup>

你可能感兴趣的:(自定义)