Problem Binding ListBoxItems from one Listbox to another

10 Oct 2008

The following XAML was posted online at MSDN.
<StackPanel Orientation="Vertical" Name="sp">
            <
ListBox Name="lb1" Width="200" Height="200" SelectionMode="Multiple">
                <
ListBoxItem>Item1</ListBoxItem>
                <
ListBoxItem>Item5</ListBoxItem>
                <
ListBoxItem>Item3</ListBoxItem>
                <
ListBoxItem>Item2</ListBoxItem>
            </
ListBox>
            <
ListBox Name="lb2" Width="200" Height="200"
                
ItemsSource="{Binding Path=SelectedItems, ElementName=lb1}">
            </
ListBox>
</
StackPanel>

This means that when user selects an item from Listbox1, they should appear in listbox 2. But what happens you select Listbox1 is that the items disappear from Listbox1 and appear in Listbox2. The items go missing altogether! The screenshots of the behavior as well as the visual tree dumps to prove this is shown below.
Initially after the XAML load
image
After selecting two items
image  
In case you are wondering about the tool I am using to run the XAML, it is called BuddiPad, my custom XAML tool which has small features like XAML error parsing, go-to-error line, loads Visual Tree, Loads Logical Trees, etc. The listbox on the top right with a whitish background is the Visual tree while the one below with a Teal like background is the Logical Tree.

If you notice, selected items appear in lb2 (in image 2) disappear from the Visual Tree for the lb1 altogether. This happens only with the ListBoxItems as ItemsSource. But if the ItemsSource was bound to some other dependency property like "Roles" or "Users" or any other user defined props in the class, it does not break.

The workaround is to  create new ListBoxItem and copy content property. Create a Selected property which is a Dependency Property as shown.

public
IList Selected
        {
            get { return (IList)GetValue(SelectedProperty); }
            set { SetValue(SelectedProperty, value); }
        }

      
// Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
       
public static readonly DependencyProperty SelectedProperty =
            DependencyProperty.Register("Selected", typeof(IList), typeof(Window1), new UIPropertyMetadata(null));

The XAML is shown  for reference.

<
StackPanel Orientation="Vertical" Name
="sp">
            <
ListBox x:Name="lb1" Width="200" Height="200" SelectionMode="Multiple" SelectionChanged
="lb1_SelectionChanged">
                <
ListBoxItem
>
                    <
Button>Item1</Button
>
                </
ListBoxItem
>
                <
ListBoxItem>Item5</ListBoxItem
>
                <
ListBoxItem>Item3</ListBoxItem
>
                <
ListBoxItem>Item2</ListBoxItem
>
            </
ListBox
>
            <
ListBox x:Name="lb2" Width="200" Height
="200"
                
ItemsSource="{Binding Selected
}">
            </
ListBox
>
</
StackPanel>

Now inside the constructor of the Window which has the listboxes, set the DataContext for sp so that it knows where to look for Selected.

public
Window1()
        {
            InitializeComponent();
            sp.DataContext = this;
            Selected = new List<object>();
        }

Now in the SelectionChanged event of Lb1, you update the Selected property collection accordingly. The code which does just that is shown.

private void
lb1_SelectionChanged(object sender, SelectionChangedEventArgs e)
       {
           IList l = new List<Object>();
          
           foreach (var x in lb1.SelectedItems)
           {
               l.Add(Clone(x));
           }
           Selected = l;
         
//BindingOperations.GetBindingExpressionBase(lb2, ListBox.ItemsSourceProperty).UpdateTarget();
      
}

Again, another problem arises here. How do we clone WPF elements? The idea is to get XAML for the given WPF element, convert it back to object. Simply way to clone. The code for the function Clone() is shown.

public
Object Clone(Object o)
{
       string xamlCode = XamlWriter.Save(o);
       return XamlReader.Load(new XmlTextReader(new StringReader(xamlCode)));
}

I posted solution for this problem at : http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/3da8c3b9-0a91-49c6-a460-f9215a294f29

Just in case, here is the draft.