使用一些切换按钮激活列表框中的过滤器

Activating filters in a ListBox with some toggle buttons

好的,我有一个 ListBox 绑定到来自 ViewModel 的 属性。 它已经填充了这样的 Caliburn.Micro.BindableCollection

public BindableCollection<QueueTask> QueueTasks
        {
            get
            {
                return this._queueProcessor.Queue;
            }
        }

队列任务具有如下属性:

public class QueueTask : PropertyChangedBase
    {
        private StatusCode _status;
        private int _completedPercent;

        public StatusCode Status
        {
            get => _status;
            set
            {
                _status = value;
                this.NotifyOfPropertyChange(() => Status);
            }
        }

        public int CompletedPercent
        {
            get { return _completedPercent; }
            set
            {
                _completedPercent = value;
                this.NotifyOfPropertyChange(() => CompletedPercent);
            }
        }
    }

现在您可以看到他的属性之一是 StatusCode 枚举

public enum StatusCode
    {
        Waiting = 1,    
        Processing,    
        Finished,    
        Error
    } 

现在真正的问题是…… 我如何(在视图中)过滤列表框以仅显示基于 StatusCode 的列表的一部分,为每个 StatusCode 使用 4 个 ToggleButtons。因此,如果我按下(或激活)完成按钮,并且错误仅显示在满足该条件的列表框中。如果他们都活跃,则向所有人展示。如果有 none 标记则不显示任何内容。

我知道它可以使用 ICollectionView 来完成。如果它可以在 xaml 中更好,尽管它可以在视图后面的代码中。我的想法是将多个 ICollectionView 绑定到另一个。作为一个链条,每个切换按钮都会激活或停用每个切换按钮的过滤(尽管我仍然必须了解如何为单个切换按钮执行此操作)。我不知道这是否是最好的方法,所以欢迎任何帮助。

对不起我的英语我只会说西班牙语...

立即给link一个给source

它的视觉效果可以在 the screenshot

中看到

对于过滤器值,我创建了 FilterValue 模型。 这对于了解我们的属性何时更改是必要的。

public class FilterValue
{
    private bool _value;

    public bool Value
    {
        get { return _value; }
        set
        {
            _value = value;
            ValueChanged?.Invoke();
        }
    }

    public event Action ValueChanged;
}

QueueTask class 中,我添加了 TaskName 属性 以明确列表中的任务。

public class QueueTask : INotifyPropertyChanged
{
    private StatusCode _status;

    private int _completedPercent;

    private string _taskName;

    public StatusCode Status
    {
        get { return _status; }
        set
        {
            _status = value;
            OnPropertyChanged(nameof(Status));
        }
    }

    public int CompletedPercent
    {
        get { return _completedPercent; }
        set
        {
            _completedPercent = value;
            OnPropertyChanged(nameof(CompletedPercent));
        }
    }

