Flex ItemRenderer可防止在文本输入之间使用Tab键

时间:2009-05-18 17:56:59

标签: flex flex3 usability itemrenderer

我有一个自定义 ItemRenderer ,在3个面板中分别显示5个文本输入:

<?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 docs也没有帮助。

9 个答案:

答案 0 :(得分:3)

我使用mx:VBox作为我的数据网格的rendererIsEditor =“true”的自定义itemRenderer,我也遇到了Tab键顺序问题。

我想到的是itemRenderer需要实现IFocusManagerComponent,以便主应用程序的FocusManager()能够正确地选中它。我尝试实现该接口:

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

......事实证明要做很多很复杂的接口函数。

然而在我的情况下我很幸运;我的itemRenderer中只有一个TextInput元素(其余的只是自定义代码,验证器和格式化程序)所以我将我的itemRenderer从mx:VBox转换为mx:TextInput(已经实现了IFocusManagerComponent):

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

瞧!我的标签订单问题已修复。

我认为对于那些拥有更复杂的itemRenderer的人来说,你需要在你的类中完全实现IFocusManagerComponent接口...这可能是好的,因为它看起来会告诉flex如何自定义tab通过您的itemRenderer字段。或者您可以将顶级标记更改为已实现接口的内容,例如:您可以将mx:VBox嵌套在以下内容中:

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

......也许它有用吗?代码比我更复杂的人应该试试看看会发生什么。

答案 1 :(得分:3)

我遇到了与“ListBase派生”组件中使用的itemRender相同的问题。 我发现所有“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。我能找到的唯一解决方法是创建一个从List派生的MyList组件,并以这种方式覆盖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
        }
    }

然后使用“&lt; MyList tabChildren =”true“/&gt;”而不是“&lt; mx:List /&gt;”组件让我回到了ItemRender中的tabbing功能。

希望它有所帮助,

答案 2 :(得分:0)

我想我可能正朝着正确的方向前进,但我还没有完全在那里。

我有我的主应用程序,Horizo​​ntalList使用自定义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>

我更新了原始问题中的代码示例,以包含整个ItemRenderer MXML(适用部分);这是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();
    }
}

基本上,我在这里尝试做的是通过检查每个文本字段的key_down事件上的TAB键来重新实现Tab键,并使用它将焦点移动到下一个TextInput(从第一个TextInput包装到第一个TextInput)持续)。但是这没有达到预期的效果,并且焦点仍会跳到此Horizo​​ntalList之外的下一个控件。

任何想法从哪里开始?

答案 3 :(得分:0)

你为什么要做tabChildren =“false”?你不想勾选HorziontalList的孩子吗?因为itemRenderer是列表的子项....

答案 4 :(得分:0)

这似乎与我使用的方法无法实现。我没有使用带有自定义项目渲染器的列表,而是切换到单项视图组件和单独的列表来显示所有项目的摘要,这让我可以解决我的问题。

答案 5 :(得分:0)

我遇到了同样的问题,试图重新实现标签按钮的行为来解决它。成功的线索就是使用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]);
    }
}

答案 6 :(得分:0)

答案 7 :(得分:0)

我遇到了同样的问题,即在我的AdvancedDataGrid中标记了一个itemRenderer(顺便说一句,我使用的是Flex SDK 3.5),但这篇文章对于让我使用tab友好的itemRenderer非常有用,所以我会喜欢做出贡献:)

要使其生效,您还需要更改网格和gridColumn上的一些属性。

让我们先谈谈网格和gridColumn。

众所周知,当您将网格的“可编辑”属性设置为“true”时,您可以选中每个列单元格(假设您没有将列的“可编辑”属性设置为“false”)。

第1步,让您的网格“可编辑”属性设置为“true”

第2步,让您的网格列“可编辑”属性也设置为“true”,将“rendererIsEditor”设置为“true”

将dataField设置为伪造字段非常重要,因为我们将渲染器设置为编辑器,这意味着您在itemRenderer中更新的任何内容都将分配给dataField,即您将dataField设置为“Foo”,其类型为int,你有非原始对象填充comboBox itemRenderer。进行选择时,该对象将被分配到“Foo”

第3步,使您的网格列“dataField”属性也设置为虚假字段。

现在让我们做一下启用tabbing来处理itemRenderer

的事情

我知道这不是优化版本,但这将适用于第一次传递。

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中,那就是它。

享受。

答案 8 :(得分:0)

基本上您想要删除焦点更改事件的默认行为。我想你需要这样做:

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.