UPDATE I've enhanced my solution in a couple blog posts that can be found here and here. The new version allows for a more generic approach using MVVM.
A possible solution came to me, and Phil confirmed that it was the right approach. I have to use an ItemsControl with a horizontal StackPanel as the ItemsPanel. Then I created a DataTemplate for my data type and used it for the ItemTemplate on my ItemsControl.
The data template:
<DataTemplate x:Key="DataNodeStackedDataTemplate" DataType="my:DataNode">
<ListBox ItemsSource="{Binding Children}"
Style="{StaticResource StackedListBoxStyle}"/>
</DataTemplate>
The ItemsControl:
<ItemsControl x:Name="MillerColumnsView" VerticalContentAlignment="Stretch"
ItemTemplate="{StaticResource DataNodeStackedDataTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The code-behind:
private void StackedListBox_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
var lb = sender as ListBox;
if (lb == null) return;
var dn = lb.DataContext as DataNode;
if (dn == null) return;
int index = MillerColumnsView.Items.IndexOf(dn);
if (index == -1) return;
index++;
while (MillerColumnsView.Items.Count > index)
MillerColumnsView.Items.RemoveAt(index);
if (dn.Children == null) return;
// this Select() call performs some restructuring of the tree to
// appropriate display the correct nodes in the next ListBox
dn.Select(dn.AvailableItems.ElementAt(lb.SelectedIndex));
if (dn.Children.Count() == 0) return;
MillerColumnsView.Items.Add(dn.Children.ElementAt(0));
}
This automatically removes and creates ListBoxes for each level of selection. A little syling and it could look pretty, too!