Skip to content

Commit f3c58de

Browse files
committed
Added 59th post.
1 parent a33768d commit f3c58de

File tree

15 files changed

+695
-0
lines changed

15 files changed

+695
-0
lines changed
28.7 KB
Loading

59-WPFCollectionViewSource/README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# WPF's CollectionViewSource
2+
3+
CollectionViewSource has existed for a long time in WPF and was recently introduced in Silverlight 3. My next post will cover CollectionViewSource in the context of Silverlight. But before covering that topic, I've decided to provide some background about why we introduced this class in WPF.
4+
5+
## Views in WPF
6+
7+
When a user binds a WPF property to a collection of data, WPF automatically creates a view to wrap the collection, and binds the property to the view, not the raw collection. This behavior always happens, and is independent of CollectionViewSource.
8+
9+
Views provide four types of functionality: sorting, filtering, grouping and tracking the current item. The scenarios you can implement with these four simple features are endless!
10+
11+
The type of view created by WPF depends on the collection type. There are essentially 3 types of views automatically generated by WPF, all deriving from the CollectionView base class:
12+
13+
- ListCollectionView -> Created when the collection implements IList.
14+
- BindingListCollectionView -> Created when the collection implements IBindingList.
15+
- EnumerableCollectionView -> Created when the collection implements nothing but IEnumerable. This class is internal to WPF.
16+
17+
Before CollectionViewSource was introduced in WPF, you could manipulate the automatically generated view (called the "default view") by obtaining the ItemCollection returned by the ItemsControl.Items property. ItemCollection is a "hybrid" class - it's both a collection (implementing ICollection, IList, and IEnumerable) containing the list of items in the ItemsControl, and a view (deriving from CollectionView) that exposes properties to manipulate the default view. Here's an example:
18+
19+
this.MyItemsControl.Items.SortDescriptions.Add(new SortDescription(...));
20+
21+
ItemCollection doesn't contain the implementation for any "view" related functionality though - all of its "view" methods are delegated. If the ItemsControl has items added directly to it in XAML (not data bound), ItemCollection delegates view operations to a private property of type "InnerItemCollectionView" (you can find it in Reflector - it's appropriately called "_internalView"). If the ItemsControl is data bound, the ItemCollection delegates all view operations to the automatically generated default view that wraps the collection.
22+
23+
There is one more view in WPF that you may come across: CompositeCollectionView. Naturally, this view is automatically created when the property is data bound to a CompositeCollection. CompositeCollections have also existed in WPF for a long time. They allow merging several individual collections and individual items into one single collection in XAML.
24+
25+
<ItemsControl.ItemsSource>
26+
<CompositeCollection>
27+
<ListBoxItem><TextBlock Text="Hello" /></ListBoxItem>
28+
<CollectionContainer Collection="{Binding Source={StaticResource Collection1}}"/>
29+
<CollectionContainer Collection="{Binding Source={StaticResource Collection2}}"/>
30+
</CompositeCollection>
31+
</ItemsControl.ItemsSource>
32+
33+
Instead of using the default view, the user can also create his own view to wrap the collection. This could be a custom view (implementing ICollectionView) or one of the existing public views - typically ListCollectionView or BindingListCollectionView. For example:
34+
35+
ListCollectionView lcv = new ListCollectionView(myList);
36+
this.MyItemsControl.ItemsSource = lcv;
37+
lcv.SortDescriptions.Add(new SortDescription(...));
38+
39+
In this scenario, WPF will not wrap this view in another view, it simply uses the one it's given, as expected. The user can then use his ListCollectionView object to perform view-related operations (such as adding sort descriptions).
40+
41+
## CollectionViewSource in WPF
42+
43+
All of these features already existed in WPF before we decided to add CollectionViewSource. We could already do filtering, sorting, grouping, we could track and set the current item, we could retrieve the default view created by WPF and manipulate it, and we could manually create multiple views of the same collection. So, why did we add CollectionViewSource?
44+
45+
The main reason was to enable those view-related operations in XAML - previously they could only be done in code. Without XAML support, tools like Blend can not provide a good tooling experience for these features. The most common uses of CollectionViewSource are to specify sorting and grouping directly in XAML, or to use XAML to hook up a filter handler defined in code. I've shown several samples of this syntax before (<a href="http://www.zagstudio.com/blog/454">this post</a> shows filtering, <a href="http://www.zagstudio.com/blog/367">this post</a> shows grouping, and <a href="http://www.zagstudio.com/blog/362">this post</a> shows sorting and grouping combined), so I won't go into that here.
46+
47+
CollectionViewSource is NOT a view, unlike the classes I described above. If you look in Reflector, you will notice that it doesn't even implement ICollectionView - a requirement for a class to be considered a "view". CollectionViewSource is simply a class that once given a collection (by setting its Source property) creates and exposes the corresponding view (through the View property), and that allows adding sorting and grouping (unfortunately not filtering) directly in XAML.
48+
49+
CollectionViewSource also provides several other methods useful to obtain and manipulate views. Among them is yet another way to retrieve the view that wraps a collection:
50+
51+
ListCollectionView lcv = CollectionViewSource.GetDefaultView(myCollection) as ListCollectionView;
52+
lcv.SortDescriptions.Add(new SortDescription(...));
53+
54+
This is my favorite way of getting a view for a collection because 1) it doesn't require a handle to the ItemsControl like when using ItemCollection and 2) I don't need to create the view myself. This method completes the list of ways to retrieve the view for a particular collection.
55+
56+
CollectionViewSource also enables another interesting scenario. If a particular CollectionViewSource points to different collections at different times, it remembers all the views that it created to wrap those collections. If a source that has already been set in the past is set again, CVS recognizes it and reuses the view it created originally. This behavior is useful in hierarchical binding scenarios. To illustrate this point, I created a very simple three-level master-detail scenario with the following XAML:
57+
58+
<Window.Resources>
59+
<CollectionViewSource Source="{Binding}" x:Key="cvs1"/>
60+
<CollectionViewSource Source="{Binding Source={StaticResource cvs1}, Path=Lifts}" x:Key="cvs2"/>
61+
<CollectionViewSource Source="{Binding Source={StaticResource cvs2}, Path=Runs}" x:Key="cvs3"/>
62+
</Window.Resources>
63+
64+
<ListBox ItemsSource="{Binding Source={StaticResource cvs1}}" DisplayMemberPath="Name"/>
65+
<ListBox ItemsSource="{Binding Source={StaticResource cvs2}}" DisplayMemberPath="Name" />
66+
<ListBox ItemsSource="{Binding Source={StaticResource cvs3}}" />
67+
68+
The DataContext for this window is set to a data source with the following structure:
69+
70+
public class Mountains : ObservableCollection<Mountain>
71+
{
72+
...
73+
}
74+
75+
public class Mountain
76+
{
77+
public ObservableCollection<Lift> Lifts { get; set; }
78+
...
79+
}
80+
81+
public class Lift
82+
{
83+
public ObservableCollection<string> Runs { get; set; }
84+
...
85+
}
86+
87+
Here is a screenshot:
88+
89+
<img src="Images/WPFCollectionViewSource.png" class="postImage" />
90+
91+
In this sample, cvs1 points to the mountains collection and creates a ListCollectionView to wrap it; cvs2 holds one of three collections: the Lifts collection for the first, second or third mountain; and cvs3 holds one of eight collections of Runs. Now imagine that you pick the first mountain (Whistler) and the second lift from that mountain (Garbanzo Express), then you switch to the second mountain (Stevens Pass) and back to the first mountain again (Whistler). At this point, you would expect the second lift (Garbanzo Express) to still be selected. And it is. Because the second CollectionViewSource stores the view last used to wrap that particular Lifts collection, reusing it next time it needs to display that same collection, the current item is preserved.
92+
93+
If you made it so far, you know more about CollectionViewSource than you will ever need! My next post will include a similar discussion in the context of Silverlight.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 10.00
3+
# Visual Studio 2008
4+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPFCollectionViewSource", "WPFCollectionViewSource\WPFCollectionViewSource.csproj", "{F2FE2F10-9EFA-49A9-9242-84DE002FE053}"
5+
EndProject
6+
Global
7+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
8+
Debug|Any CPU = Debug|Any CPU
9+
Release|Any CPU = Release|Any CPU
10+
EndGlobalSection
11+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
12+
{F2FE2F10-9EFA-49A9-9242-84DE002FE053}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
13+
{F2FE2F10-9EFA-49A9-9242-84DE002FE053}.Debug|Any CPU.Build.0 = Debug|Any CPU
14+
{F2FE2F10-9EFA-49A9-9242-84DE002FE053}.Release|Any CPU.ActiveCfg = Release|Any CPU
15+
{F2FE2F10-9EFA-49A9-9242-84DE002FE053}.Release|Any CPU.Build.0 = Release|Any CPU
16+
EndGlobalSection
17+
GlobalSection(SolutionProperties) = preSolution
18+
HideSolutionNode = FALSE
19+
EndGlobalSection
20+
EndGlobal
Binary file not shown.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Application x:Class="WPFCollectionViewSource.App"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
StartupUri="Window1.xaml">
5+
<Application.Resources>
6+
7+
</Application.Resources>
8+
</Application>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Configuration;
4+
using System.Data;
5+
using System.Linq;
6+
using System.Windows;
7+
8+
namespace WPFCollectionViewSource
9+
{
10+
/// <summary>
11+
/// Interaction logic for App.xaml
12+
/// </summary>
13+
public partial class App : Application
14+
{
15+
}
16+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Collections.ObjectModel;
6+
7+
namespace WPFCollectionViewSource
8+
{
9+
public class Mountains : ObservableCollection<Mountain>
10+
{
11+
public Mountains()
12+
{
13+
// Whistler
14+
Mountain whistler = new Mountain("Whistler");
15+
Lift bigRedExpress = new Lift("Big Red Express");
16+
bigRedExpress.Runs.Add("Headwall");
17+
bigRedExpress.Runs.Add("Fisheye");
18+
bigRedExpress.Runs.Add("Jimmy's Joker");
19+
Lift garbanzoExpress = new Lift("Garbanzo Express");
20+
garbanzoExpress.Runs.Add("Raven");
21+
Lift orangeChair = new Lift("Orange chair");
22+
orangeChair.Runs.Add("Orange peel");
23+
orangeChair.Runs.Add("Banana peel");
24+
orangeChair.Runs.Add("Upper Dave Murray Downhill");
25+
whistler.Lifts.Add(bigRedExpress);
26+
whistler.Lifts.Add(garbanzoExpress);
27+
whistler.Lifts.Add(orangeChair);
28+
29+
// Stevens Pass
30+
Mountain stevensPass = new Mountain("Stevens Pass");
31+
Lift tyeMill = new Lift("Tye Mill");
32+
tyeMill.Runs.Add("Roller coaster");
33+
tyeMill.Runs.Add("Skid road");
34+
tyeMill.Runs.Add("Crest trail");
35+
Lift jupiterChair = new Lift("Jupiter chair");
36+
jupiterChair.Runs.Add("Corona bowl");
37+
jupiterChair.Runs.Add("Lower gemini");
38+
Lift southernCrossChair = new Lift("Southern cross chair");
39+
southernCrossChair.Runs.Add("Orion");
40+
southernCrossChair.Runs.Add("Aquarius face");
41+
stevensPass.Lifts.Add(tyeMill);
42+
stevensPass.Lifts.Add(jupiterChair);
43+
stevensPass.Lifts.Add(southernCrossChair);
44+
45+
// Crystal Mountain
46+
Mountain crystal = new Mountain("Crystal Mountain");
47+
Lift rainierExpress = new Lift("Rainier Express");
48+
rainierExpress.Runs.Add("Iceberg ridge");
49+
rainierExpress.Runs.Add("Pro course");
50+
rainierExpress.Runs.Add("Lucky shot");
51+
Lift greenValley = new Lift("Green Valley");
52+
greenValley.Runs.Add("Green back");
53+
greenValley.Runs.Add("Northway ridge");
54+
crystal.Lifts.Add(rainierExpress);
55+
crystal.Lifts.Add(greenValley);
56+
57+
this.Add(whistler);
58+
this.Add(stevensPass);
59+
this.Add(crystal);
60+
}
61+
}
62+
63+
public class Mountain
64+
{
65+
private ObservableCollection<Lift> lifts;
66+
67+
public ObservableCollection<Lift> Lifts
68+
{
69+
get { return lifts; }
70+
set { lifts = value; }
71+
}
72+
73+
private string name;
74+
75+
public string Name
76+
{
77+
get { return name; }
78+
set { name = value; }
79+
}
80+
81+
public Mountain(string name)
82+
{
83+
this.name = name;
84+
lifts = new ObservableCollection<Lift>();
85+
}
86+
}
87+
88+
public class Lift
89+
{
90+
private ObservableCollection<string> runs;
91+
92+
public ObservableCollection<string> Runs
93+
{
94+
get { return runs; }
95+
set { runs = value; }
96+
}
97+
98+
private string name;
99+
100+
public string Name
101+
{
102+
get { return name; }
103+
set { name = value; }
104+
}
105+
106+
public Lift(string name)
107+
{
108+
this.name = name;
109+
runs = new ObservableCollection<string>();
110+
}
111+
}
112+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System.Reflection;
2+
using System.Resources;
3+
using System.Runtime.CompilerServices;
4+
using System.Runtime.InteropServices;
5+
using System.Windows;
6+
7+
// General Information about an assembly is controlled through the following
8+
// set of attributes. Change these attribute values to modify the information
9+
// associated with an assembly.
10+
[assembly: AssemblyTitle("WPFCollectionViewSource")]
11+
[assembly: AssemblyDescription("")]
12+
[assembly: AssemblyConfiguration("")]
13+
[assembly: AssemblyCompany("Microsoft")]
14+
[assembly: AssemblyProduct("WPFCollectionViewSource")]
15+
[assembly: AssemblyCopyright("Copyright © Microsoft 2009")]
16+
[assembly: AssemblyTrademark("")]
17+
[assembly: AssemblyCulture("")]
18+
19+
// Setting ComVisible to false makes the types in this assembly not visible
20+
// to COM components. If you need to access a type in this assembly from
21+
// COM, set the ComVisible attribute to true on that type.
22+
[assembly: ComVisible(false)]
23+
24+
//In order to begin building localizable applications, set
25+
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
26+
//inside a <PropertyGroup>. For example, if you are using US english
27+
//in your source files, set the <UICulture> to en-US. Then uncomment
28+
//the NeutralResourceLanguage attribute below. Update the "en-US" in
29+
//the line below to match the UICulture setting in the project file.
30+
31+
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32+
33+
34+
[assembly: ThemeInfo(
35+
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
36+
//(used if a resource is not found in the page,
37+
// or application resource dictionaries)
38+
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
39+
//(used if a resource is not found in the page,
40+
// app, or any theme specific resource dictionaries)
41+
)]
42+
43+
44+
// Version information for an assembly consists of the following four values:
45+
//
46+
// Major Version
47+
// Minor Version
48+
// Build Number
49+
// Revision
50+
//
51+
// You can specify all the values or you can default the Build and Revision Numbers
52+
// by using the '*' as shown below:
53+
// [assembly: AssemblyVersion("1.0.*")]
54+
[assembly: AssemblyVersion("1.0.0.0")]
55+
[assembly: AssemblyFileVersion("1.0.0.0")]

59-WPFCollectionViewSource/WPFCollectionViewSource/WPFCollectionViewSource/Properties/Resources.Designer.cs

Lines changed: 71 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)