【问题标题】:Make a collection filter for an undefined number of ComboBoxes为未定义数量的组合框创建一个集合过滤器
【发布时间】:2019-10-15 05:34:53
【问题描述】:

背景

有一个 ListView 包含 2 列:文件列(字符串)和工作表列(组合框)。

  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 都有一个 common 下拉列表的 WorksheetColumns。在此列表中,“-无-”是默认值,甚至可以被所有 ComboBox 采用。但是,所有其他项目一次只能由 1 个 ComboBox 选择。

例如,列表为“-无-”、“1”、“2”、“3”、“4”、“5”、“6”、“7”、“8”...时每个 ComboBox 都以“- None -”开头,每个 ComboBox 都有所有列表项可供选择。但是当一个组合框选择例如“1”时,所有其他组合框不再在其下拉列表中包含它。如果“1”然后切换到其他任何东西,ComboBoxes 可以再次选择它。因此,只有未选择的项目和“-无-”必须显示在下拉列表中。

  1. 每个 WorksheetItem 都具有 ID (int)、Name (string) 和 Selected (bool) 等属性。

  2. 最后也是最重要的:选择例如“1”的 ComboBox 的下拉列表中仍应包含“1”。

总结为一个简短的例子,ComboBoxes 是“BOY”,WorksheetColumns 是“girl”。想象一下 BOY1 选择女朋友:他可以是单身(“-无-”),选择 girl1(因为她没有CURRENTLY被任何 BOY 选择),girl2(相同),但不是 girl3(因为她被BOY2选中)。同时,BOY2可以选择单身(“-无-”),切换到girl1或girl2,或留在girl3。任务是将girl3 包含在仅适用于BOY2 的选项列表中。

由于 ComboBox 的数量与 WorksheetColumns 一样未定义,因此我无法在 XAML 或 C# 中创建变量。

以前

我是advised 将一个方法放入包含 WorksheetItems 的类中以获取 WorksheetItems 的子集合。该方法返回所有未选中的项目,一个ID等于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 到多个 ComboBoxes(它们中的每一个都必须有一点不同的 ItemsSource,因为“keepColumn”参数保持 WorksheetItem 选择这个 ComboBox - 这就是目标)。

我有 XAML、ComboBox_SelectionChanged 事件和 OnWorksheetItemPropertyChanged(在“Selected”发生更改时发生)。

代码

现在我的 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*/));
    }
}

我的 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();
    }
}

我的WorksheetColumn 类:

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 会立即从下拉列表中丢失此项,因为它的“Select”属性变为 true。 不重要:不再被ComboBox选中后,其“Select”属性变为false,出现在列表中但ComboBox当前没有选中任何内容;换句话说,如果您选择任何内容,它只会清除组合框的选择。

【问题讨论】:

  • 你所听到的叫做绑定。没有人可以在这里解释它,因为它太宽泛了。但是请看docs.microsoft.com/en-us/dotnet/framework/wpf/data/…。浏览所有子页面。
  • @NawedNabiZada 我知道绑定是什么。我的 ItemsSource 已绑定
  • 那么在这种情况下:它们中的任何一个都是正确的吗?,不,它们不是。删除选择更改的项并绑定您选择的项目。如果您有按钮,请使用命令
  • 仍然值得通过该链接,阅读所有内容。完成后,还要检查一下:wintellect.com/model-view-viewmodel-mvvm-explained。你会喜欢在 WPF 中编程
  • @NawedNabiZada 我已经更新了这个问题。请,看看你有没有新的建议

标签: c# wpf xaml combobox itemssource


【解决方案1】:

问题是:我不知道在哪里调用这个方法

很可能,您根本不应该调用它。我基于此声明:

方法的结果应该设置为ItemsSource

这告诉我你有这些项目控件(例如ComboBox),你应该做的是通过CollectionViewSource对象间接绑定WorksheetColumns集合。 CollectionViewSource 对象将允许您过滤视图,您可以通过提供您当前在方法中拥有的谓词来做到这一点,即header =&gt; header.ID == 0 || header.Selected == false || header.ID == keepColumn。

尚不清楚keepColumn 参数的来源,但如果它不是某个视图模型中的属性,它可能应该是。无论如何,一旦您对集合绑定模型进行了更多研究并了解了CollectionViewSource 的工作原理,我希望您能够弄清楚如何在过滤器中获取keepColumn 值,而不会遇到太多麻烦。

如果在研究相关文档并尝试解决问题后,您仍然无法弄清楚如何使其工作并且您的同事无法帮助您,请随时发布新问题。但在那个问题中,请确保包含一个好的Minimal, Reproducible Example,清楚地显示您尝试过的内容,并详细说明该代码的作用、您希望它做什么以及您需要帮助的具体内容。

顺便说一句……

我了解到不可能从 XAML 调用方法

嗯,这不是真的。至少有四种方法可以“从 XAML 调用”(取决于具体含义):

  • 事件处理程序。发布事件的 XAML 对象将使这些事件在 XAML 中显示为属性。您可以将属性值设置为方法的名称,该方法将为您订阅事件。
  • ICommand 绑定。大多数地方您可以提供ICommand,您也可以选择传递CommandParameter。这是传递给ICommand 的CanExecute() 和Execute() 方法的object 引用。您可以通过传递数组来传递多个参数。无论出于何种原因执行命令时都会调用ICommand.Execute() 方法。
  • IValueConverter 和 IMultiValueConverter。这些也接受参数。当需要进行值转换时,通常在绑定的上下文中调用接口中的相关方法。
  • 最后,虽然以上都是专门的场景,并且对某些人来说可能不符合“从 XAML 调用方法”的条件(因为它们不是通用调用),但您始终可以使用ObjectDataProvider 调用任何可以返回任何类型值的方法。您可以调用实例上的方法或静态方法。您可以将参数传递给该方法。您甚至可以调用构造函数来创建对象的实例。 (警告: 尝试将其硬塞到您的场景中可能很诱人……不要这样做!它可能无论如何都行不通,因为 ObjectDataProvider 是一次性的但是,即使您以某种方式解决了这个问题,也肯定是处理这种情况的错误方法。)

【讨论】:

  • 我已经更新了很多问题。请,看看你有没有新的建议
【解决方案2】:

尝试使用转换器

[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 是..

    <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>

【讨论】:

  • WorksheetColumns 的数量没有定义,所以不可能声明变量,至少在我看来是这样。我已经更新了很多问题。请,看看你有没有新的建议
【解决方案3】:

您可以将您的方法更改为一个属性,它允许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。

【讨论】:

  • 问题是 ImportColumn 和 WorksheetColumns 是两个不相连的类。另外,两者的数量都没有定义。我已经更新了很多问题。请,看看你有没有新的建议
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-25
  • 1970-01-01
  • 2013-01-23
相关资源
最近更新 更多