Chapter 16. TreeView and ListView


The TreeView control displays hierarchical data. Perhaps the most prominent tree view of all time is the left side of Windows Explorer, where all the user's disk drives and directories are displayed. A tree view also shows up in the left side of the Microsoft Document Viewer used to display Microsoft Visual Studio and .NET documentation. The tree view in the Document Viewer shows all the .NET namespaces, followed by nested classes and structures, and then methods and properties, among other information.

Each item on the Windows Presentation Foundation TreeView control is an object of type TreeViewItem. A TreeViewItem is usually identified by a short text string but also contains a collection of nested TreeViewItem objects. In this way, TreeView is very similar to Menu, and TreeViewItem is very similar to MenuItem, as you can see from the following selected class hierarchy showing all major controls covered in the previous two chapters:

Control

       ItemsControl

                 HeaderedItemsControl

                             MenuItem

                             ToolBar

                             TreeViewItem

                 MenuBase (abstract)

                            ContextMenu

                            Menu

                 StatusBar

                 TreeView

As you'll recall, ItemsControl is also the parent class of Selector, which is the parent class of ListBox and ComboBox. ItemsControl contains an important property named Items, which is a collection of the items that appear listed in the control. To ItemsControl the HeaderedItemsControl adds a property named Header. Although this Header property is of type Object, very often it's just a text string.

Just as Menu is a collection of the top-level MenuItem objectsand hence has no Header property itselfTreeView is a collection of top-level TreeViewItem objects and also has no Header property.

The following program populates a TreeView control "manually"that is, with explicit hard-coded items.

ManuallyPopulateTreeView.cs

[View full width]

//--------- ------------------------------------------------ // ManuallyPopulateTreeView.cs (c) 2006 by Charles Petzold //------------------------------------------------ --------- using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; namespace Petzold.ManuallyPopulateTreeView { public class ManuallyPopulateTreeView : Window { [STAThread] public static void Main() { Application app = new Application(); app.Run(new ManuallyPopulateTreeView()); } public ManuallyPopulateTreeView() { Title = "Manually Populate TreeView"; TreeView tree = new TreeView(); Content = tree; TreeViewItem itemAnimal = new TreeViewItem(); itemAnimal.Header = "Animal"; tree.Items.Add(itemAnimal); TreeViewItem itemDog = new TreeViewItem(); itemDog.Header = "Dog"; itemDog.Items.Add("Poodle"); itemDog.Items.Add("Irish Setter"); itemDog.Items.Add("German Shepherd"); itemAnimal.Items.Add(itemDog); TreeViewItem itemCat = new TreeViewItem(); itemCat.Header = "Cat"; itemCat.Items.Add("Calico"); TreeViewItem item = new TreeViewItem(); item.Header = "Alley Cat"; itemCat.Items.Add(item); Button btn = new Button(); btn.Content = "Noodles"; itemCat.Items.Add(btn); itemCat.Items.Add("Siamese"); itemAnimal.Items.Add(itemCat); TreeViewItem itemPrimate = new TreeViewItem(); itemPrimate.Header = "Primate"; itemPrimate.Items.Add("Chimpanzee"); itemPrimate.Items.Add("Bonobo"); itemPrimate.Items.Add("Human"); itemAnimal.Items.Add(itemPrimate); TreeViewItem itemMineral = new TreeViewItem(); itemMineral.Header = "Mineral"; itemMineral.Items.Add("Calcium"); itemMineral.Items.Add("Zinc"); itemMineral.Items.Add("Iron"); tree.Items.Add(itemMineral); TreeViewItem itemVegetable = new TreeViewItem(); itemVegetable.Header = "Vegetable"; itemVegetable.Items.Add("Carrot"); itemVegetable.Items.Add("Asparagus"); itemVegetable.Items.Add("Broccoli"); tree.Items.Add(itemVegetable); } } }



The program shows two different ways to add TreeViewItem objects to a parent item. You can explicitly create an object of type TreeViewItem and add that to the ItemCollection object referenced by the Items property. This approach is necessary if the item has child items. However, if the item has no children, a much simpler approach is to pass a text string to the Add method of the Items collection. You'll even notice that one of the items (for the type of cat known as Noodles) is actually a Button control because Noodles is, well, a very special cat.

This program gives you an opportunity to explore the user interface of TreeView. You can click a plus sign to expand an item and click the minus sign to collapse it. The TreeView also has a complete keyboard interface based around the arrow keys.

Other than that, the program doesn't show any useful generalized techniques for programming the TreeView. Although it's common to populate a Menu control by manually adding items and subitems, populating a TreeView control in this way is probably quite rare in real-life applications. It's much more common to populate a TreeView control based on some database or other information external to the program.

For example, a TreeView control that displays disk drives and directories uses classes from the System.IO namespace to populate the TreeView. Here's a program that assembles a TreeView control showing all the directories on the current system drive. (The system drive is usually drive C, of course, but if a drive has multiple bootable partitions, the system drive could be something else.)

Let the name of this program be a warning to you: This program shows the wrong way to populate a TreeView with a directory tree!

RecurseDirectoriesInefficiently.cs

[View full width]

//--------- ------------------------------------------------------- // RecurseDirectoriesInefficiently.cs (c) 2006 by Charles Petzold //------------------------------------------------ ---------------- using System; using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; namespace Petzold.RecurseDirectoriesInefficiently { public class RecurseDirectoriesInefficiently : Window { [STAThread] public static void Main() { Application app = new Application(); app.Run(new RecurseDirectoriesInefficiently()); } public RecurseDirectoriesInefficiently() { Title = "Recurse Directories Inefficiently"; TreeView tree = new TreeView(); Content = tree; // Create TreeViewItem based on system drive. TreeViewItem item = new TreeViewItem(); item.Header = Path.GetPathRoot (Environment.SystemDirectory); item.Tag = new DirectoryInfo(item .Header as string); tree.Items.Add(item); // Fill recursively. GetSubdirectories(item); } void GetSubdirectories(TreeViewItem item) { DirectoryInfo dir = item.Tag as DirectoryInfo; DirectoryInfo[] subdirs; try { // Get subdirectories. subdirs = dir.GetDirectories(); } catch { return; } // Loop through subdirectories. foreach (DirectoryInfo subdir in subdirs) { // Create a new TreeViewItem for each directory. TreeViewItem subitem = new TreeViewItem(); subitem.Header = subdir.Name; subitem.Tag = subdir; item.Items.Add(subitem); // Recursively obtain subdirectories. GetSubdirectories(subitem); } } } }



