# Implementierung einer erweiterten ListBox in WPF: Skalierung, Sortierung und Drag-and-Drop
Die Entwicklung benutzerdefinierter WPF-Steuerelemente mit Drag-and-Drop, Skalierung und flexibler Sortierungsunterstützung ist eine anspruchsvolle, aber essenzielle Aufgabe für moderne Benutzeroberflächen. In diesem Artikel tauchen wir tief in die Implementierung zweier Schlüsselfunktionen ein: eine erweiterte ListBox und ein Panel mit animiertem Element-Dragging. Die Lösung berücksichtigt Trennung der Verantwortlichkeiten, Leistungsoptimierung und Unterstützung für verschiedene Element-Layout-Strategien.
Skalierung: Auslagerung der Funktionalität in ExtendedListBox
Um die Skalierung von Listenelementen richtig zu implementieren, haben wir diese Funktionalität in eine separate ExtendedListBox-Steuerelement extrahiert, die von der standardmäßigen ListBox erbt. Dieser Ansatz vermeidet die Komplizierung der Logik des animierten Drag-Panels und ermöglicht die Wiederverwendung der Komponente. Wichtige Implementierungslemente:
- Abhängigkeitseigenschaft
Scalemit Limits überMinScale/MaxScale - Behandlung des
MouseWheel-Ereignisses für Skalierungsänderungen - Berechnung des neuen Skalierungsfaktors mit sanfter Begrenzung
Der Hauptalgorithmus zur Behandlung des Mausrads:
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;
}
}
Es ist entscheidend, die Elementvorlage über ItemTemplate richtig einzurichten. Die Skalierung wird mit ScaleTransform angewendet, das an die Eigenschaft des Steuerelements gebunden ist:
<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>
Dieses Muster gewährleistet die korrekte Elementtransformation bei jedem Skalierungswert ohne zusätzliche Code-Behind-Berechnungen.
Layout-Strategien: Benutzerdefinierte Sortierung in DragAnimatedPanel
Um flexible Elementlayouts zu implementieren, haben wir das DragAnimatedPanel mit Unterstützung für vier Sortierungsstrategien erstellt. Die Basisarchitektur überschreibt die Methoden MeasureOverride und ArrangeOverride, was volle Kontrolle über das Messen und Anordnen der Kindelemente ermöglicht.
Schlüsselphasen des Betriebs:
- Berechnung der erforderlichen Panelgrößen in
MeasureOverride - Bestimmung der Elementpositionen in
ArrangeOverride - Dynamischer Wechsel der Strategien über die
ILayoutStrategy-Schnittstelle
Beispielhafte Basisimplementierung von MeasureOverride für diagonales Layout:
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;
}
Unterstützte Layout-Strategien:
- Horizontales Band (alle Elemente in einer Zeile)
- Vertikale Spalte (alle Elemente in einer Spalte)
- Tabellenlayout (feste Anzahl von Spalten)
- Zeilenfüllung (dynamische Spaltenberechnung)
Jede Strategie implementiert die ILayoutStrategy-Schnittstelle mit Methoden:
public interface ILayoutStrategy
{
Size ResultSize { get; }
void MeasureLayout(Size availablePanelSize, List<Size> sizes, bool isDragging);
int GetIndex(Point position);
ItemLayoutInfo GetLayoutInfo(int index);
}
Dieser Ansatz gewährleistet Erweiterbarkeit und einfachen Austausch von Sortierungsalgorithmen ohne Änderung der Hauptpanel-Logik.
Drag-and-Drop: Element-Dragging-Algorithmus
Die Drag-and-Drop-Implementierung umfasst drei kritische Phasen: Erkennen des Drag-Starts, Behandlung der Bewegung und Abschließen des Ergebnisses. Um Fehltrigger zu verhindern, haben wir Überprüfungen für die Drückdauer und den Cursor-Verdrängungsschwellenwert hinzugefügt.
Drag-Initialisierungsprozess:
- Erfassen von Koordinaten und Zeit in
OnMouseLeftButtonDown - Überprüfung der Bedingungen in
OnMouseMove(minimale Haltezeit und Verdrängung) - Erfassen des Elements und der Maus in
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();
}
Eine wichtige Optimierung ist die Überprüfung des Verdrängungsschwellenwerts (10 Pixel), bevor die Bewegung verarbeitet wird. Dies reduziert die Belastung bei der Arbeit mit einer großen Anzahl von Elementen:
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))
{
// Movement logic
}
}
Während der Elementbewegung berechnen wir dynamisch seine Position und seinen Index in der Sammlung über die SwapElement-Methode neu, die die Datenquelle über die IList-Schnittstelle korrekt aktualisiert.
Wichtige Erkenntnisse
- Skalierung in eine separate Steuerelement extrahieren, um dem Prinzip der Einzelverantwortung zu folgen
- Die
ILayoutStrategy-Schnittstelle nutzen, um mehrere Sortierungsalgorithmen ohne Änderung der Hauptlogik zu unterstützen - Cursor-Verdrängungsschwellenwert (10+ Pixel) ist entscheidend für die Drag-and-Drop-Leistung bei einer großen Anzahl von Elementen
- Für korrekte UI-Updates während des Element-Draggings immer mit
ZIndexarbeiten und die Maus überCaptureMouseerfassen
— Editorial Team
Noch keine Kommentare.