# Implementación de un ListBox avanzado en WPF: escalado, ordenación y arrastrar y soltar
Desarrollar controles personalizados de WPF con arrastrar y soltar, escalado y soporte para ordenación flexible es una tarea desafiante pero esencial para interfaces modernas. En este artículo, profundizaremos en la implementación de dos componentes clave: un ListBox mejorado y un panel con arrastre animado de elementos. La solución incorpora separación de responsabilidades, optimización de rendimiento y soporte para diversas estrategias de disposición de elementos.
Escalado: Extracción de funcionalidad en ExtendedListBox
Para implementar correctamente el escalado de elementos de lista, extrajimos esta funcionalidad en un control ExtendedListBox separado, heredado del ListBox estándar. Este enfoque evitó complicar la lógica del panel de arrastre animado y permitió la reutilización del componente. Elementos clave de la implementación:
- Propiedad de dependencia
Scalecon límites medianteMinScale/MaxScale - Manejo del evento
MouseWheelpara cambios de escala - Cálculo del nuevo factor de escala con limitación suave
El algoritmo principal de manejo de la rueda del ratón:
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 crucial configurar correctamente la plantilla de elementos mediante ItemTemplate. El escalado se aplica usando ScaleTransform enlazado a la propiedad del control:
<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>
Este patrón asegura la transformación correcta de elementos en cualquier valor de escala sin cálculos adicionales en code-behind.
Estrategias de disposición: Ordenación personalizada en DragAnimatedPanel
Para implementar disposiciones flexibles de elementos, creamos el DragAnimatedPanel con soporte para cuatro estrategias de ordenación. La arquitectura base sobrescribe los métodos MeasureOverride y ArrangeOverride, permitiendo control total sobre la medición y disposición de elementos hijo.
Etapas clave de operación:
- Cálculo de tamaños requeridos del panel en
MeasureOverride - Determinación de posiciones de elementos en
ArrangeOverride - Cambio dinámico de estrategias mediante la interfaz
ILayoutStrategy
Ejemplo de implementación base de MeasureOverride para disposición diagonal:
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;
}
Estrategias de disposición soportadas:
- Tira horizontal (todos los elementos en una fila)
- Columna vertical (todos los elementos en una columna)
- Disposición en tabla (número fijo de columnas)
- Relleno de filas (cálculo dinámico de columnas)
Cada estrategia implementa la interfaz ILayoutStrategy con métodos:
public interface ILayoutStrategy
{
Size ResultSize { get; }
void MeasureLayout(Size availablePanelSize, List<Size> sizes, bool isDragging);
int GetIndex(Point position);
ItemLayoutInfo GetLayoutInfo(int index);
}
Este enfoque asegura extensibilidad y reemplazo fácil de algoritmos de ordenación sin cambiar la lógica principal del panel.
Arrastar y soltar: Algoritmo de arrastre de elementos
La implementación de arrastrar y soltar incluye tres etapas críticas: detección del inicio del arrastre, manejo del movimiento y confirmación del resultado. Para prevenir activaciones falsas, agregamos verificaciones de duración de presión y umbral de desplazamiento del cursor.
Proceso de inicialización del arrastre:
- Captura de coordenadas y tiempo en
OnMouseLeftButtonDown - Verificación de condiciones en
OnMouseMove(tiempo mínimo de retención y desplazamiento) - Captura del elemento y ratón en
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();
}
Una optimización clave es verificar el umbral de desplazamiento (10 píxeles) antes de procesar el movimiento. Esto reduce la carga al trabajar con un gran número de elementos:
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
}
}
Durante el movimiento del elemento, recalculamos dinámicamente su posición e índice en la colección mediante el método SwapElement, que actualiza correctamente la fuente de datos mediante la interfaz IList.
Puntos clave
- Extraer el escalado a un control separado para adherirse al principio de responsabilidad única
- Usar la interfaz
ILayoutStrategypara soportar múltiples algoritmos de ordenación sin cambiar la lógica principal - El umbral de desplazamiento del cursor (10+ píxeles) es crítico para el rendimiento de arrastrar y soltar con un gran número de elementos
- Para actualizaciones correctas de la interfaz de usuario durante el arrastre de elementos, siempre trabajar con
ZIndexy capturar el ratón medianteCaptureMouse
— Editorial Team
Aún no hay comentarios.