Posted at

[C#/WPF/xaml]Sliderを改造する中で、標準のコントロールを改造する方法を知る


やりたいこと


  • なぜか、仕事でつくるアプリで「Slider」を改造することが多い。

  • また、動きは標準のコントロールと変わらないが、見た目を改造することが多い。

なので、スライダを改造する中で、ほかのコントロールを改造する勘所もついでに知りたい。


ゴール

こんなスライダーを試しに作ってみたい(ツマミを楕円にしただけ)。

image.png


流れ


  • まずは標準の「Slider」をxamlに追加する。

  • そのコントロールを右クリックして「テンプレートの編集」→「コピーして編集」を選択するimage.png
    image.png

  • そうすると、xamlの中に<Window.Resources>が追加され、その中に標準のSliderに使われているControlTemplate一式が入れられる。

  • そこから、必要ない部分を間引いて、必要な部分を付け足す。(0からは作りたくないので、個人的にはこれが作り方として楽だと思う)

  • 標準Sliderは「縦置き」「横置き」で大きくTemplateが分かれていて、さらにその中で「TickPlacement」(メモリの位置の設定値)の値によって、Thumb(つまみ)のTemplateが変わる、という少々ややこしい構成になっている。

  • 今回はなるべく単純にしたいので、「横置きのみ」「Tickなし」に限定して、標準Sliderから要らない部分をごそっと間引いて、スタイルをつくることにする。


最低限(横置きのみ/Tickなし)のSliderのスタイル

要らないものを間引いて、少々コメントを追記し、下記のように限定したスタイルにした。

(それでもかなり長いが...)


a.xaml

<Window x:Class="WpfApp24.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp24"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="200">
<Window.Resources>
<!-- 色の定義 -->
<SolidColorBrush x:Key="SliderThumb.Static.Foreground" Color="#FFE5E5E5"/>
<SolidColorBrush x:Key="SliderThumb.MouseOver.Background" Color="#FFDCECFC"/>
<SolidColorBrush x:Key="SliderThumb.MouseOver.Border" Color="#FF7Eb4EA"/>
<SolidColorBrush x:Key="SliderThumb.Pressed.Background" Color="#FFDAECFC"/>
<SolidColorBrush x:Key="SliderThumb.Pressed.Border" Color="#FF569DE5"/>
<SolidColorBrush x:Key="SliderThumb.Disabled.Background" Color="#FFF0F0F0"/>
<SolidColorBrush x:Key="SliderThumb.Disabled.Border" Color="#FFD9D9D9"/>
<SolidColorBrush x:Key="SliderThumb.Static.Background" Color="#FFF0F0F0"/>
<SolidColorBrush x:Key="SliderThumb.Static.Border" Color="#FFACACAC"/>
<SolidColorBrush x:Key="SliderThumb.Track.Border" Color="#FFD6D6D6"/>
<SolidColorBrush x:Key="SliderThumb.Track.Background" Color="#FFE7EAEA"/>

<!-- リピートボタン部のスタイル -->
<Style x:Key="RepeatButtonTransparent" TargetType="{x:Type RepeatButton}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Rectangle Fill="{TemplateBinding Background}" Height="{TemplateBinding Height}" Width="{TemplateBinding Width}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

<!-- 横置きスライダーのツマミのテンプレート -->
<ControlTemplate x:Key="SliderThumbHorizontalDefault" TargetType="{x:Type Thumb}">
<Grid HorizontalAlignment="Center" UseLayoutRounding="True" VerticalAlignment="Center">
<Path x:Name="grip" Data="M 0,0 C0,0 11,0 11,0 11,0 11,18 11,18 11,18 0,18 0,18 0,18 0,0 0,0 z" Fill="{StaticResource SliderThumb.Static.Background}" Stretch="Fill" SnapsToDevicePixels="True" Stroke="{StaticResource SliderThumb.Static.Border}" StrokeThickness="1" UseLayoutRounding="True" VerticalAlignment="Center"/>
</Grid>

<!-- ツマミのトリガー設定(マウスオーバーなどで色を変えたりする) -->
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Fill" TargetName="grip" Value="{StaticResource SliderThumb.MouseOver.Background}"/>
<Setter Property="Stroke" TargetName="grip" Value="{StaticResource SliderThumb.MouseOver.Border}"/>
</Trigger>
<Trigger Property="IsDragging" Value="true">
<Setter Property="Fill" TargetName="grip" Value="{StaticResource SliderThumb.Pressed.Background}"/>
<Setter Property="Stroke" TargetName="grip" Value="{StaticResource SliderThumb.Pressed.Border}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Fill" TargetName="grip" Value="{StaticResource SliderThumb.Disabled.Background}"/>
<Setter Property="Stroke" TargetName="grip" Value="{StaticResource SliderThumb.Disabled.Border}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

<!-- 横置きスライダーのテンプレート -->
<ControlTemplate x:Key="SliderHorizontal" TargetType="{x:Type Slider}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

<!-- 真ん中の横棒 -->
<Border x:Name="TrackBackground" BorderBrush="{StaticResource SliderThumb.Track.Border}" BorderThickness="1" Background="{StaticResource SliderThumb.Track.Background}" Height="4.0" Margin="5,0" Grid.Row="1" VerticalAlignment="center">
<Canvas Margin="-6,-1">
<!-- SelectionStart~Endで色が変わる部分 -->
<Rectangle x:Name="PART_SelectionRange" Fill="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" Height="4.0" Visibility="Hidden"/>
</Canvas>
</Border>
<Track x:Name="PART_Track" Grid.Row="1">
<Track.DecreaseRepeatButton>
<RepeatButton Command="{x:Static Slider.DecreaseLarge}" Style="{StaticResource RepeatButtonTransparent}"/>
</Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton>
<RepeatButton Command="{x:Static Slider.IncreaseLarge}" Style="{StaticResource RepeatButtonTransparent}"/>
</Track.IncreaseRepeatButton>
<Track.Thumb>
<Thumb x:Name="Thumb" Focusable="False" Height="18" OverridesDefaultStyle="True" Template="{StaticResource SliderThumbHorizontalDefault}" VerticalAlignment="Center" Width="11"/>
</Track.Thumb>
</Track>
</Grid>
</Border>

<!-- スライダー全体のトリガー設定 -->
<ControlTemplate.Triggers>
<Trigger Property="IsSelectionRangeEnabled" Value="true">
<Setter Property="Visibility" TargetName="PART_SelectionRange" Value="Visible"/>
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="Foreground" TargetName="Thumb" Value="Blue"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

<!-- スライダー本体のスタイル -->
<Style x:Key="SliderStyle1" TargetType="{x:Type Slider}">
<Setter Property="Stylus.IsPressAndHoldEnabled" Value="false"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource SliderThumb.Static.Foreground}"/>
<Setter Property="Template" Value="{StaticResource SliderHorizontal}"/>
</Style>

