Flex ItemRenderer предотвращает использование табуляции между вводом текста - PullRequest
7 голосов
/ 18 мая 2009

У меня есть пользовательский ItemRenderer , который отображает 5 текстовых вводов на каждой из 3 панелей:

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox 
    xmlns:mx="http://www.adobe.com/2006/mxml"
    height="300"
    width="800"
    creationComplete="onCreationComplete()"
>
    <!-- code-behind -->
    <mx:Script source="ChainListRenderer.mxml.as" />

    <mx:Label text="{data.title}" fontSize="25" fontWeight="bold" width="100%" textAlign="center" />
    <mx:HBox>
        <mx:Panel id="triggerPanel" title="Trigger" width="260">
            <mx:VBox id="tpBoxes" width="100%" paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5">
                <mx:TextInput id="trigger1" width="100%" textAlign="left" tabIndex="0" tabEnabled="true" />
                <mx:TextInput id="trigger2" width="100%" textAlign="left" tabIndex="1" tabEnabled="true" />
                <mx:TextInput id="trigger3" width="100%" textAlign="left" tabIndex="2" tabEnabled="true" />
                <mx:TextInput id="trigger4" width="100%" textAlign="left" tabIndex="3" tabEnabled="true" />
                <mx:TextInput id="trigger5" width="100%" textAlign="left" tabIndex="4" tabEnabled="true" />
            </mx:VBox>
        </mx:Panel>
        <mx:Panel id="linkPanel" title="Link" width="260">
            <mx:VBox id="lpBoxes" width="100%" paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5">
                <mx:TextInput id="link1" width="100%" textAlign="left" tabIndex="5" tabEnabled="true" />
                <mx:TextInput id="link2" width="100%" textAlign="left" tabIndex="6" tabEnabled="true" />
                <mx:TextInput id="link3" width="100%" textAlign="left" tabIndex="7" tabEnabled="true" />
                <mx:TextInput id="link4" width="100%" textAlign="left" tabIndex="8" tabEnabled="true" />
                <mx:TextInput id="link5" width="100%" textAlign="left" tabIndex="9" tabEnabled="true" />
            </mx:VBox>
        </mx:Panel>
        <mx:Panel id="answerPanel" title="Answer" width="260">
            <mx:VBox id="apBoxes" width="100%" paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5">
                <mx:TextInput id="answer1" width="100%" textAlign="left" tabIndex="10" tabEnabled="true" />
                <mx:TextInput id="answer2" width="100%" textAlign="left" tabIndex="11" tabEnabled="true" />
                <mx:TextInput id="answer3" width="100%" textAlign="left" tabIndex="12" tabEnabled="true" />
                <mx:TextInput id="answer4" width="100%" textAlign="left" tabIndex="13" tabEnabled="true" />
                <mx:TextInput id="answer5" width="100%" textAlign="left" tabIndex="14" tabEnabled="true" />
            </mx:VBox>
        </mx:Panel>
    </mx:HBox>
</mx:VBox>

К сожалению, при использовании в качестве ItemRenderer табуляция между текстовыми вводами не работает, даже со значениями tabIndex выше. Если я скопирую этот код в собственное приложение MXML, вкладка между текстовыми вводами будет работать как положено.

Кто-нибудь знает, как восстановить вкладки в этом сценарии? Будет жаль, если мне придется выпустить это приложение без такого простого элемента юзабилити.

Полагаю, мне может понадобиться реализовать mx.managers.IFocusManagerComponent, но я не могу найти примеров того, как это сделать, и Документы FocusManager тоже не помогают.

Ответы [ 9 ]

3 голосов
/ 11 марта 2010

Я столкнулся с той же проблемой с itemRender, использованным в компоненте «ListBase производная». Я обнаружил, что все компоненты, производные от ListBase, оборачивают все элементы в ListBaseContentHolder.

Из источника ListBase:

/**
 *  An internal display object that parents all of the item renderers,
 *  selection and highlighting indicators and other supporting graphics.
 *  This is roughly equivalent to the <code>contentPane</code> in the 
 *  Container class, and is used for managing scrolling.
 */
protected var listContent:ListBaseContentHolder;

Свойства этого класса tabChildren и tabEnabled по умолчанию имеют значение false. Единственный обходной путь, который я мог найти, - это создать компонент MyList, производный от List, и переопределить метод createChildren (где инициализируется listContent) следующим образом:

import flash.display.DisplayObject;
import mx.controls.List;

public class MyList extends List {
    override protected function createChildren():void {
            super.createChildren();
            this.listContent.tabChildren = this.tabChildren
            this.listContent.tabEnabled = this.tabEnabled
        }
    }

