커스텀 비헤이비어를 작성하기 전에 SliderHorizontal 템플릿에 추가할 것이 있습니다. 지금까지는 ToolTip(툴팁)을 어떻게든 이용해 보려고 했지만 시나리오에 부합되지 않는 면이 있었습니다. 그래서 툴팁을 대체할 Popup(팝업)을 사용하려고 합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <!-- 툴팁 팝업 --> <Popup x:Name="PART_ToolTipPopup" AllowsTransparency="True" PlacementTarget="{Binding ElementName=Thumb}" Placement="Center" > <StackPanel> <Border CornerRadius="5" Background="SkyBlue"> <TextBlock Text="{Binding Value, ElementName=PART_Track, StringFormat=N0}" Foreground="Black" HorizontalAlignment="Center" Margin="10,5"/> </Border> <ed:RegularPolygon Fill="SkyBlue" Margin="0,-8,0,0" Panel.ZIndex="-1" PointCount="4" Width="10" Height="15"/> </StackPanel> </Popup> | cs |
이전 포스트에서 작성했던 툴팁은 잊어버리고 PART_Track 아래에 팝업을 추가합니다. 이전 포스트에서 다뤘던 툴팁 말풍선의 템플릿 그대로 가져왔습니다. 다른 점은 ContentPresenter 대신에 여러모로 다루기 쉬운 원래 TextBlock으로 대체했습니다. 값을 표출하기 위해 ElementName 문법을 이용해 PART_Track의 Value 속성과 바인딩했습니다. StringFormat 속성은 이전 포스트를 봤다면 설명이 더 이상 필요없을 거라 생각합니다.
ToolTipToPopupSliderBehavior.cs
1 2 3 4 5 6 | public class ToolTipToPopupSliderBehavior : Behavior<Slider> { // 생략 } | cs |
툴팁을 팝업으로 대체하는 의미로 ToolTipToPopupSliderBehavior 비헤이비어를 작성합니다. Behavior를 상속 받았으며 Slider를 지정할 요소로 정의했습니다.
1 2 3 4 5 6 7 | #region Variable private Thumb _thumb; private Popup _popup; private double _changedHorizontalOffset; #endregion | cs |
XAML 코드에 정의한 썸과 팝업을 찾아 담아놓을 변수를 정의합니다. 추가로 팝업이 가로 방향으로 움직일 크기에 필요한 변수를 함께 정의합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #region VerticalOffset : 팝업 세로 위치 public int VerticalOffset { get { return (int)GetValue(VerticalOffsetProperty); } set { SetValue(VerticalOffsetProperty, value); } } public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.Register( "VerticalOffset", typeof(int), typeof(ToolTipToPopupSliderBehavior), new PropertyMetadata(0)); #endregion | cs |
팝업의 세로 방향 이동은 움직이지 않고 고정됩니다. 팝업의 세로 위치를 정의할 프로퍼티를 추가합니다. 이번 포스트의 주안점은 아니지만 의존 속성으로 정의했습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #region Proteced method protected override void OnAttached() { base.OnAttached(); AssociatedObject.Loaded += AssociatedObject_Loaded; } protected override void OnDetaching() { AssociatedObject.Loaded -= AssociatedObject_Loaded; _thumb.DragDelta -= Thumb_DragDelta; _thumb.DragStarted -= Thumb_DragStarted; _thumb.DragCompleted -= Thumb_DragCompleted; AssociatedObject.PreviewKeyDown -= Slider_PreviewKeyDown; AssociatedObject.KeyUp -= Slider_KeyUp; _thumb = null; _popup = null; base.OnDetaching(); } #endregion | cs |
OnAttached와 OnDetaching 메소드를 재정의합니다. Behavior를 작성할 때 이 두 메소드를 재정의하는 일은 필수라 할 수 있습니다. AssociatedObject를 통해 XAML 코드에 적용한 UI 요소를 가져옵니다. OnAttached 메소드에서는 가져온 요소에 Loaded 이벤트를 연결하고 OnDetaching 메소드에서는 Loaded 이벤트를 연결 해제합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #region Event handler private void AssociatedObject_Loaded(object sender, RoutedEventArgs e) { _popup = GetVisualChild<Popup>(AssociatedObject); _thumb = GetVisualChild<Thumb>(AssociatedObject); if (_thumb == null || _popup == null) return; _thumb.DragStarted += Thumb_DragStarted; _thumb.DragDelta += Thumb_DragDelta; _thumb.DragCompleted += Thumb_DragCompleted; AssociatedObject.PreviewKeyDown += Slider_PreviewKeyDown; AssociatedObject.KeyUp += Slider_KeyUp; } // 생략 #endregion | cs |
AssociatedObject_Loaded 이벤트 핸들러에서는 AssociatedObject 통해 받아온 슬라이더에서 팝업과 썸을 찾아 필요한 이벤트를 연결합니다. 썸을 드래그할 때 발생하는 이벤트 DragStarted, DragDelta, DragCompleted 3가지를 연결합니다. 키보드 이벤트를 지원하기 위해 PreviewKeyDown, KeyUp 이벤트 2가지도 연결합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #region Event handler < Mouse private void Thumb_DragDelta(object sender, DragDeltaEventArgs e) { _popup.HorizontalOffset += +e.HorizontalChange; _popup.HorizontalOffset += -e.HorizontalChange; _changedHorizontalOffset = e.HorizontalChange; } private void Thumb_DragStarted(object sender, DragStartedEventArgs e) { _popup.VerticalOffset = VerticalOffset + _thumb.ActualHeight; _popup.IsOpen = true; } private void Thumb_DragCompleted(object sender, DragCompletedEventArgs e) { _popup.IsOpen = false; } #endregion | cs |
Thumb_DragStarted 이벤트 핸들러에서는 팝업을 열어주고 Thumb_DragCompleted 이벤트 핸들러에서는 팝업을 닫아줍니다. Thumb_DragDelta 이벤트 핸들러에서는 마우스가 움직인 크기만큼 팝업의 HorizontalOffset을 업데이트해줍니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #region Event handler < Keyboard private async void Slider_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) { await Task.Delay(100); if (e.Key == System.Windows.Input.Key.Left || e.Key == System.Windows.Input.Key.Right) { _popup.VerticalOffset = VerticalOffset + _thumb.ActualHeight; _popup.HorizontalOffset += +_changedHorizontalOffset; _popup.HorizontalOffset += -_changedHorizontalOffset; _popup.IsOpen = true; } } private async void Slider_KeyUp(object sender, System.Windows.Input.KeyEventArgs e) { await Task.Delay(100); _popup.IsOpen = false; } #endregion | cs |
키보드 관련 이벤트 핸들러도 비슷한 패턴으로 되어 있습니다. 약간의 차이점이라면 키보드를 누르고 있을 때 짧은 간격으로 반복해서 들어오는 이벤트를 지연시키기 위한 코드가 추가된 점입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #region UI Helper < Common public static T GetVisualChild<T>(DependencyObject parent) where T : Visual { if (parent == null) return null; T child = default(T); int numVisuals = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < numVisuals; i++) { Visual v = (Visual)VisualTreeHelper.GetChild(parent, i); child = v as T; if (child == null) { child = GetVisualChild<T>(v); } if (child != null) { break; } } return child; } #endregion | cs |
GetVisualChild 메소드는 VisualTree를 뒤져서 원하는 요소를 찾기 윈한 UI Helper 클래스의 일부입니다. Name으로 찾을 수도 있습니다만 이 예제의 경우 썸과 팝업은 유일한 요소이기때문에 타입으로 찾는 메소드를 사용했습니다.
실행해서 태스트해봅니다. 사실 이 예제도 만족스럽지않습니다. 팝업의 움직임에서 약간의 떨림이 보입니다. 커스텀 비헤이비어 예제로서는 괜찮아 보이는데 결과물로서는 좀 아쉬움이 남습니다. 다음 기회에 다른 방법을 시도해봐야 겠습니다.
'XAML 뽀개기' 카테고리의 다른 글
030. 데이타템플릿 & 컨버터(DataTemplate & Converter) #1 (0) | 2018.04.11 |
---|---|
029. 툴팁 말풍선 슬라이더(ToolTip Balloon Slider) #3 (0) | 2018.04.09 |
028. 툴팁 말풍선 슬라이더(ToolTip Balloon Slider) #2 (0) | 2018.03.22 |
027. 툴팁 말풍선 슬라이더(ToolTip Balloon Slider) #1 (0) | 2018.03.20 |
026. 툴팁 말풍선(ToolTip Balloon) (0) | 2018.03.15 |
025. 커스텀 트리거 & 액션(Custom Trigger & Action) #2 (0) | 2018.03.09 |