안녕하세요, 이번 글에서는 Microsoft에서 제공하는 WPF [ContextMenu의 스타일 및 템플릿] 예제를 톺아보도록 하겠습니다.
ContextMenu
Control에서 해당 Control의 Context에 고유한 기능을 노출할 수 있도록 하는 Popup 메뉴를 나타냅니다. 일반적으로 오른쪽 마우스 버튼 또는 키보드의 메뉴 단추를 통해 UI(사용자 인터페이스)에 나타냅니다.
<TextBox Height="100" Width="200" Margin="10">
Default
<TextBox.ContextMenu>
<ContextMenu>
<MenuItem Header="No.1"/>
<MenuItem Header="No.2"/>
<Separator />
<MenuItem Header="No.3"/>
<MenuItem Header="No.4"/>
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
⭐ MenuItem을 사용하여 Menu 내의 선택 가능한 항목을 나타냅니다.
⭐ Separator로 Control 항목을 구분하는 데 사용할 수 있습니다.
[ ContextMenu 기본 속성 ]
HasDropShadow
상황에 맞는 메뉴가 그림자와 함께 표시되는지 여부를 설정합니다. 상황에 맞는 메뉴가 끌어 놓은 그림자와 함께 나타나면 true이고, 그렇지 않으면 false입니다.
<ContextMenu HasDropShadow="True" ... />
HorizontalOffset
대상 원점과 팝업 맞춤 간의 가로 거리를 설정합니다. 기본값은 0입니다.
<TextBox ContextMenuService.HorizontalOffset="-100" ... >
Default
<TextBox.ContextMenu>
<ContextMenu ... />
</TextBox.ContextMenu>
</TextBox>
Placement
ContextMenu 위치를 설정합니다. 기본값은 MousePoint이며 다른 값으로 Absolute, AbsolutePoint, Bottom, Center, Custom, Left, Mouse, RelativePoint, Right와 Top이 있습니다. 값에 대한 자세한 설명은 이 페이지에서 참고하시길 바랍니다.
<TextBox ContextMenuService.Placement="Center" ... >
Default
<TextBox.ContextMenu>
<ContextMenu ... />
</TextBox.ContextMenu>
</TextBox>
StaysOpen
ContextMenu를 자동으로 닫을지 여부를 나타내는 값을 설정합니다. IsOpen 속성이 true로 변경될 때까지 메뉴가 열려 있으면 false이고, 그렇지 않으면 false입니다.
<ContextMenu StaysOpen="True" ... />
VerticalOffset
대상 원점과 팝업 맞춤 지점 간의 세로 거리를 설정합니다.
<TextBox ContextMenuService.VerticalOffset="-100" ... >
Default
<TextBox.ContextMenu>
<ContextMenu ... />
</TextBox.ContextMenu>
</TextBox>
[ ContextMenu Style ]
예제의 XAML 코드를 ContextMenu에 적용하면 위와 같은 결과가 나옵니다. 이러한 결과에 도달하도록 코드 순서로 속성을 살펴보면 다음과 같습니다.
<Style TargetType="{x:Type ContextMenu}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Grid.IsSharedSizeScope" Value="true" />
<Setter Property="HasDropShadow" Value="True" />
<Setter Property="Template">
<Setter.Value>
...
</Setter.Value>
</Setter>
</Style>
SnapsToDevicePixels
렌더링 하는 동안 이 요소의 렌더링에 디바이스 관련 픽셀 스냅(Pixel Snap)을 사용할지 여부를 결정하는 값을 설정합니다. 픽셀 스냅을 사용하면 값을 true로 설정하고, 그렇지 않으면 false(기본값)로 설정합니다.
⭐ 픽셀 스냅(Pixel Snap)을 사용하는 이유
WPF에서 시스템 DPI 설정에 맞게 자동으로 크기를 조정하게 됩니다. 이러한 조정할 때 가장자리가 흐려지거나 반투명하게 표시되는 문제가 발생하게 됩니다. 이 문제를 해결하기 위해서 픽셀 스냅 기능을 사용하여 객체의 가장자리를 픽셀에 맞추어 고정시키는 기능을 제공합니다. 픽셀 스냅을 사용하면 객체에 작은 단위의 offset을 적용하여 객체의 크기를 장치 픽셀에 맞추거나 일부분을 렌더링 시점에서 제거하여 해결하는 방법입니다.
OverridesDefaultStyle
테마 스타일 속성을 포함할지 여부를 설정합니다. 테마 스타일 속성을 사용하지 않으면 true입니다. 테마 스타일 속성을 사용하면(false일 경우) 직접 지정한 속성을 제외한 나머지 부분은 Local Application Style로 설정됩니다.(기본값은 false입니다.)
⭐ Template에 설정을 하지 않으면 아무것도 나오지 않는 것처럼 보일 수 있습니다.
Grid.IsSharedSizeScope
여러 Grid 요소가 크기 정보를 공유할지 여부를 설정합니다. 크기 정보를 공유하면 true이고, 그렇지 않으면 false입니다.
HasDropShadow
상황에 맞는 메뉴가 그림자와 함께 표시되는지 여부를 설정합니다. 상황에 맞는 메뉴가 끌어 놓은 그림자와 함께 나타나면 true이고, 그렇지 않으면 false입니다.
[ ContextMenu Style : Template ]
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContextMenu}">
<Border x:Name="Border" Background="{StaticResource MenuPopupBrush}" BorderThickness="1">
<Border.BorderBrush>
<SolidColorBrush Color="{StaticResource BorderMediumColor}" />
</Border.BorderBrush>
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="HasDropShadow" Value="true">
<Setter TargetName="Border" Property="Padding" Value="0,3,0,3" />
<Setter TargetName="Border" Property="CornerRadius" Value="4" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
예제에서 사용되는 Tempalte는 Control의 외형을 지정해줄 수 있는 ControlTemplate입니다.
⭐ Template에 대해서 좀 더 아시고 싶으신 분은 이 페이지를 참고하시길 바랍니다.
ContextMenu 구성
예제의 Template에서 구성한 ContextMenu는 위의 이미지와 같이 Border가 StackPanel을 감싸는 형태로 구성되어있습니다.
Border(Border)
다른 요소의 주위에 윤곽선, 배경 또는 둘 다를 그립니다.
⭐ Border 속성
x:Name
Border에 이름을 붙이는 것입니다. 이름을 붙이는 것과 동시에 다른 곳에서 사용할 수 있도록 만들어줍니다.
Background
Border의 배경색을 설정합니다. 예제에서는 미리 정의된 색 MenuPopupBrush로 설정했습니다.
BorderThickness
Border의 윤곽선 두께를 설정합니다.
BorderBrush
Border의 윤곽선 색을 설정합니다. 예제에서는 SolidColorBrush를 사용하여 미리 정의한 BorderMediumColor 색으로 설정했습니다.
StackPanel
가로 또는 세로 방향으로 한 줄로 자식 요소를 정렬합니다.
⭐ StackPanel 속성
IsItemsHost
ItemsControl에 의해 생성된 UI(사용자 인터페이스) 항목에 대한 컨테이너임을 나타내는 Panel 값을 설정합니다. 항목 호스트이면 true이고, 그렇지 않으면 false(기본값)입니다.
KeyboardNavigation.DirectionalNavigation
이 속성이 설정된 요소의 자식에 대한 방향 탐색 동작을 설정합니다. 값으로는 Contained, Continue, Cycle, Local, None, Once가 있습니다. 값에 대한 자세한 설명은 이 페이지를 참고하시길 바랍니다.
ContextMenu 이벤트
예제에서는 ContextMenu의 이벤트로 HasDropShadow의 변화를 Triggers로 표현하였습니다.
Triggers
<ControlTemplate.Triggers>
<Trigger Property="HasDropShadow" Value="true">
<Setter TargetName="Border" Property="Padding" Value="0,3,0,3" />
<Setter TargetName="Border" Property="CornerRadius" Value="4" />
</Trigger>
</ControlTemplate.Triggers>
HasDropShadow의 값이 true일 경우 "Border"라는 이름을 가진 Control의 Padding을 "0,3,0,3"으로 CornerRadius를 "4"로 설정합니다.
⭐ Padding : Border 두께와 자식 요소 사이의 간격을 설정합니다.
⭐ CornerRadius : Border의 모퉁이가 둥근 정도를 설정합니다.
전체 코드
<Style TargetType="{x:Type ContextMenu}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Grid.IsSharedSizeScope" Value="true" />
<Setter Property="HasDropShadow" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContextMenu}">
<Border x:Name="Border" Background="{StaticResource MenuPopupBrush}" BorderThickness="1">
<Border.BorderBrush>
<SolidColorBrush Color="{StaticResource BorderMediumColor}" />
</Border.BorderBrush>
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="HasDropShadow" Value="true">
<Setter TargetName="Border" Property="Padding" Value="0,3,0,3" />
<Setter TargetName="Border" Property="CornerRadius" Value="4" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--Control colors.-->
<Color x:Key="WindowColor">#FFE8EDF9</Color>
<Color x:Key="ContentAreaColorLight">#FFC5CBF9</Color>
<Color x:Key="ContentAreaColorDark">#FF7381F9</Color>
<Color x:Key="DisabledControlLightColor">#FFE8EDF9</Color>
<Color x:Key="DisabledControlDarkColor">#FFC5CBF9</Color>
<Color x:Key="DisabledForegroundColor">#FF888888</Color>
<Color x:Key="SelectedBackgroundColor">#FFC5CBF9</Color>
<Color x:Key="SelectedUnfocusedColor">#FFDDDDDD</Color>
<Color x:Key="ControlLightColor">White</Color>
<Color x:Key="ControlMediumColor">#FF7381F9</Color>
<Color x:Key="ControlDarkColor">#FF211AA9</Color>
<Color x:Key="ControlMouseOverColor">#FF3843C4</Color>
<Color x:Key="ControlPressedColor">#FF211AA9</Color>
<Color x:Key="GlyphColor">#FF444444</Color>
<Color x:Key="GlyphMouseOver">sc#1, 0.004391443, 0.002428215, 0.242281124</Color>
<!--Border colors-->
<Color x:Key="BorderLightColor">#FFCCCCCC</Color>
<Color x:Key="BorderMediumColor">#FF888888</Color>
<Color x:Key="BorderDarkColor">#FF444444</Color>
<Color x:Key="PressedBorderLightColor">#FF888888</Color>
<Color x:Key="PressedBorderDarkColor">#FF444444</Color>
<Color x:Key="DisabledBorderLightColor">#FFAAAAAA</Color>
<Color x:Key="DisabledBorderDarkColor">#FF888888</Color>
<Color x:Key="DefaultBorderBrushDarkColor">Black</Color>
<!--Control-specific resources.-->
<Color x:Key="HeaderTopColor">#FFC5CBF9</Color>
<Color x:Key="DatagridCurrentCellBorderColor">Black</Color>
<Color x:Key="SliderTrackDarkColor">#FFC5CBF9</Color>
<Color x:Key="NavButtonFrameColor">#FF3843C4</Color>
<LinearGradientBrush x:Key="MenuPopupBrush"
EndPoint="0.5,1"
StartPoint="0.5,0">
<GradientStop Color="{DynamicResource ControlLightColor}"
Offset="0" />
<GradientStop Color="{DynamicResource ControlMediumColor}"
Offset="0.5" />
<GradientStop Color="{DynamicResource ControlLightColor}"
Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="ProgressBarIndicatorAnimatedFill"
StartPoint="0,0"
EndPoint="1,0">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#000000FF"
Offset="0" />
<GradientStop Color="#600000FF"
Offset="0.4" />
<GradientStop Color="#600000FF"
Offset="0.6" />
<GradientStop Color="#000000FF"
Offset="1" />
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
이 글의 내용은 아래의 사이트에서 기초합니다.