Затем использование «» вместо компонента «» вернуло мне функциональность табуляции в ItemRender.

Надеюсь, это поможет,

3 голосов
/ 03 сентября 2009

Я использовал mx: VBox в качестве пользовательского itemRenderer с rendererIsEditor = "true" для моей таблицы данных, и я также столкнулся с проблемой порядка табуляции.

Что я понял, так это то, что itemRenderer должен реализовать IFocusManagerComponent, чтобы FocusManager () основного приложения мог правильно вкладывать в него. Я попытался реализовать этот интерфейс:

<?xml version="1.0"?>
<mx:VBox implements="mx.managers.IFocusManagerComponent" ...>
 [the rest of my itemRenderer code]
</mx:VBox>

... и это оказывается довольно сложным для выполнения ... множество функций интерфейса для реализации.

Однако в моем случае мне повезло; У меня был только один элемент TextInput в моем itemRenderer (все остальное было просто пользовательским кодом, валидаторами и форматерами), поэтому я преобразовал свой itemRenderer из mx: VBox в mx: TextInput (который уже реализует IFocusManagerComponent):

<?xml version="1.0"?>
<mx:TextInput ...>
 [the rest of my itemRenderer code]
</mx:TextInput>

Вуаля! Моя проблема с порядком вкладок была исправлена.

Я полагаю, что для тех из вас, кто имеет более сложные itemRenderers, вывод заключается в том, что вам нужно либо полностью реализовать интерфейс IFocusManagerComponent в своем классе ... что, вероятно, хорошо, поскольку похоже, что он говорит flex, как настраивать вкладки через ваши поля itemRenderer. Или, возможно, вы могли бы изменить свой тег верхнего уровня на что-то, что уже реализует интерфейс, например: могли бы вы вложить mx: VBox во что-то вроде:

<mx:Container focusIn="FocusManager.setFocus(trigger1)">

... и возможно ли это работает? Кто-то с более сложным кодом, чем я должен попробовать и посмотреть, что происходит.

0 голосов
/ 15 ноября 2011

В основном вы хотите удалить поведение по умолчанию для события изменения фокуса. Я думаю, вам нужно сделать это:

1. yourRenderer.addEventListener(FocusEvent.KEY_FOCUS_CHANGE, onKeyFocusChange);
2. since you want to stop tab key, in the handler, do this: 
        if (event.keyCode == Keyboard.TAB)
            event.preventDefault()
3. in your keyDown handler, catch TAB, then you can manually move your focus.
0 голосов
/ 27 апреля 2011

У меня были те же проблемы с вкладками одного из itemRenderer в моем AdvancedDataGrid (кстати, я использую Flex SDK 3.5), но этот пост был чрезвычайно полезен, так как позволил мне сделать мой ItemRenderer удобным для вкладок, чтобы я мог хотел бы также внести свой вклад:)

Чтобы это работало, вам также нужно изменить несколько свойств в grid и gridColumn.

Давайте сначала поговорим о grid и gridColumn.

Как вы все знаете, когда вы устанавливаете свойство "editable" сетки в значение "true", вы можете перемещаться по каждой ячейке столбца (при условии, что вы не установили свойство "editable" столбца в значение "false").

Шаг # 1 , установите для свойства "editable" вашей сетки значение "true"

Шаг # 2 , установите для свойства "editable" столбца вашей сетки значение, равное "true", а для rendererIsEditor - "true"

Важно установить для dataField фиктивное поле, потому что, поскольку мы устанавливаем средство визуализации в качестве редактора, это означает, что все, что вы обновляете в itemRenderer, будет назначено на dataField, т.е. вы устанавливаете dataField в «Foo», который имеет тип int и у вас есть не примитивные объекты, заполняющие ComboBox itemRenderer. Когда вы сделаете выбор, этот объект будет присвоен "Foo"

Шаг № 3 , сделайте так, чтобы свойство столбца вашей сетки dataField также устанавливало поддельное поле.

Теперь давайте сделаем так, чтобы вкладки работали на itemRenderer

Я знаю, что это не оптимизированная версия, но она подойдет для 1-го прохода.

import mx.core.mx_internal;
import mx.managers.IFocusManagerComponent;
use namespace mx_internal;

public class FriendlyItemRendererContainer extends HBox implements IFocusManagerComponent
{

