none
"Copying" items from one ListBox to another?

    Question

  • I'm running into an issue where I have some items defined in XAML markup for a ListBox.  I then want to show a list of all the SELECTED items in my ListBox in another ListBox control (as a summary basically).  

    So I tried the following simple example where I'm binding the ItemsSource of my summary ListBox to the SelectedItems property of my first ListBox:

       <StackPanel Orientation="Vertical">  
            <ListBox x:Name="lb1" Width="200" Height="200" SelectionMode="Extended">  
                <ListBoxItem>ListBoxItem1</ListBoxItem> 
                <ListBoxItem>ListBoxItem2</ListBoxItem> 
                <ListBoxItem>ListBoxItem3</ListBoxItem> 
                <ListBoxItem>ListBoxItem4</ListBoxItem> 
                <ListBoxItem>ListBoxItem5</ListBoxItem> 
                <ListBoxItem>ListBoxItem6</ListBoxItem> 
            </ListBox> 
            <ListBox x:Name="lb2" Width="200" Height="200" 
                     ItemsSource="{Binding Path=SelectedItems, ElementName=lb1}">  
                  
            </ListBox> 
        </StackPanel> 
     
    What happens is that items seem to be removed from the first ListBox when they're selected.  I presume because you can't have the same object in two separate places.  I tried doing this through code as well via the SelectionChanged event, but ran into similar problems.

    I'm sure this is a somewhat common scenario ... is there something I'm missing?
    Friday, October 10, 2008 5:02 PM

Answers

  • Thinking fundamentals, it makes sense now. Each element is part of a Visual Tree (Tree=> One element in a tree cannot have more than one parent). So if the same listbox item was present in both lb1 and lb2, it no longer would be a tree.
    So binding removes item from one portion tree and puts it into another.

    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

    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; 
               
            } 
     
            
    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  
                string xamlCode = XamlWriter.Save(o); 
                return XamlReader.Load(new XmlTextReader(new StringReader(xamlCode))); 

    Let me know if this fixes your problem. It definitely works for me.




    • Marked as answer by Kofoed Friday, October 10, 2008 10:59 PM
    Friday, October 10, 2008 9:35 PM

All replies

  • If you notice the Visual Tree after selecting a few items, the items disappear altogether from the tree.
    I made a small post about this on my blog.( I dont know why it happens, I just blogged to keep a record of this interesting behavior)

    See if the items missing from Visual Tree itself can tell you something.
    Friday, October 10, 2008 6:14 PM
  •  Yes, I noticed that behavior, hence my post.  I'm pretty sure it has to do with not being able to "add" an item to another ListBox when it already exists in the first ListBox without removing it first. 

    I'm just unsure how to get around it.
    Friday, October 10, 2008 9:13 PM
  • Thinking fundamentals, it makes sense now. Each element is part of a Visual Tree (Tree=> One element in a tree cannot have more than one parent). So if the same listbox item was present in both lb1 and lb2, it no longer would be a tree.
    So binding removes item from one portion tree and puts it into another.

    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

    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; 
               
            } 
     
            
    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  
                string xamlCode = XamlWriter.Save(o); 
                return XamlReader.Load(new XmlTextReader(new StringReader(xamlCode))); 

    Let me know if this fixes your problem. It definitely works for me.




    • Marked as answer by Kofoed Friday, October 10, 2008 10:59 PM
    Friday, October 10, 2008 9:35 PM
  • Good workaround!
    Friday, October 10, 2008 10:59 PM