Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/angularjs/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在WPF中对ListView和DataGrids执行同步分组/筛选?_Wpf_Listview_Collectionviewsource_Listcollectionview - Fatal编程技术网

如何在WPF中对ListView和DataGrids执行同步分组/筛选?

如何在WPF中对ListView和DataGrids执行同步分组/筛选?,wpf,listview,collectionviewsource,listcollectionview,Wpf,Listview,Collectionviewsource,Listcollectionview,我正在尝试在我的应用程序中创建软件在其音乐选择界面中使用的相同效果(下面的屏幕截图) 下面是一个带有DataGrid的面板,上面是一个带有一些显示分组行的ListView的面板。 当我在上面板的“流派”列表中单击,比如说“摇滚”,其他列表将更新,数据网格也会相应地过滤。如果我继续单击上面面板中的其他列表,DataGrid筛选将变得越来越严格,并相应地不断更新(仅显示与上面的筛选器匹配的行) 另外,还有额外的行:All(N项)和[Empty],我想它们必须以某种方式添加到视图源代码中 我开始阅读

我正在尝试在我的应用程序中创建软件在其音乐选择界面中使用的相同效果(下面的屏幕截图)

下面是一个带有DataGrid的面板,上面是一个带有一些显示分组行的ListView的面板。 当我在上面板的“流派”列表中单击,比如说“摇滚”,其他列表将更新,数据网格也会相应地过滤。如果我继续单击上面面板中的其他列表,DataGrid筛选将变得越来越严格,并相应地不断更新(仅显示与上面的筛选器匹配的行)

另外,还有额外的行:
All(N项)
[Empty]
,我想它们必须以某种方式添加到视图源代码中

我开始阅读这个类,因为它的文档中说:

绑定到数据集合时,可能需要对数据进行排序、筛选或分组。为此,请使用集合视图


在我看来,分组和筛选都是我想在这里完成的,但我发现缺少示例,甚至不知道从哪里开始,无论是ViewModel方面还是XAML方面。

这是一个非常广泛的问题,所以我将向您展示一种实现类似您所寻找的东西的方法。当然,有多种方法可以达到相同的结果。这种方法恰好与您已经尝试使用的东西一起使用。我也不知道它是否涵盖了您正在寻找的所有功能

假设您有一个轨迹的viewmodel,其外观如下所示:

internal class Track
{
    public string Genre { get; private set; }
    public string Artist { get; private set; }
    public string Album { get; private set; }
    public string Title { get; private set; }
    public string FileName { get; private set; }

    public Track(string genre, string artist, string album, string title, string fileName)
    {
        Genre = genre;
        Artist = artist;
        Album = album;
        Title = title;
        FileName = fileName;
    }
}
您需要为您的整体视图创建一个viewmodel,其中包含这些轨迹的可观察集合、该集合的集合视图以及过滤器的其他集合(屏幕截图的顶部)。我在本地组装了一些东西,结果看起来像这样(需要一些清理):

以下是测试应用程序的屏幕截图:


我不知道这是否符合您正在寻找的功能要求,但希望它至少能为您提供一个很好的参考,指导您如何做您正在尝试做的事情。

我非常感谢您回答的大量细节。从你的代码来看,我觉得有点不那么愚蠢,因为WPF似乎要求你了解很多类,并为听起来不那么复杂的东西编写很多代码。。。正如您所说,“有很多不同的方法可以实现相同的想法”(感觉这更像是一件坏事而不是好事——与Python的“做任何事情的一种显而易见的方法”相比),这并没有帮助@heltonbiker请记住,CollectionView的分组功能实际上只用于在控件(如ListView)内将内容分组到类别中,在这种情况下,只需要很少的代码。但是,我使用它的目的完全不同,所以我必须编写所有代码。您只能使用CollectionView的过滤功能,并以不同的方式处理填充顶部区域的操作。我想不出一种方法可以真正减少代码(可能更多代码),但其他方法可能会感觉更干净。由于您的回答,我做了一些细微的改动,使它发挥了作用。在我看来,引导我找到解决方案的关键点是使用
observetecollection
ListCollectionView
ICollectionView
类/接口对收集源进行分层,以及使用
CollectionViewSource.GetDefaultView(collection)
进行正确设置。过滤部分是我根据自己的需要进行定制的地方,我的过滤函数每次运行时都会从当前选定的属性中获取比较值。谢谢你的大力支持,希望我能投更多的票:D!
internal class MainWindowVM : INotifyPropertyChanged
{
    // Persistent filter values
    private static readonly FilterValue EmptyFilter;
    private static readonly FilterValue AllFilter;
    private static readonly FilterValue[] CommonFilters;

