Click here to Skip to main content
15,893,668 members
Please Sign up or sign in to vote.
1.00/5 (1 vote)
See more:
My code below will move Selected Items from the Left to the Right Listview but I get an error when trying to remove that item from the Leftside (Memberlist.Items.Remove(item).

The error is "Operations is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource.

If I set MemberList.ItemSource = null it just removes all items. Any advice is appreciated.

Also, I would like to allow users to click a button and all items will be move. any recommendations how to make that work is appreciated.

What I have tried:

private void Button_Click(object sender, RoutedEventArgs e)
       {

           foreach (var item in new ArrayList(MembersList.SelectedItems))
           {

               MembersSelected.Items.Add(item);
               MembersList.Items.Remove(item);
           }
       }
Posted
Updated 14-Nov-17 12:03pm
Comments
Richard MacCutchan 14-Nov-17 10:59am    
The error message is quite clear. You must not try to manipulate the items in the ListView directly when it is being fed by a binding source. Modify the source and tell the UI to refresh itself.
Kenneth Haugland 14-Nov-17 11:09am    
You keep changing the listview by removing items, so you will have to count backwards
Member 13481361 14-Nov-17 13:03pm    
Gents,
thanks for the feedback. Can you give me a code example of what you are speaking of?
Graeme_Grant 14-Nov-17 19:46pm    
see below...
Karthik_Mahalingam 14-Nov-17 23:13pm    
use  Reply  button, to post Comments/query to the user, so that the user gets notified and responds to your text.

1 solution

If you have databound a collection, then modify the collection and the ListView will update. This is only true for collection that implement INotifyCollectionChanged. INotifyCollectionChanged is used by the data binding system to communicate changes between the subscriber (UI) and the subject (data) - this is called the Observer pattern[^].

For example, the ObservableCollection<> class implements INotifyCollectionChanged, so the ListViews will change automatically (through data binding) however the List<> does not use INotifyCollectionChanged and the ListViews won't see the change.

The alternative, as the two ListViews are working with the same data, bind each ListView to a separate CollectionViewSource. Add a grouping to the data items if one does not already exist - this is to identify which list the item belongs to. Now set the filter of each CollectionViewSource to the gouping property and the ListViews will show their respective groups. Now, to move data items from one ListView to the other, simply change the grouping property on the data item and the ListViews will automatically update and the item appears to move from one ListView to the other. Lastly, for this to work, your data item needs to implement INotifyPropertyChanged and the collection of data items needs to implement INotifyCollectionChanged.

** Update: I decided to put together a quick example of two working versions:
  1. Moving objects from one collection to another
  2. Using CollectionViewSource filtering and changing a property on the selected item

First we need a model:
C#
public class PersonModel : INotifyPropertyChanged
{
    private string name;
    public string Name
    {
        get { return name; }
        set
        {
            name= value;
            RaisePropertyChanged();
        }
    }

    private string group = "A";
    public string Group
    {
        get { return group; }
        set {
                group = value;
                RaisePropertyChanged();
            }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void RaisePropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Now we can set up the collections, CollectionViewSources and the test data:
C#
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;

        Models1A = new ObservableCollection<PersonModel>(Models.Where(x => GroupA(x)));
        Models1B = new ObservableCollection<PersonModel>(Models.Where(x => GroupB(x)));

        const string filterProperty = nameof(PersonModel.Group);

        CsvA.Source = Models;
        SetFiltering(CsvA, GroupA, filterProperty);

        CsvB.Source = Models;
        SetFiltering(CsvB, GroupB, filterProperty);
    }

    private void SetFiltering(CollectionViewSource Csv, Predicate<PersonModel> filter, string FilterProperty)
    {
        Csv.IsLiveFilteringRequested = true;
        Csv.LiveFilteringProperties.Add(FilterProperty);
        Csv.View.Filter = x => filter((PersonModel)x);
    }

    public ObservableCollection<PersonModel> Models1A { get; }
    public ObservableCollection<PersonModel> Models1B { get; }

    private Predicate<PersonModel> GroupA { get; } = new Predicate<PersonModel>(x => x.Group == "A");
    private Predicate<PersonModel> GroupB { get; } = new Predicate<PersonModel>(x => x.Group == "B");

    public ObservableCollection<PersonModel> Models { get; }
        = new ObservableCollection<PersonModel>
        {
            new PersonModel { Name = "Person 1", Group = "B" },
            new PersonModel { Name = "Person 2" },
            new PersonModel { Name = "Person 3" },
            new PersonModel { Name = "Person 4" }
        };

    public CollectionViewSource CsvA { get; } = new CollectionViewSource();
    public CollectionViewSource CsvB { get; } = new CollectionViewSource();

    private void OnManualMove(object sender, RoutedEventArgs e)
    {
        var model = (PersonModel)((Button)sender).DataContext;
        if (Models1A.Contains(model))
        {
            Models1A.Remove(model);
            Models1B.Add(model);
        }
        else
        {
            Models1B.Remove(model);
            Models1A.Add(model);
        }
    }

    private void OnFilteredMove(object sender, RoutedEventArgs e)
    {
        var model = (PersonModel)((Button)sender).DataContext;
        model.Group = model.Group == "A" ? "B" : "A";
    }
}

Lastly, the Window (UI):
XML
<Window
    x:Class="SwitchLists.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Code Project Q&A  |  Move item between Lists"
    Height="350" Width="525" WindowStartupLocation="CenterScreen">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <Grid.Resources>

            <Style TargetType="{x:Type ListBox}">
                <Setter Property="Margin" Value="10"/>
            </Style>

            <DataTemplate x:Key="ManualItemTempate">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="{Binding Name}"/>
                    <Button Content="MOVE"
                            Padding="2" Margin="2" Grid.Column="1"
                            Click="OnManualMove"/>
                </Grid>
            </DataTemplate>

            <DataTemplate x:Key="FilterItemTempate">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="{Binding Name}"/>
                    <Button Content="MOVE"
                            Padding="2" Margin="2" Grid.Column="1"
                            Click="OnFilteredMove"/>
                </Grid>
            </DataTemplate>

            <Style TargetType="{x:Type ListBoxItem}">
                <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            </Style>
        </Grid.Resources>

        <TextBlock Text="Between Lists"/>

        <ListBox Grid.Row="1"
                 ItemsSource="{Binding Models1A}"
                 ItemTemplate="{StaticResource ManualItemTempate}">

        </ListBox>
        <ListBox Grid.Row="1" Grid.Column="1"
                 ItemsSource="{Binding Models1B}"
                 ItemTemplate="{StaticResource ManualItemTempate}">

        </ListBox>

        <TextBlock Text="CollectionViewSource" Grid.Row="2"/>
        
        <ListBox Grid.Row="3"
                 ItemsSource="{Binding CsvA.View}"
                 ItemTemplate="{StaticResource FilterItemTempate}">

        </ListBox>
        <ListBox Grid.Row="3" Grid.Column="1"
                 ItemsSource="{Binding CsvB.View}"
                 ItemTemplate="{StaticResource FilterItemTempate}">

        </ListBox>

    </Grid>

</Window>

Now, when you click the "Move" button, the item will switch Lists:
  • The top two lists are moving from one collection to the other
  • The bottom two are Filtered views to the same collection with only the item Group property changing


UPDATE Below I've separated the C# code and Xaml to help clarify the two different solutions.

Yes, you are right, there is shared code. They share the main collection (Models) and they share the Predicate filter to seperate the data into two different groupings.

However there are two different solutions:

1. Manually (physically) moving (OnManualMove) an item (PersonModel) from one collection (Models1a) to the other collection (Models1b).

The C# code:
C#
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
 
        Models1A = new ObservableCollection<PersonModel>(Models.Where(x => GroupA(x)));
        Models1B = new ObservableCollection<PersonModel>(Models.Where(x => GroupB(x)));
    }
 
    public ObservableCollection<PersonModel> Models1A { get; }
    public ObservableCollection<PersonModel> Models1B { get; }
 
    private Predicate<PersonModel> GroupA { get; } = new Predicate<PersonModel>(x => x.Group == "A");
    private Predicate<PersonModel> GroupB { get; } = new Predicate<PersonModel>(x => x.Group == "B");
 
    public ObservableCollection<PersonModel> Models { get; }
        = new ObservableCollection<PersonModel>
        {
            new PersonModel { Name = "Person 1", Group = "B" },
            new PersonModel { Name = "Person 2" },
            new PersonModel { Name = "Person 3" },
            new PersonModel { Name = "Person 4" }
        };
 
    private void OnManualMove(object sender, RoutedEventArgs e)
    {
        var model = (PersonModel)((Button)sender).DataContext;
        if (Models1A.Contains(model))
        {
            Models1A.Remove(model);
            Models1B.Add(model);
        }
        else
        {
            Models1B.Remove(model);
            Models1A.Add(model);
        }
    }
}

