Archives for November 2006

Flex 2 AS 3 onReleaseOutside

I just ran across an interesting issue with Flex 2/AS 3, the onReleaseOutside event handler no longer exists for MovieClips (or any Interactive Object). According the Adobe AS 2 - AS 3 migration documentation onReleaseOutside has been replaced in the new event model by a mouseUp event after a call to flash.display.InteractiveObject.setCapture(). Upon further investigation the InteractiveObject.setCapture() no longer exists either.

So effectively there is no easy way to catch when a user presses the mouse button, drags the cursor off the pressed object and then releases the mouse button. This causes a serious problem, especially for items that are dragged around since the mouse cursor often comes off the dragged item. Without a proper onReleaseOutside handler if the user releases the mouse button while outside a dragged item, the item will continue to be dragged around until the user mouses back over the item and then clicks and releases. Thankfully there is a relatively clean solution.

In the code below I simply added an extra statement in the start drag function that adds a MouseUp listener to the Application stage. Now if the user clicks and drags off an item and then releases the mouse button, the Application stage will catch the MouseUp and stop the item drag. In the stop drag function I added a statement to remove the listener, in order to cut down on an unnecessary event handling. Not as nice as having a proper onReleaseOutside event but at least it works.

<?xml version="1.0" ?>
<mx:Application
	xmlns:mx="http://www.adobe.com/2006/mxml"
	layout="absolute">
	<mx:Script>
		<![CDATA[
			import flash.events.MouseEvent;
			
			private function handleRedStartDrag(event:MouseEvent):void
			{
				stage.addEventListener(MouseEvent.MOUSE_UP, handleRedStopDrag);
				var bounds:Rectangle = new Rectangle(bounds_cv.x, bounds_cv.y, bounds_cv.width - red_cv.width, bounds_cv.height-red_cv.height);
				red_cv.startDrag(false, bounds);
			
			}
			
			private function handleRedStopDrag(event:MouseEvent):void
			{
				red_cv.stopDrag();
				stage.removeEventListener(MouseEvent.MOUSE_UP, handleRedStopDrag);
			}			
			
			private function handleGreenStartDrag(event:MouseEvent):void
			{
				var bounds:Rectangle = new Rectangle(bounds_cv.x, bounds_cv.y, bounds_cv.width - green_cv.width, bounds_cv.height-green_cv.height);
				green_cv.startDrag(false, bounds);
			
			}
			
			private function handleGreenStopDrag(event:MouseEvent):void
			{
				green_cv.stopDrag();
			}

		]]>
	</mx:Script>


<!-- bounding rectangle -->
<mx:Canvas 
	id="bounds_cv" 
	x="50" 
	y="50" 
	width="300" 
	height="200"
	backgroundColor="#CCCCCC"  
	borderStyle="solid"/>

<!-- The red box being dragged -->
<mx:Canvas 
	id="red_cv" 
	backgroundColor="#ff0000" 
	height="50" 
	width="50" 
	x="50" 
	y="50" 
	mouseDown="handleRedStartDrag(event);" 
	mouseUp="handleRedStopDrag(event);"  />

<!-- green box being dragged -->
<mx:Canvas 
	id="green_cv" 
	backgroundColor="#00ff00" 
	height="50" 
	width="50" 
	x="50" 
	y="200" 
	mouseDown="handleGreenStartDrag(event);" 
	mouseUp="handleGreenStopDrag(event);"  />

</mx:Application>

You can test it here, click and drag the red or green box around in the rectangle and then release the mouse button outside of the rectangle in the grey area. The green box uses only the normal MouseUp handler to stop the drag, while the red box has the added statement for the listening to the mouseUp on the Application. Note that if you drag and release outside of the swf the release event is not captured, this is the same issue with the AS 2 onReleaseOutside, no way around it that I am aware of. Thanks Senocular for the tip on using the stage.

DataGrid Drag Image

One of the great features in Flex is the ability to enable drag and drop from data grids with one simple statement, dragEnabled="true". Flick the switch and away you go dragging rows here and there. But what happens when you do not want to see the entire row being dragged around. What if you want to display something more meaniful and less bulky than the entire visible row. Well if you are using the mx.controls.DataGrid you are stuck, however there is a solution. Extend the DataGrid. Here is a working sample, once it is loaded do a right click to view the source code.

By default the DataGrid's dragImage property is protected and set to 'mx.controls.dataGridClasses.DataGridDragProxy'. To enable setting a custom dragImage on the DataGrid you can simply extend it to allow the use of a use defined drag image class.

<mx:DataGrid xmlns:mx="http://www.adobe.com/2006/mxml">
	<mx:Script>
		<![CDATA[
		import mx.controls.dataGridClasses.DataGridDragProxy;
		import mx.core.IUIComponent;
		
		/**
		* @public
		* class to use as DragProxy image
		* set the default value to the standard DataGridDragProxy class
		*/
		[Bindable]
		public var dragProxyImage: Class = DataGridDragProxy; 
		
		override protected function get dragImage():IUIComponent
		{
			var image:IUIComponent = new dragProxyImage();
			image.owner = this;
			return image;
		}
		]]>
	</mx:Script>
</mx:DataGrid>

Now that the DataGrid has been extended to allow a custom drag image, let's see how to use it. The first thing to do is to create a class to use as the drag image. It's a good idea to look over the source code for the mx.controls.dataGridClasses.DataGridDragProxy class as you will be able to see how to create a DragProxy that can work with one or multiple selected rows. Next, drop the new DataGrid into your mxml file, set the dragProxyImage value and you are done.

<controls:DataGrid 
	dataProvider="{dataSource}" 
	rowHeight="40" 
	dragEnabled="true" 
	height="140" 
	dragProxyImage="com.dgrigg.controls.CustomDragProxy" 
	allowMultipleSelection="true">
	
	<controls:columns>
		<mx:DataGridColumn headerText="Image" dataField="image">
			<mx:itemRenderer>
				<mx:Component>
					<mx:Image source="{data.image}"/>
				</mx:Component>
			</mx:itemRenderer>
		</mx:DataGridColumn>
		<mx:DataGridColumn headerText="Product" dataField="name"/>
		<mx:DataGridColumn headerText="Description" dataField="description"/>
	</controls:columns>
</controls:DataGrid>	

This same solution will work with the mx.controls.List. Click here to view a working sample. Once it is loaded do a right click to view the source code.