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メソッドとかにしといた方がいいのかもしれませんね。

