とりあえず、こういうものを作ります。10x10の格子の中に机やらパソコンやらの文字列があってクリックで選択できる。
何もない部分はクリックしても選択できない。
選択したものの座標が画面下のテキストボックスに表示されて、ここの数字をいじると格子の上のアイテムもそこに移動するって感じです。
全体的な構成としては、上の格子部分はListBoxを使っています。
んで、下にTextBoxが2つ。
ListBoxはScrollViewerとStackPanelに入れてます。(サイズ調整の関係で)
というわけで、早速つくりに入ります。
格子の中に表示される1つのアイテムを表すクラスを作ります。
INotifyPropertyChangedを実装する形でさくっと作りました。プロパティはRow,Col,Nameの3つです。
using System.ComponentModel;
namespace WpfGridListBox
{
public class RoomItem : INotifyPropertyChanged
{
#region INotifyPropertyChanged メンバ
public event PropertyChangedEventHandler PropertyChanged;
protected void FirePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region Row
private int row;
public int Row
{
get { return row; }
set
{
row = value;
FirePropertyChanged("Row");
}
}
#endregion
#region Col
private int col;
public int Col
{
get { return col; }
set
{
col = value;
FirePropertyChanged("Col");
}
}
#endregion
#region Name
private string name;
public string Name
{
get { return name; }
set
{
name = value;
FirePropertyChanged("Name");
}
}
#endregion
}
}
Rowが今いる行で、Colが列。Nameが表示用の文字列になります。
WindowのDataContextにこれのコレクションをつっこんで初期化します。
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfGridListBox
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
var items = new ObservableCollection<RoomItem>
{
new RoomItem { Row = 0, Col = 1, Name="机" },
new RoomItem { Row = 5, Col = 5, Name="椅子" },
new RoomItem { Row = 7, Col = 2, Name="ベッド" },
new RoomItem { Row = 2, Col = 8, Name="鏡" },
new RoomItem { Row = 3, Col = 1, Name="冷蔵庫" },
new RoomItem { Row = 1, Col = 2, Name="ゴミ箱" },
new RoomItem { Row = 1, Col = 5, Name="本棚" },
new RoomItem { Row = 5, Col = 7, Name="宝石箱" },
new RoomItem { Row = 2, Col = 2, Name="パソコン" },
new RoomItem { Row = 8, Col = 9, Name="テレビ" },
};
DataContext = items;
}
}
}
画面にListBoxとTextBoxを2つ置いてバインドします。
<Window x:Class="WpfGridListBox.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfGridListBox="clr-namespace:WpfGridListBox"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
<ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True"/>
</StackPanel>
</ScrollViewer>
<StackPanel Grid.Row="1">
<TextBox Text="{Binding Row, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Col, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Grid>
</Window>
これを実行すると、わざわざListBoxをScrollViewerとStackPanelに入れているのかというと、Gridに直接ListBoxを置くとListBoxが無駄に広がってしまうからです。
普通はそれでいいんだけど、今回はその動作が邪魔なのでStackPanelに入れてAlignmentを明示的に指定してます。
ここまでで実行すると下のような感じ。
次に、RoomItemに対してDataTemplateを定義します。Nameプロパティの値を表示するだけなので、いたってシンプル。
このDataTemplateをStyleを使ってListBoxに関連付けます。
<Window x:Class="WpfGridListBox.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfGridListBox="clr-namespace:WpfGridListBox"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="roomItemTemplate" DataType="{x:Type WpfGridListBox:RoomItem}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
<Style x:Key="roomItemListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="ItemTemplate" Value="{StaticResource roomItemTemplate}" />
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
<ListBox ItemsSource="{Binding}" Style="{StaticResource roomItemListBoxStyle}" IsSynchronizedWithCurrentItem="True"/>
</StackPanel>
</ScrollViewer>
<StackPanel Grid.Row="1">
<TextBox Text="{Binding Row, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Col, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Grid>
</Window>
実行するとこんな感じ。
ここから、ListBoxの中を10x10の格子状にしていきます。使うのはGridでRowDefinitionとColumnDefinitionを10個用意して、正方形になるようにSharedSizeGroupも指定します。
ということで、ListBoxのItemsPanelにGridをしかけます。
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Grid ShowGridLines="True" IsItemsHost="True" Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition SharedSizeGroup="cell" Height="Auto"/>
<RowDefinition SharedSizeGroup="cell" Height="Auto" />
<RowDefinition SharedSizeGroup="cell" Height="Auto" />
<RowDefinition SharedSizeGroup="cell" Height="Auto" />
<RowDefinition SharedSizeGroup="cell" Height="Auto" />
<RowDefinition SharedSizeGroup="cell" Height="Auto" />
<RowDefinition SharedSizeGroup="cell" Height="Auto" />
<RowDefinition SharedSizeGroup="cell" Height="Auto" />
<RowDefinition SharedSizeGroup="cell" Height="Auto" />
<RowDefinition SharedSizeGroup="cell" Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="cell" Width="Auto"/>
<ColumnDefinition SharedSizeGroup="cell" Width="Auto" />
<ColumnDefinition SharedSizeGroup="cell" Width="Auto" />
<ColumnDefinition SharedSizeGroup="cell" Width="Auto" />
<ColumnDefinition SharedSizeGroup="cell" Width="Auto" />
<ColumnDefinition SharedSizeGroup="cell" Width="Auto" />
<ColumnDefinition SharedSizeGroup="cell" Width="Auto" />
<ColumnDefinition SharedSizeGroup="cell" Width="Auto" />
<ColumnDefinition SharedSizeGroup="cell" Width="Auto" />
<ColumnDefinition SharedSizeGroup="cell" Width="Auto" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</Setter.Value>
実行すると結構カオス。
すべて0,0の位置に重なってしまうのでかなりカオスなことになってる。
ItemsContainerStyleプロパティにGrid.RowとGrid.Columnの添付プロパティを指定しないとすべて0,0の場所に重なってしまう。
ということでStyleに追加。
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Grid.Row" Value="{Binding Row}" />
<Setter Property="Grid.Column" Value="{Binding Col}" />
</Style>
</Setter.Value>
</Setter>
これで実行すると、最初の画像のようになる。
ListBoxだからってアイテムが連続して列挙されなくてもかまわないという例でした。
突き詰めると「ListBoxをカスタマイズして都道府県の地図を選択するUIを作成する」みたいなことも出来てしまいます。
おそろしや…