In theory, the program looks fine. The window constructor creates the TreeView control and creates the first TreeViewItem. This item's Header property is assigned the path root of the current system drive (for example, a text string like "C:\") and the Tag property gets a DirectoryInfo object based on that same string. The constructor then passes this TreeViewItem object to GetSubdirectories.

GetSubdirectories is a recursive method. It uses the GetDirectories method of DirectoryInfo to obtain all the child subdirectories. (A try-catch block is necessary in case it encounters a directory where access is forbidden.) For each subdirectory, the method creates a new TreeViewItem object and then calls GetSubdirectories with that new item. Structurally, this is precisely the way we once wrote command-line utilities to display directory trees.

Of course, what's fine for the command line is terrible for a graphical environment. If you dare to run this program, you'll discover that it spends a long time grinding through the disk before displaying the window. Not only is the time delay a problem, but the program is doing much more than it needs to. It's highly unlikely the user needs to see every single directory the program is adding to the TreeView. The program may even be exercising parts of the hard drive that haven't been touched since the machine left the factory!

A much better TreeView for displaying a directory tree obtains subdirectories only when they're requiredfor example, when the user clicks the plus sign next to a directory. Actually, that's a little too late. The plus sign is displayed only if the item has child items, so the program has to be one step ahead of the user. For each item that is displayed, the subitems need to be present, but the sub-subitems don't need to be immediately included.

When you delay loading data, such as I've described, you're using a technique known as "lazy loading." To do TreeView lazy loading properly, you need to be familiar with TreeViewItem events, of which there are four. The Expanded event signals when a user clicks a plus sign to display child items, or when a program manually sets the IsExpanded property to true. The Collapse event signals the opposite: Child items are being hidden. The Selected and Unselected events indicate that an item is being selected by the user or the IsSelected property has changed. All four events have accompanying On methods.

In addition, the TreeView itself has a SelectedItemChanged event. However, the SelectedItem property of TreeView is read-only, so if you want to programmatically set an initial selected item in a TreeView, set the IsSelected property of the actual TreeViewItem.

Let's see if we can use these events to implement a directory TreeView that actually works well. To make this exercise even more fun, let's display a list of files when a directory is selected. And to make it even more fun than that, let's use little bitmaps in the TreeView. These bitmaps, obtained from the outline\16color_nomask directory of the Visual Studio image library, require a white background. I modified some of them to make the device resolution 96 DPI. The images I used are 35Floppy.bmp, Cddrive.bmp, Clsdfold.bmp ("closed folder"), Drive.bmp, and Openfold.bmp. As in Windows Explorer, the folder image changes from closed to open when a directory is selected.

In a nod towards reusability, this project begins with a class that inherits from TreeViewItem called ImagedTreeViewItem.

ImagedTreeViewItem.cs

[View full width]

//--------------------------------------------------- // ImagedTreeViewItem.cs (c) 2006 by Charles Petzold //--------------------------------------------------- using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; namespace Petzold.RecurseDirectoriesIncrementally { public class ImagedTreeViewItem : TreeViewItem { TextBlock text; Image img; ImageSource srcSelected, srcUnselected; // Constructor makes stack with image and text. public ImagedTreeViewItem() { StackPanel stack = new StackPanel(); stack.Orientation = Orientation .Horizontal; Header = stack; img = new Image(); img.VerticalAlignment = VerticalAlignment.Center; img.Margin = new Thickness(0, 0, 2, 0); stack.Children.Add(img); text = new TextBlock(); text.VerticalAlignment = VerticalAlignment.Center; stack.Children.Add(text); } // Public properties for text and images. public string Text { set { text.Text = value; } get { return text.Text; } } public ImageSource SelectedImage { set { srcSelected = value; if (IsSelected) img.Source = srcSelected; } get { return srcSelected; } } public ImageSource UnselectedImage { set { srcUnselected = value; if (!IsSelected) img.Source = srcUnselected; } get { return srcUnselected; } } // Event overrides to set image. protected override void OnSelected (RoutedEventArgs args) { base.OnSelected(args); img.Source = srcSelected; } protected override void OnUnselected (RoutedEventArgs args) { base.OnUnselected(args); img.Source = srcUnselected; } } }



The ImagedTreeViewItem class sets the Header property it inherits from TreeViewItem to a StackPanel. The StackPanel children are an Image object and a TextBlock. A public property named Text lets a consumer of this control set the Text property of the TextBlock. The two other public properties are both of type ImageSource and are named SelectedImagethe image that is to appear when the item is selectedand UnselectedImage, the image that appears otherwise. Overrides of the OnSelected and OnUnselected methods are responsible for setting the Source property of the Image object to the proper ImageSource.

The DirectoryTreeViewItem class inherits from ImagedTreeViewItem and supplies the two images that its base class requires. The constructor of this class has a DirectoryInfo parameter that it saves as a field and exposes through its public DirectoryInfo property. (Another way to think about this is that DirectoryTreeViewItem is a visual wrapper around a DirectoryInfo object.) Notice that the constructor also sets the Text property that ImagedTreeViewItem defines.

DirectoryTreeViewItem.cs

[View full width]

//--------- --------------------------------------------- // DirectoryTreeViewItem.cs (c) 2006 by Charles Petzold //------------------------------------------------ ------ using System; using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; namespace Petzold.RecurseDirectoriesIncrementally { public class DirectoryTreeViewItem: ImagedTreeViewItem { DirectoryInfo dir; // Constructor requires DirectoryInfo object. public DirectoryTreeViewItem(DirectoryInfo dir) { this.dir = dir; Text = dir.Name; SelectedImage = new BitmapImage( new Uri("pack://application:,, /Images/OPENFOLD.BMP")); UnselectedImage = new BitmapImage( new Uri("pack://application:,, /Images/CLSDFOLD.BMP")); } // Public property to obtain DirectoryInfo object. public DirectoryInfo DirectoryInfo { get { return dir; } } // Public method to populate with items. public void Populate() { DirectoryInfo[] dirs; try { dirs = dir.GetDirectories(); } catch { return; } foreach (DirectoryInfo dirChild in dirs) Items.Add(new DirectoryTreeViewItem(dirChild)); } // Event override to populate subitems. protected override void OnExpanded (RoutedEventArgs args) { base.OnExpanded(args); foreach (object obj in Items) { DirectoryTreeViewItem item = obj as DirectoryTreeViewItem; item.Populate(); } } } }



The public Populate method obtains all the subdirectories of the DirectoryInfo object associated with the item, and creates new DirectoryTreeViewItem objects that are children of this item. Originally, I defined Populate as a private method that was called from the constructor. You'll see shortly why I had to make it public and not called by default.