    private ObservableCollection<Track> mTracks;
    private ListCollectionView mTracksView;

    private FilterValue mSelectedGenre;
    private FilterValue mSelectedArtist;
    private FilterValue mSelectedAlbum;

    private bool mIsRefreshingView;

    public ICollectionView Tracks { get { return mTracksView; } }

    public IEnumerable<FilterValue> Genres
    {
        get { return CommonFilters.Concat(mTracksView.Groups.Select(g => new FilterValue((CollectionViewGroup)g))); }
    }

    public IEnumerable<FilterValue> Artists
    {
        get
        {
            if (mSelectedGenre != null)
            {
                if (mSelectedGenre.Group != null)
                {
                    return CommonFilters.Concat(mSelectedGenre.Group.Items.Select(g => new FilterValue((CollectionViewGroup)g)));
                }
                else if (mSelectedGenre == AllFilter)
                {
                    return CommonFilters.Concat(mTracksView.Groups.SelectMany(genre => ((CollectionViewGroup)genre).Items.Select(artist => new FilterValue((CollectionViewGroup)artist))));
                }
            }
            return new FilterValue[] { EmptyFilter };
        }
    }

    public IEnumerable<FilterValue> Albums
    {
        get
        {
            if (mSelectedArtist != null)
            {
                if (mSelectedArtist.Group != null)
                {
                    return CommonFilters.Concat(mSelectedArtist.Group.Items.Select(g => new FilterValue((CollectionViewGroup)g)));
                }
                else if (mSelectedArtist == AllFilter)
                {
                    // TODO: This is getting out of hand at this point. More groups will make it even worse. Should handle this in a better way.
                    return CommonFilters.Concat(mTracksView.Groups.SelectMany(genre => ((CollectionViewGroup)genre).Items.SelectMany(artist => ((CollectionViewGroup)artist).Items.Select(album => new FilterValue((CollectionViewGroup)album)))));
                }
            }
            return new FilterValue[] { EmptyFilter };
        }
    }

    // The following "Selected" properties assume that only one group can be selected
    // from each category. These should probably be expanded to allow for selecting
    // multiple groups from the same category.

    public FilterValue SelectedGenre
    {
        get { return mSelectedGenre; }
        set
        {
            if (!mIsRefreshingView && mSelectedGenre != value)
            {
                mSelectedGenre = value;
                RefreshView();
                NotifyPropertyChanged("SelectedGenre", "Artists");
            }
        }
    }

    public FilterValue SelectedArtist
    {
        get { return mSelectedArtist; }
        set
        {
            if (!mIsRefreshingView && mSelectedArtist != value)
            {
                mSelectedArtist = value;
                RefreshView();
                NotifyPropertyChanged("SelectedArtist", "Albums");
            }
        }
    }

    public FilterValue SelectedAlbum
    {
        get { return mSelectedAlbum; }
        set
        {
            if (!mIsRefreshingView && mSelectedAlbum != value)
            {
                mSelectedAlbum = value;
                RefreshView();
                NotifyPropertyChanged("SelectedAlbum");
            }
        }
    }

    static MainWindowVM()
    {
        EmptyFilter = new FilterValue("[Empty]");
        AllFilter = new FilterValue("All");
        CommonFilters = new FilterValue[]
        {
            EmptyFilter,
            AllFilter
        };
    }

    public MainWindowVM()
    {
        // Prepopulating test data
        mTracks = new ObservableCollection<Track>()
        {
            new Track("Genre 1", "Artist 1", "Album 1", "Track 1", "01 - Track 1.mp3"),
            new Track("Genre 2", "Artist 2", "Album 1", "Track 2", "02 - Track 2.mp3"),
            new Track("Genre 1", "Artist 1", "Album 1", "Track 3", "03 - Track 3.mp3"),
            new Track("Genre 1", "Artist 3", "Album 2", "Track 4", "04 - Track 4.mp3"),
            new Track("Genre 2", "Artist 2", "Album 2", "Track 5", "05 - Track 5.mp3"),
            new Track("Genre 3", "Artist 4", "Album 1", "Track 1", "01 - Track 1.mp3"),
            new Track("Genre 3", "Artist 4", "Album 4", "Track 2", "02 - Track 2.mp3"),
            new Track("Genre 1", "Artist 3", "Album 1", "Track 3", "03 - Track 3.mp3"),
            new Track("Genre 2", "Artist 2", "Album 3", "Track 4", "04 - Track 4.mp3"),
            new Track("Genre 2", "Artist 5", "Album 1", "Track 5", "05 - Track 5.mp3"),
            new Track("Genre 1", "Artist 1", "Album 2", "Track 6", "06 - Track 6.mp3"),
            new Track("Genre 3", "Artist 4", "Album 1", "Track 7", "07 - Track 7.mp3")
        };

        mTracksView = (ListCollectionView)CollectionViewSource.GetDefaultView(mTracks);

        // Note that groups are hierarchical. Based on this setup, having tracks with
        // the same artist but different genres would place them in different groups.
        // Grouping might not be the way to go here, but it gives us the benefit of
        // auto-generating groups based on the values of properties in the collection.
        mTracksView.GroupDescriptions.Add(new PropertyGroupDescription("Genre"));
        mTracksView.GroupDescriptions.Add(new PropertyGroupDescription("Artist"));
        mTracksView.GroupDescriptions.Add(new PropertyGroupDescription("Album"));

        mTracksView.Filter = FilterTrack;

        mSelectedGenre = EmptyFilter;
        mSelectedArtist = EmptyFilter;
        mSelectedAlbum = EmptyFilter;
    }

