如何根据项目的sub属性过滤ICollectionView?

时间:2018-07-09 18:08:02

标签: c# .net wpf

我正在尝试按集合中各项(处理器对象)的子属性进行过滤,但无法执行。该属性是一个简单的布尔值,用于指示该项目是启用还是禁用。列表中已启用的项目应在第一个标签上的另一个列表框中列出(已启用的处理器)。

下面是一个完整的工作示例,可以粘贴到入门WPF应用程序中

如果您取消注释此行

_selectedProcessorsView.Filter = processorFilter;

在MainWindow_VM()构造函数中,处理器不再显示在“配置”选项卡中。

两个主要问题:

  1. 为什么过滤会影响两个列表?
  2. 如何解决此问题,以使第一个选项卡上的已启用处理器列表框仅绑定到第二个选项卡中的选中项?

注意::我代码末尾的三个类(Config,Settings和Processor)是我无法修改的类库的一部分。WPF应用只是围绕该类的UI库。

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <Window.DataContext>
        <local:MainWindow_VM></local:MainWindow_VM>
    </Window.DataContext>

    <Grid>
        <TabControl>
            <TabItem Header="enabled processors">
                <ListBox   ItemsSource="{Binding selectedProcessorsView}"   VerticalAlignment="Top" FontSize="14">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Path=processorType}"></TextBlock>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>
            </TabItem>
            <TabItem Header="configuration">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <ListView Grid.Column="0" ItemsSource="{Binding Path=reportTypes}"   SelectedItem="{Binding selectedConfiguration}">
                        <ListView.ItemTemplate>
                            <DataTemplate>
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Text="{Binding Path=id, Mode=OneWay}"></TextBlock>
                                </StackPanel>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                    <ListView Grid.Column="1" ItemsSource="{Binding reportProcessors}"  SelectedItem="{Binding selectedProcessor}">
                        <ListView.View>
                            <GridView>
                                <GridViewColumn Header="Enabled"  >
                                    <GridViewColumn.CellTemplate>
                                        <DataTemplate>
                                            <CheckBox IsChecked="{Binding Path=Settings.isEnabled}" ></CheckBox>
                                        </DataTemplate>
                                    </GridViewColumn.CellTemplate>
                                </GridViewColumn>
                                <GridViewColumn Header="Processor"  DisplayMemberBinding="{Binding Path=processorType, Mode=OneWay}" />
                                <GridViewColumn Header="ID" DisplayMemberBinding="{Binding Path=processorId, Mode=OneWay}"   >
                                </GridViewColumn>
                            </GridView>
                        </ListView.View>
                    </ListView>
                </Grid>
            </TabItem>
        </TabControl>

    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
    public abstract class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
        {
            if (object.Equals(storage, value)) return false;

            storage = value;
            this.OnPropertyChanged(propertyName);
            return true;
        }

        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var eventHandler = this.PropertyChanged;
            if (eventHandler != null)
            {
                eventHandler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    public class MainWindow_VM : BindableBase
    {
        public MainWindow_VM()
        {
            reportTypes.Add(new Config()
            {
                id = Guid.NewGuid().ToString(),
                Processors = new Dictionary<string, Processor>() {
                    { "Processor 1", new Processor() { processorType = "Blue" } },
                    { "Processor 2", new Processor() { processorType = "Yellow" } }
                }
            });

            reportTypes.Add(new Config()
            {
                id = Guid.NewGuid().ToString(),
                Processors = new Dictionary<string, Processor>() {
                    { "Processor 3", new Processor() { processorType = "Green" } },
                    { "Processor 4", new Processor() { processorType = "Red" } }
                }
            });

            _selectedProcessorsView = CollectionViewSource.GetDefaultView(reportProcessors);
            //_selectedProcessorsView.Filter = processorFilter;
        }

        public bool processorFilter(object item)
        {
            bool result = true;
            Processor p = item as Processor;

            if (p.Settings.isEnabled == false)
                result = false;

            return result;
        }

        private Config _selectedConfiguration;
        public Config selectedConfiguration
        {
            get { return _selectedConfiguration; }
            set
            {
                SetProperty(ref _selectedConfiguration, value);

                reportProcessors.Clear();

                foreach (KeyValuePair<string, Processor> kvp in value.Processors)
                    reportProcessors.Add(kvp.Value);
            }
        }

        public ObservableCollection<Config> reportTypes { get; } = new ObservableCollection<Config>();

        public ObservableCollection<Processor> reportProcessors { get; } = new ObservableCollection<Processor>();

        private ICollectionView _selectedProcessorsView;
        public ICollectionView selectedProcessorsView
        {
            get { return _selectedProcessorsView; }
        }
    }

///These three classes below are part of a separate class library that I cannot modify

    public class Config
    {
        public string id { get; set; }
        public Dictionary<string, Processor> Processors { get; set; } 
    }
    public class Processor
    {
        public string processorType { get; set; }
        public Settings Settings { get; set; } = new Settings() {
            isEnabled = false
        };
    }
    public class Settings
    {
        public bool isEnabled { get; set; }
    }
}

