Создайте фильтр коллекции для неопределенного числа ComboBox - PullRequest
0 голосов
/ 15 октября 2019

Фон

Существует ListView, который содержит 2 столбца: Столбец файла (строка) и Столбец таблицы (ComboBox).

  1. У меня есть незафиксированныйколичество ComboBox (от 1 до бесконечности), которое зависит от количества столбцов в файле (это « ImportColumns », чтобы избежать путаницы).
  2. В списке есть столбцыпрограмма (от 2 до бесконечности). Наименьший случай выглядит следующим образом: «- Нет -», «1». Но это может быть также "- None -", "col1", "element2", "any name", ..., "item99999". Это « WorksheetColumns ».
  3. Значения ImportColumns и WorksheetColumns полностью не связаны: это могут быть 1: 2, 1: 200, 200: 25 и т. Д.
  4. Каждый ComboBox имеет общий раскрывающийся список WorksheetColumns. В этом списке «- None -» используется по умолчанию и может использоваться всеми ComboBoxами. Тем не менее, все остальные элементы могут быть выбраны только одним ComboBox за раз.

Например, список "- None -", "1", "2", "3", "4 "," 5 "," 6 "," 7 "," 8 "... Когда каждый ComboBox начинается с" - None - ", каждый ComboBox имеет все элементы списка на выбор. Но когда ComboBox выбирает, например, «1», все остальные ComboBox больше не имеют его в своем раскрывающемся списке. Если «1» затем переключается на что-либо еще, ComboBoxes может снова выбрать его. Таким образом, только не выбранные элементы и «- Нет -» должны отображаться в раскрывающемся списке.

Каждый WorksheetItem имеет свойства, такие как ID (int), Имя (строка) и Выбранный (bool).

Последнее и самое важное: ComboBox, который выбрал, например, "1", должен по-прежнему иметь "1" в своем раскрывающемся списке .

Для суммированияв кратком примере , ComboBox - это "BOY", WorksheetColumns - это "girl". Представьте себе, что BOY1 выбирает девушку: он может быть одиноким ("- None -"), выбрать girl1 (потому что она не CURRENTLY , выбранная любым BOY), girl2 (то же самое), но не girl3 (потому что онабыл выбран BOY2). В то же время, BOY2 может стать одиноким ("- None -"), переключиться на girl1 или girl2, или остаться с girl3 . Задача состоит в том, чтобы включить girl3 в список параметров только для BOY2.

Поскольку число ComboBox точно так же не определено, как WorksheetColumns, я не могу создавать переменные в XAML или C #.

Ранее

Я посоветовал добавить метод в класс, который содержит WorksheetItems, чтобы получить вложенную коллекцию WorksheetItems. Метод возвращает все невыбранные элементы, элемент с идентификатором, равным 0, и элемент, выбранный текущим ComboBox (заданный аргументом keepColumn):

public List<WorksheetColumn> GetWorksheetColumnHeaders(int keepColumn)
    {
        return WorksheetColumns.Where(header => header.ID == 0 || header.Selected == false || header.ID == keepColumn).ToList();
    }

Проблемаесть: я не знаю, где вызвать этот метод. Я читал, что невозможно вызвать метод из XAML, но результат метода должен быть установлен как ItemsSource для нескольких ComboBox (каждый из них должен иметь немного разные ItemsSource из-за того аргумента keepColumn, который сохраняет WorksheetItem выбраннымэтот самый ComboBox - это и есть цель).

У меня есть событие XAML, ComboBox_SelectionChanged и OnWorksheetItemPropertyChanged (происходит, когда изменяется «Выбранное»).

Код

Прямо сейчас мой XAML использует свойство, которое не возвращает необходимый элемент, вместо метода:

