The September Community Technology Preview (CTP) build of WinFX, which was released at the 2005 PDC, contains numerous API changes. While the basic design and principles discussed in Programming Windows Presentation Foundation: Beta Edition are unchanged, some of the details are different, as this list discusses.
General
· The Text property on the Window class has been renamed Title.
· To set the window title via the Page class, use the WindowTitle property instead of the Text property.
· The WindowsUIAutomation assembly has been replaced with the UIAutomationProvider and UIAutomationTypes assemblies.
· The NavigationApplication class has been removed and navigation features moved to the Application class.
· VS Project Template Avalon Express Application replaced with Avalon Web Browser Application.
· VS Project Template Avalon Navigation Application dropped because the NavigationApplication class no longer exists.
· The StartingUp event on the Application class has been renamed Startup, and its argument type has renamed from StartingUpCancelEventArgs to StartupEventArgs.
· The WPF assemblies are no longer in C:/WINDOWS/Microsoft.NET/Windows/v6.0.4030, but in C:/WINDOWS/Microsoft.NET/Windows/v6.0.5070. Also, for your convenience, Visual Studio 2005 b2, with the appropriate WPF extensions installed, lists the WPF assemblies directly in the Add Reference dialog in the .NET tab.
Layout
The panels all remain the same in this release, but the Table has changed slightly.
Table
Beta 1 offered TableHeader and TableFooter elements, which could be added to a Table. These have been removed. Table now uses the more general-purpose concept of a TableRowGroup instead. A Table can contain any number of TableRowGroups, allowing ranges of rows to be styled in a consistent fashion. If you want to provide row headers and footers with a different style from the rest of the table, just put the header and footer rows into their own TableRowGroups.
TableCell no longer allows plain text content. Use a Paragraph child inside each TableCell to wrap the text.
GridSplitter
The September CTP now makes it easy to allow the user to resize a grid interactively. If you would like a particular cell or range of cells in a Grid to be resizable, just add a GridSplitter. This provides a draggable element that will change row or column sizes. By default it renders as a grey bar, but you customize its appearance by setting its Fill property.
By default, a GridSplitter will align itself to the right-hand side of its containing cells, and will resize its containing column. You can change its orientation with the HorizontalAlignment and VerticalAlignment properties just as you would with any other element. These default to Left and Stretch, but if you change them to Stretch and Top, for example, the GridSplitter will stretch across the top of the containing cell.
By default the GridSplitter will guess whether it’s meant to be resizing rows or columns based on its shape. If it is wider than it is tall, it will resize its row, otherwise it will resize its column. This means that it’s usually sufficient to set the HoritzontalAlignment and VerticalAlignment properties, and the GridSplitter will just do the right thing. But if you want, you can be explicit with the ResizeDirection property. This defaults to Auto, but you can set it to Rows or Columns.
This example arranges a TreeView, a ListBox, and a TextBox in a Grid, and allows the three controls to be resized using two GridSplitters:
<Grid xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="4*" />
</Grid.ColumnDefinitions>
<TreeView Margin="0,0,5,0" Grid.Row="0" Grid.Column="0">
<TreeViewItem Header="Foo">
<TreeViewItem Header="Spong"/>
</TreeViewItem>
<TreeViewItem Header="Bar">
<TreeViewItem Header="Quux"/>
</TreeViewItem>
</TreeView>
<GridSplitter Width="5" Grid.Row="0" Grid.Column="0" />
<ListBox Grid.Row="0" Grid.Column="1">
<TextBlock>Foo</TextBlock>
<TextBlock>Bar</TextBlock>
<TextBlock>Quux</TextBlock>
</ListBox>
<TextBox Grid.Row="1" Grid.ColumnSpan="2" Margin="0,5,0,0"/>
<GridSplitter HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="5" ResizeDirection="Rows" Grid.Row="1" Grid.ColumnSpan="2" />
</Grid>
Here are the results:
Controls
The September introduces some new controls, and also changes the way in which radio buttons are used.
TextBlock
· The TextWrap property on the TextBlock class has been replaced TextWrapping.
· The TextContent property on the TextBlock class has been renamed Text.
Radio Buttons
Beta 1 provided a RadioButtonList class to act as a container for radio buttons. This made it somewhat cumbersome to control the layout of radio buttons, so this class no longer exists. Now you just add RadioButton elements directly to any panel. By default, radio buttons will be grouped into mutually exclusive sets based on their containing panel. For example, this example contains two sets of three radio buttons, each set in a StackPanel in its own GroupBox
<StackPanel Orientation="Horizontal"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
VerticalAlignment="Top">
<GroupBox Header="Group A" Margin="10">
<StackPanel Margin="5">
<RadioButton>First choice</RadioButton>
<RadioButton>Second choice</RadioButton>
<RadioButton>Third choice</RadioButton>
</StackPanel>
</GroupBox>
<GroupBox Header="Group B" Margin="10">
<StackPanel Margin="5">
<RadioButton>First choice (B)</RadioButton>
<RadioButton>Second choice (B)</RadioButton>
<RadioButton>Third choice (B)</RadioButton>
</StackPanel>
</GroupBox>
</StackPanel>
It looks like this:
As you can see, each of the two groups allows a button to be picked.
This default behavior usually provides exactly the behavior you require. However, there are two situations in which you might want more control over the grouping. You might want two groups of radio buttons that share the same parent panel. And you might want to split one set of radio buttons across two panels. In this case, you can use the GroupName property to indicate which radio buttons are to act as mutually exclusive selection groups. We can modify the previous example like so:
<StackPanel Orientation="Horizontal"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
VerticalAlignment="Top">
<GroupBox Header="Group A" Margin="10">
<StackPanel Margin="5">
<RadioButton GroupName="RadioA">First choice</RadioButton>
<RadioButton GroupName="RadioA">Second choice</RadioButton>
<RadioButton GroupName="RadioA">Third choice</RadioButton>
</StackPanel>
</GroupBox>
<GroupBox Header="Group A (continued)" Margin="10">
<StackPanel Margin="5">
<RadioButton GroupName="RadioA">First choice</RadioButton>
<RadioButton GroupName="RadioA">Second choice</RadioButton>
<RadioButton GroupName="RadioA">Third choice</RadioButton>
</StackPanel>
</GroupBox>
</StackPanel>
Now, even though the buttons are partitioned into two GroupBoxes, only one button from all six may be selected at any time, because they all have the same GroupName.
GroupBox
A GroupBox control has been added. This contains a single element, which will usually be a layout panel of some kind. It draws a rounded border around the contents. It is a HeaderedContentControl, allowing a caption to be supplied. Its default visuals are the standard look for group boxes in windows. Here’s an example:
<GroupBox Header="Group A" Margin="10">
<TextBlock TextWrapping="Wrap" Margin="5"
Foreground="Black">
This is some text in a
<Span FontFamily="Lucida Console">GroupBox</Span>.
</TextBlock>
</GroupBox>
It looks like this:
Of course being a HeaderedContentControl, it supports the content model, both for the main body but also for the caption. The following example abuses this feature, and illustrates why you should never let Ian design your UI:
<GroupBox Margin="10">
<GroupBox.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Rectangle Fill="VerticalGradient PaleGreen White"
Grid.RowSpan="3" Grid.ColumnSpan="3" />
<Ellipse Fill="Green" Grid.Row="0" Grid.Column="0" />
<Ellipse Fill="Cyan" Grid.Row="0" Grid.Column="1" />
<Ellipse Fill="Green" Grid.Row="0" Grid.Column="2" />
<Ellipse Fill="Cyan" Grid.Row="1" Grid.Column="0" />
<Ellipse Fill="Cyan" Grid.Row="1" Grid.Column="2" />
<Ellipse Fill="Green" Grid.Row="2" Grid.Column="0" />
<Ellipse Fill="Cyan" Grid.Row="2" Grid.Column="1" />
<Ellipse Fill="Green" Grid.Row="2" Grid.Column="2" />
<TextBlock Grid.Row="1" Grid.Column="1">
This caption is a little overdone...
</TextBlock>
</Grid>
</GroupBox.Header>
<TextBlock TextWrapping="Wrap" Margin="5"
Foreground="Black">
This is some text in a
<Span FontFamily="Lucida Console">GroupBox</Span>.
</TextBlock>
</GroupBox>
Here’s the result:
TreeView
The September CTP introduces the long-awaited TreeView control. Nodes in the TreeView are represented as a hierarchy of TreeViewItem elements. These are HeaderedContentControl items, so building TreeView hierarchies is pretty similar to building Menu hierarchies. Here’s an example:
<TreeView>
<TreeViewItem Header="Foo">
<TreeViewItem Header="Spong">
<TreeViewItem Header="Quux" />
<TreeViewItem Header="Baz" />
</TreeViewItem>
</TreeViewItem>
<TreeViewItem Header="Bar">
<TreeViewItem Header="Fizz" />
<TreeViewItem Header="Bang" />
</TreeViewItem>
</TreeView>
It looks like this:
As you would expect, the TreeViewItems raise Collapsed, Expanded, Selected, and Unselected routed events, enabling you to discover when the user interacts with the tree.
Notice that by default, non-leaf items are rendered with the usual +/- symbol to indicate an expandable or collapsible node. Leaf items do not show this symbol.
This text-only view is a bit dull, but since the items are all HeaderedContentControls, they support the content model, so we can put whatever we like in there! As an unadventurous start, we can recreate the ‘icon with text’ look used with the tree control in most Win32 applications. This is best done by adding a Style for the TreeViewItem in a Resources section:
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse Width="11" Fill="Blue" Height="11" />
<TextBlock Text="{Binding}" Margin="5,0" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.Resources>
Here’s the result:
As with all controls that support the content model, the sky is the limit – you can use whatever you like as the content of each node. There is no need to be constrained by the limitations of the Win32 tree view control. (Although there is something to be said for sticking with idioms your users will be familiar with…)
ListView
The September CTP also introduces a ListView control. This supports the details view offered by the Win32 ListView, but it does not current offer the other view styles.
The ListView is best suited to displaying items using data binding. You can hard-code items into your XAML using the ListViewItem and ListViewSubItem elements, but a data-bound example demonstrates the power of the control much better, and with less effort! Here’s the markup for a ListView that displays information about running processes:
<ListView x:Name="processListView">
<ListView.View>
<DetailsView>
<DetailsView.Columns>
<Column Header="Name">
<Column.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProcessName}" />
</DataTemplate>
</Column.CellTemplate>
</Column>
<Column Header="ID">
<Column.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id}" />
</DataTemplate>
</Column.CellTemplate>
</Column>
<Column Header="Working Set">
<Column.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding WorkingSet}" />
</DataTemplate>
</Column.CellTemplate>
</Column>
</DetailsView.Columns>
</DetailsView>
</ListView.View>
</ListView>
This ListView can then be initialized with the following code in the code-behind:
Process[] processList = Process.GetProcesses();
processListView.ItemsSource = processList;
The results are shown here:
RichTextBox
In Beta 1, it was possible to preload a RichTextBox with initial content by putting plain text into your XAML:
<RichTextBox>
Initial text
</RichTextBox>
The RichTextBox now insists that you provide its content in a FlowDocument, so the example above would need to be changed to this:
<RichTextBox>
<FlowDocument>
<Paragraph>Initial text</Paragraph>
</FlowDocument>
</RichTextBox>
AccessKey and AccessText
In Beta 1, the AccessKey element was used to underline a letter to indicate that pressing Alt plus that letter will have the same effect as clicking on some control. This was a little cumbersome compared to the old ampersand syntax most Win32 and Windows Forms developers are familiar with. While a windows forms developer might use “&Click” as the caption of a button in order to underline the C, WPF required this:
<TextBlock><AccessKey>C</AccessKey>lick</TextBlock>
AccessKey has gone in the September CTP. It has been replaced with the easier-to-use AccessText element. This allows a similar approach to the ampersand syntax. However, ampersands are somewhat awkward in XAML, on account of needing to use the & entity, so an underscore is used instead:
<AccessText>_Click</AccessText>
Slider and ScrollBar
Beta 1 used different types for vertical and horizontal orientations of sliders and scrollbars. But now, instead of VerticalSlider, HorizontalSlider, VerticalScrollBar and HorizontalScrollBar, there are just two types: Slider and ScrollBar. Both have an Orientation property which may be set to either Horizontal or Vertical.
Data Binding
· The ObservableCollection<T> class has been moved from the System.Windows.Data namespace to the System.Collections.ObjectModel namespace.
· TextBox data binding changes no longer happen as the user types, but instead when focus moves out of the TextBox.
· The GetDefaultView method has been moved from the BindingOperations class to the CollectionViewSource source.
· Unlike the Beta 1 build, data binding and DataTables works well enough to use in the September CTP. Check the samples included with the book for usage.
· The XmlDataSource class has been renamed XmlDataProvider.
Filtering
In the Beta 1 build, filtering was set with a delegate on the ICollectionView interface:
void filterButton_Click(object sender, RoutedEventArgs e) {
DataSourceProvider ds = (DataSourceProvider)FindResource("Family");
ICollectionView view =
CollectionViewSource.GetDefaultView((IListSource)ds.Data);
if( view.Filter == null ) {
view.Filter = delegate(object item) {
return ((Person)item).Age >= 18;
};
}
else {
view.Filter = null;
}
}
This still works with object data sources where the ICollectionView implementation is a ListCollectionView, e.g. when the data source is an ObservableCollection<T> or an XML data source, but in the September CTP, the Filter property is ignored or setting it throws an exception for other kinds of data sources. However, if the ICollectionView implementation is an instance of the BindListCollectionView class, you can pass a filter string expression using the CustomFilter property:
void filterButton_Click(object sender, RoutedEventArgs e) {
DataSourceProvider ds = (DataSourceProvider)FindResource("Family");
BindingListCollectionView view = (BindingListCollectionView)
CollectionViewSource.GetDefaultView((IListSource)ds.Data);
if( view.CustomFilter == null ) {
view.CustomFilter = "Age >= 18";
}
else {
view.CustomFilter = null;
}
}
ObjectDataProvider
The ObjectDataSource class has been renamed ObjectDataProvider. Further, the Asynchronous property has been replaced with the IsAsynchronous property.
One other major change is that, while the TypeName property of the ObjectDataProvider (nee ObjectDataSource) is still a string instead of an {x:Type} declaration (the way the rest of WPF works when specifying types), it now requires an assembly name, even if it’s a reference to a type in the current assembly.
In Beta 1, the following declaration:
<ObjectDataProvider
x:Key="Family"
TypeName="PersonBinding.RemotePeopleLoader"
Asynchronous="True" />
becomes the following declaration in the September CTP:
<ObjectDataProvider
x:Key="Family"
TypeName="PersonBinding.RemotePeopleLoader,PersonBinding"
IsAsynchronous="True" />
By RTM, I expect (hope) it will look like this:
<ObjectDataProvider
x:Key="Family"
Type="{x:Type local:RemotePeopleLoader}"
IsAsynchronous="True" />
Custom Data Source Providers
The IDataSource interface from the System.Windows.Data namespace has been replaced with the DataSourceProvider base class. In Beta 1, you’d implement the Data property on the IDataSource interface to expose the data, like so:
class MyDataSourceProvider : DataSourceProvider {
#region IDataSource Members
public object Data {
get {
object data = null;
// get data...
return data;
}
}
public event EventHandler DataChanged;
public void Refresh() { }
#endregion
...
}
Instead, in the September CTP, you override the BeginQuery method from the DataSourceProvider base class and call the OnQueryFinished method on the base class when the data is available:
public class MyDataSourceProvider : DataSourceProvider {
protected override void BeginQuery() {
object data = null;
// get data...
base.OnQueryFinished(data);
}
...
}
Styles
Except for animation, discussed later, no changes.
Resources
Support for resource merging has been added. The build system now handles assembly manifest resources in Avalon projects in the same way as it always has for other .NET project types.
Resource Merging
ResourceDictionary now supports merging. It has a MergedDictionaries property, allowing you to add a collection of ResourceDictionaries to be merged into this dictionary. This means it is no longer necessary to merge resources manually as shown on page 216 the book in Example 6-24. Instead you can just add one ResourceDictionary to another’s MergedDictionaries, e.g.:
ResourceDictionary skinResources = new FooSkinResources();
ResourceDictionary nonSkinAppResources = new DrawingResources();
skinResources.MergedDictionaries.Add(nonSkinAppResources);
Assembly Manifest Resources
In beta 1, if you added a file as an Assembly Manifest Resource (i.e. the ‘Embedded Resource’ item type; not the same thing as the ‘Resource’ item type), the embedded resource name would be the leaf filename. If the file was in a subdirectory, that directory name would not be included. Nor would the default project namespace be incorporated.
This is inconsistent with how assembly manifest resources are built with other .NET project types. The September CTP brings WPF projects into line with other project types. The build system now puts the namespace and path name in. So if the project default namespace is Foo, and a resource called Moggie.jpg is added from an Images subdirectory, the embedded resource will now be called “Foo.Images.Moggie.jpg” instead of “Moggie.jpg”.
Graphics
Some of the graphics APIs have been simplified in the September CTP.
FlowDirection
The FlowDirection enumeration has been trimmed down. It used to offer several text flow directions that were not supported. This enumeration now contains only the two directions that are supported: LeftToRight and RightToLeft.
PathFigure Start and Close
In beta 1, there were special segment types called StartSegment and CloseSegment. StartSegment was required on every PathFigure to indicate the initial point of the path. Since all path figures are required to have a starting point, this is now indicated by the PathFigureStartPoint property – you no longer need a special ‘segment’ just to indicate the starting point. Not only is this simpler (one fewer segment required in every PathFigure) it also makes more sense – the starting point wasn’t really a ‘segment’ as such, it was just a point.
CloseSegment has also been removed, and instead PathFigure has an IsClosed property to indicate whether the figure is to be closed or open.
ArcSegment
The ArcSegment.XRotation has been renamed RotationAngle. (The old name was slightly misleading – there was no particular reason to label it as ‘X’ rotation.)
The LargeArc property us now called IsLargeArc, to bring it in line with standard .NET class library naming conventions.
The SweepFlag is now called SweepDirection and is now an enumeration rather than a Boolean. Instead of the cryptic True or False values, it now uses rather more meaningful Clockwise and Counterclockwise values.
CombinedGeometry
The CombinedGeometry class’s CombineMode property is now called GeometryCombineMode.
Animation
The biggest change with animation is that storyboards no longer live in the Storyboards property element. Instead, all storyboards now live inside either the Triggers section, or a Resources section.
In a related change, animations no longer start from whenever the first window’s content got loaded. This is how beta 1 worked, and it meant animations in subsequently opened windows could easily be finished before the window even opened! To make the animation start when the window appears, just trigger it from the Loaded event. Here’s an example:
<Window ...>
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="myEllipse"
Storyboard.TargetProperty="(Ellipse.Width)"
From="10" To="300" Duration="0:0:5"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Window.Triggers>
<Ellipse x:Name="myEllipse" Fill="Red" Height="100" />
</Window>
Animations can now be applied to any element, not just top-level elements, styles, and templates. All elements have a Triggers section, and animations can be added anywhere.
You no longer need to find a ClockController to start an animation from code. You can just call Begin directly on a Storyboard.
Triggered animations now support ‘handoff’. In beta 1, if one animation started while another was in progress on the same property, the two animations would compose. Depending on which combination of the By, From, and To, properties was used, or whether keyframes were used, this could often lead to a visible interruption in animation. As of the September CTP, handoff is now used.
Handoff means that instead of using the ‘base’ value, the currently animated value is used as a reference point. The base value is the value that a property would have if no animations were in effect. Discontinuities in animation often resulted from a new animation taking either a fixed value or the base value as its starting point, rather than whatever the current value is. (The alternative, composition, often produced unexpected effects.) So now, if an animation is triggered, it will take as its effective base value whatever the current value animated of the property is, rather than its true base value.
The SetterTimeline element has been removed. The target of an animation is now specified using attached properties:
<DoubleAnimation Storyboard.TargetName="myEllipse"
Storyboard.TargetProperty="(Ellipse.Width)"
From="10" To="300" Duration="0:0:5" />
Custom Controls
The ThemeStyleKeyProperty property has been renamed DefaultStyleKeyProperty.
ClickOnce Deployment
· .xapp extension replaced with .wba extension and “Express Application” renamed “Web Browser Application”
· Build no longer produces a <projectName>.exe.deploy file as part of the build process; only as part of the publication process.
· The publication process no longer automatically adds a certificate; you’ll have to do it manually.
· Preliminary testing indicates that WPF locally installed ClickOnce applications can now be deployed with Intranet permissions, although Internet permissions don’t seem to work any better than beta 1 (that is: not at all).
· You’ll need to reference the System.Deployment assembly to access programmatic access to the ClickOnce API, e.g. getting to the URL arguments.
XAML
No changes.
Interop
You can host a web page directly by surfing to an URL, e.g.
<Page
x:Class="AvalonApplication3.Page1"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
WindowTitle="Page1">
<Grid>
<TextBlock FontSize="36" TextWrapping="Wrap">
Check out
<Hyperlink
NavigateUri="http://sellsbrothers.com">
sellsbrothers.com
</Hyperlink>.
</TextBlock>
</Grid>
</Page>
ElementHost
The ElementHost class’s AddChild method has been replaced with the Add method on the Controls collection of the ElementHost class.
The following in Beta 1:
ElementHost host = ...;
System.Windows.Controls.Button button = ...;
host.AddChild(button);
is replaced with the following in the September CTP:
ElementHost host = ...;
System.Windows.Controls.Button button = ...;
host.Controls.Add(button);
WindowsFormsHost
In Beta 1, child Windows Forms controls were added to a WindowsFormsHost element to the Controls collection:
<wfi:WindowsFormsHost>
<wfi:WindowsFormsHost.Controls>
<wf:DataGridView ... />
...
</wfi:WindowsFormsHost.Controls>
</wfi:WindowsFormsHost>
In the September CTP, this no longer compiles because you’re only allowed a single child:
<wfi:WindowsFormsHost>
<wf:DataGridView ... />
</wfi:WindowsFormsHost>
Async
No changes.