    public function FriendlyItemRendererContainer ()
    {
    super();
    addEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeHandler);       
    }

    private var _listData:DataGridListData;
    //This is required to make it work as itemEditor
    public var text:String;

    private function keyFocusChangeHandler(event:FocusEvent):void
    {
            if (event.keyCode == Keyboard.TAB &&
                ! event.isDefaultPrevented() &&
                findNextChildToFocus(event.shiftKey))
            {

                event.preventDefault();

            }

    }

    private function findNextChildToFocus(shiftKey:Boolean):Boolean
    {
          var myChildrenAry:Array = getChildren();
      var incr:int = shiftKey ? -1 : 1;
      var index:int = shiftKey ? myChildrenAry.length : 0;
      var focusChildIndex:int = 0;
      var found:Boolean = false;

         for (focusChildIndex = 0; focusChildIndex < myChildrenAry.length; ++focusChildIndex)
     {
        if (!(myChildrenAry[focusChildIndex] as UIComponent).visible ||
            (myChildrenAry[focusChildIndex] is Container))
        {
            //If it's an invisible UIComponent or a container then just continue
            continue;
        }

            if (myChildrenAry[focusChildIndex] is TextInput)
        {
                    if (systemManager.stage.focus == (myChildrenAry[focusChildIndex] as TextInput).getTextField())
                    {
                        (myChildrenAry[focusChildIndex] as UIComponent).drawFocus(false);
                        found = true;
                        break;
                    }
        }
        else
        {
                    if (systemManager.stage.focus == myChildrenAry[focusChildIndex])
                    {
                        (myChildrenAry[focusChildIndex] as UIComponent).drawFocus(false);
                        found = true;
                        break;
                    }
       }
         }

         if (!found)
     {
        focusChildIndex = 0;
      }

      while (true)
      {
                focusChildIndex = focusChildIndex + incr;

                if ((focusChildIndex < 0) || (focusChildIndex >= myChildrenAry.length))
                {
                    UIComponentGlobals.nextFocusObject = null;
                    return false;
                }
                else
                if (myChildrenAry[focusChildIndex] is UIComponent)
                {
                (myChildrenAry[focusChildIndex] as UIComponent).setFocus();
                (myChildrenAry[focusChildIndex] as UIComponent).drawFocus(true);

                    break;
                }
        }


        return true;
    }

    override public function setFocus():void
    {
       var myChildrenAry:Array = getChildren();
       if (myChildrenAry.length > 0)
       {
        for (var i:int = 0; i < myChildrenAry.length; ++i)
        {
            if ((myChildrenAry[i] is UIComponent) && (myChildrenAry[i] as UIComponent).visible)
            {
               (myChildrenAry[i] as UIComponent).setFocus();
                   (myChildrenAry[i] as UIComponent).drawFocus(true);
               break;
            }
       }
    }

    }

    public function get listData():BaseListData
    {
            return _listData;
    }

    public function set listData(value:BaseListData):void
    {
            _listData = DataGridListData(value);
    }  
}

Пример использования его на вашем itemRenderer:

<FriendlyItemRendererContainer xmlns:mx="http://www.adobe.com/2006/mxml">

<mx:TextInput/>
<mx:Button label="Boo"/>
<mx:RadioButton/>
<<mx:TextInput/>
<mx:Button label="Baa"/>

</FriendlyItemRendererContainer>

Затем просто поместите это в gridColumn и все.

Наслаждайтесь.

0 голосов
/ 28 мая 2010

Вот как это работает с ComboBox itemEditor:

http://blog.flexmonkeypatches.com/2008/02/18/simple-datagrid-combobox-as-item-editor-example/

0 голосов
/ 27 декабря 2009

У меня была такая же проблема, я решил ее, пытаясь переопределить поведение кнопки табуляции Ключ к успеху - просто использовать метод event.preventdefault(). Используемый код показан впереди.

private function initFocusMap():void {
    focusMap = {
        InNom:benefPersona.InApePat,
        InApePat:benefPersona.InApeMat,
        InApeMat:benefPersona.InFecNacimiento,
        InFecNacimiento:benefPersona.InRFC,
        InRFC:benefPersona.InCURP,
        InCURP:benefPersona.InIdSexo,
        InIdSexo:benefPersona.InIdParentesco,
        InIdParentesco:benefPersona.InPorc,
        InPorc:domBeneficiario.InCalle,
        InCalle:domBeneficiario.InNumExterior,
        InNumExterior:domBeneficiario.InNumInterior,
        InNumInterior:domBeneficiario.InCP,
        InCP:domBeneficiario.InColonia,
        InColonia:domBeneficiario.InIdPais,
        InIdPais:domBeneficiario.InIdEdo,
        InIdEdo:domBeneficiario.InIdCiudad,
        InIdCiudad:benefPersona.InNom                   
    }
}

