ListBoxItemをクリッピングして選択できる領域を限定する
WPFのItemsControl(ListBox、ComboBox、Menu、TreeViewなど)はItemTemplateプロパティを持っており、このプロパティにDataTemplateを定義することで各アイテムの表示をカスタマイズできるようになっています。
※データテンプレートの詳細については、下記のページをご参照ください。
このデータテンプレートを利用して、たとえばListBoxの各アイテムを下記のように円形の表示とすることができます。
これのXAMLは下記のようになります。
XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
x:Class="ListBoxItemSelectionPoint.Window1"
Title="Window1" Height="300" Width="300" FontSize="20">
<ListBox HorizontalContentAlignment="Center" BorderBrush="#00000000">
<ListBox.ItemTemplate>
<DataTemplate x:Name="DataTemplate">
<Grid>
<Ellipse Width="250" Height="70" Margin="10,5,10,5" Fill="#FFBBBBBB"/>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<System:String>リストアイテム1</System:String>
<System:String>リストアイテム2</System:String>
<System:String>リストアイテム3</System:String>
</ListBox>
</Window>
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
x:Class="ListBoxItemSelectionPoint.Window1"
Title="Window1" Height="300" Width="300" FontSize="20">
<ListBox HorizontalContentAlignment="Center" BorderBrush="#00000000">
<ListBox.ItemTemplate>
<DataTemplate x:Name="DataTemplate">
<Grid>
<Ellipse Width="250" Height="70" Margin="10,5,10,5" Fill="#FFBBBBBB"/>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<System:String>リストアイテム1</System:String>
<System:String>リストアイテム2</System:String>
<System:String>リストアイテム3</System:String>
</ListBox>
</Window>
一見何の問題もないように見えますがこれには1つ重要な問題があります。せっかく円形の表示としたにもかかわらず円の外側部分をクリックした場合でもアイテムを選択できてしまうのです。この場合にはアイテムはListBox既定のVirtualizingStackPanelによるレイアウトですのでまだ良いほうですが、もっと広範囲にアイテムがレイアウトされる場合などは意図していない選択を行ってしまう可能性があります。
この問題の解決方法をいろいろと試行錯誤した結果、ListBoxItemのClipプロパティを利用してアイテムそのものをクリッピングすることで求める動作を実現することができました。もし、他にもっと良い方法をご存知の方がいらっしゃいましたら、教えていただけますと幸いです。
以下がその解決方法を適用したXAMLとなります。
XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
x:Class="ListBoxItemSelectionPoint.ClipedListBoxItem"
Title="ClipedListBoxItem"
Width="300" Height="300" FontSize="20">
<ListBox HorizontalContentAlignment="Center" BorderBrush="#00000000">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="#FFBBBBBB"/>
<Setter Property="Width" Value="250"/>
<Setter Property="Height" Value="70"/>
<Setter Property="Margin" Value="10,5,10,5"/>
<Setter Property="Clip" Value="M249.5,35C249.5,54.0538238691624,193.759451353934,69.5,125,69.5C56.2405486460662,69.5,0.5,54.0538238691624,0.5,35C0.5,15.9461761308376,56.2405486460662,0.5,125,0.5C193.759451353934,0.5,249.5,15.9461761308376,249.5,35z"/>
</Style>
</ListBox.Resources>
<System:String>リストアイテム1</System:String>
<System:String>リストアイテム2</System:String>
<System:String>リストアイテム3</System:String>
</ListBox>
</Window>
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
x:Class="ListBoxItemSelectionPoint.ClipedListBoxItem"
Title="ClipedListBoxItem"
Width="300" Height="300" FontSize="20">
<ListBox HorizontalContentAlignment="Center" BorderBrush="#00000000">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="#FFBBBBBB"/>
<Setter Property="Width" Value="250"/>
<Setter Property="Height" Value="70"/>
<Setter Property="Margin" Value="10,5,10,5"/>
<Setter Property="Clip" Value="M249.5,35C249.5,54.0538238691624,193.759451353934,69.5,125,69.5C56.2405486460662,69.5,0.5,54.0538238691624,0.5,35C0.5,15.9461761308376,56.2405486460662,0.5,125,0.5C193.759451353934,0.5,249.5,15.9461761308376,249.5,35z"/>
</Style>
</ListBox.Resources>
<System:String>リストアイテム1</System:String>
<System:String>リストアイテム2</System:String>
<System:String>リストアイテム3</System:String>
</ListBox>
</Window>
Clipプロパティに設定するGeometryデータは、EllipeをPathに変換してPathのDataプロパティにあるものを利用します。具体的にはExpression BlendでEllipseを配置し、Ellipseを選択した状態でメニューの[オブジェクト]-[パス]-[パスに変換]を選択します。
これの実行結果は下記のようになり、円の外側をクリックしてもアイテムを選択することはできません。もちろんSelectionChangedイベントも発生しません。