# 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
Scaleavec limites viaMinScale/MaxScale - Gestion de l'événement
MouseWheelpour les changements d'échelle - Calcul du nouveau facteur d'échelle avec limitation fluide
L'algorithme principal de gestion de la molette de la souris :
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.
É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 :
- 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
ILayoutStrategypour 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
ZIndexet capturer la souris viaCaptureMouse
— Editorial Team
Aucun commentaire pour le moment.