</Window.Resources>

<!-- ========== -->
<!-- 画面メイン -->
<!-- ========== -->
<Grid>
<StackPanel VerticalAlignment="Center" >
<Slider Name="MySlider" Style="{DynamicResource SliderStyle1}" />
<TextBlock Text="{Binding Value, ElementName=MySlider}"/>
</StackPanel>
</Grid>
</Window>



名前付きパーツ

標準のコントロールのスタイルには名前付きパーツというパーツが入っている。

ここでいうところの以下の2つ。


a.xaml

<Rectangle x:Name="PART_SelectionRange" Fill="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" Height="4.0" Visibility="Hidden"/>


<Track x:Name="PART_Track" Grid.Row="1">

こいつらは、Sliderコントロールの処理の中で、名前を使っていろいろなことを行っているので、勝手に名前を変えたり、削除してはいけない。

(例えばPART_Trackは、ツマミをドラッグしたときにツマミを移動するなどの処理をしているので、これを勝手に消すと、ツマミをつまんでも動かなくなる)

なので、Sliderの標準的な動作を保ちたい(見た目だけ変えたい等)のときは、名前付きパーツは残しておくこと。

名前付きパーツは、msdocsの各コントロールのページに名前付きパーツとして挙げられている。

改造する際は、こちらも確認すること。

https://docs.microsoft.com/ja-jp/dotnet/framework/wpf/controls/slider-styles-and-templates

image.png


見た目を変える


ツマミを丸くする

まずは、スライダーのツマミのところを、自分の好きな形にする。今回は、真ん丸のツマミにする。

ツマミは、Sliderのテンプレートの中の下記の部分で書かれている。


a.xaml

<Track.Thumb>

<Thumb x:Name="Thumb" Focusable="False" Height="18" OverridesDefaultStyle="True" Template="{StaticResource SliderThumbHorizontalDefault}" VerticalAlignment="Center" Width="11"/>
</Track.Thumb>

