Нестандартное использование ItemRenderer. Часть вторая

Евгений Кулиш
04 мая 2008
Евгений Кулиш,
Flex разработчик OWOX, ББЦ, Translot

Итак, приступим к реализации наших компонентов.
Базовый ренедрер мы реализуем на базе компонента Canvas, для того, чтобы мы могли свободно управлять расположением объектов. Создаем файл EditableItemRenderer.mxml и задаем компоненту некоторые свойства:


<?xml version="1.0" encoding="utf-8"?>
<Canvas xmlns="*" xmlns:mx="http://www.adobe.com/2006/mxml"
	creationComplete="create()" initialize="init()" width="100%" horizontalScrollPolicy="off" height="22" verticalCenter="-2">
</Canvas>

На стадии инициализации компонента создаем объект, который будет отображать текст:


private var _labelObject:Label;
						
private function init():void
{
	this._labelObject = new Label();
	this._labelObject.percentWidth = 100;
	this._labelObject.percentHeight = 100;
	this._labelObject.setStyle("paddingLeft", 5);
	this._labelObject.includeInLayout = true;
}

Так как тип компонента Label у нас не изменяется, то мы позволим наследникам и другим компонентам изменять только свойства созданного объекта:


public function get labelObject():Label
{
	return this._labelObject;
}

Теперь дадим возможность задавать нужный нам класс для редактирования текста и доступа к его свойствам:


public function set editObject(value:UIComponent):void
	{
		this._editObject = value;
		if (this._editObject != null) {
			this._editObject.percentWidth = 100;
			this._editObject.percentHeight = 100;
			this._editObject.includeInLayout = true;
			this._editObject.setStyle("paddingLeft", 5)
			this.addChild(this._editObject);
			this.invalidateDisplayList();
		}
	}
			
	public function get editObject():UIComponent
	{
		return this._editObject;
	}

Следующий, очень важный этап, описать функциональность, которая позволит работать с данными, полученными из Data Provider. Flex во процессе работы с компонентами ListBase, для передачи данных рендереру и получению их назад использует свойство data у компонента которое необходимо реализовать у нашего компонента. Но прежде, добавим пару методов, которые позволят использовать рендерер для колонок с любым именем. Имя колонки мы можем получить из значения, которое DataGrid передает свойству listBase типа BaseListData, правда только в том случае, если рендерер реализует интерфейс IDropInListItemRenderer. Следовательно, добавляем в заголовок компонента строку вида: implements="mx.controls.listClasses.IDropInListItemRenderer" и описываем свойство listBase:


private var _listData:BaseListData;
			
	public function set listData(value:BaseListData):void
	{
		this._listData = value;
	}
	
public function get listData():BaseListData
	{
		return this._listData;
	}

А теперь можно реализовать механизм работы с данными, не забывая, что каждое поле у нас будет описываться парой label/value:


private var _data:Object;
private var _label:String;
private var _value:Object;
			
	public function set data(value:Object):void
	{
		this._data = value;
		if (this._data != null) {
			var col:DataGridListData = DataGridListData(this.listData);
			if (this.isDataAssigned(this._data, col)) {
				if (this._data[col.dataField].hasOwnProperty("label")) {
					this._label = this._data[col.dataField].label;
// задаем текст для отображения нашему компоненту Label
					this._labelObject.text = this._label;
				}
				if (this._data[col.dataField].hasOwnProperty("value")) {
					this._value = this._data[col.dataField].value;
				}
			} else {
				if (this._labelObject != null) {
					this._labelObject.text = "";
				}
				this._label = null;
				this._value = null;
			}
		}
	}
	
	public function get data():Object
	{
		if (this._data != null) {
			var col:DataGridListData = DataGridListData(this.listData);
			if (!this.isDataAssigned(this._data, col)) {
				this._label = null;
				this._value = null;
			}
		}
		return this._data;
	}
			
	private function isDataAssigned(data:Object, col:DataGridListData):Boolean
	{
		return ( (col != null) && (data.hasOwnProperty(col.dataField) ) );
	}

