Flex 4.5 Spark 雖然提供了 DataGrid
可是它沒有像 mx:AdvancedDataGrid 那樣支援多 Column 排序功能
看到 Adobe Cookbooks – How do I perform a multicolumn sort in a Spark DataGrid?
有人提供,可是那幾乎是寫死在組件外部
不能讓 User 自己選擇排序欄位
所以我想辦法繼承 Spark DataGrid,複寫 columnHeaderGroup_clickHandler 函式
當 User 在 Column Header 同時按下 Ctrl + Mouse Left Click 時
提供多重 Column 排序的功能

MultiColSortableDataGrid.as

package {
	import mx.collections.ICollectionView;
	import mx.collections.IList;
	import mx.collections.ISort;
	import mx.collections.ISortField;
	import mx.collections.Sort;
	import mx.styles.AdvancedStyleClient;
 
	import spark.components.DataGrid;
	import spark.components.Grid;
	import spark.components.gridClasses.GridColumn;
	import spark.components.gridClasses.GridSortField;
	import spark.events.GridEvent;
	import spark.events.GridSortEvent;
 
	/**
	 *  Multi-column sortable data grid class.
	 *  Extends spark data grid and override columnHeaderGroup_clickHandler function.
	 *  Provide multi-column sort function when ctrl + left click on column header.
	 */
	public class MultiColSortableDataGrid extends DataGrid {
 
		/**
		 *  @private
		 */
		override protected function columnHeaderGroup_clickHandler(event:GridEvent):void {
			const column:GridColumn = event.column;
			var columnIndices:Vector.<int>;
 
			if (!enabled || !sortableColumns || !column || !column.sortable)
				return;
 
			columnIndices = Vector.<int>([column.columnIndex]);
 
			// If the sort isn't cancelled, will also update the columnHeaderGroup
			// visibleSortIndiciatorIndices.
 
			sortByMultiColumns(columnIndices, true, event.ctrlKey);
		}
 
 
 
	    /**
		 *  Copy and modify from DataGrid::sortByColumns function.
	     *  Add one more argument multiColSort, decide if continue to use old sort fields.
		 *  Achieve multi-column sort function.
	     */
		public function sortByMultiColumns(columnIndices:Vector.<int>,
							isInteractive:Boolean = false , multiColSort:Boolean = false):Boolean {
 
			const dataProvider:ICollectionView = this.dataProvider as ICollectionView;
			if (!dataProvider)
				return false;
 
			var sort:ISort = dataProvider.sort;
			if (sort) {
				sort.compareFunction = null;
			} else {
				sort = new Sort();
			}
			var sortFields:Array = createSortFields(columnIndices, sort.fields, isInteractive);
			if (!sortFields)
				return false;
 
			var oldSortFields:Array = (dataProvider.sort) ? dataProvider.sort.fields : null;
 
 
			// implements multi-column sort function.
			if (multiColSort && oldSortFields) {
				if (columnHeaderGroup) {
					var colSortIndices:Vector.<int> = columnHeaderGroup.visibleSortIndicatorIndices;
					for (var j:int = colSortIndices.length - 1 ; j >= 0 ; --j) {
						var idx:int = colSortIndices[j];
						if (columnIndices.indexOf(idx) < 0) {
							columnIndices.unshift(idx);
						}
					}
				}
 
				outer: for (var i:int = oldSortFields.length - 1 ; i >= 0 ; --i) {
					var oldField:ISortField = oldSortFields[i] as ISortField;
 
					for (var k:int = 0 ; k < sortFields.length ; ++k) {
						var newField:ISortField = sortFields[k] as ISortField;
 
						if (newField.name == oldField.name) {
							continue outer;
						}
					}
					sortFields.unshift(oldField);
				}
			}
 
 
			// Dispatch the "changing" event. If preventDefault() is called
			// on this event, the sort operation will be cancelled.  If columnIndices or
			// sortFields are changed, the new values will be used.
			if (isInteractive) {
				// This is a shallow copy which means only the pointers to the ISortField objects
				// are copied to the new Array, not the ISortField objects themselves.
				if (oldSortFields)
					oldSortFields = oldSortFields.concat();
 
				if (hasEventListener(GridSortEvent.SORT_CHANGING)) {
					const changingEvent:GridSortEvent =
						new GridSortEvent(GridSortEvent.SORT_CHANGING,
						false, true,
						columnIndices,
						oldSortFields, /* intended to be read-only but no way to enforce this */
						sortFields);
 
					// The event was cancelled so don't sort.
					if (!dispatchEvent(changingEvent))
						return false;
 
					// Update the sort columns since they might have changed.
					columnIndices = changingEvent.columnIndices;
					if (!columnIndices)
						return false;
 
					// Update the new sort fields since they might have changed.
					sortFields = changingEvent.newSortFields;
					if (!sortFields)
						return false;
				}
			}
 
			// Remove each old SortField that's not a member of the new sortFields Array
			// as a "styleClient" of this DataGrid.
 
			if (oldSortFields) {
				for each (var oldSortField:ISortField in oldSortFields) {
					var oldASC:AdvancedStyleClient = oldSortField as AdvancedStyleClient;
					if (!oldASC || (oldASC.styleParent != this) || (sortFields.indexOf(oldASC) != -1))
						continue;
					removeStyleClient(oldASC);
				}
			}
 
			// Add new SortFields as "styleClients" of this DataGrid so that they
			// inherit this DataGrid's locale style. 
 
			for each (var newSortField:ISortField in sortFields) {
				var newASC:AdvancedStyleClient = newSortField as AdvancedStyleClient;
				if (!newASC || (newASC.styleParent == this))
					continue;
				addStyleClient(newASC);
			}
 
			sort.fields = sortFields;
 
			dataProvider.sort = sort;
			dataProvider.refresh();
 
			if (isInteractive) {
				// Dispatch the "change" event.
				if (hasEventListener(GridSortEvent.SORT_CHANGE)) {
					const changeEvent:GridSortEvent =
						new GridSortEvent(GridSortEvent.SORT_CHANGE,
						false, true,
						columnIndices,
						oldSortFields, sortFields);
					dispatchEvent(changeEvent);
				}
 
				// Update the visible sort indicators.
				if (columnHeaderGroup)
					columnHeaderGroup.visibleSortIndicatorIndices = columnIndices;
			}
 
			return true;
		}
 
 
	    /**
	     *  @private
	     *  Copy from DataGrid::createSortFields function.
	     */
		private function createSortFields(columnIndices:Vector.<int>,
									previousFields:Array, preservePrevious:Boolean):Array {
			if (columnIndices.length == 0)
				return null;
 
			var fields:Array = new Array();
 
			for each (var columnIndex:int in columnIndices) {
				var col:GridColumn = this.getColumnAt(columnIndex);
				if (!col)
					return null;
 
				var dataField:String = col.dataField;
 
				// columns all must have a dataField or a labelFunction or a sortCompareFunction
				if (dataField == null && col.labelFunction == null && col.sortCompareFunction == null)
					return null;
 
				const isComplexDataField:Boolean = (dataField && (dataField.indexOf(".") != -1));
				var sortField:ISortField = null;
				var sortDescending:Boolean = col.sortDescending;
 
				// Check if we just sorted this column.
				sortField = findSortField(dataField, fields, isComplexDataField);
 
				// If we haven't sorted by this column yet, check if
				// we've sorted by this column in the previous sort.
				if (!sortField && previousFields)
					sortField = findSortField(dataField, previousFields, isComplexDataField);
				else
					preservePrevious = false;
 
				// Previously sorted column, so flip sortDescending.
				if (sortField)
					sortDescending = !sortField.descending;
 
				// Create a SortField from the column.  If the sortField was found in the
				// previousFields and we need to preserve them, create a new sort field for the column.
				if (!sortField || preservePrevious)
					sortField = col.sortField;
 
				col.sortDescending = sortDescending;
				sortField.descending = sortDescending;
				fields.push(sortField);
			}
 
			return fields;
		}
 
 
	    /**
	     *  @private
	     *  Copy from DataGrid::getColumnAt function.
	     */
		private function getColumnAt(columnIndex:int):GridColumn {
			const grid:Grid = grid;
			if (!grid || !grid.columns)
				return null;
			const columns:IList = grid.columns;
			return ((columnIndex >= 0) && (columnIndex < columns.length)) ?
											columns.getItemAt(columnIndex) as GridColumn : null;
		}
 
 
		/**
		 *  @private
		 *  Copy from DataGrid::findSortField function.
		 * 
		 *  Finds a SortField using the provided dataField and returns it.
		 *  If the dataField is complex, it tries to find a GridSortField
		 *  with a matching dataFieldPath.
		 *
		 *  @param dataField The dataField of the column.
		 *  @param fields The array of SortFields to search through.
		 *  @param isComplexDataField true if the dataField is a path.
		 */
		private static function findSortField(dataField:String, fields:Array, isComplexDataField:Boolean):ISortField {
			if (dataField == null)
				return null;
			for each (var field:ISortField in fields) {
				var name:String = field.name;
				if (isComplexDataField && (field is GridSortField)) {
					name = GridSortField(field).dataFieldPath;
				}
				if (name == dataField)
					return field;
			}
			return null;
		}
	}
}

以上組件就能提供多重欄位排序功能
假如還想要顯示排序優先順序號碼
就需要再提供自訂的 Skin
參考原始檔案

Spark 多重欄位排序線上範例:

This movie requires Flash Player 9

原始檔案下載