ツマミ(Thumb)のテンプレートとしてSliderThumbHorizontalDefaultを使っているので、そちらを見る。(上の方に書かれている)


ツマミのテンプレートを直す

SliderThumbHorizontalDefaultで、ツマミの見た目は下記のように描かれている。


a.xaml

<Grid HorizontalAlignment="Center" UseLayoutRounding="True" VerticalAlignment="Center">

<Path x:Name="grip" Data="M 0,0 C0,0 11,0 11,0 11,0 11,18 11,18 11,18 0,18 0,18 0,18 0,0 0,0 z" Fill="{StaticResource SliderThumb.Static.Background}" Stretch="Fill" SnapsToDevicePixels="True" Stroke="{StaticResource SliderThumb.Static.Border}" StrokeThickness="1" UseLayoutRounding="True" VerticalAlignment="Center"/>
</Grid>

要は、あの標準のツマミの四角いところを、Pathで書いている。

これを、下記のように丸(Ellipse)にしてやる。(色とか幅、高さはそのままを持ってくる)

※名前(grip)は、その下のTriggerのところで、MouseOver時や無効化時に色を変える時に使われているので、同じ名前を付けてやること。


a.xaml

<Grid HorizontalAlignment="Center" UseLayoutRounding="True" VerticalAlignment="Center">

<Ellipse x:Name="grip" Width="11" Height="18"
Fill="{StaticResource SliderThumb.Static.Background}" Stretch="Fill" SnapsToDevicePixels="True" Stroke="{StaticResource SliderThumb.Static.Border}" StrokeThickness="1" UseLayoutRounding="True" VerticalAlignment="Center"/>
</Grid>

これで、ツマミが円(楕円)になる。

image.png


まとめ

Sliderの改造を通じて、いろいろと試したが、結果としては、標準のコントロールを改造する際は、下記のように進めればよいと思われる。


  • 標準のコントロールをxamlに追加する

  • そのコントロールを右クリックして「テンプレートの編集」→「コピーして編集」を選択し、標準のTemplate、Styleをxaml内に吐き出させる

  • そこから要らない部分を間引く(但し、名前付きパーツは残しておく)

  • 目的の見た目、動作に合うようにTemplate、Styleを改造する


出来上がったコード

出来たxamlはこちら。

※ツマミを丸くしただけなので、上にあるコードとほとんど変化なし


a.xaml

<Window x:Class="WpfApp24.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp24"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="200">
<Window.Resources>
<!-- 色の定義 -->
<SolidColorBrush x:Key="SliderThumb.Static.Foreground" Color="#FFE5E5E5"/>
<SolidColorBrush x:Key="SliderThumb.MouseOver.Background" Color="#FFDCECFC"/>
<SolidColorBrush x:Key="SliderThumb.MouseOver.Border" Color="#FF7Eb4EA"/>
<SolidColorBrush x:Key="SliderThumb.Pressed.Background" Color="#FFDAECFC"/>
<SolidColorBrush x:Key="SliderThumb.Pressed.Border" Color="#FF569DE5"/>
<SolidColorBrush x:Key="SliderThumb.Disabled.Background" Color="#FFF0F0F0"/>
<SolidColorBrush x:Key="SliderThumb.Disabled.Border" Color="#FFD9D9D9"/>
<SolidColorBrush x:Key="SliderThumb.Static.Background" Color="#FFF0F0F0"/>
<SolidColorBrush x:Key="SliderThumb.Static.Border" Color="#FFACACAC"/>
<SolidColorBrush x:Key="SliderThumb.Track.Border" Color="#FFD6D6D6"/>
<SolidColorBrush x:Key="SliderThumb.Track.Background" Color="#FFE7EAEA"/>

<!-- リピートボタン部のスタイル -->
<Style x:Key="RepeatButtonTransparent" TargetType="{x:Type RepeatButton}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Rectangle Fill="{TemplateBinding Background}" Height="{TemplateBinding Height}" Width="{TemplateBinding Width}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

