XAML or HTML

026. 툴팁 말풍선(ToolTip Balloon)

XAML 뽀개기

이번 포스트는 잠시 쉬어가는 포스트입니다. 다음 포스트에 사용될 디자인 요소도 제작할 겸해서 말입니다. 구글에서 말풍선 이미지 검색해보면 매우 다양한 말풍선 디자인을 찾을 있습니다. 참고하세요. 저는 최대한 간소하게 표현해볼까 합니다.

 

 

다음 포스트에서 사용될 예제를 먼저 보도록 합시다. 기본 슬라이더의 Thumb() 드래그하면 현재 Value() 말풍선 안에서 표출되는 간단한 기능입니다. 실제 구현은 그리 간단하지 않습니다만 다음 포스트에서 만들어 보려고 합니다.

 

1
2
3
4
5
6
7
8
<!-- ToolTip balloon #1 -->
<StackPanel Margin="5">
    <Border CornerRadius="5" Background="SkyBlue">
        <TextBlock Text="Lorem Ipsum is simply dummy text." 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>
cs

 

첫번째 방법입니다. 말풍선 몸체와 꼭지는 세로로 정렬되는 형태이므로 전체를 StackPanel 그룹화했습니다. 값이 들어갈 TextBlock Border 한번 감싸주었습니다. 이렇게 하면 TextBlock 문자열이 들어와도 자동으로 Border 밀어내 늘어나므로 나름 효율적입니다.

 

말풍선 꼭지는 Microsoft.Expression.Drawing.dll 어셈블리를 참조하면 사용할 있는 도형 RegularPolygon을 이용해 만들었습니다. 삼각형 꼭지를 사용할 수도 있지만 사각형(마름모) 꼭지를 만들어서 사용할 수도 있습니다. StackPanel로 감싸주었기 때문에 레이어 순서가 문제가 되지만 Panel.Zindex 속성에 "-"값을 주어 레이어 순서를 추가로 변경하면 문제가 되지 않습니다. 약간의 위치조정은 Margin 속성을 이용해 조정했습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
<!-- ToolTip balloon #2 -->
<Grid Margin="5">
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
 
    <Path Fill="PaleVioletRed" Margin="0,-8,0,0" Grid.Row="1" 
          Width="10" Height="15" Data="M5,0 L10,7.5 L5,15 L0,7.5 z"/>
    <Rectangle RadiusX="5" RadiusY="5" Fill="PaleVioletRed"/>
    <TextBlock Text="Lorem Ipsum is simply dummy text." HorizontalAlignment="Center" Margin="10,5"/>
</Grid>
cs

 

두번째 방법입니다. Grid 이용해 말풍선 몸체와 꼭지를 그룹화했습니다. 말풍선 몸체와 꼭지는 Grid RowDefinition 속성을 이용해 2개로 나누어진 Row 각각 위치하도록 했습니다. 말풍선 몸체는 Rectangle 이용했습니다. 첫번째 방법과 조금 다른 점이라면 Border CornerRadius 속성이고 Rectangle RadiusX, RadiusY 속성을 이용해 코너의 라운드를 표현하는 점입니다. 그리고 Rectangle TextBlock 자식으로 가질 없습니다.

 

Grid StackPanel 달리 레이어 정렬에 강하게 영향을 주지 않으므로 말풍선 꼭지를 먼저 선언하고 몸체를 선언해 가려지지 않도록 했습니다. 꼭지는 Path 이용했습니다. Path Width, Height Data 속성을 이용해 Path Point들의 위치에 따라 그려지므로 다루기가 까다롭습니다. 깔끔한 픽셀 단위 작업이 쉽지 않습니다. 약간의 위치조정은 첫번째 방법과 동일하게 Margin 속성을 이용했습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- ToolTip balloon #3 -->
<Grid Margin="5">
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
            
    <Border CornerRadius="5" Background="DarkSeaGreen">
        <TextBlock Text="Lorem Ipsum is simply dummy text." HorizontalAlignment="Center" Margin="10,5"/>
    </Border>
    <ed:RegularPolygon Fill="DarkSeaGreen" Margin="0,0,0,-4" Grid.Row="1"
                       PointCount="3" Width="10" Height="8" RenderTransformOrigin="0.5,0.5">
        <ed:RegularPolygon.RenderTransform>
            <TransformGroup>
                <ScaleTransform ScaleY="-1" ScaleX="1"/>
            </TransformGroup>
        </ed:RegularPolygon.RenderTransform>
    </ed:RegularPolygon>
</Grid>
cs

 