2 个答案:

答案 0 :(得分:0)

  

为什么过滤会影响两个列表?

{Binding reportProcessors}{Binding selectedProcessorsView}绑定到同一视图。当您绑定到ObservableCollection<T>或与此相关的任何其他类型的源集合时,实际上是在绑定到自动生成的CollectionView而不是实际的源集合本身。

  

如何解决此问题,以使第一个选项卡上的已启用处理器列表框仅绑定到第二个选项卡中的选中项?

绑定到两个不同的视图。但是,当isEnabled属性实际上设置为新值时,您还需要通知视图。否则,当您检查CheckBoxes时,过滤器将不会更新。您正在使用Setting类的事实使这一点变得有些复杂,但是您可以在Processor类中添加一个属性,然后对该类进行实时过滤。 SettingsProcessor都需要实现INotifyPropertyChanged事件。

请参考以下示例代码。它应该给你这个主意。

public class MainWindow_VM : BindableBase
{
    public MainWindow_VM()
    {
        ...

        ListCollectionView selectedProcessorsView = new ListCollectionView(reportProcessors);
        selectedProcessorsView.LiveFilteringProperties.Add(nameof(Processor.IsEnabled));
        selectedProcessorsView.IsLiveFiltering = true;
        selectedProcessorsView.Filter = processorFilter;

        _selectedProcessorsView = selectedProcessorsView;
    }
    ...
 }

public class Processor : INotifyPropertyChanged
{
    public string processorType { get; set; }

    public Settings Settings { get; set; } = new Settings()
    {
        isEnabled = false
    };

    public bool IsEnabled => Settings.isEnabled;

    public Processor()
    {
        Settings.PropertyChanged += Settings_PropertyChanged;
    }

