Итак, приступим к реализации наших компонентов.
Базовый ренедрер мы реализуем на базе компонента 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"];
}
}
На этом создание базового рендерера можно считать законченным. В заключительной части мы рассмотрим, каким способом его можно использовать для создания нестандартных таблиц.