The class also overrides the OnExpanded method. The OnExpanded method essentially brings into view all the child subdirectories. At this time, all these child subdirectories need to be populated with their own subdirectories so that they properly display plus signs (or not). That's the job of the OnExpanded method.

The DirectoryTreeView class derives from TreeView. This is the class responsible for obtaining all the disk drives and beginning the process of creating DirectoryTreeViewItem objects.

DirectoryTreeView.cs

[View full width]

//-------------------------------------------------- // DirectoryTreeView.cs (c) 2006 by Charles Petzold //-------------------------------------------------- using System; using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; namespace Petzold.RecurseDirectoriesIncrementally { public class DirectoryTreeView : TreeView { // Constructor builds partial directory tree. public DirectoryTreeView() { RefreshTree(); } public void RefreshTree() { BeginInit(); Items.Clear(); // Obtain the disk drives. DriveInfo[] drives = DriveInfo .GetDrives(); foreach (DriveInfo drive in drives) { char chDrive = drive.Name.ToUpper ()[0]; DirectoryTreeViewItem item = new DirectoryTreeViewItem(drive.RootDirectory); // Display VolumeLabel if drive ready; otherwise just DriveType. if (chDrive != 'A' && chDrive != 'B' && drive.IsReady && drive .VolumeLabel.Length > 0) item.Text = String.Format("{0} ({1})", drive.VolumeLabel, drive.Name); else item.Text = String.Format("{0} ({1})", drive.DriveType, drive.Name); // Determine proper bitmap for drive. if (chDrive == 'A' || chDrive == 'B') item.SelectedImage = item .UnselectedImage = new BitmapImage( new Uri("pack:/ /application:,,/Images/35FLOPPY.BMP")); else if (drive.DriveType == DriveType.CDRom) item.SelectedImage = item .UnselectedImage = new BitmapImage( new Uri("pack:/ /application:,,/Images/CDDRIVE.BMP")); else item.SelectedImage = item .UnselectedImage = new BitmapImage( new Uri("pack:/ /application:,,/Images/DRIVE.BMP")); Items.Add(item); // Populate the drive with directories. if (chDrive != 'A' && chDrive != 'B' && drive.IsReady) item.Populate(); } EndInit(); } } }



Although not used in this project, I gave DirectoryTreeView a public RefreshTree method. This might be called from a Refresh menu item. Here it's just called from the constructor. The RefreshTree method goes into an initialization block by calling BeginInit, deletes all its items, and recreates them. After BeginInit is called, the TreeView won't attempt to keep itself visually updated because that could slow down the construction of the tree.

The RefreshTree method obtains an array of DriveInfo objects describing all the drives on the machine. The RootDirectory property of DriveInfo is an object of type DirectoryInfo, so it's easy to create a DirectoryTreeViewItem from it. However, the A and B drives have to be handled with kid gloves. The next-to-last thing you want when initializing a TreeView control is for a floppy disk drive to start spinning. The very last thing you want is for a message box to pop up complaining that there's no disk in the drive! That's why this code doesn't touch the IsReady property of DriveInfo for floppy drives. Also, notice that the method calls Populate for the drive only if the drive is not a floppy disk and the IsReady property is true.

The final class in the RecurseDirectoriesIncrementally project is a class with that name that inherits from Window.

RecurseDirectoriesIncrementally.cs

[View full width]

//--------- ------------------------------------------------------- // RecurseDirectoriesIncrementally.cs (c) 2006 by Charles Petzold //------------------------------------------------ ---------------- using System; using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; namespace Petzold.RecurseDirectoriesIncrementally { class RecurseDirectoriesIncrementally : Window { StackPanel stack; [STAThread] public static void Main() { Application app = new Application(); app.Run(new RecurseDirectoriesIncrementally()); } public RecurseDirectoriesIncrementally() { Title = "Recurse Directories Incrementally"; // Create Grid as content of window. Grid grid = new Grid(); Content = grid; // Define ColumnDefinition objects. ColumnDefinition coldef = new ColumnDefinition(); coldef.Width = new GridLength(50, GridUnitType.Star); grid.ColumnDefinitions.Add(coldef); coldef = new ColumnDefinition(); coldef.Width = GridLength.Auto; grid.ColumnDefinitions.Add(coldef); coldef = new ColumnDefinition(); coldef.Width = new GridLength(50, GridUnitType.Star); grid.ColumnDefinitions.Add(coldef); // Put DirectoryTreeView at left. DirectoryTreeView tree = new DirectoryTreeView(); tree.SelectedItemChanged += TreeViewOnSelectedItemChanged; grid.Children.Add(tree); Grid.SetColumn(tree, 0); // Put GridSplitter in center. GridSplitter split = new GridSplitter(); split.Width = 6; split.ResizeBehavior = GridResizeBehavior.PreviousAndNext; grid.Children.Add(split); Grid.SetColumn(split, 1); // Put scrolled StackPanel at right. ScrollViewer scroll = new ScrollViewer(); grid.Children.Add(scroll); Grid.SetColumn(scroll, 2); stack = new StackPanel(); scroll.Content = stack; } void TreeViewOnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> args) { // Get selected item. DirectoryTreeViewItem item = args .NewValue as DirectoryTreeViewItem; // Clear out the DockPanel. stack.Children.Clear(); // Fill it up again. FileInfo[] infos; try { infos = item.DirectoryInfo.GetFiles(); } catch { return; } foreach (FileInfo info in infos) { TextBlock text = new TextBlock(); text.Text = info.Name; stack.Children.Add(text); } } } }



The constructor creates a Grid with three columnsone for the DirectoryTreeView, one for the GridSplitter, and the third for a ScrollViewer that contains a StackPanel. This is where files are displayed.

The constructor sets an event handler for the SelectedItemChanged event and processes that event by clearing all child elements from the StackPanel and then filling it up again with all the files in the selected directory. Of course, you can't do anything with these files, but you're well on your way to writing a clone of Windows Explorer.

You might recall the ItemTemplate property of ListBox. In Chapter 13, "ListBox Selection," I showed you how to define a DataTemplate object that you can set to the ItemTemplate property. This DataTemplate includes a VisualTree that describes how items are to be visually represented in the ListBox.

That ItemTemplate property is defined by ItemsControl, so it's inherited by TreeView. Moreover, a class named HierarchicalDataTemplate derives from DataTemplate, and this class is ideal for describing hierarchical data from which a TreeView is built. To use this facility, you'll want to begin by defining a class that describes the hierarchical data you want the TreeView to display.

