はじめに
ネタが思いつかないので苦し紛れに始めた「○○もどきを作る」シリーズも今回が3回目。私の中で WPF 熱が再燃しました。というか WCF ちょっと飽きた。
今回のターゲットは Firefox の SideBar。お気に入りをツリー表示したりするアレです。Firefox の SideBar は拡張さえ入れなければ非常にシンプルなのでマネするのは難しくなさそう。VisualStudio の DockingWindow とか作るなら死ねますが…。
まずは XAML を記述して SideBar の外観を作成します
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SideBarSample"> <!--つまみ部分の Thumb 用スタイル--> <Style x:Key="ThumbStyle" TargetType="{x:Type Thumb}"> <Setter Property="Width" Value="6"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Thumb}"> <!--境界線だけ表示--> <Border BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}"/> <ControlTemplate.Triggers> <!--マウスカーソルが上にあるときはカーソルを変更--> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Cursor" Value="{x:Static Cursors.SizeWE}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!--ヘッダ部分に表示する TextBlock 用スタイル--> <Style x:Key="TextBlockStyle" TargetType="{x:Type TextBlock}"> <Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="Margin" Value="5,1,1,1"/> </Style> <!--CloseButton の上にマウスカーソルがあるときに表示するイメージ--> <BitmapImage x:Key="CloseMouseOverImage" UriSource="Images/close_mouseover.png"/> <!--SideBar を閉じるボタン用のスタイル--> <Style x:Key="CloseButtonStyle" TargetType="{x:Type Button}"> <Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="IsTabStop" Value="False"/> <Setter Property="Focusable" Value="False"/> <Setter Property="Margin" Value="2,1,5,1"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Image x:Name="PART_CloseButtonImage" Source="Images/close_normal.png" Width="13" Height="13" Stretch="Uniform"/> <ControlTemplate.Triggers> <!--マウスカーソルが上にあるときは画像を変更--> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Source" TargetName="PART_CloseButtonImage" Value="{StaticResource CloseMouseOverImage}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!--SideBar のスタイル--> <Style TargetType="{x:Type local:SideBar}"> <Setter Property="Background" Value="{x:Static SystemColors.ControlBrush}"/> <Setter Property="BorderBrush" Value="{x:Static SystemColors.ControlDarkBrush}"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:SideBar}"> <Border Background="{x:Static SystemColors.WindowBrush}" BorderBrush="{TemplateBinding BorderBrush}"> <DockPanel LastChildFill="True"> <!--サイズを変更するつまみになる Thumb--> <Thumb x:Name="PART_Thumb" Style="{StaticResource ThumbStyle}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1,0,1,0" DockPanel.Dock="Right"/> <!--ヘッダ部分--> <Border BorderThickness="0,0,0,1" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" Height="25" DockPanel.Dock="Top"> <DockPanel LastChildFill="True"> <Button Style="{StaticResource CloseButtonStyle}" Command="{x:Static local:SideBar.CloseCommand}" DockPanel.Dock="Right"/> <TextBlock Style="{StaticResource TextBlockStyle}" Text="{TemplateBinding Header}" Foreground="{TemplateBinding Foreground}" DockPanel.Dock="Left"/> </DockPanel> </Border> <!--コンテンツ部分--> <ContentPresenter/> </DockPanel> </Border> <ControlTemplate.Triggers> <Trigger Property="DockPanel.Dock" Value="Right"> <!--右側にドッキングされているときは Thumb を左側に表示--> <Setter Property="DockPanel.Dock" TargetName="PART_Thumb" Value="Left"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
SideBar をドラッグして幅を調節するツマミには Thumb を使っています。サイズ変更可能な事が利用者に分かるように、マウスが上にある時はカーソルを変更するようにしています。
GridSplitter を使えばこの手間は不要なんですが、そもそも Grid 使ってないし。SideBar の幅を変更するコードはどうせ書かないといけないし。
SideBar の動作も一気に記述します
いつもなら記事数を稼ぐために「次回へ続く」ところですが、今回作成する SideBar は大したコードじゃないので、もう記述してしまいます。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Windows.Controls.Primitives; namespace SideBarSample { [TemplatePart(Name = SideBar.PART_THUMB, Type = typeof(Thumb))] public class SideBar : HeaderedContentControl { internal const string PART_THUMB = "PART_Thumb"; private Thumb _thumb; static SideBar() { DefaultStyleKeyProperty.OverrideMetadata(typeof(SideBar), new FrameworkPropertyMetadata(typeof(SideBar))); // CloseButton を押した時に実行するコマンドをクラスに登録 CloseCommand = new RoutedCommand("CloseCommand", typeof(SideBar)); CommandManager.RegisterClassCommandBinding(typeof(SideBar), new CommandBinding(CloseCommand, OnCloseCommand)); } // CloseButton を押した時に実行するコマンド public static RoutedCommand CloseCommand { get; private set; } private static void OnCloseCommand(object sender, ExecutedRoutedEventArgs e) { SideBar sideBar = sender as SideBar; if (sideBar != null) { // SideBar を非表示にする sideBar.Visibility = Visibility.Collapsed; } } public override void OnApplyTemplate() { base.OnApplyTemplate(); _thumb = GetTemplateChild(PART_THUMB) as Thumb; if (_thumb != null) { _thumb.DragDelta += new DragDeltaEventHandler(OnThumbDragDelta); } } // Thumb をドラッグされたら SideBar の幅を変更する private void OnThumbDragDelta(object sender, DragDeltaEventArgs e) { double ajustX = Width; switch (DockPanel.GetDock(this)) { case Dock.Left: ajustX += e.HorizontalChange; break; case Dock.Right: ajustX -= e.HorizontalChange; break; } if (0 < ajustX) { Width = ajustX; } } } }
説明が不要なほど簡単ですね。すみません。