<!-- 横置きスライダーのツマミのテンプレート -->
<ControlTemplate x:Key="SliderThumbHorizontalDefault" TargetType="{x:Type Thumb}">
<Grid HorizontalAlignment="Center" UseLayoutRounding="True" VerticalAlignment="Center">
<!--<Path x:Name="grip" Data="M 0,0 C0,0 11,0 11,0 11,0 11,18 11,18 11,18 0,18 0,18 0,18 0,0 0,0 z" Fill="{StaticResource SliderThumb.Static.Background}" Stretch="Fill" SnapsToDevicePixels="True" Stroke="{StaticResource SliderThumb.Static.Border}" StrokeThickness="1" UseLayoutRounding="True" VerticalAlignment="Center"/>-->
<Ellipse x:Name="grip" Width="11" Height="18"
Fill="{StaticResource SliderThumb.Static.Background}" Stretch="Fill" SnapsToDevicePixels="True" Stroke="{StaticResource SliderThumb.Static.Border}" StrokeThickness="1" UseLayoutRounding="True" VerticalAlignment="Center"/>
</Grid>

<!-- ツマミのトリガー設定(マウスオーバーなどで色を変えたりする) -->
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Fill" TargetName="grip" Value="{StaticResource SliderThumb.MouseOver.Background}"/>
<Setter Property="Stroke" TargetName="grip" Value="{StaticResource SliderThumb.MouseOver.Border}"/>
</Trigger>
<Trigger Property="IsDragging" Value="true">
<Setter Property="Fill" TargetName="grip" Value="{StaticResource SliderThumb.Pressed.Background}"/>
<Setter Property="Stroke" TargetName="grip" Value="{StaticResource SliderThumb.Pressed.Border}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Fill" TargetName="grip" Value="{StaticResource SliderThumb.Disabled.Background}"/>
<Setter Property="Stroke" TargetName="grip" Value="{StaticResource SliderThumb.Disabled.Border}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

<!-- 横置きスライダーのテンプレート -->
<ControlTemplate x:Key="SliderHorizontal" TargetType="{x:Type Slider}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

<!-- 真ん中の横棒 -->
<Border x:Name="TrackBackground" BorderBrush="{StaticResource SliderThumb.Track.Border}" BorderThickness="1" Background="{StaticResource SliderThumb.Track.Background}" Height="4.0" Margin="5,0" Grid.Row="1" VerticalAlignment="center">
<Canvas Margin="-6,-1">
<!-- SelectionStart~Endで色が変わる部分 -->
<Rectangle x:Name="PART_SelectionRange" Fill="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" Height="4.0" Visibility="Hidden"/>
</Canvas>
</Border>
<Track x:Name="PART_Track" Grid.Row="1">
<Track.DecreaseRepeatButton>
<RepeatButton Command="{x:Static Slider.DecreaseLarge}" Style="{StaticResource RepeatButtonTransparent}"/>
</Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton>
<RepeatButton Command="{x:Static Slider.IncreaseLarge}" Style="{StaticResource RepeatButtonTransparent}"/>
</Track.IncreaseRepeatButton>
<Track.Thumb>
<Thumb x:Name="Thumb" Focusable="False" Height="18" OverridesDefaultStyle="True" Template="{StaticResource SliderThumbHorizontalDefault}" VerticalAlignment="Center" Width="11"/>
</Track.Thumb>
</Track>
</Grid>
</Border>

<!-- スライダー全体のトリガー設定 -->
<ControlTemplate.Triggers>
<Trigger Property="IsSelectionRangeEnabled" Value="true">
<Setter Property="Visibility" TargetName="PART_SelectionRange" Value="Visible"/>
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="Foreground" TargetName="Thumb" Value="Blue"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

<!-- スライダー本体 -->
<Style x:Key="SliderStyle1" TargetType="{x:Type Slider}">
<Setter Property="Stylus.IsPressAndHoldEnabled" Value="false"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource SliderThumb.Static.Foreground}"/>
<Setter Property="Template" Value="{StaticResource SliderHorizontal}"/>
</Style>

</Window.Resources>

<!-- ========== -->
<!-- 画面メイン -->
<!-- ========== -->
<Grid>
<StackPanel VerticalAlignment="Center" >
<Slider Name="MySlider" Style="{DynamicResource SliderStyle1}" />
<TextBlock Text="{Binding Value, ElementName=MySlider}"/>
</StackPanel>
</Grid>
</Window>



参考

[WPF] WPF でスライダーのデザインをカスタマイズ

http://gootara.org/library/2016/06/wpf-sdc.html

msdocsのSliderのページ

https://docs.microsoft.com/ja-jp/dotnet/framework/wpf/controls/slider-styles-and-templates