    private void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        OnPropertyChanged(nameof(IsEnabled));
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class Settings : INotifyPropertyChanged
{
    private bool _isEnabled;
    public bool isEnabled
    {
        get { return _isEnabled; }
        set { _isEnabled = value; OnPropertyChanged(); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
    }
}

答案 1 :(得分:0)

最后,我采用了以下方法,将mm8对第一个问题的回答与我自己的包装器类(用作Processor对象(模型)的视图模型)结合在一起。

每当选中或取消选中复选框时,都会刷新listviewcollection,从而更新第一个选项卡中显示的collection。

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
    public abstract class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
        {
            if (object.Equals(storage, value)) return false;

            storage = value;
            this.OnPropertyChanged(propertyName);
            return true;
        }

        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var eventHandler = this.PropertyChanged;
            if (eventHandler != null)
            {
                eventHandler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    public class MainWindow_VM : BindableBase
    {
        public MainWindow_VM()
        {
            reportTypes.Add(new Config()
            {
                id = Guid.NewGuid().ToString(),
                Processors = new Dictionary<string, Processor>() {
                    { "Processor 1", new Processor() { processorType = "Blue" } },
                    { "Processor 2", new Processor() { processorType = "Yellow" } }
                }
            });

            reportTypes.Add(new Config()
            {
                id = Guid.NewGuid().ToString(),
                Processors = new Dictionary<string, Processor>() {
                    { "Processor 3", new Processor() { processorType = "Green" } },
                    { "Processor 4", new Processor() { processorType = "Red" } }
                }
            });

            selectedProcessorsView = new ListCollectionView(processors);
            selectedProcessorsView.Filter = processorFilter;
        }

        public bool processorFilter(object item)
        {
            bool result = true;
            ProcessorWrapper p = item as ProcessorWrapper;

            if (p.isEnabled == false)
                result = false;

            return result;
        }

        private Config _selectedConfiguration;
        public Config selectedConfiguration
        {
            get { return _selectedConfiguration; }
            set
            {
                SetProperty(ref _selectedConfiguration, value);

                processors.Clear();

                foreach (KeyValuePair<string, Processor> kvp in value.Processors)
                    processors.Add(new ProcessorWrapper(kvp.Value, this.selectedProcessorsView));
            }
        }

        public ObservableCollection<Config> reportTypes { get; } = new ObservableCollection<Config>();

        public ObservableCollection<ProcessorWrapper> processors { get; } = new ObservableCollection<ProcessorWrapper>();

        public ListCollectionView selectedProcessorsView { get; set; }
    }

    public class ProcessorWrapper : BindableBase
    {
        public ProcessorWrapper(Processor processor, ListCollectionView lcv)
        {
            _processor = processor;
            _lcv = lcv;
        }

        private Processor _processor;
        private ListCollectionView _lcv;

        private string _processorType;
        public string processorType
        {
            get {

                _processorType = _processor.processorType;
                return _processorType;
            }
        }

        private bool _isEnabled;
        public bool isEnabled
        {
            get {
                _isEnabled = this._processor.Settings.isEnabled;
                return _isEnabled; }
            set
            {
                SetProperty(ref _isEnabled, value);
                this._processor.Settings.isEnabled = _isEnabled;
                _lcv.Refresh();
            }
        }
    }


    /// <summary>
    /// These classes belong to a separate class library, should not be modified.  
    /// </summary>

    public class Config
    {
        public string id { get; set; }
        public Dictionary<string, Processor> Processors { get; set; } 
    }
    public class Processor
    {
        public string processorType { get; set; }
        public Settings Settings { get; set; } = new Settings() {
            isEnabled = false
        };
    }
    public class Settings
    {
        public bool isEnabled { get; set; }
    }
}

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <Window.DataContext>
        <local:MainWindow_VM></local:MainWindow_VM>
    </Window.DataContext>

    <Grid>
        <TabControl>
            <TabItem Header="enabled processors">
                <ListBox ItemsSource="{Binding selectedProcessorsView}" VerticalAlignment="Top" FontSize="14">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Path=processorType}"></TextBlock>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>
            </TabItem>
            <TabItem Header="configuration">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <ListView Grid.Column="0" ItemsSource="{Binding Path=reportTypes}"   SelectedItem="{Binding selectedConfiguration}">
                        <ListView.ItemTemplate>
                            <DataTemplate>
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Text="{Binding Path=id, Mode=OneWay}"></TextBlock>
                                </StackPanel>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                    <ListView Grid.Column="1" ItemsSource="{Binding processors}"  SelectedItem="{Binding selectedProcessor}">
                        <ListView.View>
                            <GridView>
                                <GridViewColumn Header="Enabled"  >
                                    <GridViewColumn.CellTemplate>
                                        <DataTemplate>
                                            <CheckBox IsChecked="{Binding Path=isEnabled}" />
                                        </DataTemplate>
                                    </GridViewColumn.CellTemplate>
                                </GridViewColumn>
                                <GridViewColumn Header="Processor"  DisplayMemberBinding="{Binding Path=processorType, Mode=OneWay}" />
                            </GridView>
                        </ListView.View>
                    </ListView>
                </Grid>
            </TabItem>
        </TabControl>
    </Grid>
</Window>