WPF에서 고급 ListBox 구현: 스케일링, 정렬 및 드래그 앤 드롭
드래그 앤 드롭, 스케일링, 유연한 정렬 기능을 갖춘 사용자 지정 WPF 컨트롤을 개발하는 것은 도전적이지만 현대 인터페이스에 필수적인 작업입니다. 이 글에서는 향상된 ListBox와 애니메이션 요소 드래깅을 지원하는 패널 두 가지 핵심 구성 요소 구현 방법을 깊이 있게 살펴보겠습니다. 이 솔루션은 관심사 분리, 성능 최적화, 다양한 요소 레이아웃 전략 지원을 포함합니다.
스케일링: ExtendedListBox로 기능 추출
리스트 항목 스케일링을 제대로 구현하기 위해 이 기능을 표준 ListBox에서 상속된 별도의 ExtendedListBox 컨트롤로 추출했습니다. 이 접근 방식은 애니메이션 드래그 패널의 로직을 복잡하게 만드는 것을 피하고 구성 요소 재사용을 가능하게 했습니다. 주요 구현 요소:
MinScale/MaxScale를 통한 제한이 있는 의존성 속성ScaleMouseWheel이벤트 처리로 스케일 변경- 부드러운 클램핑을 사용한 새로운 스케일 팩터 계산
주요 마우스 휠 처리 알고리즘:
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;
}
}
ItemTemplate을 통해 아이템 템플릿을 제대로 설정하는 것이 중요합니다. 스케일링은 컨트롤의 속성에 바인딩된 ScaleTransform을 사용하여 적용됩니다:
<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>
이 패턴은 추가 코드 비하인드 계산 없이 모든 스케일 값에서 요소 변환이 올바르게 이루어지도록 보장합니다.
레이아웃 전략: DragAnimatedPanel에서의 사용자 지정 정렬
유연한 요소 레이아웃을 구현하기 위해 네 가지 정렬 전략을 지원하는 DragAnimatedPanel을 만들었습니다. 기본 아키텍처는 MeasureOverride와 ArrangeOverride 메서드를 재정의하여 자식 요소의 측정 및 배치를 완전히 제어할 수 있게 합니다.
주요 동작 단계:
MeasureOverride에서 필요한 패널 크기 계산ArrangeOverride에서 요소 위치 결정ILayoutStrategy인터페이스를 통한 동적 전략 전환
대각 레이아웃을 위한 MeasureOverride 기본 구현 예시:
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;
}
지원되는 레이아웃 전략:
- 가로 스트립 (모든 요소를 한 행에 배치)
- 세로 컬럼 (모든 요소를 한 열에 배치)
- 테이블 레이아웃 (고정 열 수)
- 행 채우기 (동적 열 계산)
각 전략은 ILayoutStrategy 인터페이스를 구현하며 다음 메서드를 포함합니다:
public interface ILayoutStrategy
{
Size ResultSize { get; }
void MeasureLayout(Size availablePanelSize, List<Size> sizes, bool isDragging);
int GetIndex(Point position);
ItemLayoutInfo GetLayoutInfo(int index);
}
이 접근 방식은 확장성을 보장하며 주요 패널 로직을 변경하지 않고 정렬 알고리즘을 쉽게 교체할 수 있습니다.
드래그 앤 드롭: 요소 드래깅 알고리즘
드래그 앤 드롭 구현에는 세 가지 중요한 단계가 포함됩니다: 드래깅 시작 감지, 이동 처리, 결과 커밋. 오작동을 방지하기 위해 누름 지속 시간과 커서 이동 임계값 검사를 추가했습니다.
드래깅 초기화 과정:
OnMouseLeftButtonDown에서 좌표와 시간 캡처OnMouseMove에서 조건 확인 (최소 유지 시간 및 이동량)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();
}
주요 최적화는 이동 처리 전에 이동 임계값 (10 픽셀)을 확인하는 것입니다. 이는 많은 요소를 다룰 때 부하를 줄입니다:
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
}
}
요소 이동 중에는 SwapElement 메서드를 통해 컬렉션 내 위치와 인덱스를 동적으로 재계산하며, IList 인터페이스를 통해 데이터 소스를 올바르게 업데이트합니다.
주요 요점
- 단일 책임 원칙을 준수하기 위해 스케일링을 별도 컨트롤로 추출
- 주요 로직 변경 없이 여러 정렬 알고리즘 지원을 위해
ILayoutStrategy인터페이스 사용 - 많은 요소가 있는 드래그 앤 드롭 성능 향상을 위해 커서 이동 임계값 (10+ 픽셀)이 핵심
- 요소 드래깅 중 올바른 UI 업데이트를 위해 항상
ZIndex를 사용하고CaptureMouse로 마우스 캡처
— Editorial Team
아직 댓글이 없습니다.