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