Here is such a class, named DiskDirectory, that is basically a wrapper around a DirectoryInfo object. In fact, if DirectoryInfo were not sealed, I would have derived DiskDirectory from DirectoryInfo and the class would have been simplified a bit. I wouldn't have needed the constructor or the Name property, for example.

DiskDirectory.cs

[View full width]

//---------------------------------------------- // DiskDirectory.cs (c) 2006 by Charles Petzold //---------------------------------------------- using System; using System.Collections.Generic; using System.IO; namespace Petzold.TemplateTheTree { public class DiskDirectory { DirectoryInfo dirinfo; // Constructor requires DirectoryInfo object. public DiskDirectory(DirectoryInfo dirinfo) { this.dirinfo = dirinfo; } // Name property returns directory name. public string Name { get { return dirinfo.Name; } } // Subdirectories property returns collection of DiskDirectory objects. public List<DiskDirectory> Subdirectories { get { List<DiskDirectory> dirs = new List<DiskDirectory>(); DirectoryInfo[] subdirs; try { subdirs = dirinfo .GetDirectories(); } catch { return dirs; } foreach (DirectoryInfo subdir in subdirs) dirs.Add(new DiskDirectory (subdir)); return dirs; } } } }



The crucial property here is Subdirectories, which basically calls GetDirectories and then creates a DiskDirectory object for each subdirectory and adds that to a List collection. This List object is what it returns from the property.

The TemplateTheTree program makes use of the DiskDirectory class in defining a data template for a TreeView.

TemplateTheTree.cs

[View full width]

//------------------------------------------------ // TemplateTheTree.cs (c) 2006 by Charles Petzold //------------------------------------------------ using System; using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; namespace Petzold.TemplateTheTree { public class TemplateTheTree : Window { [STAThread] public static void Main() { Application app = new Application(); app.Run(new TemplateTheTree()); } public TemplateTheTree() { Title = "Template the Tree"; // Create TreeView and set as content of window. TreeView treevue = new TreeView(); Content = treevue; // Create HierarchicalDataTemplate based on DiskDirectory. HierarchicalDataTemplate template = new HierarchicalDataTemplate(typeof(DiskDirectory)); // Set Subdirectories property as ItemsSource. template.ItemsSource = new Binding ("Subdirectories"); // Create FrameworkElementFactory for TextBlock. FrameworkElementFactory factoryTextBlock = new FrameworkElementFactory(typeof(TextBlock)); // Bind Text property with Name property from DiskDirectory. factoryTextBlock.SetBinding(TextBlock .TextProperty, new Binding("Name")); // Set this Textblock as the VisualTree of the template. template.VisualTree = factoryTextBlock; // Create a DiskDirectory object for the system drive. DiskDirectory dir = new DiskDirectory( new DirectoryInfo( Path.GetPathRoot(Environment .SystemDirectory))); // Create a root TreeViewItem and set its properties. TreeViewItem item = new TreeViewItem(); item.Header = dir.Name; item.ItemsSource = dir.Subdirectories; item.ItemTemplate = template; // Add TreeViewItem to TreeView. treevue.Items.Add(item); item.IsExpanded = true; } } }



The HierarchicalDataTemplate object created here is based on the DiskDirectory class. This template will be associated with a TreeViewItem object, which essentially displays a DiskDirectory object. HierarchicalDataTemplate has an ItemsSource property for the source of its Items collection. This is the Subdirectories property of DiskDirectory.

The next step is to build a visual tree based on FrameworkElementFactory objects. To keep this example simple, the visual tree for the TreeViewItem is merely a TextBlock. The Text property of this TextBlock is bound with the Name property from DiskDirectory.

