为未定义数量的 ComboBoxes 创建一个集合过滤器
Make a collection filter for an undefined number of ComboBoxes
背景
有一个包含 2 列的 ListView:文件列(字符串)和工作表列(组合框)。
- 我有一个不固定数量的组合框(从 1 到无穷大),这取决于文件中的列数(这些是“ImportColumns”以避免混淆)。
- 程序中有列的列表(从 2 到无穷大)。最小的情况如下:“-None-”,“1”。但也可以是“-None-”、"col1"、"element2"、"any name"、...、"item99999"。这些是“WorksheetColumns”。
- ImportColumns 和 WorksheetColumns 的计数完全没有关联:可以是1:2、1:200、200:25等
- 每个 ComboBox 都有一个 common WorksheetColumns 下拉列表。在这个列表中,“- None -”是默认的,甚至可以被所有的ComboBox取用。但是,所有其他项目一次只能由 1 个 ComboBox 选择。
例如列表为“-None-”、“1”、“2”、“3”、“4”、“5”、“6”、“7”、“ 8"... 当每个ComboBox都以"- None -"开头时,每个ComboBox都有所有列表项可供选择。但是当 ComboBox 选择例如“1”时,所有其他 ComboBox 的下拉列表中不再有它。如果“1”然后切换为其他任何内容,ComboBoxes 可以再次 select 它。因此,只有未被 select 编辑的项目和“- None -”必须显示在下拉列表中。
每个 WorksheetItem 都具有 ID(整数)、名称(字符串)和 Selected(布尔)等属性。
最后也是最重要的:一个 ComboBox,其中 selected 例如“1”应该在其下拉列表中仍然有“1”.
用一个简短的例子来总结,组合框是"BOY",工作表列是"girl"。试想BOY1选女朋友:他可以单身("-None-"),选girl1(因为她不是任何BOY选择的CURRENTLY),girl2(同),但不是 girl3(因为她是被 BOY2 选择的)。同时,BOY2可以选择单身(“-None-”),转为girl1或girl2,或留在girl3。任务是仅将 girl3 包含在 BOY2 的选项列表中。
由于 ComboBox 的数量与 WorksheetColumns 一样未定义,我无法在 XAML 或 C# 中创建变量。
以前
我 在包含 WorksheetItems 的 class 中放置一个方法来获取 WorksheetItems 的子集合。方法return所有非Selected项,ID等于0的项和项select被当前ComboBox编辑(由参数"keepColumn"):
public List<WorksheetColumn> GetWorksheetColumnHeaders(int keepColumn)
{
return WorksheetColumns.Where(header => header.ID == 0 || header.Selected == false || header.ID == keepColumn).ToList();
}
问题是:我不知道在哪里调用这个方法。我读到不可能从 XAML 调用方法,但该方法的结果应设置为 ItemsSource 到多个组合框(由于 "keepColumn" 参数,每个组合框必须具有不同的 ItemsSource它使 WorksheetItem select 由这个 ComboBox 编辑 - 这就是目标)。
我有 XAML、ComboBox_SelectionChanged 事件和 OnWorksheetItemPropertyChanged(在 "Selected" 更改时发生)。
代码
现在我的 XAML 使用 属性,它没有 return 需要的项目,而不是方法:
<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 class(包含集合、子集合和方法):
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();
}
}
我的工作表列class:
public class WorksheetColumn : BaseViewModel
{
public int ID;
public string ColumnName { get; set; }
public bool Selected
{
get;
set;
}
public override string ToString()
{
return ColumnName;
}
}
截至目前
我唯一需要的是 - 让 selected WorksheetItem 留在 selected 的 ComboBox 的下拉列表中。否则,ComboBox 会立即从下拉列表中丢失此项,因为它的 "Select" 属性 变为 true。 不重要:不再被ComboBoxselect编辑后,其"Select"属性变为false,它出现在列表中,但 ComboBox 当前没有任何内容 selected;换句话说,如果你 select 任何东西,它只会清除组合框的 selection。
The problem is: I don't know where to call this method
很可能,您根本不应该调用它。我基于以下声明:
the result of the method should be set as ItemsSource
这告诉我你有这些项目控件(例如 ComboBox
),你 应该 做的是间接绑定 WorksheetColumns
集合,通过一个 CollectionViewSource
对象。 CollectionViewSource
对象将允许您过滤视图,您可以通过提供方法中当前拥有的谓词来实现,即 header => header.ID == 0 || header.Selected == false || header.ID == keepColumn
.
尚不清楚 keepColumn
参数的来源,但如果它不是某个视图模型中的 属性,那么它可能应该是。无论如何,一旦您更多地研究了集合绑定模型并了解 CollectionViewSource
的工作原理,我希望您能够弄清楚如何在您的过滤器中获取 keepColumn
值而无需太多麻烦了。
如果在研究了相关文档并尝试解决问题后,您仍然无法弄清楚如何让它工作并且您的同事也无法帮助您,请随时post 一个新问题。但是在那个问题中,请确保你包含一个很好的 Minimal, Reproducible Example 来清楚地显示你已经尝试过的内容,并详细解释该代码的作用,你希望它做什么,以及你具体需要什么帮助.
顺便说一下……
I've read that it's impossible to call a method from XAML
嗯,那不是真的。方法至少有四种关键方式 "called from XAML" (取决于其中一种的确切含义):
- 事件处理程序。 XAML 发布事件的对象将使这些事件作为属性出现在 XAML 中。您可以将属性值设置为一个方法的名称,该方法将为您订阅事件。
ICommand
绑定。大多数地方您可以提供 ICommand
,您也可以选择传递 CommandParameter
。这是传递给 ICommand
的 CanExecute()
和 Execute()
方法的 object
引用。您可以通过传递数组来传递多个参数。 ICommand.Execute()
方法在由于任何原因执行命令时被调用。
IValueConverter
和 IMultiValueConverter
。这些也有参数。需要进行值转换时调用接口中的相关方法,一般在绑定的上下文中。
- 最后,虽然以上都是特殊场景,对某些人来说可能不符合 "calling a method from XAML"(因为它们不是 通用 调用),您始终可以使用
ObjectDataProvider
来调用可以 return 任何类型值的任何方法。您可以调用实例上的方法或静态方法。您可以将参数传递给该方法。您甚至可以调用构造函数来创建对象的实例。 (警告: 尝试将其强加到您的场景中可能很诱人......不要这样做!它可能无论如何都行不通,因为 ObjectDataProvider
是一个一次性的事情。但即使你以某种方式破解它,处理这种情况肯定是错误的方法。)
您可以将您的方法更改为 属性,它允许 Binding
:
public class ImportColumn {
private int keepColumn;
public List<WorksheetColumn> WorksheetColumnHeaders => WorksheetColumns.Where(header => header.ID == 0 || header.Selected == false || header.ID == keepColumn).ToList();
}
您必须将其放入新的 class 并将每个 ItemsSource
绑定到此 属性,如下所示:
<ComboBox ItemsSource="{Binding WorksheetColumnHeaders}"/>
但这不是进行绑定的正确方法,因为您不应混合视图和数据,您应该查看 MVVM 模式并查看 ICommand
,如其他答案所建议的那样。
尝试使用转换器
[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>
背景
有一个包含 2 列的 ListView:文件列(字符串)和工作表列(组合框)。
- 我有一个不固定数量的组合框(从 1 到无穷大),这取决于文件中的列数(这些是“ImportColumns”以避免混淆)。
- 程序中有列的列表(从 2 到无穷大)。最小的情况如下:“-None-”,“1”。但也可以是“-None-”、"col1"、"element2"、"any name"、...、"item99999"。这些是“WorksheetColumns”。
- ImportColumns 和 WorksheetColumns 的计数完全没有关联:可以是1:2、1:200、200:25等
- 每个 ComboBox 都有一个 common WorksheetColumns 下拉列表。在这个列表中,“- None -”是默认的,甚至可以被所有的ComboBox取用。但是,所有其他项目一次只能由 1 个 ComboBox 选择。
例如列表为“-None-”、“1”、“2”、“3”、“4”、“5”、“6”、“7”、“ 8"... 当每个ComboBox都以"- None -"开头时,每个ComboBox都有所有列表项可供选择。但是当 ComboBox 选择例如“1”时,所有其他 ComboBox 的下拉列表中不再有它。如果“1”然后切换为其他任何内容,ComboBoxes 可以再次 select 它。因此,只有未被 select 编辑的项目和“- None -”必须显示在下拉列表中。
每个 WorksheetItem 都具有 ID(整数)、名称(字符串)和 Selected(布尔)等属性。
最后也是最重要的:一个 ComboBox,其中 selected 例如“1”应该在其下拉列表中仍然有“1”.
用一个简短的例子来总结,组合框是"BOY",工作表列是"girl"。试想BOY1选女朋友:他可以单身("-None-"),选girl1(因为她不是任何BOY选择的CURRENTLY),girl2(同),但不是 girl3(因为她是被 BOY2 选择的)。同时,BOY2可以选择单身(“-None-”),转为girl1或girl2,或留在girl3。任务是仅将 girl3 包含在 BOY2 的选项列表中。
由于 ComboBox 的数量与 WorksheetColumns 一样未定义,我无法在 XAML 或 C# 中创建变量。
以前
我
public List<WorksheetColumn> GetWorksheetColumnHeaders(int keepColumn)
{
return WorksheetColumns.Where(header => header.ID == 0 || header.Selected == false || header.ID == keepColumn).ToList();
}
问题是:我不知道在哪里调用这个方法。我读到不可能从 XAML 调用方法,但该方法的结果应设置为 ItemsSource 到多个组合框(由于 "keepColumn" 参数,每个组合框必须具有不同的 ItemsSource它使 WorksheetItem select 由这个 ComboBox 编辑 - 这就是目标)。
我有 XAML、ComboBox_SelectionChanged 事件和 OnWorksheetItemPropertyChanged(在 "Selected" 更改时发生)。
代码
现在我的 XAML 使用 属性,它没有 return 需要的项目,而不是方法:
<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 class(包含集合、子集合和方法):
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();
}
}
我的工作表列class:
public class WorksheetColumn : BaseViewModel
{
public int ID;
public string ColumnName { get; set; }
public bool Selected
{
get;
set;
}
public override string ToString()
{
return ColumnName;
}
}
截至目前
我唯一需要的是 - 让 selected WorksheetItem 留在 selected 的 ComboBox 的下拉列表中。否则,ComboBox 会立即从下拉列表中丢失此项,因为它的 "Select" 属性 变为 true。 不重要:不再被ComboBoxselect编辑后,其"Select"属性变为false,它出现在列表中,但 ComboBox 当前没有任何内容 selected;换句话说,如果你 select 任何东西,它只会清除组合框的 selection。
The problem is: I don't know where to call this method
很可能,您根本不应该调用它。我基于以下声明:
the result of the method should be set as ItemsSource
这告诉我你有这些项目控件(例如 ComboBox
),你 应该 做的是间接绑定 WorksheetColumns
集合,通过一个 CollectionViewSource
对象。 CollectionViewSource
对象将允许您过滤视图,您可以通过提供方法中当前拥有的谓词来实现,即 header => header.ID == 0 || header.Selected == false || header.ID == keepColumn
.
尚不清楚 keepColumn
参数的来源,但如果它不是某个视图模型中的 属性,那么它可能应该是。无论如何,一旦您更多地研究了集合绑定模型并了解 CollectionViewSource
的工作原理,我希望您能够弄清楚如何在您的过滤器中获取 keepColumn
值而无需太多麻烦了。
如果在研究了相关文档并尝试解决问题后,您仍然无法弄清楚如何让它工作并且您的同事也无法帮助您,请随时post 一个新问题。但是在那个问题中,请确保你包含一个很好的 Minimal, Reproducible Example 来清楚地显示你已经尝试过的内容,并详细解释该代码的作用,你希望它做什么,以及你具体需要什么帮助.
顺便说一下……
I've read that it's impossible to call a method from XAML
嗯,那不是真的。方法至少有四种关键方式 "called from XAML" (取决于其中一种的确切含义):
- 事件处理程序。 XAML 发布事件的对象将使这些事件作为属性出现在 XAML 中。您可以将属性值设置为一个方法的名称,该方法将为您订阅事件。
ICommand
绑定。大多数地方您可以提供ICommand
,您也可以选择传递CommandParameter
。这是传递给ICommand
的CanExecute()
和Execute()
方法的object
引用。您可以通过传递数组来传递多个参数。ICommand.Execute()
方法在由于任何原因执行命令时被调用。IValueConverter
和IMultiValueConverter
。这些也有参数。需要进行值转换时调用接口中的相关方法,一般在绑定的上下文中。- 最后,虽然以上都是特殊场景,对某些人来说可能不符合 "calling a method from XAML"(因为它们不是 通用 调用),您始终可以使用
ObjectDataProvider
来调用可以 return 任何类型值的任何方法。您可以调用实例上的方法或静态方法。您可以将参数传递给该方法。您甚至可以调用构造函数来创建对象的实例。 (警告: 尝试将其强加到您的场景中可能很诱人......不要这样做!它可能无论如何都行不通,因为ObjectDataProvider
是一个一次性的事情。但即使你以某种方式破解它,处理这种情况肯定是错误的方法。)
您可以将您的方法更改为 属性,它允许 Binding
:
public class ImportColumn {
private int keepColumn;
public List<WorksheetColumn> WorksheetColumnHeaders => WorksheetColumns.Where(header => header.ID == 0 || header.Selected == false || header.ID == keepColumn).ToList();
}
您必须将其放入新的 class 并将每个 ItemsSource
绑定到此 属性,如下所示:
<ComboBox ItemsSource="{Binding WorksheetColumnHeaders}"/>
但这不是进行绑定的正确方法,因为您不应混合视图和数据,您应该查看 MVVM 模式并查看 ICommand
,如其他答案所建议的那样。
尝试使用转换器
[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>