XAML or HTML

029. 툴팁 말풍선 슬라이더(ToolTip Balloon Slider) #3

XAML 뽀개기

이전 포스트들에서 여러가지 방법으로 (Thumb) 말풍선 툴팁(ToolTip) 따라다니는 슬라이더(Slider) 만들어 보았습니다. 그런데 뭔가 만족스럽지 않았습니다. 그래서  깔끔한 방법이 없을까 한참을 고민하고 태스트하고 찾아보던 끝에 하나의 포스트를 이어가기로 했습니다.

 

예제에 사용된 대부분의 코드는 동일하니 달라진 점만 일부 설명합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 툴팁 팝업 -->
<Popup x:Name="PART_ToolTipPopup"
       PlacementTarget="{Binding ElementName=Thumb}"
       Placement="Center" VerticalOffset="-30"
       AllowsTransparency="True">
    <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

XAML 코드에서 사용된 Popup 그대로 사용됩니다. 추가로 이전 예제에서 불필요하게 사용되었던 VerticalOffset 프로퍼티는 -30으로 고정 시켰습니다. 팝업의 세로 위치는 변하지 않으므로 미리 고정되어도 같습니다.

 

1
2
3
4
5
6
7
8
9
<!-- New Behavior -->
<Slider Value="5" Minimum="0" Maximum="100" IsSnapToTickEnabled="True"
        TickFrequency="1" TickPlacement="Both"
        VerticalAlignment="Center" Margin="10,0"
        Style="{DynamicResource Style_Slider}">
    <i:Interaction.Behaviors>
        <bhv:ToolTipToPopupSliderBehavior />
    </i:Interaction.Behaviors>
</Slider>
cs

 

슬라이더의 XAML 코드입니다. 달라진 점은 IsSnapToTickEnabled 프로퍼티를 True 활성화하여 Tick 스냅이 걸리도록 점입니다. 썸이 이동할 1 단위로 스냅된다는 시나리오를 추가했습니다. TickFrequency, TickPlacement 등의 프로퍼티는 Tick 디자인 화면에 노출되도록 하는 프로퍼티들입니다.

 

아래부터는 새로운 방식으로 만든 비헤이비어(Behavior)입니다.

 

ToolTipToPopupSliderBehavior.cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void AssociatedObject_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
    _popup.HorizontalOffset = 0;
    _popup.IsOpen = true;
 
    var xMoveOffset = AssociatedObject.ActualWidth / (AssociatedObject.Maximum - AssociatedObject.Minimum);
    if (e.NewValue > e.OldValue && e.NewValue != AssociatedObject.Maximum)
    {
        _popup.HorizontalOffset += xMoveOffset;
    }
    else if (e.NewValue < e.OldValue && e.OldValue != AssociatedObject.Maximum)
    {
        _popup.HorizontalOffset -= xMoveOffset;            
    }
}
cs

이번 새로운 비헤이비어에서는 마우스와 키보드 이벤트를 각각 따로 이용하지 않고 동시에 적용되는 ValueChanged 이벤트를 이용했습니다. 이전에는 각각에 해당하는 이벤트를 핸들링했기 때문에 조금 중복되는 느낌을 받았는데 공통으로 발생하는 ValueChanged 이벤트를 이용함으로서  깔끔해진 느낌이 나는 합니다.

 

그리고 이번에는 슬라이더의 MinimumMaximum 프로퍼티의 값과 실제 ActualWidth의 크기를 이용해 썸이 움직였을때 팝업이 움직여야하는 HorizontalOffset 값을 매번 계산하여 초기화하고 다시 넣어주도록 하였습니다슬라이더의 크기가 고정이 아니라 윈도우 리사이징  어떤 이유로 변경이 가능하다는 가정된 시나리오 아래에 그렇게 되도록 했습니다.

 

그리고 기존에 Key.Left와 Key.Right의 판단은 OldValue와 NewValue를 비교하는 것으로 대체했습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#region Event handler < Mouse
 
private void _thumb_MouseLeave(object sender, MouseEventArgs e)
{
    _popup.IsOpen = false;
}
 
#endregion
 
#region Event handler < Keyboard
                
private void Slider_PreviewKeyUp(object sender, KeyEventArgs e)
{
    _popup.IsOpen = false;
}
 
#endregion
cs

 

팝업이 닫히는 동작의 경우는 이전 방법 그대로 각각의 이벤트 핸들러를 이용하도록 했습니다.



제가 사용했던 여러 방식들이 정답이 아니거나 옳지 못할 수도 있습니다. 하지만 나름대로는 몇일, 몇시간을 투자해서 얻은 결론이라 흥미로웠습니다. 나중에라도 나은 방법을 알게 된다면 기쁜 마음으로 다시 공유하게 같습니다.

 

마지막으로 아래 링크는 이번 예제에서는 직접적으로 사용되진 않았지만 예제 프로젝트 안에 일부 주석과 FollowingPopup.cs 파일 등에 남겨 두었습니다. 키보드도 함께 지원해야 한다는 저의 시나리오에 부합되지 않아 사용하지 않았습니다만 괜찮은 참고자료인 같아 남겨둡니다. 간단한 내용이지만 다른 시선으로 눈을 뜨게 해준 stackoverflow에서 찾은 질문과 답변입니다. 저에게는 QueryCursor라는 이벤트를 이렇게도 사용할 있구나 하는 새로운 발견이었습니다.

 

참고 : Make Tooltip of WPF slider stay on screen while dragging