ListBoxのItemsPanelにテンプレートを設定すれば、項目のレイアウトを大きく変えることができます。 ItemsPanelにはPanel系のコンテナを指定することが可能です。 当然、WrapPanelも使えます。
本来、WrapPanelは子コントロールを左から右へ順に配置し、ボックスの端で折り返します。 (javaでいうところのFlowLayoutですね。) しかし、ListBox.ItemsPanelにWrapPanelを使うとちょっと表示がおかしくなってしまいます。 スクロールバーが表示されて折り返しが効かなくなるのです。
どうやらListBoxは内部にScrollViewerを持っていて、その下にItemsPanelを配置するようですね。 WrapPanelのWidthに固定値を設定すればスクロールバーの表示は回避できます。 ですがそれだとレイアウトが制限されてしまいます。 自由にレイアウトするには、ListBox内のScrollViewerの幅にあわせてテンプレートのWrapPanelの幅が変わるような仕組みが必要です。
バインディングでWrapPanelの幅とScrollViewerの幅を連携させることができれば1番楽なんでしょうけれど、その方法は見つかりませんでした。 というわけで当面の回避策としてListBoxのSizeChangedイベントで幅を連携させる方法を取ってみました。
簡単なサンプルコードを書くと、こんな感じ。 まずはMainWindow.xamlは、
<Window x:Class="ListBoxItemsPanelTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="300" Width="300"> <ListBox Name="listBox" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" SizeChanged="OnListBoxSizeChanged" > <ListBox.ItemsPanel> <ItemsPanelTemplate> <WrapPanel/> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBox.ItemTemplate> <DataTemplate> <Border BorderThickness="1" BorderBrush="Gray"> <TextBlock Text="{Binding}"/> </Border> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Window>
MainWindow.xaml.csは、
using System; using System.Collections.ObjectModel; using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace ListBoxItemsPanelTest { public partial class MainWindow : Window { public ObservableCollection<string> Items { get; set; } public MainWindow() { InitializeComponent(); Items = new ObservableCollection<string>(); for (int i = 0; i < 40; i++) Items.Add(i % 5 == 0 ? "あいうえお" : "" + i); listBox.ItemsSource = Items; } private void OnListBoxSizeChanged(object sender, SizeChangedEventArgs e) { ScrollViewer itemsViewer = (ScrollViewer)FindControl(listBox, typeof(ScrollViewer)); WrapPanel itemsPanel = (WrapPanel)FindControl(listBox, typeof(WrapPanel)); itemsPanel.Width = itemsViewer.ActualWidth; } // 最初に見つかったコントロールを返す private DependencyObject FindControl(DependencyObject obj, Type controlType) { if (obj == null) return null; if (obj.GetType() == controlType) return obj; int childrenCount = VisualTreeHelper.GetChildrenCount(obj); for (int i = 0; i < childrenCount; i++) { DependencyObject child = VisualTreeHelper.GetChild(obj, i); DependencyObject descendant = FindControl(child, controlType); if (descendant != null && descendant.GetType() == controlType) { return descendant; } } return null; } } }
FindControlメソッドは前の投稿の使いまわしです。 ちょくちょく使いそうなメソッドなのでユーティリティクラスのstaticメソッドとかにしといた方がいいのかもしれませんね。