private function kfcHandler(event:FocusEvent):void {
    var id:String = ""
    if (event.target is AperClsDateField || event.target is AperClsCombo) {
        id = event.target.id;
    } else {
        id = event.target.parent.id;
    }
    if (id != "InIdDelegacionMunic") {
        event.preventDefault();             
        focusManager.setFocus(focusMap[id]);
    }
}
0 голосов
/ 24 августа 2009

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

0 голосов
/ 03 июня 2009

Почему вы делаете tabChildren = "false"? Разве вы не хотите вкладывать детей в HorziontalList? поскольку itemRenderer является дочерним элементом списка ....

0 голосов
/ 20 мая 2009

Я думаю, что могу двигаться в правильном направлении, но я еще не совсем там.

У меня есть основное приложение с горизонтальным списком, использующим пользовательский ItemRenderer:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application 
    xmlns:mx="http://www.adobe.com/2006/mxml" 
    xmlns:com="ventures.view.component.*"
    layout="absolute"
    backgroundColor="#ffffff"
    preinitialize="onPreInitialize()"
    click="onClick(event)"
>
    <!-- code-behind -->
    <mx:Script source="ConsumptionChain.as" />

    <!-- show chain links -->
    <mx:HorizontalList
        id="ChainList"
        direction="horizontal"
        dataProvider="{chainData}"
        rollOverColor="#ffffff"
        selectionColor="#ffffff"
        horizontalScrollPolicy="off"
        left="20"
        right="20"
        top="20"
        height="300"
        minWidth="802"
        rowHeight="300"
        columnWidth="800"
        tabChildren="false"
        itemRenderer="ventures.view.ItemRenderer.ChainListRenderer"
    />

</mx:Application>

Я обновил пример кода в исходном вопросе, чтобы он содержал весь MXM ItemRenderer (соответствующие части); и вот код-код ActionScript:

/*
 * ChainListRenderer.mxml.as -- code-behind for ChainListRenderer.mxml
 */

import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;

//used to combine all textboxes in a single array to make looping through them with the TAB key easier
private var allBoxes:Array;

public function expandPanel(e:Event):void {
    trace(e.currentTarget);                
    var state : String = e.currentTarget.parent.parent.id;                
    if (state != this.currentState)
        this.currentState = state;
}

private function onCreationComplete():void{
    //this function will be run on each object via the map function
    function forEach(o:Object, index:int, ar:Array):void{
        o.addEventListener(FocusEvent.FOCUS_IN, expandPanel)
        o.addEventListener(MouseEvent.CLICK, stopBubble);           //don't propagate click events (which return to base state)
        o.addEventListener(KeyboardEvent.KEY_DOWN, customTabbing);  //fix tabbing between text fields
    }

    this.addEventListener(KeyboardEvent.KEY_DOWN, customTabbing);

    //loop over textboxes and add the focusIn event listener
    tpBoxes.getChildren().map(forEach);
    lpBoxes.getChildren().map(forEach);
    apBoxes.getChildren().map(forEach);

    //create an "allBoxes" array that is used by the customTabbing function
    allBoxes = tpBoxes.getChildren();
    function forEachTabbing(o:Object, index:int, ar:Array):void {
        allBoxes.splice(allBoxes.length, 0, o);
    }
    lpBoxes.getChildren().map(forEachTabbing);
    apBoxes.getChildren().map(forEachTabbing);
}

//this function is used to prevent event bubbling
private function stopBubble(e:Event):void {
    e.stopPropagation();
}

//this function re-implements tabbing between text fields, which is broken when inside an itemRenderer
public function customTabbing(e:KeyboardEvent):void {
    trace('keyCode: ' && e.keyCode.toString());
    if (e.keyCode == flash.ui.Keyboard.TAB){
        trace(focusManager.getFocus());
        //loop over array of all text-boxes
        for (var i:int = 0; i < allBoxes.length; i++){
            trace(i.toString());
            //if event (keypress) current target is the current TextInput from the array
            if (e.currentTarget == allBoxes[i]){
                //then focus the NEXT TextInput, and wrap if at last one
                allBoxes[((i+1) % allBoxes.length)].setFocus();
                //break out of the loop
                break;
            }
        }

        //prevent arrow keys from navigating the horizontal list
        e.stopPropagation();
    }
}

По сути, я пытаюсь повторно реализовать табуляцию, проверяя клавишу TAB на событии key_down каждого текстового поля и используя его для перемещения фокуса на следующий TextInput (перенос на первый TextInput из прошлой). Это не дает желаемого эффекта, и фокус все равно выпадает на следующий элемент управления вне этого горизонтального списка.

Есть идеи, куда идти?

...