Developpez.com - Microsoft DotNET
X

Choisissez d'abord la catégorieensuite la rubrique :


Ajouter des fonctionnalités à une ListBox

Date de publication : 07/12/2009

Par Samuel Blanchard (Site perso) (Blog)
 


       Version PDF (Miroir)   Version hors-ligne (Miroir)
Viadeo Twitter Facebook Share on Google+        



0. Introduction
I. Prérequis
II. Création du projet
III. Un item non sélectionnable par la souris
IV. Test de notre nouvel item
V. Une ListBox qui empêche la sélection par clavier
VI. Test final
VII. Utilisation possible
VIII. Conclusion
IX. Amélioration possible
X. Remerciement


0. Introduction

Nous allons montrer, à travers un exemple, la puissance d'utilisation des ListBoxs en Silverlight.

Notre exemple nous permettra de mettre en place des items d'une ListBox non sélectionnables par l'utilisateur, c'est-à-dire en gérant la souris et le clavier. En revanche, la sélection restera possible en fixant les propriétés SelectedItem et SelectedIndex.


I. Prérequis

Ces exemples ont été développés en Silverlight 3 mais devraient fonctionner dans les versions supérieures.


II. Création du projet

Commençons par créer notre projet Silverlight.

image
Puis

image

III. Un item non sélectionnable par la souris

Commençons par créer une classe ListBoxSelectableItem, héritée de ListBoxItem, dans notre projet Silverlight.

image

Cette classe comportera la nouvelle propriété CanSelect, une Dependency Property (ajouté via le snippet propdp). Cette propriété nous permettra de déterminer si l'item peut être sélectionné ou non par l'utilisateur.
public class ListBoxSelectableItem : ListBoxItem
{
    /// <summary>
    /// CanSelect
    /// </summary>
    public bool CanSelect
    {
        get { return (bool)GetValue(CanSelectProperty); }
        set { SetValue(CanSelectProperty, value); }
    }

    // Using a DependencyProperty as the backing store for CanSelect.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CanSelectProperty =
        DependencyProperty.Register("CanSelect", typeof(bool), typeof(ListBoxSelectableItem), new PropertyMetadata(true));
}
Afin d'empêcher la souris de sélectionner l'item, on surcharge dans le ListBoxSelectableItem, la méthode de gestion de pression de bouton gauche de la souris
/// <summary>
/// Surcharge de l'evenement de pression du bouton gauche de la souris
/// </summary>
/// <param name="e"></param>
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
    e.Handled = !this.CanSelect;
    base.OnMouseLeftButtonDown(e);
}
Le e.Handled permet de prendre en compte ou non l'événement de la souris selon la valeur de la propriété CanSelect de l'item.


IV. Test de notre nouvel item

Afin de vérifier que la souris est pris ou non en compte pour nos items, mettons en place, dans notre MainPage.xaml , un test rapide.

On ajoute tout d'abord un namespace « my »se référant à notre Assembly.


Puis on passe à notre test :
<StackPanel Orientation="Vertical" Width="200">        
    
    <!-- Affichage de l'index selectionne de la ListBox -->
    <TextBlock FontWeight="Bold" Text="{Binding ElementName=ListBox, Path=SelectedIndex}"></TextBlock>

    <!-- la ListBox -->
    <ListBox x:Name="ListBox">
        <!-- Un item de type controle -->
        <TextBlock Text="Hello"></TextBlock>
        <!-- Un item selectionnable -->
        <my:ListBoxSelectableItem CanSelect="True" Content="Selectionnable"></my:ListBoxSelectableItem>
        <!-- Un item non selectionnable -->
        <my:ListBoxSelectableItem CanSelect="False" Content="Non selectionnable avec la souris"></my:ListBoxSelectableItem>
        <!-- Un item Classique -->
        <ListBoxItem Content="Bye"></ListBoxItem>
    </ListBox>
    
</StackPanel>

On note que la ListBox utilisée est une ListBox classique. Plusieurs types d'item se côtoient à l'intérieur : Des contrôles, des ListBoxItem et notre ListBoxSelectableItem.

Descendant de ListBoxItem, notre ListBoxSelectableItem est considérée comme un ListBoxItem classique par la ListBox. Ce n'est pas tout à fait le cas pour le contrôle TextBlock comme on le verra tout à l'heure.


Pour l'instant, testons :

image
On peut sélectionner l'item 1 « Selectionnable » mais pas l'item 2 « Non Selectionnable avec la souris »

Comment se comporte l'application avec le clavier ?

image

Pas tout à fait comme on le souhaiterait. Puisque l'utilisateur est capable de sélectionner l'élement 2 alors qu'il n'est pas sélectionnable normalement. En fait le clavier n'étant pas encore géré, la situation est tout à fait normale.


V. Une ListBox qui empêche la sélection par clavier

Lorsque l'utilisateur frappe une touche du clavier sur le ListBoxSelectableItem, il est déjà trop tard, l'item est déjà sélectionné. Il faut intercepter l'événement en amont afin qu'il ne soit pas diffusé plus bas. En surchargeant le contrôle ListBox on pourra récupérer cet évenement :

Commençons par rajouter une nouvelle classe ListBoxSelectable héritée de ListBox.

image

Nous prenons le parti dans notre ListBoxSelectable de remplacer le ListBoxItem de base par notre propre ListBoxSelectableItem pour faciliter notre développement. Ainsi la propriété CanSelect sera forcément présente dans tous les containers de chaque item.

