Implémenter la fonctionnalité de réorganisation par glisser-déposer dans une ListBox

La réorganisation des éléments dans une ListBox par glisser-déposer est une fonctionnalité couratne dans les interfaces utilisateur. Pour l'implémenter, il faut gérer principalement deux événements : la pression du bouton de la souris lors du début du glissement et l'événement de dépôt.

Gestion du début du glissement L'événement PreviewMouseLeftButtonDown est déclenché lorsqu'un utilisateur clique sur un élément de la ListBox et commence à le faire glisser. Dans ce gestionnaire, nous devons d'abord déterminer quel élément est ciblé.

private void ListBoxSort_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    // Récupérer la position du curseur par rapport à la ListBox
    Point position = e.GetPosition(ListBoxSort);
    // Effectuer un test de visibilité pour trouver l'élément sous le curseur
    HitTestResult hitResult = VisualTreeHelper.HitTest(ListBoxSort, position);

    if (hitResult == null)
    {
        return; // Aucun élément sous le curseur
    }

    // Rechercher l'élément ListBoxItem parent de l'élément visé
    ListBoxItem targetItem = FindVisualParent<ListBoxItem>(hitResult.VisualHit);

    // Vérifier si un élément valide est trouvé et s'il correspond à l'élément sélectionné
    if (targetItem == null || targetItem.Content != ListBoxSort.SelectedItem)
    {
        return; // Ce n'est pas un élément valide ou il ne correspond pas à la sélection actuelle
    }

    // Créer un objet de données pour le glisser-déposer, contenant l'élément déplacé
    var draggedData = new DataObject(targetItem.Content as Person);
    // Lancer l'opération de glisser-déposer
    DragDrop.DoDragDrop(ListBoxSort, draggedData, DragDropEffects.Move);
}

Gestion de l'événement de dépôt L'événement Drop est déclenché lorsque l'utilisateur dépose l'élément glissé sur la ListBox. C'est ici que la logique de réorganisation est appliquée.

private void ListBoxSort_Drop(object sender, DragEventArgs e)
{
    // Récupérer la position du curseur lors du dépôt
    Point dropPosition = e.GetPosition(ListBoxSort);
    // Effectuer un test de visibilité pour trouver l'élément cible
    HitTestResult dropHitResult = VisualTreeHelper.HitTest(ListBoxSort, dropPosition);

    if (dropHitResult == null)
    {
        return; // Dépôt hors des éléments de la liste
    }

    // Récupérer l'objet de données glissé (l'élément source)
    var sourceItemData = e.Data.GetData(typeof(Person)) as Person;
    if (sourceItemData == null)
    {
        return; // Le format de données n'est pas reconnu
    }

    // Trouver l'élément ListBoxItem sous lequel l'élément a été déposé
    ListBoxItem targetListBoxItem = FindVisualParent<ListBoxItem>(dropHitResult.VisualHit);
    if (targetListBoxItem == null)
    {
        return; // Pas d'élément cible valide
    }

    // Récupérer les données de l'élément cible
    var targetItemData = targetListBoxItem.Content as Person;

    // Si l'élément source est le même que l'élément cible, ne rien faire
    if (ReferenceEquals(targetItemData, sourceItemData))
    {
        return;
    }

    // Mettre à jour la collection de données
    // Supprimer l'élément source de sa position actuelle
    _persons.Remove(sourceItemData);
    // Insérer l'élément source à la nouvelle position cible
    int targetIndex = _persons.IndexOf(targetItemData);
    _persons.Insert(targetIndex, sourceItemData);
}

Structure des données Pour que le glisser-déposer fonctionne et que la réorganisation soit gérée, la source de données de la ListBox doit être une collection observable, telle que ObservableCollection<T>.

// La collection observable qui alimente la ListBox
private ObservableCollection<Person> _persons = new ObservableCollection<Person>();

// Initialisation des données d'exemple
private void InitializeData()
{
    _persons.Add(new Person { Name = "Element Un", Order = "1" });
    _persons.Add(new Person { Name = "Element Deux", Order = "2" });
    _persons.Add(new Person { Name = "Element Trois", Order = "3" });
    _persons.Add(new Person { Name = "Element Quatre", Order = "4" });
    _persons.Add(new Person { Name = "Element Cinq", Order = "5" });

    // S'abonner à l'événement de changement de collection pour mettre à jour les ordres
    _persons.CollectionChanged += Persons_CollectionChanged;
}

Mise à jour des ordres après modification Lorsque la collection sous-jacente est modifiée (ajout, suppression ou déplacement d'éléments), il est nécessaire de mettre à jour la propriété Order de chaque élément pour refléter sa nouvelle position. Ceci est géré via l'événement CollectionChanged.

private void Persons_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    // Mettre à jour la propriété 'Order' de chaque élément
    // L'implémentation exacte peut varier en fonction des actions (Add, Remove, Move)
    if (e.Action == NotifyCollectionChangedAction.Remove)
    {
        // Si des éléments ont été retirés, réindexer ceux qui suivent
        for (int i = e.OldStartingIndex; i < _persons.Count; i++)
        {
            _persons[i].Order = (i + 1).ToString(); // Utiliser i+1 pour un ordonnancement basé sur 1
        }
    }
    else if (e.Action == NotifyCollectionChangedAction.Add)
    {
        // Si des éléments ont été ajoutés, réindexer ceux qui suivent la nouvelle insertion
        for (int i = e.NewStartingIndex; i < _persons.Count; i++)
        {
            _persons[i].Order = (i + 1).ToString();
        }
    }
    // Pour l'action 'Move', une logique plus complexe pourrait être nécessaire
    // si l'ordre n'est pas automatiquement géré par la structure de données.
}

Définition de la classe d'élément et d'une classe utilitaire La classe Person représente les données de chaque élément et doit implémenter INotifyPropertyChanged pour que les modifications de propriétés soient correctement reflétées dans l'interface utilisaetur. La classe Utils contient une méthode utilitaire pour trouver le parent visuel d'un élément.

// Classe représentant les données d'un élément dans la ListBox
public class Person : INotifyPropertyChanged
{
    public string Name { get; set; }

    private string _order;
    public string Order
    {
        get { return _order; }
        set
        {
            if (_order != value)
            {
                _order = value;
                OnPropertyChanged(nameof(Order));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

// Classe utilitaire pour naviguer dans l'arbre visuel
internal static class VisualTreeUtils
{
    // Méthode pour trouver le premier ancêtre visuel d'un type spécifié
    public static T FindVisualParent<T>(DependencyObject child) where T : class
    {
        DependencyObject parent = VisualTreeHelper.GetParent(child);
        while (parent != null)
        {
            if (parent is T typedParent)
            {
                return typedParent;
            }
            parent = VisualTreeHelper.GetParent(parent);
        }
        return null;
    }
}

Étiquettes: WPF ListBox drag and drop Réorganisation ObservableCollection

Publié le 24 août à 09h54