# Implementace rozšířeného ListBox v WPF: škálování, řazení a drag-and-drop
Vývoj vlastních WPF ovládacích prvků s podporou drag-and-drop, škálování a flexibilního řazení je složitá, ale nezbytná úloha pro moderní uživatelská rozhraní. V tomto článku podrobně rozebereme implementaci dvou klíčových komponent: rozšířeného ListBox a panelu s animovaným přenášením prvků. Řešení zahrnuje oddělení odpovědností, optimalizaci výkonu a podporu různých strategií umístění prvků.
Škálování: vyčlenění funkce do ExtendedListBox
Pro správnou implementaci škálování prvků seznamu jsme tuto funkcionalitu vyčlenili do samostatného ovládacího prvku ExtendedListBox, který dědí od standardního ListBox. Tento přístup umožnil vyhnout se zbytečnému složení logiky panelu s animovaným přenášením a zajistil opakované použití komponenty. Klíčové prvky implementace:
- Dependency property
Scales omezeními přesMinScale/MaxScale - Zpracování události
MouseWheelpro změnu škály - Výpočet nového koeficientu s plynulým omezením
Hlavní algoritmus zpracování kolečka myši:
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;
}
}
Kriticky důležité je správně nastavit šablonu prvku přes ItemTemplate. Pro aplikaci škály se používá ScaleTransform s vazbou na vlastnost ovládacího prvku:
<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>
Tento vzor zaručuje správnou transformaci prvků při jakékoli hodnotě škály bez dalších výpočtů v kódu.
Strategie umístění: vlastní řazení v DragAnimatedPanel
Pro implementaci flexibilního umístění prvků jsme vytvořili panel DragAnimatedPanel s podporou čtyř strategií řazení. Základní architektura je postavena na přepsání metod MeasureOverride a ArrangeOverride, což umožňuje plnou kontrolu nad procesem měření a umístění podřízených prvků.
Klíčové etapy práce:
- Výpočet požadovaných velikostí panelu v
MeasureOverride - Určení pozic prvků v
ArrangeOverride - Dynamické přepínání strategií přes rozhraní
ILayoutStrategy
Příklad základní implementace MeasureOverride pro diagonální umístění:
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;
}
Podporované strategie umístění:
- Horizontální řada (všechny prvky v jednom řádku)
- Vertikální sloupec (všechny prvky v jednom sloupci)
- Tabulkové umístění (fixní počet sloupců)
- Vyplňování po řádcích (dynamický výpočet sloupců)
Každá strategie implementuje rozhraní ILayoutStrategy s metodami:
public interface ILayoutStrategy
{
Size ResultSize { get; }
void MeasureLayout(Size availablePanelSize, List<Size> sizes, bool isDragging);
int GetIndex(Point position);
ItemLayoutInfo GetLayoutInfo(int index);
}
Tento přístup zajišťuje rozšiřitelnost a snadnou výměnu algoritmů řazení bez změny hlavní logiky panelu.
Drag-and-drop: algoritmus přenášení prvků
Implementace drag-and-drop zahrnuje tři kritické etapy: detekci začátku přenášení, zpracování pohybu a fixaci výsledku. Pro prevenci falešných spuštění jsme implementovali kontrolu délky stisknutí a prahu posunu kurzoru.
Proces inicializace přenášení:
- Fixace souřadnic a času v
OnMouseLeftButtonDown - Kontrola podmínek v
OnMouseMove(minimální čas držení a posun) - Zachycení prvku a myši v
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();
}
Klíčová optimalizace — kontrola prahu posunu (10 pixelů) před zpracováním pohybu. To snižuje zátěž při práci s velkým množstvím prvků:
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))
{
// Logika peremescheniya
}
}
Při pohybu prvku dynamicky přepočítáváme jeho pozici a index v kolekci přes metodu SwapElement, která správně aktualizuje zdroj dat přes rozhraní IList.
Co je důležité
- Škálování je třeba vyčlenit do samostatného ovládacího prvku pro dodržení principu jediné odpovědnosti
- Používejte rozhraní
ILayoutStrategypro podporu mnoha algoritmů řazení bez změny hlavní logiky - Prah posunu kurzoru (10+ pixelů) je kritický pro výkon při drag-and-drop s velkým počtem prvků
- Pro správnou aktualizaci UI při přenášení prvků je nezbytné pracovat s
ZIndexa zachytit myš přesCaptureMouse
— Editorial Team
Zatím žádné komentáře.