Member 13481361 Ответов: 1

Как переместить выбранные элементы из одного элемента listviewitem в другой с помощью C# WPF


Мой код ниже будет перемещать выбранные элементы слева направо Listview, но я получаю ошибку при попытке удалить этот элемент с левой стороны (Memberlist.Предметы.Удалить элементы).

Ошибка заключается в том, что "операции недопустимы, пока используется ItemsSource. Доступ и изменение элементов с помощью ItemsControl.ItemsSource.

Если я установлю MemberList.ItemSource = null он просто удаляет все элементы. Любой совет приветствуется.

Кроме того, я хотел бы разрешить пользователям нажимать кнопку, и все элементы будут перемещены. любые рекомендации, как сделать эту работу, приветствуются.

Что я уже пробовал:

private void Button_Click(object sender, RoutedEventArgs e)
       {

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

               MembersSelected.Items.Add(item);
               MembersList.Items.Remove(item);
           }
       }

Richard MacCutchan

Сообщение об ошибке совершенно ясно. Вы не должны пытаться манипулировать элементами в ListView непосредственно, когда он подается источником привязки. Измените источник и попросите пользовательский интерфейс обновить себя.

Kenneth Haugland

Вы продолжаете изменять listview, удаляя элементы, поэтому вам придется считать в обратном порядке

Member 13481361

Мужская уборная,
спасибо за обратную связь. Не могли бы вы привести мне пример кода того, о чем вы говорите?

Graeme_Grant

смотреть ниже...

Karthik_Mahalingam

использовать  Ответить  кнопка, чтобы отправить комментарии/запрос пользователю, чтобы пользователь получил уведомление и ответил на ваш текст.

1 Ответов

Рейтинг:
6

Graeme_Grant

Если у вас есть привязанная к базе данных коллекция, то измените ее, и ListView обновится. Это верно только для коллекции, реализующей INotifyCollectionChanged. INotifyCollectionChanged используется системой привязки данных для передачи изменений между подписчиком (UI) и субъектом (data) - это называется шаблон Observer[^].

Например, ObservableCollection<> класс реализует INotifyCollectionChanged, таким образом, ListViews будет изменяться автоматически (через привязку данных), однако List<> не прибегать INotifyCollectionChanged и просмотры списков не увидят изменений.

Альтернативный вариант, поскольку два представления списка работают с одними и теми же данными, привязать каждое представление списка к отдельному CollectionViewSource Добавьте группировку к элементам данных, если таковой еще не существует - это делается для определения того, к какому списку принадлежит данный элемент . Теперь установите фильтр каждого из них CollectionViewSource к свойству gouping и ListViews будут показаны их соответствующие группы. Теперь, чтобы переместить элементы данных из одного ListView в другой, просто измените свойство группировки на элементе данных, и ListViews автоматически обновится, и элемент, по-видимому, переместится из одного ListView в другой. Наконец, чтобы это сработало, ваш элемент данных должен быть реализован INotifyPropertyChanged а сбор элементов данных необходимо реализовать INotifyCollectionChanged.

** Обновление: Я решил собрать воедино краткий пример двух рабочих версий:

  1. Перемещение объектов из одной коллекции в другую
  2. Использование фильтрации CollectionViewSource и изменение свойства выбранного элемента

Сначала нам нужна модель:
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));
    }
}

Теперь мы можем настроить коллекции, CollectionViewSourceи тестовые данные:
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";
    }
}

Наконец, окно (пользовательский интерфейс):
<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>

Теперь, когда вы нажмете кнопку "Переместить", элемент будет переключаться между списками:
  • Два верхних списка перемещаются из одной коллекции в другую
  • Нижние два являются отфильтрованными представлениями для одной и той же коллекции с изменением только свойства группы элементов


ОБНОВЛЕНИЕ Ниже я разделил код C# и Xaml, чтобы помочь прояснить эти два различных решения.

Да, вы правы, есть общий код. Они совместно используют основную коллекцию (модели) и фильтр предикатов для разделения данных на две различные группы.

Однако есть два разных решения:

1. Вручную (физически) двигать (OnManualMove) элемента (PersonModel) из одной коллекции (Models1a) в другой коллекции (Models1b).

Код 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>


Member 13481361

Я очень благодарен вам за эти примеры. Я использую их и дам вам знать результат.

Graeme_Grant

Есть два разных решения, выберите то, которое лучше всего подходит для вас. Мне нравится второй, так как он также может управлять сортировкой и группировкой... :)

Member 13481361

Грант,
В ваших комментариях говорится, что у них два разных решения. Я не понимаю, так как кажется, что все три раздела кода связаны друг с другом. Кроме того, в вашем Xaml вы используете ListBox, а я использую ListView. Есть ли разница? Я знаю, что ваш код-это просто пример для меня, но я пытаюсь понять, как я могу применить его к тому, что я пытаюсь сделать. Прямо сейчас я все еще немного озадачен. Но я буду продолжать в том же духе...Я очень ценю, что вы уделили мне время.

Graeme_Grant

Я обновил решение, переместил детали отсюда и добавил код/xaml, чтобы сделать его более понятным для вас.

Member 13481361

Грант,

спасибо, ваше обновление многое проясняет...Теперь я попробую это сделать. Я дам вам знать результат.

Graeme_Grant

Добро пожаловать.

Дополнительное примечание: Если вы используете CollectionViewSource в проекте, предикаты, используемые с фильтрами, можно изменить, и представление изменится, не касаясь данных коллекции. Мощный совет, который нужно держать в заднем кармане, если на каком-то этапе вы хотите реализовать динамическую фильтрацию в реальном времени! ;)

Member 13481361

Грант,
У меня есть еще один вопрос. Теперь вы, вероятно, ответили в своем предыдущем объяснении, и оно еще не дошло до вас, но позвольте мне все равно спросить.

В вашем примере вы используете PersonModel в качестве сбора данных правильно? Если это так, то поскольку мои данные импортируются в ListView1(ModelsA) из контейнера (SelectedMembers), который на самом деле пришел из CSV-файла. Когда моя форма загружается, Listview1 заполняется данными...теперь я выберу данные, которые хочу переместить в Listview2(ModelsB). Является ли моя PersonModel (SelectedMembers) или ListView1?
Надеюсь, вы понимаете, что я пытаюсь сказать.

Graeme_Grant

Я использую подход "data first" (техника WPF), а не "UI first" (техника WinForm), поэтому я загружаю данные в коллекцию данных, и пользовательский интерфейс отражает данные - то есть представление (data). Изменение данных приведет к изменению представления. Затем вы можете иметь разные представления для одних и тех же данных. Сила привязки данных WPF.

Member 13481361

Грант,

попался! Спасибо снова...я дам вам знать, как это происходит.

Graeme_Grant

Пенни будет падать с подходом "сначала данные", чем больше вы его используете. Как только вы поймете это, вы найдете новую свободу, от которой вас заблокировал подход "UI first". Тогда вам будет интересно перейти на следующий уровень с помощью MVVM и шаблонов проектирования... Вы можете получить немного украдкой заглянуть в это статья[^] ;)

Member 13481361

Грант,
спасибо, я взгляну на эту статью.

Member 13481361

Грант,
хотел, чтобы вы знали, что ваши примеры помогли мне. Сейчас все работает так, как должно.
Еще раз спасибо!

Graeme_Grant

Добро пожаловать. :)