Retour à l'accueil

ListBox personnalisée dans WPF : glisser-déposer et mise à l'échelle

L'article est consacré au développement de contrôles WPF personnalisés avec mise à l'échelle, glisser-déposer et support de tri flexible. Deux composants clés sont considérés : ExtendedListBox pour la mise à l'échelle et DragAnimatedPanel pour le transfert animé d'éléments. Des exemples de code sont fournis et les stratégies d'implémentation sont expliquées.

Création de ListBox dans WPF avec glisser-déposer et mise à l'échelle
Advertisement 728x90

# Implémentation d'une ListBox avancée en WPF : mise à l'échelle, tri et glisser-déposer

Développer des contrôles WPF personnalisés avec glisser-déposer, mise à l'échelle et support de tri flexible est une tâche ardue mais essentielle pour les interfaces modernes. Dans cet article, nous explorons en profondeur l'implémentation de deux composants clés : une ListBox améliorée et un panneau avec glisser d'éléments animés. La solution intègre la séparation des responsabilités, l'optimisation des performances et le support de diverses stratégies de mise en page des éléments.

Mise à l'échelle : Extraction de la fonctionnalité dans ExtendedListBox

Pour implémenter correctement la mise à l'échelle des éléments de liste, nous avons extrait cette fonctionnalité dans un contrôle ExtendedListBox séparé, hérité de la ListBox standard. Cette approche a évité de compliquer la logique du panneau de glisser animé et a permis la réutilisation du composant. Éléments clés de l'implémentation :

  • Propriété de dépendance Scale avec limites via MinScale/MaxScale
  • Gestion de l'événement MouseWheel pour les changements d'échelle
  • Calcul du nouveau facteur d'échelle avec limitation fluide

L'algorithme principal de gestion de la molette de la souris :

Google AdInline article slot
private void ProcessScale(MouseWheelEventArgs e)
{
    const double DELTA_DIVISOR = 1000d;

    double zoomScale = e.Delta / DELTA_DIVISOR;
    double newScaleFactor = Scale + zoomScale;

    if (newScaleFactor >= MinScale && newScaleFactor <= MaxScale)
    {
        Scale = newScaleFactor;
    }
    else if (newScaleFactor < MinScale)
    {
        Scale = MinScale;
    }
    else if (newScaleFactor > MaxScale)
    {
        Scale = MaxScale;
    }
}

Il est crucial de configurer correctement le modèle d'élément via ItemTemplate. La mise à l'échelle est appliquée en utilisant ScaleTransform lié à la propriété du contrôle :

<customWpfControls:ExtendedListBox.ItemTemplate>
    <DataTemplate>
        <Border Margin="10">
            <Image Source="{Binding ImageSource}">
                <Image.LayoutTransform>
                    <TransformGroup>
                        <ScaleTransform 
                            ScaleX="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type customWpfControls:ExtendedListBox}}}" 
                            ScaleY="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type customWpfControls:ExtendedListBox}}}"/>
                    </TransformGroup>
                </Image.LayoutTransform>
            </Image>
        </Border>
    </DataTemplate>
</customWpfControls:ExtendedListBox.ItemTemplate>

Ce modèle garantit une transformation correcte des éléments à n'importe quelle valeur d'échelle sans calculs supplémentaires en code-behind.

Stratégies de mise en page : Tri personnalisé dans DragAnimatedPanel

Pour implémenter des mises en page d'éléments flexibles, nous avons créé le DragAnimatedPanel avec support de quatre stratégies de tri. L'architecture de base surcharge les méthodes MeasureOverride et ArrangeOverride, permettant un contrôle total sur la mesure et l'arrangement des éléments enfants.

Google AdInline article slot

Étapes clés du fonctionnement :

  • Calcul des tailles requises du panneau dans MeasureOverride
  • Détermination des positions des éléments dans ArrangeOverride
  • Basculer dynamiquement les stratégies via l'interface ILayoutStrategy

Exemple d'implémentation de base de MeasureOverride pour une mise en page diagonale :