    private void RefreshView()
    {
        // Refreshing the view will cause all of the groups to be deleted and recreated, thereby killing
        // our selected group. We will track when a refresh is happening and ignore those group changes.
        if (!mIsRefreshingView)
        {
            mIsRefreshingView = true;
            mTracksView.Refresh();
            mIsRefreshingView = false;
        }
    }

    private bool FilterTrack(object obj)
    {
        Track track = (Track)obj;
        Func<FilterValue, string, bool> filterGroup = (filter, trackName) => filter == null || filter.Group == null || trackName == (string)filter.Group.Name;
        return
            filterGroup(mSelectedGenre, track.Genre) &&
            filterGroup(mSelectedArtist, track.Artist) &&
            filterGroup(mSelectedAlbum, track.Album);
    }

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(params string[] propertyNames)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            foreach (String propertyName in propertyNames)
            {
                handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    #endregion
}

internal class FilterValue
{
    private string mName;

    public CollectionViewGroup Group { get; set; }
    public string Name { get { return Group != null ? Group.Name.ToString() : mName; } }

    public FilterValue(string name)
    {
        mName = name;
    }

    public FilterValue(CollectionViewGroup group)
    {
        Group = group;
    }

    public override string ToString()
    {
        return Name;
    }
}
<Window x:Class="WPFApplication1.MainWindow"
        x:ClassModifier="internal"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WPFApplication1"
        Title="MainWindow" Height="600" Width="800">
    <Window.DataContext>
        <local:MainWindowVM />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="5" />
            <RowDefinition Height="2*" />
        </Grid.RowDefinitions>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Border
                BorderThickness="1 1 0 0"
                SnapsToDevicePixels="True"
                BorderBrush="{x:Static SystemColors.ControlDarkDarkBrush}">
                <TextBlock
                    Margin="4 1"
                    Text="Genre" />
            </Border>
            <Border
                Grid.Column="1"
                Margin="-1 0 0 0"
                BorderThickness="1 1 0 0"
                SnapsToDevicePixels="True"
                BorderBrush="{x:Static SystemColors.ControlDarkDarkBrush}">
                <TextBlock
                    Margin="4 1"
                    Text="Artist" />
            </Border>
            <Border
                Grid.Column="2"
                Margin="-1 0 0 0"
                BorderThickness="1 1 1 0"
                SnapsToDevicePixels="True"
                BorderBrush="{x:Static SystemColors.ControlDarkDarkBrush}">
                <TextBlock
                    Margin="4 1"
                    Text="Album" />
            </Border>
            <ListBox
                Grid.Row="1"
                ItemsSource="{Binding Genres}"
                SelectedItem="{Binding SelectedGenre, UpdateSourceTrigger=Explicit}"
                SelectionChanged="ListBox_SelectionChanged" />
            <ListBox
                Grid.Row="1"
                Grid.Column="1"
                ItemsSource="{Binding Artists}"
                SelectedItem="{Binding SelectedArtist, UpdateSourceTrigger=Explicit}"
                SelectionChanged="ListBox_SelectionChanged" />
            <ListBox
                Grid.Row="1"
                Grid.Column="2"
                ItemsSource="{Binding Albums}"
                SelectedItem="{Binding SelectedAlbum, UpdateSourceTrigger=Explicit}"
                SelectionChanged="ListBox_SelectionChanged" />
        </Grid>
        <GridSplitter
            Grid.Row="1"
            HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch" />
        <DataGrid
            Grid.Row="2"
            ItemsSource="{Binding Tracks}" />
    </Grid>
</Window>
internal partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var expression = BindingOperations.GetBindingExpression((DependencyObject)sender, Selector.SelectedItemProperty);
        if (expression != null)
        {
            expression.UpdateSource();
        }
    }
}