Ajouter des fonctionnalités simples à une listbox en silverlight
Date de publication : 01/06/2010 , Date de mise à jour : 01/06/2010
Par
Samuel Blanchard (Site perso) (Blog)
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.
Puis
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.
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 >
< / summary >
public bool CanSelect
{
get { return (bool )GetValue (CanSelectProperty); }
set { SetValue (CanSelectProperty, value); }
}
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 >
< / 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 " >
< TextBlock FontWeight = " Bold " Text = " {Binding ElementName=ListBox, Path=SelectedIndex} " > < / TextBlock >
< ListBox x : Name = " ListBox " >
< TextBlock Text = " Hello " > < / TextBlock >
< my : ListBoxSelectableItem CanSelect = " True " Content = " Selectionnable " > < / my : ListBoxSelectableItem >
< my : ListBoxSelectableItem CanSelect = " False " Content = " Non selectionnable avec la souris " > < / my : ListBoxSelectableItem >
< 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 :
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 ?
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.
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 >
< / summary >
< param name = " item " > < / param >
< returns > < / returns >
protected override bool IsItemItsOwnContainerOverride (object item)
{
return item is ListBoxSelectableItem;
}
< summary >
< / 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 >
< / 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 >
< / 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 >
< / 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.
|
< my : ListBoxSelectable x : Name = " ListBox " >
< TextBlock Text = " Hello " > < / TextBlock >
< my : ListBoxSelectableItem CanSelect = " True " Content = " Selectionnable " > < / my : ListBoxSelectableItem >
< my : ListBoxSelectableItem CanSelect = " False " Content = " Non selectionnable avec la souris et le clavier " > < / my : ListBoxSelectableItem >
< TextBlock Text = " Bye " > < / TextBlock >
< / my : ListBoxSelectable >
|
On lance et l'on sélectionne l'index 1
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.
Copyright © 2010 Samuel Blanchard.
Aucune reproduction, même partielle, ne peut être faite de ce site ni 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.