The Xaml used is as follows:
XML
<Grid.Resources>
    <DataTemplate x:Key="ManualItemTempate">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <TextBlock Text="{Binding Name}"/>
            <Button Content="MOVE"
                    Padding="2" Margin="2" Grid.Column="1"
                    Click="OnManualMove"/>
        </Grid>
    </DataTemplate>
</Grid.Resources>

<ListBox Grid.Row="1"
     ItemsSource="{Binding Models1A}"
     ItemTemplate="{StaticResource ManualItemTempate}"/>

<ListBox Grid.Row="1" Grid.Column="1"
     ItemsSource="{Binding Models1B}"
     ItemTemplate="{StaticResource ManualItemTempate}"/>

2. Changing (OnFilteredMove) an item (PersonModel) grouping (PersonModel.Group) and the CollectionViewSource Filters automatically changes the collections from one collection (CsvA) to the other collection (CsvB).

The C# code:
C#
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
 
        const string filterProperty = nameof(PersonModel.Group);
 
        CsvA.Source = Models;
        SetFiltering(CsvA, GroupA, filterProperty);
 
        CsvB.Source = Models;
        SetFiltering(CsvB, GroupB, filterProperty);
    }
 
    private void SetFiltering(CollectionViewSource Csv, Predicate<PersonModel> filter, string FilterProperty)
    {
        Csv.IsLiveFilteringRequested = true;
        Csv.LiveFilteringProperties.Add(FilterProperty);
        Csv.View.Filter = x => filter((PersonModel)x);
    }
 
    private Predicate<PersonModel> GroupA { get; } = new Predicate<PersonModel>(x => x.Group == "A");
    private Predicate<PersonModel> GroupB { get; } = new Predicate<PersonModel>(x => x.Group == "B");
 
    public ObservableCollection<PersonModel> Models { get; }
        = new ObservableCollection<PersonModel>
        {
            new PersonModel { Name = "Person 1", Group = "B" },
            new PersonModel { Name = "Person 2" },
            new PersonModel { Name = "Person 3" },
            new PersonModel { Name = "Person 4" }
        };
 
    public CollectionViewSource CsvA { get; } = new CollectionViewSource();
    public CollectionViewSource CsvB { get; } = new CollectionViewSource();
 
    private void OnFilteredMove(object sender, RoutedEventArgs e)
    {
        var model = (PersonModel)((Button)sender).DataContext;
        model.Group = model.Group == "A" ? "B" : "A";
    }
}