Метод isDataAssigned служит для проверки, задано ли описание колонки и есть ли среди данных поле, необходимое для текущей колонки.

Состояния

Переключение состояний будем осуществлять изменением свойства visible у наших объектов отображения и редактирования. Сами состояния будем задавать строками (по аналогии с компонентом States):


private var _currentState:String;
			
override public function get currentState():String
	{
		return this._currentState;
	} 

override public function set currentState(value:String):void
	{
		if ( this._listData != null ) {
			var _owner:DataGridBase = (this._listData.owner as DataGridBase);
			switch (this.currentState) {
				case "Edit":
					if ((this._label != null) && (this._value != null)) {
						this._labelObject.visible = false;
						if (this._editObject != null) {
							this._editObject.visible = true;
							this._editObject.setFocus();
						}
						if (_owner != null) {
			            			_owner.dragEnabled = false;
			            		}
			            		if (this.setDataProperty("__is_edited__", true) == null) {
			            			this.saveValues();
			            		}
				            		
			            		this.setDataProperty("__is_edited__", true);
				      }
		            		
		            		
					break;
				case "View":
					this._labelObject.visible = true;
					if (this._editObject != null) {
						this._editObject.visible = false;
					}
					if (_owner != null) {
		            			_owner.dragEnabled = true;
		            		}
		            		this.setDataProperty("__is_edited__", null);
					break;
				case "Cancel":
					this._labelObject.visible = true;
					if (this._editObject != null) {
						this._editObject.visible = false;
					}
					if (_owner != null) {
		            			_owner.dragEnabled = true;
		            		}
		            		this.setDataProperty("__is_edited__", null);
		            		
					this.restoreValues();
						
					break;
				}
			}
		this._currentState = value;
	}

Хочу обратить внимание на некоторые нюансы. Так, чтобы избежать паразитного эффекта от перетаскивания объектов в процессе выделения мышкой текста в редактируемом элементе, мы в статусе Edit у владельца рендерера отключаем свойство dragEnabled и включаем во остальных статусах. Еще один момент связан с реализацией механизма рендереров непосредственно в компоненте DataGrid. Разработчики Flex в целях оптимизации и экономии памяти, создают ренедреры не для всех объектов Data Provider, а только для тех, которые отображаются на экране. Поэтому, чтобы в процессе скорлинга наши рендереры оставались в нужном нам состоянии, приходится непосредственно в данных сохранять флаг состояния и получать его в процессе отображения. Здесь этот механизм реализован в функциях setDataProperty и getDataProperty:


public function setDataProperty(property_name:String, value:Object):void
	{
		if (this._data != null) {
			var col:DataGridListData = DataGridListData(this.listData);
			if (this.isDataAssigned(this._data, col)) {
				this._data[col.dataField][property_name] = value;
			}
		}
	}

	public function getDataProperty(property_name:String):Object
	{
		var result:Object = null;
		if ( this._data != null ) {
			var col:DataGridListData = DataGridListData(this.listData);
			if ( this.isDataAssigned(this._data, col) && this._data[col.dataField].hasOwnProperty(property_name) ) {
				result = this._data[col.dataField][property_name];
			}
		}

		return result;
	}

И последнее: для того, чтобы мы могли отменить введенные значения и вернуть прежние в статусе Cancel, мы вводим два вспомогательных метода saveValues и restoreValues:


private var tmpData:Object;

public function saveValues():void
	{
		if (this.listData != null) {
			this.tmpData = new Object();
			this.tmpData["value"] = ObjectUtil.copy(this._value);
			this.tmpData["label"] = ObjectUtil.copy(this._label);
		}
	}

	public function restoreValues():void
	{
		if ( (this.listData != null) && (this.tmpData != null) ) {
			this._value = this.tmpData["value"];
			this._label = this.tmpData["label"];
		}
	}

На этом создание базового рендерера можно считать законченным. В заключительной части мы рассмотрим, каким способом его можно использовать для создания нестандартных таблиц.

Метки: Flex, RIA

Комментарии

Добавить комментарий