protected override Size MeasureOverride(Size availableSize)
{
    foreach (UIElement child in Children)
    {
        child.Measure(availableSize);
    }

    if (Children.Count == 0)
    {
        _calculatedSize = new Size();
    }
    else
    {
        List<Size> childSizes = new List<Size>(Children.Count);
        foreach (UIElement child in Children)
        {
            childSizes.Add(child.DesiredSize);
        }

        double width = childSizes.Sum(x => x.Width);
        double height = childSizes.Sum(x => x.Height);

        _calculatedSize = new Size(width, height);
    }

    return _calculatedSize;
}

Stratégies de mise en page prises en charge :

Google AdInline article slot
  • Bande horizontale (tous les éléments sur une ligne)
  • Colonne verticale (tous les éléments sur une colonne)
  • Mise en page en tableau (nombre fixe de colonnes)
  • Remplissage de lignes (calcul dynamique des colonnes)

Chaque stratégie implémente l'interface ILayoutStrategy avec les méthodes :

public interface ILayoutStrategy
{
    Size ResultSize { get; }
    void MeasureLayout(Size availablePanelSize, List<Size> sizes, bool isDragging);
    int GetIndex(Point position);
    ItemLayoutInfo GetLayoutInfo(int index);
}

Cette approche garantit l'extensibilité et le remplacement facile des algorithmes de tri sans modifier la logique principale du panneau.

Glisser-déposer : Algorithme de glisser des éléments

L'implémentation du glisser-déposer inclut trois étapes critiques : détection du début du glisser, gestion du mouvement et validation du résultat. Pour éviter les déclenchements intempestifs, nous avons ajouté des vérifications de durée d'appui et de seuil de déplacement du curseur.

Processus d'initialisation du glisser :

  • Capture des coordonnées et du temps dans OnMouseLeftButtonDown
  • Vérification des conditions dans OnMouseMove (temps minimum d'appui et déplacement)
  • Capture de l'élément et de la souris dans StartDrag
private void OnMouseLeftButtonDown(object sender, MouseEventArgs e)
{
    Point mousePos = Mouse.GetPosition(this);
    _lastMousePosX = mousePos.X;
    _lastMousePosY = mousePos.Y;
    _mouseDownTime = DateTime.Now;
    _mouseSelectedElement = GetChildThatHasMouseOver();
}

private void StartDrag(MouseEventArgs e)
{
    DraggedElement = _mouseSelectedElement;
    _draggedIndex = Children.IndexOf(DraggedElement);
    Point p = GetItemVisualPoint(DraggedElement);
    _x = p.X;
    _y = p.Y;

    SetZIndex(DraggedElement, DRAG_ELEMENT_Z_INDEX);
    CaptureMouse();
}

Une optimisation clé est la vérification du seuil de déplacement (10 pixels) avant de traiter le mouvement. Cela réduit la charge lors du travail avec un grand nombre d'éléments :

private void OnDragOver(MouseEventArgs e)
{
    const double MOUSE_DIF = 10d;
    Point mousePos = Mouse.GetPosition(this);
    double difX = mousePos.X - _lastMousePosX;
    double difY = mousePos.Y - _lastMousePosY;
    
    if ((Math.Abs(difX) > MOUSE_DIF || Math.Abs(difY) > MOUSE_DIF))
    {
        // Logique de mouvement
    }
}

Pendant le mouvement de l'élément, nous recalculons dynamiquement sa position et son index dans la collection via la méthode SwapElement, qui met à jour correctement la source de données via l'interface IList.

Points clés à retenir

  • Extraire la mise à l'échelle dans un contrôle séparé pour respecter le principe de responsabilité unique
  • Utiliser l'interface ILayoutStrategy pour supporter plusieurs algorithmes de tri sans modifier la logique principale
  • Le seuil de déplacement du curseur (10+ pixels) est critique pour les performances du glisser-déposer avec un grand nombre d'éléments
  • Pour des mises à jour UI correctes pendant le glisser d'éléments, travailler toujours avec ZIndex et capturer la souris via CaptureMouse

— Editorial Team

Advertisement 728x90

Lire ensuite