The Xaml used is as follows:
XML
<Grid.Resources>
    <DataTemplate x:Key="FilterItemTempate">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <TextBlock Text="{Binding Name}"/>
            <Button Content="MOVE"
                    Padding="2" Margin="2" Grid.Column="1"
                    Click="OnFilteredMove"/>
        </Grid>
    </DataTemplate>
</Grid.Resources>

<ListBox Grid.Row="3"
         ItemsSource="{Binding CsvA.View}"
         ItemTemplate="{StaticResource FilterItemTempate}"/>

</ListBox>
<ListBox Grid.Row="3" Grid.Column="1"
         ItemsSource="{Binding CsvB.View}"
         ItemTemplate="{StaticResource FilterItemTempate}"/>

The Predicate Filter is just a static lambda method that is used to select data and needs to be called to be used. You don't need to use it for your project. You also don't need to have a single collection for your items however the data is common to both lists and acts as a central repository for your data that can be used elsewhere in your app - this could be replaced by your storage like a database.

I hope that this clarifies.
 
Share this answer
 
v3
Comments
Member 13481361 14-Nov-17 23:40pm    
I thank you very much for the examples. I will put them to use and let you know the outcome.
Graeme_Grant 14-Nov-17 23:43pm    
There are two different solutions, pick the one that works best for you. I like the second one as it also can control sorting and grouping... :)
Member 13481361 15-Nov-17 18:51pm    
Grant,
Your comments says theirs two different solutions. I'm not understanding as it appears that all three section of code is tied together. Also in your Xaml you use a ListBox I'm using a ListView. Is there a difference? I know your code is just and example for me, but I'm trying to see how I can apply it to what I'm trying to accomplish. Right now I'm still a little puzzled. But I'll keep at it...I do appreciate to taking the time.
Graeme_Grant 15-Nov-17 20:30pm    
I have update the solution and moved the details from here and added code/xaml to make it clearer for you.
Member 13481361 15-Nov-17 23:58pm    
Grant,

thanks, your update clarify matters much...Now I will give it a try. Will let you know the result.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900