地図などを背景にして、印を上に書き加えることを想定してます。データはグループ(Group)配列とそれぞれに位置データ(Spot)配列の階層構造になってます。例えばグループとして、レストラン・コンビニなどの業種、Spotとしては店の位置とその規模を円の大きさで示すなど。あとエスコンのレーダーかいくぐりミッションとか
コードは以下のことができます。
- 左のリスト
- データグループ名(0~4)を表示
- 行を選択すると対応するグループに属する円を黄色にする
- 右のチャート
- 各Spotデータが示す位置に円を描画、円の中央にはグループ名
- 円の上にカーソルを置くと同じグループに属する円が太くなる
- 円をクリックするとそのグループが選択状態になり黄色に、また左のリストの選択もそれに対応する
左はいたって普通のListBoxですが、右もListBoxです。ListBoxをカスタマイズするにしても通常ItemsPanelにはStackPanelやWrapPanelを指定するでしょうが、ここではCanvasを使うことで自由な位置への配置を可能にします。その代わり位置を指定しないと全部左上に重なってしまうので、ItemContainerStyleでCanvas.LeftとCanvas.Top添付プロパティを設定します。
同一グループ内の複数の円を描くにはItemsControlを使用。ItemsControlはListBoxの親クラスです。ListBoxを使用すると同一グループ内の個々の円に対する選択が発生してしまうのでこちらを使用してますが、用途によってはListBoxでも可。つまりListBoxの中にListBoxを使っていることになります。下の図でいえば、左のListBoxで選択状態にある"0"に相当するのが、右の黄色箱で囲んだ部分になります。左右とも同じデータをバインディングしたListBox、描画方法が違うだけ。
左右で選択が連携するのは簡単、左のSelectedItemを右のSelectedItemにTwoWayでバインドするだけ。下図の黄色箱どこでもクリックすれば左のListBoxの0の行をクリックしたのと同じはずですが、黄色箱のItemsPanelとなっているCanvasはマウスイベントを拾わないので、円内部だけがクリックに反応します。
無理やりな部分があまりないので転用しやすいと思います。これだけのことがXAMLだけで実現できてしまうとは、面白い技術だなと。
<Window x:Class="RxMouseDragTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <DataTemplate x:Key="GroupListItemTemplate"> <TextBlock Text="{Binding GroupNumber}"/> </DataTemplate> <DataTemplate x:Key="SpotTemplate"> <Grid> <TextBlock Text="{Binding Number}" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White"/> <Ellipse Width="{Binding Width}" Height="{Binding Height}" Fill="Transparent"> <Ellipse.Style> <Style TargetType="{x:Type Ellipse}"> <Setter Property="Stroke" Value="Red"/> <Setter Property="StrokeThickness" Value="3"/> <Style.Triggers> <DataTrigger Binding="{Binding IsSelected,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContentControl}}" Value="true"> <Setter Property="Stroke" Value="Gold"/> </DataTrigger> <DataTrigger Binding="{Binding IsMouseOver,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContentControl}}" Value="true"> <Setter Property="StrokeThickness" Value="6"/> </DataTrigger> </Style.Triggers> </Style> </Ellipse.Style> </Ellipse> </Grid> </DataTemplate> <Style x:Key="SpotContainerStyle" TargetType="{x:Type ContentPresenter}"> <Setter Property="Canvas.Left" Value="{Binding Left}"/> <Setter Property="Canvas.Top" Value="{Binding Top}"/> </Style> <ItemsPanelTemplate x:Key="GroupPanelTemplate"> <Canvas Background="Transparent"/> </ItemsPanelTemplate> <DataTemplate x:Key="GroupTemplate"> <ItemsControl ItemsSource="{Binding Spots}" ItemTemplate="{StaticResource SpotTemplate}" ItemContainerStyle="{StaticResource SpotContainerStyle}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl> </DataTemplate> </Window.Resources> <Grid Background="Black" ClipToBounds="True"> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <ListBox Name="GroupList" Grid.Column="0" ItemsSource="{Binding Groups}" ItemTemplate="{StaticResource GroupListItemTemplate}"/> <ListBox Name="ChartBase" Grid.Column="1" ItemsPanel="{StaticResource GroupPanelTemplate}" ItemTemplate="{StaticResource GroupTemplate}" ItemsSource="{Binding Groups}" SelectedItem="{Binding SelectedItem,ElementName=GroupList}" Background="{x:Null}"> </ListBox> </Grid> </Window>コード。DataContextをセットしているだけです。
public partial class MainWindow : Window { static readonly Random rnd = new Random(); public MainWindow() { InitializeComponent(); DataContext = new { Groups = Enumerable.Range(0, 5).Select(i => new SpotGroup(i, rnd.Next(1, 4))), }; } } public class SpotGroup { public int GroupNumber { get; private set; } public IEnumerable<Spot> Spots { get; private set; } public SpotGroup(int GroupNumber, int NumItems) { this.GroupNumber = GroupNumber; Spots = Enumerable.Range(0, NumItems) .Select(i => new Spot() { Number = GroupNumber }) .ToArray(); } } public class Spot { static readonly Random rnd = new Random(); public int Number { get; set; } public int Left { get; private set; } public int Top { get; private set; } public int Width { get; private set; } public int Height { get; private set; } public Spot() { Left = rnd.Next(0, 300); Top = rnd.Next(0, 250); Width = rnd.Next(0, 20) + 40; Height = rnd.Next(0, 20) + 40; } }
Comments