    public string TaskName
    {
        get { return _taskName; }
        set
        {
            _taskName = value;
            OnPropertyChanged(nameof(Status));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

然后我创建了一个包含所有元素 _allQueueTasks 的列表和一个 QueueTasks 的视图列表。 用于测试的集合我填写方法FillTempData ()。 我还创建了 Filters 字典,其中包含所有可能的 StatusCode 值。在FillFilters ()方法中填写。

public class MainViewModel : INotifyPropertyChanged
{
    private List<QueueTask> _allQueueTasks;

    private List<QueueTask> _queueTasks;

    public Dictionary<StatusCode, FilterValue> Filters { get; set; }

    public List<QueueTask> QueueTasks
    {
        get { return _queueTasks; }
        set { _queueTasks = value; OnPropertyChanged(nameof(QueueTasks)); }
    }

    public MainViewModel()
    {
        FillTempData();
        FillFilters();
    }


    private void FillFilters()
    {
        Filters = new Dictionary<StatusCode, FilterValue>();
        foreach (StatusCode code in Enum.GetValues(typeof(StatusCode)))
        {
            var newFilter = new FilterValue();
            newFilter.ValueChanged += FilterOnValueChanged;
            Filters.Add(code, newFilter);
        }
    }

    private void FilterOnValueChanged()
    {
        var filtredItems = _allQueueTasks.Where(task => Filters[task.Status].Value);
        QueueTasks = new List<QueueTask>(filtredItems);
    }

    private void FillTempData()
    {
        _allQueueTasks = new List<QueueTask>()
        {
            new QueueTask() { Status = StatusCode.Waiting, TaskName = "WaitingTask1"},
            new QueueTask() { Status = StatusCode.Waiting, TaskName = "WaitingTask2"},
            new QueueTask() { Status = StatusCode.Processing, TaskName = "ProcessingTask1"},
            new QueueTask() { Status = StatusCode.Processing, TaskName = "ProcessingTask2"},
            new QueueTask() { Status = StatusCode.Finished, TaskName = "FinishedTask1"},
            new QueueTask() { Status = StatusCode.Finished, TaskName = "FinishedTask2"},
            new QueueTask() { Status = StatusCode.Error, TaskName = "ErrorTask1"},
            new QueueTask() { Status = StatusCode.Error, TaskName = "ErrorTask2"},
        };
        QueueTasks = new List<QueueTask>();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

好了,最后一步就是视觉部分的布局了。 我用 ItemsControl 来显示 ToggleButton。 为了显示列表本身,我使用了 ListBox.

...
    <Window.DataContext>
        <viewModel:MainViewModel></viewModel:MainViewModel>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <ItemsControl ItemsSource="{Binding Filters}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <ToggleButton Margin="5" Height="25" Width="100" Content="{Binding Key}" IsChecked="{Binding Value.Value}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <ListBox Grid.Row="1" ItemsSource="{Binding QueueTasks}" DisplayMemberPath="TaskName"/>
    </Grid>
</Window>

首先要感谢 Sean Sexton post https://wpf.2000things.com/2014/01/14/986-filtering-a-listbox-using-a-collectionviewsource/

现在使用 CollectionViewSource 的解决方案很简单(或多或少)。它是通过一些代码在视图中实现的。

首次创建(CollectionViewSource 绑定到完整队列):

<Window.Resources>        
        <CollectionViewSource Source="{Binding QueueTasks, Mode=OneWay}" x:Key="FilteredQueueTasks" IsLiveFilteringRequested="True">
            <CollectionViewSource.LiveFilteringProperties>
                <clr:String>Status</clr:String>
            </CollectionViewSource.LiveFilteringProperties>
        </CollectionViewSource>
    </Window.Resources>

然后将 ListBox 绑定到此 CollectionViewSource(已简化)。然后它只显示已经被 CollectionViewSource

过滤的任务
<ListBox Grid.Row="2" ItemsSource="{Binding Source={StaticResource FilteredQueueTasks}}" />

视图背后的代码如下所示(很大)

public partial class QueueView : Window, INotifyPropertyChanged
    {
        public QueueView()
        {
            InitializeComponent();
        }

        private void QueueTasks_Filter(object sender, FilterEventArgs e)
        {
            StatusCode status = ((QueueTask) e.Item).Status;

            switch (status)
            {
                case StatusCode.Waiting:
                    e.Accepted = showWaiting;
                    break;
                case StatusCode.Processing:
                    e.Accepted = showProcessing;
                    break;
                case StatusCode.Finished:
                    e.Accepted = showFinished;
                    break;
                case StatusCode.Error:
                    e.Accepted = showWithErrors;
                    break;
                case StatusCode.Warning:
                    e.Accepted = showWithWarnings;
                    break;
                default:
                    e.Accepted = false;
                    break;
            }
        }

        private bool showWaiting = true;

        public bool ShowWaiting
        {
            get => showWaiting;

            set
            {
                showWaiting = value;
                OnPropertyChanged(nameof(ShowWaiting));
                ((CollectionViewSource)this.Resources["FilteredQueueTasks"]).View.Refresh();
            }
        }

        private bool showProcessing = true;

        public bool ShowProcessing
        {
            get => showProcessing;

            set
            {
                showProcessing = value;
                OnPropertyChanged(nameof(ShowProcessing));
                ((CollectionViewSource)this.Resources["FilteredQueueTasks"]).View.Refresh();
            }
        }

        private bool showFinished;

        public bool ShowFinished
        {
            get => showFinished;

            set
            {
                showFinished = value;
                OnPropertyChanged(nameof(ShowFinished));
                ((CollectionViewSource)this.Resources["FilteredQueueTasks"]).View.Refresh();
            }
        }

        private bool showWithErrors = true;

        public bool ShowWithErrors
        {
            get => showWithErrors;

            set
            {
                showWithErrors = value;
                OnPropertyChanged(nameof(ShowWithErrors));
                ((CollectionViewSource)this.Resources["FilteredQueueTasks"]).View.Refresh();
            }
        }

        private bool showWithWarnings = true;

        public bool ShowWithWarnigs
        {
            get => showWithWarnings;

            set
            {
                showWithWarnings = value;
                OnPropertyChanged(nameof(ShowWithWarnigs));
                ((CollectionViewSource)this.Resources["FilteredQueueTasks"]).View.Refresh();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private void Window_Initialized(object sender, EventArgs e)
        {
            ((CollectionViewSource)this.Resources["FilteredQueueTasks"]).Filter += QueueTasks_Filter;
        }
    }

然后像这样附加每个 ToggleButton:

<StackPanel Grid.Row="1" HorizontalAlignment="Right" Height="22" VerticalAlignment="Bottom" Orientation="Horizontal" Margin="-1,0,5,5">
            <ToggleButton IsChecked="{Binding ShowWithErrors, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:QueueView}}}">
                <Image Source="Images/Error.png" Margin="1" />
            </ToggleButton>
            <ToggleButton IsChecked="{Binding ShowWithWarnigs, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:QueueView}}}">
                <Image Source="Images/Warning.png" Margin="1" />
            </ToggleButton>
            <ToggleButton IsChecked="{Binding ShowFinished, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:QueueView}}}">
                <Image Source="Images/Complete.png" Margin="1" />
            </ToggleButton>
            <ToggleButton IsChecked="{Binding ShowProcessing, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:QueueView}}}">
                <Image Source="Images/Working.png" Margin="1"/>
            </ToggleButton>
            <ToggleButton IsChecked="{Binding ShowWaiting, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:QueueView}}}">
                <Image Source="Images/Movies.png" Margin="1" />
            </ToggleButton>
        </StackPanel>

它会在任何 ToggleButton 更改其状态以及队列中的任何项目更新其状态时更新过滤器。要使 LiveFilter 正常工作,属性 必须实施 INotifyPropertyChanged,以便它可以通知视图进行更改。