How to perform synchronized grouping/filtering of ListViews and DataGrids in WPF? -
i trying create, in application, same effect used software musicbee in music selection interface (screenshot below).
there lower panel datagrid, , upper panel listviews displaying grouped rows. when click, say, "rock" on "genre" list in upper panel, other lists updated , datagrid filtered accordingly. if go on clicking on other lists in upper panel, datagrid filtering becomes more , more restrictive , goes on being updated accordingly (displaying rows matching filters above).
also, there rows: all (n items)
, [empty]
, imagine have added view source somehow.
i started read listcollectionview
class, since documentation says:
"when bind data collection, may want sort, filter, or group data. that, use collection views."
it seems me grouping , filtering want accomplish here, found lack of examples , don't know start this, either viewmodel-side or xaml-side.
this broad question, show 1 way go implementing looking for. there of course multiple ways achieve same result. way happens follow along stuff trying use. have no idea if covers of features looking for.
let's have viewmodel track looks this:
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; } }
you want make viewmodel overall view contains observable collection of these tracks, collection view collection, , additional collections filters (the top part of screenshot). threw locally ended looking (needs cleanup):
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 { { return mtracksview; } } public ienumerable<filtervalue> genres { { return commonfilters.concat(mtracksview.groups.select(g => new filtervalue((collectionviewgroup)g))); } } public ienumerable<filtervalue> artists { { 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 { { if (mselectedartist != null) { if (mselectedartist.group != null) { return commonfilters.concat(mselectedartist.group.items.select(g => new filtervalue((collectionviewgroup)g))); } else if (mselectedartist == allfilter) { // todo: getting out of hand @ point. more groups make worse. should handle in 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 }; } } // following "selected" properties assume 1 group can selected // each category. these should expanded allow selecting // multiple groups same category. public filtervalue selectedgenre { { return mselectedgenre; } set { if (!misrefreshingview && mselectedgenre != value) { mselectedgenre = value; refreshview(); notifypropertychanged("selectedgenre", "artists"); } } } public filtervalue selectedartist { { return mselectedartist; } set { if (!misrefreshingview && mselectedartist != value) { mselectedartist = value; refreshview(); notifypropertychanged("selectedartist", "albums"); } } } public filtervalue selectedalbum { { 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 groups hierarchical. based on setup, having tracks // same artist different genres place them in different groups. // grouping might not way go here, gives benefit of // auto-generating groups based on values of properties in 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 view cause of groups deleted , recreated, thereby killing // our selected group. track when refresh happening , ignore 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 { { 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; } }
the view used has list box each filter , datagrid @ bottom displaying tracks.
<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>
and code-behind view. had update filter selections in view model when selection changed in view. otherwise, end setting null reason. didn't spend time investigating causing issue. worked around explicitly updating source when selection changed.
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(); } } }
here screenshot of test app:
i have no idea if meets feature requirements of looking for, @ least reference how sorts of things trying do.
Comments
Post a Comment