The constructor continues by creating an object of type DiskDirectory for the system drive. (Again, I'm keeping the example simple by not getting all the drives.) A single TreeViewItem is created with its Header property set to the Name property of the DiskDirectory object, its ItemsSource property set to the Subdirectories property of the DiskDirectory object, and its ItemTemplate property set to the HierarchicalDataTemplate we've just created. Thus, children of this root TreeViewItem will be based on the Subdirectories property of the created DiskDirectory object, but these children will be displayed using the HierarchicalDataTemplate, and this template describes where all future items come from and how they are displayed. The TreeView displays a directory tree efficiently with no event handler.

Although this program shows how to use this technique when every TreeViewItem is the same sort of object, it's also possible to use it with tree views where parent and child items may be different types. I show more template examples in Chapter 25.

The final TreeView example in this chapter uses the control to display all the Windows Presentation Foundation public classes that descend from DispatcherObject. The TreeView is ideal for this job because the items can be shown in a visual hierarchy that parallels the inheritance hierarchy. Although the program displays names of classes, let's make the code somewhat generalized by writing a class that displays the name associated with any Type object. The TypeTreeViewItem class inherits from TreeViewItem and includes a property named Type. Of course, the type of the Type property is Type.

TypeTreeViewItem.cs

//------------------------------------------------- // TypeTreeViewItem.cs (c) 2006 by Charles Petzold //------------------------------------------------- using System; using System.Windows; using System.Windows.Controls; namespace Petzold.ShowClassHierarchy {     class TypeTreeViewItem : TreeViewItem     {         Type typ;         // Two constructors.         public TypeTreeViewItem()         {         }         public TypeTreeViewItem(Type typ)         {             Type = typ;         }         // Public Type property of type Type.         public Type Type         {             set             {                 typ = value;                 if (typ.IsAbstract)                     Header = typ.Name + " (abstract)";                 else                     Header = typ.Name;             }             get             {                 return typ;             }         }     } } 



The program has two constructors to let you set the Type property when creating the object or later. Notice that the set accessor of the Type property sets the Header property of the TreeViewItem to the Name property of the Type object, possibly with the word abstract in parentheses. For example, you could create an object of TypeTreeViewItem and set the Type property like this:

TypeTreeViewItem item = new TypeTreeViewItem(); item.Type = typeof(Button); 


The item displays the simple text "Button", but the actual Type object associated with this item would still be available from the Type property. (The set accessor of the Type property could have set Header to the Type object itself, but in that case the Type object would have been displayed using the ToString method of the Type class, and ToString returns the fullyqualified namefor example, "System.Windows.Controls.Button".)

The next step in creating this program is defining a class named ClassHierarchyTreeView that inherits from TreeView.

ClassHierarchyTreeView.cs

[View full width]

//---------------------------------------------------- // ClassHierarchyTreeView (c) 2006 by Charles Petzold //---------------------------------------------------- using System; using System.Collections.Generic; using System.Reflection; using System.Windows; using System.Windows.Controls; namespace Petzold.ShowClassHierarchy { public class ClassHierarchyTreeView : TreeView { public ClassHierarchyTreeView(Type typeRoot) { // Make sure PresentationCore is loaded. UIElement dummy = new UIElement(); // Put all the referenced assemblies in a List. List<Assembly> assemblies = new List<Assembly>(); // Get all referenced assemblies. AssemblyName[] anames = Assembly.GetExecutingAssembly ().GetReferencedAssemblies(); // Add to assemblies list. foreach (AssemblyName aname in anames) assemblies.Add(Assembly.Load(aname)); // Store descendants of typeRoot in a sorted list. SortedList<string, Type> classes = new SortedList<string, Type>(); classes.Add(typeRoot.Name, typeRoot); // Get all the types in the assembly. foreach (Assembly assembly in assemblies) foreach (Type typ in assembly .GetTypes()) if (typ.IsPublic && typ. IsSubclassOf(typeRoot)) classes.Add(typ.Name, typ); // Create root item. TypeTreeViewItem item = new TypeTreeViewItem(typeRoot); Items.Add(item); // Call recursive method. CreateLinkedItems(item, classes); } void CreateLinkedItems(TypeTreeViewItem itemBase, SortedList<string, Type> list) { foreach (KeyValuePair<string, Type> kvp in list) if (kvp.Value.BaseType == itemBase .Type) { TypeTreeViewItem item = new TypeTreeViewItem(kvp.Value); itemBase.Items.Add(item); CreateLinkedItems(item, list); } } } }



Everything happens in the constructor of this class. The constructor requires a root Type that begins the hierarchy. It first creates a List object for storing all the AssemblyName objects referenced by the program, and then loads these assemblies to make Assembly objects.

The constructor then creates a SortedList object to store the Type objects representing those classes that derive from typeRoot. These Type objects are sorted by the Name property of the type. The constructor simply loops through all the Assembly objects and the Type objects in each assembly searching for these classes.

Once all the classes are accumulated in the SortedList, the constructor must create a TypeTreeViewItem for each of these classes. That's the job of the recursive CreateLinkedItems method. The first argument is TypeTreeViewItem associated with a particular class. The method simply loops through all the classes in the SortedList and finds those whose base type is that class. The method creates a new TypeTreeViewItem for each of them and adds it to the collection of the base class.

The previous two source code files and the next one are all part of the ShowClassHierarchy project. The constructor sets the window content to an object of type ClassHierarchyTreeView with a root class of DispatcherObject.

ShowClassHierarchy.cs

[View full width]

//--------------------------------------------------- // ShowClassHierarchy.cs (c) 2006 by Charles Petzold //--------------------------------------------------- using System; using System.Collections.Generic; using System.Reflection; using System.Windows; using System.Windows.Controls; namespace Petzold.ShowClassHierarchy { class ShowClassHierarchy : Window { [STAThread] public static void Main() { Application app = new Application(); app.Run(new ShowClassHierarchy()); } public ShowClassHierarchy() { Title = "Show Class Hierarchy"; // Create ClassHierarchyTreeView. ClassHierarchyTreeView treevue = new ClassHierarchyTreeView( typeof(System.Windows .Threading.DispatcherObject)); Content = treevue; } } }



You might be tempted to replace the reference to DispatcherObject with typeof(Object) to get an even bigger class hierarchy, but the logic in ClassHierarchyTreeView breaks down when two classes in different namespaces have the same name, which is the case when you begin with Object.

If the TreeView control resembles the left side of Windows Explorer, the ListView control comes closest to resembling the right side. In Windows Explorer, you can choose different views: Icons, Tiles, List, Thumbnails, or Details. You've already seen in Chapter 13 how you can define a template for the ListBox and display objects in different formats, so for many displays of objects you can use a ListBox with a custom template. The tough one, however, is the Details view, because that requires multiple columns and column headings. That's where ListView comes to the rescue.

The ListView control derives directly from ListBox. (Also, ListViewItem derives directly from ListBoxItem.) Just like ListBox, ListView has a property named Items that stores the objects displayed by the control. ListView also has an ItemsSource property that you can set to an array or other collection of objects. ListView adds just one additional property to ListBox, a property named View of type ViewBase. If View is null, ListView basically functions just like ListBox.

Currently only one class derives from ViewBase, and that's GridView, which displays objects in multiple columns with column headings. The crucial property of GridView is Columns, an object of type GridViewColumnCollection. For each column you want displayed, you'll create an object of type GridViewColumn and add it to the Columns collection. The GridViewColumn object indicates the column heading, the column width, and the property of the item you want displayed in that column.

For example, suppose you've set the Items collection of your ListView control to objects of type Personnel, a class that has properties named FirstName, LastName, EmailAddress, and so forth. In defining each column of the ListView, you indicate the particular property of Personnel (FirstName, LastName, or whatever) you want displayed in that column. You identify the property with a binding.

The first program I want to show you displays all the properties and values from the SystemParameters class. The SystemParameters class has numerous static properties such as MenuBarHeight and IsMouseWheelPresent. These particular properties indicate the default height of the application menu and whether or not the mouse attached to the computer has a mouse wheel.

The first step in displaying information in a GridView is to define a class describing the items you want to display. I want two columns in the GridView: the first for the name of the property and the second for the value. The class for storing each item I named SystemParams.

SystemParam.cs

//-------------------------------------------- // SystemParam.cs (c) 2006 by Charles Petzold //-------------------------------------------- namespace Petzold.ListSystemParameters {     public class SystemParam     {         string strName;         object objValue;         public string Name         {             set { strName = value; }             get { return strName; }         }         public object Value         {             set { objValue = value; }             get { return objValue; }         }         public override string ToString()         {             return Name + "=" + Value;         }     } } 



The ListSystemParameters class inherits from Window and creates a ListView control that fills its client area. It then creates a GridView object and sets that to the View property of the ListView.

ListSystemParameters.cs

[View full width]

//--------- -------------------------------------------- // ListSystemParameters.cs (c) 2006 by Charles Petzold //------------------------------------------------ ----- using System; using System.Collections.Generic; // for experimentation using System.ComponentModel; // for experimentation using System.Reflection; using System.Windows; using System.Windows.Data; using System.Windows.Input; using System.Windows.Controls; using System.Windows.Media; namespace Petzold.ListSystemParameters { class ListSystemParameters : Window { [STAThread] public static void Main() { Application app = new Application(); app.Run(new ListSystemParameters()); } public ListSystemParameters() { Title = "List System Parameters"; // Create a ListView as content of the window. ListView lstvue = new ListView(); Content = lstvue; // Create a GridView as the View of the ListView. GridView grdvue = new GridView(); lstvue.View = grdvue; // Create two GridView columns. GridViewColumn col = new GridViewColumn(); col.Header = "Property Name"; col.Width = 200; col.DisplayMemberBinding = new Binding ("Name"); grdvue.Columns.Add(col); col = new GridViewColumn(); col.Header = "Value"; col.Width = 200; col.DisplayMemberBinding = new Binding ("Value"); grdvue.Columns.Add(col); // Get all the system parameters in one handy array. PropertyInfo[] props = typeof (SystemParameters).GetProperties(); // Add the items to the ListView. foreach (PropertyInfo prop in props) if (prop.PropertyType != typeof (ResourceKey)) { SystemParam sysparam = new SystemParam(); sysparam.Name = prop.Name; sysparam.Value = prop.GetValue (null, null); lstvue.Items.Add(sysparam); } } } }



The first GridViewColumn is for displaying the property names of the SystemParameters class. The Header property is "Property Name" and the Width property is 200 device-independent units. The DisplayMemberBinding property of GridViewColumn must be set to a binding indicating the property of the data to display, in this case a property of SystemParam. This column should display the Name property of SystemParam, so:

col.DisplayMemberBinding = new Binding("Name"); 


Similarly, the second column is given a Header property of "Value" and bound with the Value property of SystemParams.

So far, the ListView has no items. The constructor concludes by using reflection to obtain an array of PropertyInfo objects from SystemParameters. For each property, the program creates an object of type SystemParam and assigns the Name property from the Name property of PropertyInfo and the Value property from the GetValue method of PropertyInfo. Each item is added to the Items collection.

Notice that the logic skips all properties in SystemParameters of type ResourceKey. Each property in the class exists in two forms. For example, MenuBarHeight is of type double, and MenuBarHeightKey is of type ResourceKey. The properties of type ResourceKey are mostly for use in XAML, as you'll discover in Chapter 21.

If you delete the program statement

lstvue.View = grdvue; 


the View property remains at its default null value and the ListView behaves just a ListBox. The items are displayed with strings from the ToString method of SystemParem, which certainly gives you all the information but not very attractively.

One problem with the ListSystemParameters program is that the items are not sorted. There are a couple ways to fix that problem. One approach is to sort the PropertyInfo array before the foreach loop. For this job you'll need a small class that implements the IComparer interface and has a Compare method to compare two PropertyInfo items. Here's a simple implementation of such a class:

class PropertyInfoCompare : IComparer<PropertyInfo> {     public int Compare(PropertyInfo prop1, PropertyInfo prop2)     {         return string.Compare(prop1.Name, prop2.Name);     } } 


The ListSystemParameters.cs file already includes a using directive for System.Collections.Generic. To sort the PropertyInfo array using the PropertyInfoCompare class, you simply call:

Array.Sort(props, new PropertyInfoCompare()); 


A second approach requires a using directive for the System.ComponentModel namespace (which ListSystemParameters.cs already has). You create a SortDescription object referencing a property of the items you want sorted, and add it to the SortDescriptions collection defined by ItemCollection:

lstvue.Items.SortDescriptions.Add(     new SortDescription("Name", ListSortDirection.Ascending)); 


A third approach involves a SortedList collection. This approach is shown in the next version of the program. It defines a SortedList with string objects as keys and SystemParam objects as values:

SortedList<string, SystemParam> sortlist = new SortedList<string, SystemParam>(); 


The program then fills up this SortedList from the PropertyInfo array:

foreach (PropertyInfo prop in props)     if (prop.PropertyType != typeof(ResourceKey))     {         SystemParam sysparam = new SystemParam();         sysparam.Name = prop.Name;         sysparam.Value = prop.GetValue(null, null);         sortlist.Add(prop.Name, sysparam);     } 


Notice that the property name is also the key name of the SortedList, and hence the item used for sorting. The final step is to just set the Values collection of the SortedList to the ItemsSource property of the ListView:

lstvue.ItemsSource = sortlist.Values; 


The Values property of the SortedList is the collection of sorted SystemParam objects.

This second version of the program also corrects a little problem with the second column. Because it's displaying various values, it makes more sense to right-justify the contents. There are no alignment settings on the columns. Instead, a more generalized solution involves templates.

This version of the program creates a DataTemplate, initializes it with a visual tree consisting of a right-justified TextBlock, and then sets it to the CellTemplate property of the second GridViewColumn:

ListSortedSystemParameters.cs

[View full width]

//--------- -------------------------------------------------- // ListSortedSystemParameters.cs (c) 2006 by Charles Petzold //------------------------------------------------ ----------- using Petzold.ListSystemParameters; // for SystemParam using System; using System.Collections.Generic; using System.Reflection; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; namespace Petzold.ListSortedSystemParameters { public class ListSortedSystemParameters : Window { [STAThread] public static void Main() { Application app = new Application(); app.Run(new ListSortedSystemParameters()); } public ListSortedSystemParameters() { Title = "List Sorted System Parameters"; // Create a ListView as content of the window. ListView lstvue = new ListView(); Content = lstvue; // Create a GridView as the View of the ListView. GridView grdvue = new GridView(); lstvue.View = grdvue; // Create two GridView columns. GridViewColumn col = new GridViewColumn(); col.Header = "Property Name"; col.Width = 200; col.DisplayMemberBinding = new Binding ("Name"); grdvue.Columns.Add(col); col = new GridViewColumn(); col.Header = "Value"; col.Width = 200; grdvue.Columns.Add(col); // Create DataTemplate for second column. DataTemplate template = new DataTemplate(typeof(string)); FrameworkElementFactory factoryTextBlock = new FrameworkElementFactory(typeof (TextBlock)); factoryTextBlock.SetValue(TextBlock .HorizontalAlignmentProperty, HorizontalAlignment.Right); factoryTextBlock.SetBinding(TextBlock .TextProperty, new Binding("Value")); template.VisualTree = factoryTextBlock; col.CellTemplate = template; // Get all the system parameters in one handy array. PropertyInfo[] props = typeof (SystemParameters).GetProperties(); // Create a SortedList to hold the SystemParam objects. SortedList<string, SystemParam> sortlist = new SortedList<string, SystemParam>(); // Fill up the SortedList from the PropertyInfo array. foreach (PropertyInfo prop in props) if (prop.PropertyType != typeof (ResourceKey)) { SystemParam sysparam = new SystemParam(); sysparam.Name = prop.Name; sysparam.Value = prop.GetValue (null, null); sortlist.Add(prop.Name, sysparam); } // Set the ItemsSource property of the ListView. lstvue.ItemsSource = sortlist.Values; } } }



Notice that the Text property of the TextBlock is bound to the Value property of SystemParam, so the normal DisplayMemberBinding is no longer allowed.

When I was exploring dependency properties for Chapter 8, I wrote a small console program for myself that used reflection to obtain information about the dependency properties that some WPF classes defined. But I wanted something more generalized, and that was the origin of the ClassHierarchyTreeView (described earlier in this chapter) and the DependencyPropertyListView (coming up). The final program in this chapter is named ExploreDependencyProperties and it unites these two controls in a single window separated by a splitter. When you select a class from the ClassHierarchyTreeView, the DependencyPropertyListView shows the dependency properties defined by that class, including the FrameworkMetadata flags, if any.

DependencyPropertyListView inherits from ListView and displays a collection of objects of type DependencyProperty. Each column is bound to a particular property of DependencyProperty. For example, the first column is bound to the Name property, and the second to the OwnerType property.

But here the problems begin. If you just bind a column in the ListView to the OwnerType property of DependencyProperty, you'll see strings like "System.Windows.Controls.Button." That's hard to read. Instead, I wanted the column to contain just "Button." Fortunately, there's a way to do this. When you define a DataTemplate object for the CellTemplate property of the GridViewColumn object, you also define a Binding that links the property you want to display with the element that displays it. The Binding class defines a property named Converter that can convert this bound data to a desired format. This Converter property is of type IValueConverter, which is an interface with just two methods: Convert and ConvertBack.

So, we need a class that implements the IValueConverter interface that converts a Type object (such as System.Windows.Controls.Button) to a string ("Button"), and here it is.

TypeToString.cs

[View full width]

//--------------------------------------------- // TypeToString.cs (c) 2006 by Charles Petzold //--------------------------------------------- using System; using System.Globalization; using System.Windows.Data; namespace Petzold.ExploreDependencyProperties { class TypeToString : IValueConverter { public object Convert(object obj, Type type, object param, CultureInfo culture) { // This is the class name without this namespace. return (obj as Name).Type; } public object ConvertBack(object obj, Type type, object param, CultureInfo culture) { return null; } } }



The object to be converted enters the Convert method through the first parameter. In the usage of this class, this object will be of type Type. It's cast to a Type object and then the Name property provides the name without the namespace. In my application, the ConvertBack method is never used, so it can simply return null. This conversion is also good for displaying the PropertyType property of DependencyProperty.

As you may recall, DependencyProperty defines a property named DefaultMetadata of type PropertyMetadata. This PropertyMetadata object has some useful information in it, particularly the DefaultValue property. However, many elements define their metadata by using an object of type FrameworkPropertyMetadata, which has the essential properties AffectsMeasure, AffectsArrange, AffectsRender, and others. These metadata properties are often set through a constructor using a FrameworkPropertyMetadataOptions enumeration, and this is what I focused on. I didn't want to devote a whole bunch of columns showing Boolean values of AffectsMeasure, AffectsArrange, and so on. I wanted one column that would show all the FrameworkPropertyMetadataOptions members used to create the metadata in the first place.

That's the purpose of this conversion class. The Convert method converts an object of the enumeration type FrameworkPropertyMetadata to an object of type FrameworkPropertyMetadataOptions.

MetadataToFlags.cs

[View full width]

//------------------------------------------------ // MetadataToFlags.cs (c) 2006 by Charles Petzold //------------------------------------------------ using System; using System.Globalization; using System.Windows; using System.Windows.Data; namespace Petzold.ExploreDependencyProperties { class MetadataToFlags : IValueConverter { public object Convert(object obj, Type type, object param, CultureInfo culture) { FrameworkPropertyMetadataOptions flags = 0; FrameworkPropertyMetadata metadata = obj as FrameworkPropertyMetadata; if (metadata == null) return null; if (metadata.AffectsMeasure) flags |= FrameworkPropertyMetadataOptions.AffectsMeasure; if (metadata.AffectsArrange) flags |= FrameworkPropertyMetadataOptions.AffectsArrange; if (metadata.AffectsParentMeasure) flags |= FrameworkPropertyMetadataOptions.AffectsParentMeasure; if (metadata.AffectsParentArrange) flags |= FrameworkPropertyMetadataOptions.AffectsParentArrange; if (metadata.AffectsRender) flags |= FrameworkPropertyMetadataOptions.AffectsRender; if (metadata.Inherits) flags |= FrameworkPropertyMetadataOptions.Inherits; if (metadata.OverridesInheritanceBehavior) flags |= FrameworkPropertyMetadataOptions. OverridesInheritanceBehavior; if (metadata.IsNotDataBindable) flags |= FrameworkPropertyMetadataOptions.NotDataBindable; if (metadata.BindsTwoWayByDefault) flags |= FrameworkPropertyMetadataOptions.BindsTwoWayByDefault; if (metadata.Journal) flags |= FrameworkPropertyMetadataOptions.Journal; return flags; } public object ConvertBack(object obj, Type type, object param, CultureInfo culture) { return new FrameworkPropertyMetadata(null, (FrameworkPropertyMetadataOptions)obj); } } }



When this converted item is actually displayed, the ToString method defined by the Enum structure formats the enumeration into readable comma-separated members.

It's now time to look at DependencyPropertyListView. The class derives from ListView and defines a property named Type, backed by the dependency property TypeProperty. Set the Type property of this ListView and the control displays all the DependencyProperty objects defined by that Type. The OnTypePropertyChanged method is responsible for using reflection to obtain all the DependencyProperty objects and putting them in a SortedList collection that it uses to set the ItemsSource property of ListView.

DependencyPropertyListView.cs

[View full width]

//--------- -------------------------------------------------- // DependencyPropertyListView.cs (c) 2006 by Charles Petzold //------------------------------------------------ ----------- using System; using System.Collections.Generic; using System.Reflection; using System.Windows; using System.Windows.Controls; using System.Windows.Data; namespace Petzold.ExploreDependencyProperties { public class DependencyPropertyListView : ListView { // Define dependency property for Type. public static DependencyProperty TypeProperty; // Register dependency property in static constructor. static DependencyPropertyListView() { TypeProperty = DependencyProperty .Register("Type", typeof(Type), typeof (DependencyPropertyListView), new PropertyMetadata(null, new PropertyChangedCallback(OnTypePropertyChanged))); } // Static method called when TypeProperty changes. static void OnTypePropertyChanged (DependencyObject obj, DependencyPropertyChangedEventArgs args) { // Get the ListView object involved. DependencyPropertyListView lstvue = obj as DependencyPropertyListView; // Get the new value of the Type property. Type type = args.NewValue as Type; // Get rid of all the items currently stored by the ListView. lstvue.ItemsSource = null; // Get all the DependencyProperty fields in the Type object. if (type != null) { SortedList<string, DependencyProperty> list = new SortedList<string, DependencyProperty>(); FieldInfo[] infos = type.GetFields(); foreach (FieldInfo info in infos) if (info.FieldType == typeof (DependencyProperty)) list.Add(info.Name, (DependencyProperty)info.GetValue(null)); // Set the ItemsSource to the list. lstvue.ItemsSource = list.Values; } } // Public Type property. public Type Type { set { SetValue(TypeProperty, value); } get { return (Type)GetValue (TypeProperty); } } // Constructor. public DependencyPropertyListView() { // Create a GridView and set to View property. GridView grdvue = new GridView(); this.View = grdvue; // First column displays the 'Name' property of DependencyProperty. GridViewColumn col = new GridViewColumn(); col.Header = "Name"; col.Width = 150; col.DisplayMemberBinding = new Binding ("Name"); grdvue.Columns.Add(col); // Second column is labeled 'Owner'. col = new GridViewColumn(); col.Header = "Owner"; col.Width = 100; grdvue.Columns.Add(col); // Second column displays 'OwnerType' of DependencyProperty. // This one requires a data template. DataTemplate template = new DataTemplate(); col.CellTemplate = template; // A TextBlock will display the data. FrameworkElementFactory elTextBlock = new FrameworkElementFactory(typeof(TextBlock)); template.VisualTree = elTextBlock; // Bind the 'OwnerType' property of DependencyProperty // with the Text property of the TextBlock // using a converter of TypeToString. Binding bind = new Binding("OwnerType"); bind.Converter = new TypeToString(); elTextBlock.SetBinding(TextBlock .TextProperty, bind); // Third column is labeled 'Type'. col = new GridViewColumn(); col.Header = "Type"; col.Width = 100; grdvue.Columns.Add(col); // This one requires a similar template to bind with 'PropertyType'. template = new DataTemplate(); col.CellTemplate = template; elTextBlock = new FrameworkElementFactory(typeof(TextBlock)); template.VisualTree = elTextBlock; bind = new Binding("PropertyType"); bind.Converter = new TypeToString(); elTextBlock.SetBinding(TextBlock .TextProperty, bind); // Fourth column labeled 'Default' displays // DefaultMetadata.DefaultValue. col = new GridViewColumn(); col.Header = "Default"; col.Width = 75; col.DisplayMemberBinding = new Binding ("DefaultMetadata.DefaultValue"); grdvue.Columns.Add(col); // Fifth column is similar. col = new GridViewColumn(); col.Header = "Read-Only"; col.Width = 75; col.DisplayMemberBinding = new Binding ("DefaultMetadata.ReadOnly"); grdvue.Columns.Add(col); // Sixth column, ditto. col = new GridViewColumn(); col.Header = "Usage"; col.Width = 75; col.DisplayMemberBinding = new Binding ("DefaultMetadata.AttachedPropertyUsage"); grdvue.Columns.Add(col); // Seventh column displays metadata flags. col = new GridViewColumn(); col.Header = "Flags"; col.Width = 250; grdvue.Columns.Add(col); // A template is required to convert using MetadataToFlags. template = new DataTemplate(); col.CellTemplate = template; elTextBlock = new FrameworkElementFactory(typeof(TextBlock)); template.VisualTree = elTextBlock; bind = new Binding("DefaultMetadata"); bind.Converter = new MetadataToFlags(); elTextBlock.SetBinding(TextBlock .TextProperty, bind); } } }



This class's constructor is mainly responsible for defining all the columns. Some of them are simple, and some involve the two converters I discussed.

Finally, the ExploreDependencyProperties class puts it all together. It creates a ClassHierarchyTreeView and a DependencyPropertyListView and puts a GridSplitter in between.

ExploreDependencyProperties.cs

[View full width]

//--------- --------------------------------------------------- // ExploreDependencyProperties.cs (c) 2006 by Charles Petzold //------------------------------------------------ ------------ using Petzold.ShowClassHierarchy; // for ClassHierarchyTreeView using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; namespace Petzold.ExploreDependencyProperties { public class ExploreDependencyProperties : Window { [STAThread] public static void Main() { Application app = new Application(); app.Run(new ExploreDependencyProperties()); } public ExploreDependencyProperties() { Title = "Explore Dependency Properties"; // Create Grid as content of window. Grid grid = new Grid(); Content = grid; // Three column definitions for Grid. ColumnDefinition col = new ColumnDefinition(); col.Width = new GridLength(1, GridUnitType.Star); grid.ColumnDefinitions.Add(new ColumnDefinition()); col = new ColumnDefinition(); col.Width = GridLength.Auto; grid.ColumnDefinitions.Add(col); col = new ColumnDefinition(); col.Width = new GridLength(3, GridUnitType.Star); grid.ColumnDefinitions.Add(col); // ClassHierarchyTreeView goes on left side. ClassHierarchyTreeView treevue = new ClassHierarchyTreeView(typeof(DependencyObject)); grid.Children.Add(treevue); Grid.SetColumn(treevue, 0); // GridSplitter goes in the center cell. GridSplitter split = new GridSplitter(); split.HorizontalAlignment = HorizontalAlignment.Center; split.VerticalAlignment = VerticalAlignment.Stretch; split.Width = 6; grid.Children.Add(split); Grid.SetColumn(split, 1); // DependencyPropertyListView goes on right side. DependencyPropertyListView lstvue = new DependencyPropertyListView(); grid.Children.Add(lstvue); Grid.SetColumn(lstvue, 2); // Set a binding between TreeView and ListView. lstvue.SetBinding (DependencyPropertyListView.TypeProperty, "SelectedItem.Type"); lstvue.DataContext = treevue; } } }



The last two statements of the constructor set a binding between the Type property of the DependencyPropertyListView control and the Type property of the SelectedItem property of ClassHierarchyTreeView. That SelectedItem property is actually an object of type TypeTreeViewItem that conveniently defines a property named Type. The simplicity of this binding justifies all the previous work (almost).




Applications = Code + Markup. A Guide to the Microsoft Windows Presentation Foundation
Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation (Pro - Developer)
ISBN: 0735619573
EAN: 2147483647
Year: 2006
Pages: 72

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net