Archive for January, 2008
Taking the grid out of the WPF Property Grid
The default appearance of the WPF Property Grid is very much like the Windows Forms property grid. This keeps it consistent with the default appearance of the built-in WPF controls, so that an application built without any style overrides will be visually consistent.
The WPF control model, however, enforces a strict separation of control behaviour from control appearance. So I want to put the “property grid” appearance aside for a moment and talk about the underlying behaviour of the WPF Property Grid.
The two basic features of the control are that it breaks an object down into its properties, and that it associates an editor with each property. These two features are actually independent: really, the control associates an editor with each node or row, and breaking an object down into its properties is one of the ways we can define what the nodes are. (The others are the ItemsSource property and the AddNode methods.)
Nothing about these features mandates a grid-type display. The control is really a WPF Object Editor; it just happens that its default appearance is a WinForms-style property grid. So you can use the control in any context where you want users to be able to edit property values without you having to create and bind a control for each individual property, even if you don’t want it to look anything like a grid.
Here’s a trivial example. In this design, the user sees only one property at a time, and chooses which one from a drop-down box. You might use this kind of design if you wanted to conserve screen space and if the user was likely to repeatedly adjust one value before going on to another.
You can implement this using the WPF Property Grid using the following style:
<Style x:Key="MicroGrid" TargetType="ms:PropertyGrid"> <Setter Property="DefaultMargin" Value="0" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ms:PropertyGrid"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="120" /> <ColumnDefinition Width="6" /> <ColumnDefinition Width="120" /> </Grid.ColumnDefinitions> <ComboBox ItemsSource="{TemplateBinding BindingView}" x:Name="PropertySelector" DisplayMemberPath="Node.HumanName" IsSynchronizedWithCurrentItem="True" /> <ContentControl Grid.Column="2" Content="{Binding ElementName=PropertySelector, Path=SelectedItem}" ContentTemplateSelector="{Binding EditorSelector, RelativeSource={RelativeSource TemplatedParent}}" /> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
By replacing the grid-like template with a combo box and a ContentControl that uses the WPF Property Grid’s editor selector, this completely replaces the default grid-like appearance.
Here are two more examples, the first inspired by Josh Smith’s “organisation chart” TreeView template, and the second showing a dialog-box-like style:
The templates for these, although not complicated, are too long to post here — download the trial edition if you’re interested.
Of course, there are limits to what you can do with restyling the WPF Property Grid. It would be very hard, for example, to create a template that supported non-regular layouts. Still, there’s a lot more than just grids to the WPF Property Grid.
Sorting and grouping in the WPF Property Grid
The Windows Forms property grid control has a PropertySort property which determines whether entries are sorted alphabetically, grouped by category, or both. The WPF Property Grid has no equivalent to this. So how does the WPF Property Grid support sorting and grouping?
The WPF Property Grid accesses its rows via the BindingView collection property. This sounds pretty innocent, but where there’s a data-bound collection in WPF, there’s a collection view, and that means that external code can apply whatever sorting, filtering and grouping it requires. This might be limited to alphabetical sorting and category grouping, or it might be something more application-specific.
Here’s how to reproduce the Windows Forms behaviour:
private static void Sort(PropertyGrid grid) { SortDescription alphabetical = new SortDescription("Node.HumanName", ListSortDirection.Ascending); ICollectionView view = CollectionViewSource.GetDefaultView(grid.BindingView); view.SortDescriptions.Add(alphabetical); } private static void Group(PropertyGrid grid) { PropertyGroupDescription byCategory = new PropertyGroupDescription("Node", new NodeToCategoryConverter()); ICollectionView view = CollectionViewSource.GetDefaultView(grid.BindingView); view.GroupDescriptions.Add(byCategory); }
If this is all you want, you don’t need to write the code yourself: the IsToolBarVisible property displays a toolbar which the user can use to sort and group the grid. (If you want to use CategoryAttribute-based grouping, but don’t want to use the standard toolbar, you can still use the NodeToCategoryConverter in your own grouping code; it’s a public part of the WPF Property Grid API.)
If you need customised sorting or grouping, then this code can serve as a starting point. For example, if you’re presenting a dictionary of values (or an object with many properties which don’t have the CategoryAttribute), you could group them by the first letter of the name as follows:
public class FirstLetterConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { Node node = (Node)value; return node.HumanName.Substring(0, 1).ToUpper(); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } public partial class MyWindow : Window { private static void GroupByFirstLetter(PropertyGrid grid) { SortDescription sorter = new SortDescription("Node.HumanName", ListSortDirection.Ascending); grid.BindingView.DefaultView.SortDescriptions.Add(sorter); PropertyGroupDescription grouper = new PropertyGroupDescription("Node", new FirstLetterConverter()); grid.BindingView.DefaultView.GroupDescriptions.Add(grouper); } }
Here’s the result:
The main thing to notice is that your SortDescriptions and GroupDescriptions will be applied to the elements of the BindingView class, which are of type PropertyGridRow, so you’ll typically refer to the Node property in your sorting and grouping expressions. From here you can look at the name, the value and the underlying property metadata (such as attributes), so you’ve got a great deal of flexibility to sort and group items as your specific application requires.
For example, suppose you’re using the WPF Property Grid to display and edit a set of dates — maybe milestones in a project plan represented as a Dictionary
public partial class Window1 : Window { private void SortAndGroupGridByDate() { ICollectionView view = propertyGrid1.BindingView.DefaultView; PropertyGroupDescription grouper = new PropertyGroupDescription("Node.Value", new DateToMonthConverter()); view.GroupDescriptions.Add(grouper); SortDescription sorter = new SortDescription("Node.Value", ListSortDirection.Ascending); view.SortDescriptions.Add(sorter); } } public class DateToMonthConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is DateTime) { return ((DateTime)value).ToString("MMMM yyyy"); } else { return "Non-Dates"; } } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
This gives a result like this:
In summary, although the WPF Property Grid, unlike the Windows Forms equivalent, doesn’t have a sorting and grouping API per se, its use of the WPF collection view idiom gives you a great deal more freedom to present your data organised in a way which is appropriate to your application and your users’ needs.
Custom property editors in the WPF Property Grid
I wrote previously about editing custom types in the WPF Property Grid — how to extend the grid to provide a good editing experience for, say, PhoneNumber objects. But what if you have a particular property which is of a “known” type, but the normal editing experience isn’t appropriate?
Here’s an example. Suppose you have a PrintSettings class with an Orientation property of boolean type. That’s an awful API for you to have to deal with, and even worse for your users:
Quick, does an Orientation of true mean portrait or landscape? Who knows? Even if the designer of the PrintSettings class had used an enum instead of a boolean, you might still want to improve the editing experience by providing pictures as well as the words Portrait and Landscape, so that less print-savvy users would know what the terms meant.
Unfortunately, the type editor extensibility mechanism doesn’t help us in this case. We don’t want to change the editor used for booleans in general, because that would affect the Duplex editor as well: we just want to change it for this specific property.
In the WPF Property Grid, we can do this using property editors. You create property editors in almost exactly the same way as type editors, but they have to be wired up a little differently.
To create our custom property editor, just as with a type editor, all we have to do is create a DataTemplate which can bind to a boolean value:
<DataTemplate x:Key="OrientationEditor"> <ComboBox SelectedValue="{Binding Value}" SelectedValuePath="Tag" BorderThickness="0"> <ComboBoxItem Tag="False"> <StackPanel Orientation="Horizontal"> <Border Width="30" Height="30"> <Rectangle Width="17" Height="22" Stroke="Black" StrokeThickness="1" Fill="LightGray" /> </Border> <TextBlock Text="Portrait" VerticalAlignment="Center" /> </StackPanel> </ComboBoxItem> <ComboBoxItem Tag="True"> <StackPanel Orientation="Horizontal"> <Border Width="30" Height="30"> <Rectangle Width="22" Height="17" Stroke="Black" StrokeThickness="1" Fill="LightGray" /> </Border> <TextBlock Text="Landscape" VerticalAlignment="Center" /> </StackPanel> </ComboBoxItem> </ComboBox> </DataTemplate>
Here’s the first difference from the type editor. In the type editor template, we bound our databound elements to properties of the edited type. (In the PhoneNumber example, we had a text box bound to the CountryCode property.) But a boolean doesn’t have properties. How, then, can we reference it in our DataTemplate? The WPF Property Grid solves this by requiring property editors operating on base types to instead bind to a special pseudo-property called Value. The property grid wires things up so that the Value pseudo-property actually maps to the real property, in this case Orientation.
Now, as we did with our type editor, we have to tell the WPF Property Grid to use this template for editing the PrintSettings.Orientation property. We do this by adding a PropertyEditor declaration to the grid’s Editors property:
<ms:PropertyGrid SelectedObject="{Binding}"> <ms:PropertyGrid.Editors> <ms:PropertyEditor DeclaringType="local:PrintSettings" PropertyName="Orientation" EditorTemplate="{StaticResource OrientationEditor}" /> </ms:PropertyGrid.Editors> </ms:PropertyGrid>
Note the DeclaringType and PropertyName declarations to tell the WPF Property Grid where to use the new editor.
After adding the new editor, the grid looks like this:
Now that’s a lot more meaningful!
Editing custom types in the WPF Property Grid
The WPF Property Grid knows how to edit a lot of common types — strings, numbers, dates, colours, etc. — but what if your users need to edit a property which has a type that the WPF Property Grid doesn’t know about? For example, a person’s phone number might be represented as a PhoneNumber object rather than a string.
By default, the WPF Property Grid handles unknown types by allowing users to drill into the type, in the manner of a tree view, until they get down to something that the grid does know how to handle. This is a reasonable fallback user experience, and it means that you can work with pretty much any kind of object right out of the box, even a fairly complicated one. But it would be nice to give users a way of seeing and editing the value in place, and of supplying them with type-specific tools. For example, the phone number editor could provide the option to invoke a yellow pages Web site or a directory enquiries Web service.
The WPF Property Grid supports this through a mechanism called type editors. A type editor is simply a WPF DataTemplate. Once you tell the WPF Property Grid to associate this DataTemplate with a particular type, the grid will take care of instantiating and binding the template whenever a property of that type comes up. The bindings defined in the template will take care of propagating the changes back to the data source.
Let’s see how this works. First, let’s define our PhoneNumber class as follows:
public class PhoneNumber : INotifyPropertyChanged { public string CountryCode { ... } public string RegionCode { ... } public string Number { ... } /* ... INotifyPropertyChanged support ... */ }
Then if we ask the WPF Property Grid to display a Person with two PhoneNumber properties, we get the following:
As you can see, the grid gives the user a way to edit the phone numbers, but it’s a bit utilitarian. A user might reasonably expect to be able to view and edit a phone number in place rather than having to crack it open and edit each piece individually.
We can represent the way our users would like to work with phone numbers as a DataTemplate (omitting some cosmetic details for clarity):
<DataTemplate x:Key="PhoneNumberEditor"> <StackPanel Orientation="Horizontal"> <TextBlock Text="+" /> <TextBox Text="{Binding CountryCode, UpdateSourceTrigger=PropertyChanged}" /> <TextBlock Text=" (" /> <TextBox Text="{Binding RegionCode, UpdateSourceTrigger=PropertyChanged}" /> <TextBlock Text=") " /> <TextBox Text="{Binding Number, UpdateSourceTrigger=PropertyChanged}" /> </StackPanel> </DataTemplate>
Having done this, we now need to tell the WPF Property Grid to use this template for editing PhoneNumber-type properties. We can do this by adding a TypeEditor declaration to the grid’s Editors collection:
<ms:PropertyGrid SelectedObject="{Binding}"> <ms:PropertyGrid.Editors> <ms:TypeEditor EditedType="t:PhoneNumber" EditorTemplate="{StaticResource PhoneNumberEditor}" /> </ms:PropertyGrid.Editors> </ms:PropertyGrid>
Now when we run up the grid, it looks like this:
That’s a lot more visual. Let’s go a bit further and link to a directory enquiries Web site:
<DataTemplate x:Key="PhoneNumberEditor"> <DockPanel> <TextBlock DockPanel.Dock="Right" VerticalAlignment="Center"> <Hyperlink NavigateUri="http://..." Click="Hyperlink_Click">Search</Hyperlink> </TextBlock> <StackPanel Orientation="Horizontal"> ... as before ... </StackPanel> </DockPanel> </DataTemplate>
Here’s the result:
Of course, type editors aren’t limited to textual elements. A type editor is just a DataTemplate, so it can use any kind of WPF framework element: controls, graphics, colours, animation, even 3D. But perhaps fortunately, I can’t think of an animated 3D user interface for entering a phone number, so you’re spared that particular demo. For now.
WPF Property Grid 1.0 Released!
I’m proud to announce that today we have shipped the WPF Property Grid. This is a significant control for developers creating modern applications that leverage the excellent Microsoft Windows Presentation Foundation framework.
All the information about the WPF Property Grid can be found here: http://www.mindscape.co.nz/products/WPFPropertyGrid/
Over the coming days we will be posting a series of blog entries about the WPF Property Grid and how you can leverage this powerful control to help your applications “bring sexy back”.
As with all Mindscape products, you are welcome to try it out before making a decision about purchase. You will also find that the included samples and help guide are in-depth and provide a considerable amount of learning material to ensure that you get up and running quickly with the WPF Property Grid.
We welcome any feedback about this product, or suggestions for products that you would like to see us create.
Happy downloading!
– JD
Categories
BrainDump (1)
Community Code (4)
Events (15)
F# (11)
General (50)
Lab Samples (2)
LightSpeed (249)
MegaPack (7)
News (68)
NHibernate Designer (18)
Nightly news (40)
Phone Elements (22)
Products (87)
Projects (5)
Screencast (6)
SharePoint (3)
Silverlight (14)
Silverlight Elements (59)
SimpleDB Management Tools (20)
Visual Studio (9)
VS File Explorer (7)
Web Workbench (20)
WPF (43)
WPF Diagrams (53)
WPF Elements (91)
WPF Property Grid (32)


Tagged as

Posted by Ivan Towlson on 29 January 2008