세번째 방법입니다. 두번째 방법과 동일하게 Grid 이용해 말풍선 몸체와 꼭지를 그룹화했고 Row 위치하도록 했습니다. 값이 들어갈 TextBlock Border 한번 감싸주는 첫번째 방법을 이용했습니다.

 

말풍선 꼭지는 첫번째 방법과 동일하게 도형 RegularPolygon을 이용했습니다. 첫번째 방법과 다른 점은 이번엔 삼각형이라는 점입니다. 하지만 상하 방향을 뒤집어야 하기 때문에 ScaleTransform의 ScaleY 속성에 "-"값을 주어 반전 효과를 주었습니다. RotateTransform 이용해 회전 시켜도 동일한 효과를 얻을 있습니다. 약간의 위치조정은 첫번째, 두번째 방법과 동일하게 Margin 속성을 이용했습니다.

 

1
2
3
4
5
6
7
8
<!-- ToolTip balloon #4 -->
<DockPanel Margin="5">
    <Border CornerRadius="5" Background="Gainsboro" DockPanel.Dock="Top">
        <TextBlock Text="Lorem Ipsum is simply dummy text." HorizontalAlignment="Center" Margin="10,5"/>
    </Border>
    <ed:RegularPolygon Fill="Gainsboro" Margin="0,-8,0,0" Panel.ZIndex="-1" 
                       PointCount="4" Width="10" Height="15" DockPanel.Dock="Top"/>
</DockPanel>
cs

 

4번째 방법입니다. 이번에는 DockPanel 이용해 말풍선 몸체와 꼭지를 그룹화했습니다. DockPanel 사용했기 때문에 하위 자식들은 DockPanel.Dock 의존 속성을 정의해 위치를 각각 지정해 주어야 합니다. 외에는 StackPanel 이용했던 첫번째 방법과 다른 점은 없습니다. 동일한 표현이 가능은 하지만 DockPanel 사용했다는 점이 께름칙합니다.

 

 

위에서 알아본 4가지 방법 모두는 현재로서는 문제가 보이지 않습니다. 결과적으로는 동일한 결과를 보이고 있습니다. 인터페이스를 마주하는 사용자 입장에서는 더더욱 상관없습니다. 하지만 코드 간결성, 효율성 여러면에서 문제가 발생할 수도 있습니다. 그리고 만약에 요구사항이 변경되거나 기능이 추가되었을 수정의 용이성, 한계점 어떤 문제가 발생할지 예측하기 힘듭니다.

 

 

예를 들어 표출될 컨텐츠의 양이 늘어나 여러 줄로 표현이 되어야 한다면 그에 따라 말풍선의 크기도 늘어나야 것입니다. 예제에 사용된 4가지 방법 모두는 가능하도록 코드를 작성했습니다. 하지만 Blend for VS는 아시다시피 WYSIWYG(위지위그방식의 툴입니다. 심심치 않게 디자인 윈도우에서 마우스를 조작해 디자인을 수정할 수 있습니다.

 

4가지 방법에서 각각 그룹화에 쓰인 StackPanel, Grid, DockPanel 모두를 다시 한번 살펴보면 Width, Height 속성이 어느 하나도 쓰이지 않은 것을 있습니다. 이유는 하위 자식의 컨텐츠 양이 얼마나 될지 확신하지 못하기 때문에 의도적으로 제한하지 않은 것입니다. 다른 의미로는 컨텐츠 양에 따라 유연하게 늘어나도록 일부러 정의하지 않은 것입니다


물론 컨텐츠양이 변경될 때마다 크기를 변경하는 관련 코드를 추가할 수도 있겠습니다만 그리 좋지 않은 접근 방식이라 생각합니다. 이런 요구사항이 있을 때 위지위그 방식으로 작업을 하다보면 Width, Height, Margin 등의 속성이 원치않게 자동으로 생성되는 것을 쉽게 경험할 수 있습니다.

 


결론은 위지위그 방식의 툴을 사용할 주의해야 한다는 것입니다. C# 또는 XAML 코드를 다루는 우리는 Blend for VS Visual Studio 사용이 필수고 당연한 일이기도 합니다. 그리고 디자인 윈도우에서 마우스 조작을 이용해 디자인 관련 작업을 하는 것은 매우 쉽고 직관적입니다. 하지만 위지위그 방식의 툴들이 대부분 그러하듯 때론 사용자가 의도하지 않은 코드를 생성해내기도 합니다. 그렇기 때문에 위지위그 방식의 작업 중에도 XAML 코드 확인  수정해줘야 하는 일이 때에 따라 필요할 있습니다.


상황에 따라 어떤 코드를 선택할지는 여러분의 몫이지만 어떤 차이가 있는지 어떤 방법이 더 효과적인지를 판단하 점 중요할 것 있습니다.