<ListView Grid.Row="0" Name="listView" IsSynchronizedWithCurrentItem="True" SelectionMode="Single" Margin="10" ItemsSource="{Binding ImportColumns}" >

            <ListView.View>
                <GridView AllowsColumnReorder="False">

                    <GridViewColumn Header="File column" DisplayMemberBinding="{Binding FileColumnHeader}"/>

                    <GridViewColumn Header="Worksheet column" >
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>

                                <ComboBox VerticalAlignment="Center" DataContext="{Binding DataContext,RelativeSource={RelativeSource AncestorType={x:Type ListView}}}" ItemsSource="{Binding ListOfWorksheetColumns.UnselectedWorksheetColumns, UpdateSourceTrigger=PropertyChanged}" SelectionChanged="ComboBox_SelectionChanged" SelectedIndex="0">

                                </ComboBox>

                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>

                </GridView>
            </ListView.View>

        </ListView>

Моя основная ViewModel :

public class ImportManagerViewModel : BaseViewModel
{
    public List<ImportColumn> ImportColumns { get; set; }
    public ListOfWorksheetColumnHeaders ListOfWorksheetColumns { get; set; }

    public bool IsAnyColumnImported
    {
        get;
        set;
    }

    public void OnImportItemPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        IsAnyColumnImported = ImportColumns.Any(x => x.TargetColumnIndex != 0);
    }

    public void OnWorksheetItemPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        ListOfWorksheetColumns.UnselectedWorksheetColumns = new ObservableCollection<WorksheetColumn>(ListOfWorksheetColumns.WorksheetColumns.Where(header => header.ID == 0 || header.Selected == false /*|| header.ID == 1*/));
    }
}

My ListOfWorksheetColumnHeaders класс (содержит коллекцию, вложенную коллекцию и метод):

public class ListOfWorksheetColumnHeaders : BaseViewModel
{

    public ObservableCollection<WorksheetColumn> WorksheetColumns { get; set; } = new ObservableCollection<WorksheetColumn>();

    public ObservableCollection<WorksheetColumn> UnselectedWorksheetColumns
    {
        get;
        set;
    }

    public List<WorksheetColumn> GetWorksheetColumnHeaders(int keepColumn)
    {
        return WorksheetColumns.Where(header => header.ID == 0 || header.Selected == false || header.ID == keepColumn).ToList();
    }
}

My WorksheetColumn class :

public class WorksheetColumn : BaseViewModel
{
    public int ID;

    public string ColumnName { get; set; }
    public bool Selected
    {
        get;
        set;
    }

    public override string ToString()
    {
        return ColumnName;
    }
}

На данный момент

Единственное, что мне нужно - заставить выбранный WorksheetItem оставаться в выпадающем списке ComboBox, который его выбрал. В противном случае ComboBox немедленно теряет этот элемент из раскрывающегося списка, поскольку его свойство «Выбрать» становится true . Не важно: после того, как он больше не выбран ComboBox, его свойство «Выбрать» становится false , и оно появляется в списке, но ComboBox в настоящий момент ничего не выбрал;другими словами, если вы выберете что-либо, это просто очистит выделение в выпадающем списке.

Ответы [ 3 ]

2 голосов
/ 15 октября 2019

Проблема в том, что я не знаю, где вызвать этот метод

Скорее всего, вам не следует его вообще вызывать. Я основываюсь на этом утверждении:

результат метода должен быть установлен как ItemsSource

Это говорит о том, что у вас есть эти элементы управления (например, ComboBox)где то, что вы должны делать, - это косвенное связывание коллекции WorksheetColumns через объект CollectionViewSource. Объект CollectionViewSource позволит вам отфильтровать представление, что вы можете сделать, указав в методе предикат, который у вас есть в данный момент, т. Е. header => header.ID == 0 || header.Selected == false || header.ID == keepColumn.

Непонятно, откуда берется параметр keepColumn. , но если это не свойство в модели представления где-то уже, это, вероятно, должно быть. В любом случае, как только вы изучите модель привязки коллекции и поймете, как работает CollectionViewSource, я надеюсь, что вы сможете без проблем выяснить, как получить значение keepColumn в вашем фильтре.

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

Кстати ...

Я читал, что невозможно вызвать метод из XAML

