2009-06-04 7 views
3

Comment faire un ComboBox où l'utilisateur peut sélectionner null?Null dans Flex ComboBox

Si vous créez simplement un combobox avec null dans le dataprovider, la valeur apparaît mais l'utilisateur ne peut pas sélectionner:

<mx:ComboBox id="cb" dataProvider="{[null, 'foo', 'bar']}" /> 

Y at-il un moyen de faire que sélectionner null?

Une solution de contournement consiste à ajouter un élément dans le DataProvider qui n'est pas null mais «représente» null; puis mappez entre null et cet objet chaque fois que vous accédez à la liste déroulante. Mais ce n'est pas vraiment une solution élégante; vous devrez toujours garder cette cartographie à l'esprit dans tout le code qui accède à un combobox 'nullable' ...

Éditer: expansion pourquoi pourquoi je n'aime pas la solution de contournement: Cela pourrait être fait dans une sous-classe bien sûr , mais soit je présente de nouveaux attributs (comme nullableSelectedItem); mais alors vous devez faire attention à toujours utiliser ces attributs. Ou je remplace les ComboBoxes selectedItem; mais je crains que cela ne vienne casser la classe de base: il n'aimerait peut-être pas que quelque chose change son idée de l'intérieur de l'élément sélectionné. Et même ce hack fonctionne fragile, sur le dessus de selectedItem et dataProvider ce nullItem alors doit également être traité spécial data et listData pour équarrisseurs, en labelFunction, et il est sans doute encore être exposé à des événements ComboBox ... envoie Il pourrait travailler, mais c'est un bidouillage juste pour résoudre le problème que si l'utilisateur clique sur l'élément, il n'est pas activé (pour le reste le ComboBox gère très bien). (Une autre alternative est d'avoir un composant ui délégué à un ComboBox, mais c'est encore beaucoup plus de code juste pour éviter ce petit problème)

+0

Vous pouvez créer une sous-classe de ComboBox qui encapsule uniquement ce comportement d'annulation. – Simon

+0

Un commentaire sur Spark ComboBox? – BrunoJCM

Répondre

3

La solution suivante est probablement la plus simple un:

<mx:ComboBox id="cb" dataProvider="{[{label:"", data:null}, {label:'foo', data:'foo'}, {label:'bar', data:'bar'}]}" /> 

et accéder aux données à l'aide cb.selectedItem.data

Cependant, cette solution simple ne lie pas l'environnement, comme Wouter mentionné.

Voici donc une solution plus délicate qui permettra de sélectionner des objets null:

<mx:ComboBox id="cb" dataProvider="{[null, 'foo', 'bar']}" dropdownFactory="{new ClassFactory(NullList)}" /> 

Où NullList est la classe suivante:

package test 
{ 
import mx.controls.List; 

public class NullList extends List 
{ 
    public function NullList() 
    { 
     super(); 
    } 

    override public function isItemSelectable(data:Object):Boolean 
    { 
     if (!selectable) 
      return false; 
     return true; 
    } 

} 
} 
+0

C'est une version simplifiée de la solution de contournement. Mais cela ne vous permet pas de lier l'élément selectedI à un modèle. En d'autres termes, vous ne pouvez pas faire quelque chose comme avec ceci. –

+0