Pour se faire, il est nécessaire d'ajouter deux méthodes à notre classe qui permettent d'insérer un container autour de l'item si besoin est.
public class ListBoxSelectable : ListBox
{
    /// <summary>
    /// L'item est'il déjà un ListBoxSelectorItem ?
    /// </summary>
    /// <param name="item"></param>
    /// <returns></returns>
    protected override bool IsItemItsOwnContainerOverride(object item)
    {
        return item is ListBoxSelectableItem;
    }

    /// <summary>
    /// Obtenir l'item
    /// </summary>
    /// <returns></returns>
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new ListBoxSelectableItem();
    }
}
IsItemItsOwnContainerOverride permet de déterminer si l'item est ou non un container de type ListBoxSelectableItem tandis que GetContainerForItemOverride renvoie une nouvelle instance de container.

Ainsi comme le contrôle TextBlock « Hello », de notre exemple de tout à l'heure, n'est pas un ListBoxSelectableItem, le ListBox doit donc lui rajouter automatiquement un container de type ListBoxSelectableItem.


On rajoute ensuite l'événement de gestion de frappe clavier vers le bas. Cette événement ne gère que les touches de déplacement HAUT et BAS comme la ListBox originale.
/// <summary>
/// Frapper une touche
/// </summary>
/// <param name="e"></param>
protected override void OnKeyDown(KeyEventArgs e)
{
    switch(e.Key)
    {
        case Key.Up :
        case Key.Down :
            e.Handled = true;
            break;
    }

    base.OnKeyDown(e);
}
Comme pour la souris, tout à l'heure, on fixe le e.Handled à True pour signifier que l'on prend en charge la gestion de la touche HAUT et BAS et qu'elle ne doit pas se diffuser vers les contrôles enfants. Les autres touches sont gérées normalement par la ListBox.

Fonctionnellement, Lorsque l'on appuie sur la touche BAS, l'item en dessous doit être sélectionné, sur la touche HAUT, l'item en dessus doit être sélectionné.

Hors nous souhaitons que certain item ne soient plus sélectionnable. On doit donc écrire une méthode chargée de récupérer le premier item sélectionnable dans le sens de la touche.
/// <summary>
/// Trouver un item selectionnable vers le bas (isNext = true) ou vers le haut (isNext = false)
/// </summary>
/// <param name="isNext"></param>
/// <returns></returns>
private object FindSelectableItem(bool isNext)
{
    int index = this.SelectedIndex;
    int maxIndex = this.Items.Count;

    if (isNext == true)
    {
        for (int i = index + 1; i < maxIndex; i ++)
        {
            ListBoxSelectorItem item = this.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxSelectorItem;

            if (item.CanSelect == true)
            {
                return this.Items[i];
            }
        }
    }
    else            
    {
        for (int i = index - 1; i > -1; i--)
        {
            ListBoxSelectorItem item = this.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxSelectorItem;

            if (item.CanSelect == true)
            {
                return this.Items[i];
            }
        }
    }

    return null;
}
Notez la méthode this.ItemContainerGenerator.ContainerFromIndex de la ListBox qui permet d'obtenir le container ListBoxSelectableItem par son index.


On ajoute la méthode FindSelectableItem à notre gestion des touches :
/// <summary>
/// Frapper une touche
/// </summary>
/// <param name="e"></param>

protected override void OnKeyDown(KeyEventArgs e)
{
    switch(e.Key)
    {
        case Key.Up :
        case Key.Down :

        bool isNext = e.Key == Key.Down ? true : false;
        
        object item = this.FindSelectableItem(isNext);

        if (item != null)
        {
            this.SelectedItem = item;
        }

        e.Handled = true;
        break;
    }

    base.OnKeyDown(e);
}

VI. Test final

On remplace la ListBox classique de notre test original par notre ListBoxSelectable et l'on retire le dernier item ListBoxItem qui n'est plus d'actualité désormais.
<!-- la ListBox -->
<my:ListBoxSelectable x:Name="ListBox">
    <!-- Un item de type controle -->
    <TextBlock Text="Hello"></TextBlock>
    <!-- Un item selectionnable -->
    <my:ListBoxSelectableItem CanSelect="True" Content="Selectionnable"></my:ListBoxSelectableItem>
    <!-- Un item non selectionnable -->
    <my:ListBoxSelectableItem CanSelect="False" Content="Non selectionnable avec la souris et le clavier"></my:ListBoxSelectableItem>
    <!-- Un item de type controle -->
    <TextBlock Text="Bye"></TextBlock>
</my:ListBoxSelectable>

On lance et l'on sélectionne l'index 1

image
Puis on appuie sur la touche du bas et l'item passe à un index 3 (en sautant l'index 2 non sélectionnable).


Tout fonctionne parfaitement cette fois-ci !


VII. Utilisation possible

A quoi peu servir ce genre d'item ? Si l'on considère qu'une ListBox peut servir de Menu, Il peut être utile de mettre en place des séparateurs non sélectionnable entre ces menus, par exemple.


VIII. Conclusion

En conclusion, vous avez pu découvrir quelques uns des mécanismes internes de la ListBox. Ces mécanismes nous ont permis de rajouter une fonctionnalité simple mais puissante.

Vous pouvez télécharger le projet ici.


IX. Amélioration possible

On aurait aussi pu mettre en place une Attached Property plus souple que l'héritage de la ListBox.

Cela fera peut être l'objet d'un article prochainement.


X. Remerciement

Je voudrais remercier Benjamin Roux, Benoit Gemin et Alessandra Sada pour leur relecture technique et leur apport à l'article.



               Version PDF (Miroir)   Version hors-ligne (Miroir)

Valid XHTML 1.0 TransitionalValid CSS!

Copyright © 2009 Benjamin Roux. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.

Responsable bénévole de la rubrique Microsoft DotNET : Hinault Romaric -