Ну, этоне правда. Есть как минимум четыре основных способа, которыми методы могут быть «вызваны из XAML» (в зависимости от того, что именно означает):

  • Обработчики событий. Объекты XAML, публикующие события, будут иметь эти события как атрибуты в XAML. Вы можете установить в качестве значения атрибута имя метода, и этот метод будет подписан на событие для вас.
  • ICommand привязка. В большинстве мест вы можете указать ICommand, у вас также есть возможность передать CommandParameter. Это ссылка object, переданная вашим ICommand CanExecute() и Execute() методам. Вы можете передать несколько аргументов, передав массив. Метод ICommand.Execute() вызывается при выполнении команды по любой причине.
  • IValueConverter и IMultiValueConverter. Они также принимают параметры. Соответствующие методы в интерфейсах вызываются, когда требуется преобразование значения, как правило, в контексте привязки.
  • И, наконец, в то время как все вышеперечисленные являются специализированными сценариями, и для некоторых они могут не квалифицироваться как «вызов метода». из XAML "(поскольку они не являются вызовами общего назначения ), вы всегда можете использовать ObjectDataProvider для вызова любого метода, который может возвращать любой тип значения. Вы можете вызвать метод в экземпляре или статический метод. Вы можете передать параметры в метод. Вы даже можете вызвать конструктор для создания экземпляра объекта. ( Предупреждение: может показаться заманчивым попытаться вставить это в ваш сценарий ... не делайте этого! Скорее всего, это не сработает, поскольку ObjectDataProvider - это всего лишь один выстрелНо даже если вы как-то взломали это, это определенно неправильный подход к этому сценарию.)
0 голосов
/ 15 октября 2019

попробуйте использовать конвертер

[ValueConversion(typeof(List<WorksheetColumn>), typeof(List<WorksheetColumn>))]
public class ListFilterConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        List<WorksheetColumn> worksheets = value as List<WorksheetColumn>;
        int keepColumn = (int)parameter;

        return worksheets.Where(header => header.ID == 0 || header.Selected == false || header.ID == keepColumn).ToList();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

XAML is ..

    <ListView Grid.Row="0" Name="listView" IsSynchronizedWithCurrentItem="True" SelectionMode="Single" Margin="10" ItemsSource="{Binding ImportColumns}" >
        <ListView.Resources>
            <local:ListFilterConverter x:Key="myConverter" />
            <sys:Int32 x:Key="keepcolumn1">1</sys:Int32>
        </ListView.Resources>

        <ListView.View>
            <GridView AllowsColumnReorder="False">

                <GridViewColumn Header="File column" DisplayMemberBinding="{Binding FileColumnHeader}"/>

                <GridViewColumn Header="Worksheet column" >

                    <GridViewColumn.CellTemplate>
                        <DataTemplate>

                            <ComboBox VerticalAlignment="Center" DataContext="{Binding DataContext,RelativeSource={RelativeSource AncestorType={x:Type ListView}}}" 
                                      ItemsSource="{Binding ListOfWorksheetColumns.UnselectedWorksheetColumns, Converter={StaticResource myConverter}, ConverterParameter={StaticResource keepcolumn1}}" 
                                      SelectionChanged="ComboBox_SelectionChanged" SelectedIndex="0">

                            </ComboBox>

                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>

            </GridView>
        </ListView.View>

    </ListView>
0 голосов
/ 15 октября 2019

Вы можете изменить свой метод на свойство, которое допускает Binding:

public class ImportColumn {
    private int keepColumn;

    public List<WorksheetColumn> WorksheetColumnHeaders => WorksheetColumns.Where(header => header.ID == 0 || header.Selected == false || header.ID == keepColumn).ToList();
}

. Вы должны поместить это в новый класс и привязать каждый ItemsSource к этому свойству следующим образом:

<ComboBox ItemsSource="{Binding WorksheetColumnHeaders}"/>

Но это не правильный путь для связывания, так как вы не должны смешивать свое представление и ваши данные, вы должны взглянуть на шаблон MVVM и посмотреть на ICommand, как предложено другимиответы.

...