I ran into a situation the other day where I needed to be able to display Trips to a user in the order that they were going to be executed and have them be able to move them up and down the list. I liked how Netflix tackled the issue, but wanted to give the user a little more control.
As it were, I could not find a decent solution to the problem. Google and StackOverflow mostly turned up hacks that were sure to fall apart. After much trial and error, I was able to use an IMultiValueConverter that accepted a ListBox and an item in the ListBox. From there, I could do an IndexOf and return that value back to a TextBlock for display.
The second issue of reordering I solved using the ButtonSpinner in the WPF Extended Toolkit. I had to trick it because I wanted the “down” spinner to move the item to the next higher index and the “up” spin to actually move the item down the list (from an index’s point of view). This functionality requires that the binding list is an ObservableCollection that exposes a Move event and also fires all the INotifyPropertyChanged events that WPF uses for UI refreshing.
Note that the ButtonSpinner is a content control and looks a little ugly when used by itself. The trick is to set the Padding to zero.
So now we have a ListBox that displays the current order of the items and allows the user to move the items up and down the list (which changes the order label) using the ButtonSpinners.
In my production code, I actually hide the ButtonSpinners until the user mouses over them, but I didn’t want to clutter the code. I’ll save that for a later post.
The key parts of the code are listed below. The entire project can be downloaded here. Remember to unblock after downloading.
The Converter that takes a ListBox and an item in the ListBox:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Windows.Controls;
namespace ReorderingListBox
{
public class ListBoxIndexConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var item = values[0];
if (item == null)
{
return null;
}
var lb = values[1] as ListBox;
if (lb == null)
{
return null;
}
//make it 1 based
var rv = lb.Items.IndexOf(item) + 1;
//very important because control
//we are binding to is expecting a string
return rv.ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
}
The XAML with the ListBox and ButtonSpinner:
<Window x:Class="ReorderingListBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:ReorderingListBox"
xmlns:wpf="http://schemas.xceed.com/wpf/xaml/toolkit"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<!--reference to our multi converter-->
<converters:ListBoxIndexConverter x:Key="listBoxIndexConverter" />
<!--this defines how each item in the listbox will layout-->
<DataTemplate x:Key="placesTemplate">
<StackPanel Orientation="Horizontal">
<!--shows the current order in the listbox-->
<TextBlock Width="15" Name="textBlockOrder">
<TextBlock.Text>
<!--this is a multibinding, we are into the converter-->
<!--the current object that we are bound to (a place)-->
<!--and the listbox that it lives in-->
<MultiBinding Converter="{StaticResource listBoxIndexConverter}">
<Binding />
<Binding ElementName="listBox" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<!--control from http://wpftoolkit.codeplex.com/-->
<!--note the padding has to be explicitly set-->
<!--otherwise it looks horrible more info about that-->
<!--http://timhibbard.com/blog/2012/05/08/default-padding-issue-with-buttonspinner/-->
<wpf:ButtonSpinner Padding="0"
Margin="3"
Spin="spinner_Spin" />
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ListBox Name="listBox"
ItemTemplate="{StaticResource placesTemplate}" />
</Grid>
</Window>
The code behind that constructs the list that binds to the ListBox and the event that listens to the ButtonSpinner.Spin event:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using Xceed.Wpf.Toolkit;
namespace ReorderingListBox
{
///
<summary> /// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//construct our backing list to bind to
var placesIWantToLive = new ObservableCollection
{
"Minneapolis",
"San Diego",
"Charleston",
"Philadelphia"
};
this.listBox.ItemsSource = placesIWantToLive;
}
private void spinner_Spin(object sender, SpinEventArgs e)
{
ButtonSpinner spinner = sender as ButtonSpinner;
//this will find the textblock in the same listbox item
TextBlock textOrder = spinner.FindName("textBlockOrder") as TextBlock;
ObservableCollection places = this.listBox.ItemsSource as ObservableCollection;
//back to zero based - remember we store the current index in this textblock
int current = int.Parse(textOrder.Text) - 1;
int destination = 0;
switch (e.Direction)
{
case SpinDirection.Decrease:
//because we want it to go "down" the list
destination = current + 1;
break;
case SpinDirection.Increase:
//because we want it to go "up" the list
destination = current - 1;
break;
default:
//we'll never hit here, but else statements are ugly
//and not as obvious as switch statements
break;
}
if (destination < 0)
{
//can't more the first item any higher so exit
return;
}
if (destination > places.Count + 1)
{
//can't move it any more down so exit
return;
}
//this is where the magic happens
places.Move(current, destination);
//this refreshes the textblocks holding the index
this.listBox.Items.Refresh();
//highlight the item that was moved so it is obvious to the user
this.listBox.SelectedIndex = destination;
}
}
}

![Win7 [Running]](http://timhibbard.com/blog/wp-content/uploads/2012/05/Win7-Running.png)