Oui, vous avez raison, la liaison sera plus difficile pour cette solution. (mais c'est toujours possible - vous pouvez lier à selectedIndex et utiliser une fonction simple pour obtenir l'index de l'élément). Je suis venu avec la solution qui résout votre problème exact. Espérons que cela aide – Hrundik

+0

Nice! Exactement ce que je cherchais. Il y a un problème cosmétique: l'élément null ne s'allume pas lorsque vous passez la souris dessus. –

1

Malheureusement, ceci est impossible. Cependant, une bonne solution qui ne vous obligera pas à "garder ce mapping à l'esprit" est de créer une classe héritant de ComboBox avec sa propre propriété DataProvider.

cet accesseur de propriété gérera les valeurs null et aura une représentation pour lui sur la classe super ComboBox.

+0

Je ne l'aime pas vraiment. Mais tant que personne ne trouve quelque chose qui rend un "vrai" null sélectionnable, je suppose que vous avez raison: c'est impossible, et le mieux que nous puissions faire est d'encapsuler la solution de contournement autant que possible dans une sous-classe. –

0

Un très simple, mais aussi solution très limitée est d'ajouter un prompt = "" attribut ..

Cela empêchera la ComboBox de sélectionner le premier élément dans le da taProvider automatiquement, mais dès que l'utilisateur sélectionne un élément, la ligne vide ne sera plus affichée.

+0

Etre capable de toujours sélectionner la rangée vide est tout à fait essentiel, c'est toute la question. –

+0

La prise en charge de ce scénario nécessite un codage complexe. Veuillez jeter un coup d'œil à mon autre réponse. –

5

Il semble que la seule façon de gérer correctement un élément vide consiste à ajouter un élément au fournisseur de données de la liste déroulante. La sous-classe suivante traitera ceci automatiquement. L'implémentation est un peu compliquée pour supporter les changements sur dataprovider, en termes d'ajout/suppression d'éléments et de réattribution complète du fournisseur de données lui-même (il suffit de penser aux bindings de arraycollections pour la réponse des services distants).

package {

import flash.events.Event; 
    import flash.events.FocusEvent; 
    import mx.collections.ArrayCollection; 
    import mx.collections.ICollectionView; 
    import mx.collections.IList; 
    import mx.containers.FormItem; 
    import mx.controls.ComboBox; 
    import mx.events.CollectionEvent; 
    import mx.events.ListEvent; 
    import mx.validators.Validator; 

    public class EmptyItemComboBox extends ComboBox { 

     protected var _emptyItem:Object = null; 
     protected var _originalDataProvider:ICollectionView = null; 

     public function EmptyItemComboBox() { 
      super(); 
      addEmptyItem(); 
      addEventListener(Event.CHANGE, onChange); 
     } 

     private function onChange(event:Event):void { 
      dispatchEvent(new Event("isEmptySelectedChanged")); 
     } 

     [Bindable] 
     public function get emptyItem():Object { 
      return _emptyItem; 
     } 

     public function set emptyItem(value:Object):void { 
      if (_emptyItem != value) { 
       clearEmptyItem(); 
       _emptyItem = value; 
       addEmptyItem(); 
      } 
     } 

     [Bindable(event="isEmptySelectedChanged")] 
     public function get isEmptySelected():Boolean { 
       return (selectedItem == null || (_emptyItem != null && selectedItem == _emptyItem)); 
     } 

     override public function set selectedItem(value:Object):void { 
      if (value == null && emptyItem != null) { 
       super.selectedItem = emptyItem; 
      } else if (value != selectedItem) { 
       super.selectedItem = value; 
      } 
      dispatchEvent(new Event("isEmptySelectedChanged")); 
     } 

     override public function set dataProvider(value:Object):void { 
      if (_originalDataProvider != null) { 
       _originalDataProvider.removeEventListener(
         CollectionEvent.COLLECTION_CHANGE, 
         onOriginalCollectionChange); 
      } 
      super.dataProvider = value; 
      _originalDataProvider = (dataProvider as ICollectionView); 
      _originalDataProvider.addEventListener(
        CollectionEvent.COLLECTION_CHANGE, 
        onOriginalCollectionChange) 
      addEmptyItem(); 
     } 

     private function clearEmptyItem():void { 
      if (emptyItem != null && dataProvider != null 
        && dataProvider is IList) { 
       var dp:IList = dataProvider as IList; 
       var idx:int = dp.getItemIndex(_emptyItem); 
       if (idx >=0) { 
        dp.removeItemAt(idx);  
       } 
      } 
      dispatchEvent(new Event("isEmptySelectedChanged")); 
     } 

     private function addEmptyItem():void { 
      if (emptyItem != null) { 
       if (dataProvider != null && dataProvider is IList) { 
        var dp:IList = dataProvider as IList; 
        var idx:int = dp.getItemIndex(_emptyItem); 
        if (idx == -1) { 
         var newDp:ArrayCollection = new ArrayCollection(dp.toArray().concat()); 
         newDp.addItemAt(_emptyItem, 0); 
         super.dataProvider = newDp; 
        } 
       } 
      } 
      dispatchEvent(new Event("isEmptySelectedChanged")); 
     } 

     private function onOriginalCollectionChange(event:CollectionEvent):void { 
      if (_emptyItem != null) { 
       dataProvider = _originalDataProvider; 
       addEmptyItem(); 
      } 
     } 
    } 
} 

Quelques notes sur la classe:

  • Il insérera l'objet vide automatiquement sur la liste .. ce fut une forte exigences pour mon scénario: les DataProviders a été retourné par les services à distance et ils ne pouvaient pas inclure d'éléments supplémentaires uniquement pour prendre en charge l'interface utilisateur Flex, ni je pouvais regarder manuellement chaque collection pour créer des éléments vides à chaque actualisation de collection. Comme il doit travailler avec le contenu de la collection, il créera en interne une copie de l'original avec les instances des mêmes éléments ET le vide, de sorte que l'instance de la collection originale ne sera pas touchée. tous et peuvent être réutilisés dans un autre contexte.

  • Il va écouter les changements sur le fournisseur de données d'origine, permettant de travailler dessus et même d'en assigner un nouveau: l'élément vide sera recréé automatiquement. Vous contrôlez l'instance à utiliser comme "élément vide" avec l'attribut emptyItem: ceci permet de conserver un type de données cohérent avec le reste de la collection (si vous utilisez des objets typés), ou de définir une étiquette personnalisée sur elle.

quelques exemples de code utilisant ...

<mx:Script> 
    <![CDATA[ 
     import mx.controls.Alert; 
    ]]> 
    </mx:Script> 

    <mx:ArrayCollection id="myDP"> 
    <mx:source> 
     <mx:Array> 
      <mx:Object value="1" label="First"/> 
      <mx:Object value="2" label="Second"/> 
      <mx:Object value="3" label="Third"/> 
     </mx:Array> 
    </mx:source> 
    </mx:ArrayCollection> 

    <local:EmptyItemComboBox id="comboBox" dataProvider="{myDP}" labelField="label"> 
    <local:emptyItem> 
     <mx:Object value="{null}" label="(select an item)"/> 
    </local:emptyItem> 
    </local:EmptyItemComboBox> 

    <mx:Button label="Show selected item" click="Alert.show(comboBox.selectedItem.value)"/> 

    <mx:Button label="Clear DP" click="myDP.removeAll()"/> 
    <mx:Button label="Add item to DP" click="myDP.addItem({value: '4', label: 'Fourth'})"/> 
    <mx:Button label="Reset DP" click="myDP = new ArrayCollection([{value: '1', label: 'First'}])"/> 

    <mx:Label text="{'comboBox.isEmptySelected = ' + comboBox.isEmptySelected}"/> 

</mx:Application> 

+0

Votre code pour l'ajouter automatiquement et regarder la collection originale pour les changements semble bon. Mais comme je l'ai expliqué dans la question, je n'aime vraiment pas avoir un emptyItem 'représentant' null; et avec le NullList proposé par Hrundik, ce n'est plus nécessaire. –

+0

Si vous contrôlez la création de vos collections dataprovider (et ainsi vous avez la possibilité d'ajouter le null), la solution proposée par Hrundik est la voie à suivre (grand fan du principe KISS ici :) Ma sous-classe n'est pas à propos d'avoir une instance d'objet réel par rapport à une référence null (il pourrait être réécrit en utilisant la méthode null item/dropDownFactory). Il est plus ciblé de gérer ces choses lorsque votre collection peut être actualisée à tout moment et vous n'avez pas le contrôle de ce qui est un scénario assez commun. J'espère que cela pourrait être utile si quelqu'un est confronté au même problème. –

+0

J'aime votre solution. Merci. Mon idée précédente était proche de cela, mais la mise en œuvre avait des problèmes. Maintenant, je viens de simplifier un petit peu votre: au lieu de emptyItem, j'utilise emptyLabel (string). –

0

requireSelection = « false » permettra une valeur vide et rapide vous permet d'entrer dans l'utilisation de texte pour cette valeur vide si vous » d aimer.

Questions connexes