WPF Commands – A Pattern that Works

Using commands in WPF can be frustrating. Having so many different ways to do commanding in WPF leads to some confusion. There are multiple ways to wire up commands that are equally correct, though some patterns are cleaner than others.

Below is a pattern that works.

Create static property to expose command

Making the command a property rather than a variable makes the syntax in XAML a bit cleaner.  Using RoutedUICommand allows setting the text for the command. This is useful if you want to use the command in multiple places.

Here’s code for creating the command, e.g. in a MainWindow class:

private static RoutedUICommand _pressMeCommand = new RoutedUICommand("Press Me", "PressMe", typeof(MainWindow));
public static RoutedUICommand PressMeCommand
{
    get { return _pressMeCommand; }
}

Add CanExecute and Executed handlers

Add code in code-behind (or ViewModel) for handlers. Below, we have private handlers in MainWindow. In this example, we have a boolean indicating whether the command can be executed. You could also put the logic directly in CanExecute, if it’s not used elsewhere.

private void PressMe_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = CowboyCanTalk;
}

private void PressMe_Executed(object sender, ExecutedRoutedEventArgs e)
{
    MessageBox.Show("Howdy howdy I'm a cowboy");
}

Set up command binding

You can do this from code or XAML.  Doing it in XAML is perhaps a bit cleaner. Below is an example of binding the above command to its handlers from XAML (for MainWindow). Note that we can just use command name instead of {x:Static syntax because we made the command a property.

<Window.CommandBindings>
    <CommandBinding Command="local:MainWindow.PressMeCommand"
                    CanExecute="PressMe_CanExecute"
                    Executed="PressMe_Executed"/>
</Window.CommandBindings>

 

Wire up a button

Below, we wire a button up to the command.  You could just set Content to text directly. This would be fine to do and is simpler than what we’re doing below. But putting the text into the RoutedUICommand is helpful if you use the command in more than one place (since you specify text in just one place—in the command). And menu items automatically pick up the command text. (NOTE: If you used this pattern for Content regularly, you could just put it in a style to hide the complexity).

<Button Command="local:MainWindow.PressMeCommand"
        Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}" />

Wire up a menu item

Below, we wire up a menu item in a context menu to the same command. Note that here we do have to use the x:Static syntax. But also note that we don’t need to specify text for the menu item—it comes from the RoutedUICommand.

<Window.ContextMenu>
    <ContextMenu>
        <MenuItem Command="{x:Static local:MainWindow.PressMeCommand}" />
    </ContextMenu>
</Window.ContextMenu>

Bottom line

  • We used RoutedUICommand so that we could attach text to the command and avoid specifying text for both button and menu item
  • Binding in XAML is a (tiny) bit less code than doing it in code-behind

Other thoughts

  • You could also use a DelegateCommand pattern, passing in lambdas for both CanExecute and Executed. Use of a DelegateCommand is common in MVVM architectures.

 

 

Creating a ListBox that Shows All Predefined System Colors

The System.Windows.SystemColors class contains a series of static properties that expose the current predefined system colors.  The properties come in triplets.  For each system color Xxx, there are XxxBrush, XxxBrushKey and XxxColor properties.

We can easily create a little WPF application to display the current system colors by building a collection of brushes and then binding the collection to a ListBox.

Here’s the final result:

Below is the code that I used to create the list.

I start with a little utility class that wraps up a named system color and its Brush:

    public class ColorInfo
    {
        public string Name { get; set; }
        public Brush Brush { get; set; }

        public ColorInfo(string name, Brush brush)
        {
            Name = name;
            Brush = brush;
        }
    }

Next, I add a collection to my main Window class, which will store a list of ColorInfo objects:

    private ObservableCollection<ColorInfo> allSystemColors;
    public ObservableCollection<ColorInfo> AllSystemColors
    {
        get { return allSystemColors; }
    }

In the window’s constructor, I populate this list, using reflection.  Notice that I iterate through all of the properties in the SystemColors class and grab only those whose names end in “Brush”.

        allSystemColors = new ObservableCollection<ColorInfo>();

        Type scType = typeof(SystemColors);
        foreach (PropertyInfo pinfo in scType.GetProperties())
        {
            if (pinfo.Name.EndsWith("Brush"))
                allSystemColors.Add(new ColorInfo(pinfo.Name.Remove(pinfo.Name.Length - 5),
                                                (Brush)pinfo.GetValue(null, null)));
        }

All that remains is to bind this collection to a ListBox.  We do this in the XAML:

    <ListBox ItemsSource="{Binding ElementName=mainWindow, Path=AllSystemColors}"
		ScrollViewer.HorizontalScrollBarVisibility="Disabled"
		ScrollViewer.VerticalScrollBarVisibility="Auto" >
    	<ListBox.ItemsPanel>
    		<ItemsPanelTemplate>
    			<WrapPanel />
			</ItemsPanelTemplate>
		</ListBox.ItemsPanel>
		<ListBox.ItemTemplate>
	        <DataTemplate>
	        	<StackPanel Orientation="Vertical">
	                <Rectangle Fill="{Binding Path=Brush}" Stroke="Black" Margin="5" StrokeThickness="1" Height="74" Width="120"/>
	                <TextBlock Text="{Binding Path=Name}" HorizontalAlignment="Center"/>
				</StackPanel>
	        </DataTemplate>
	    </ListBox.ItemTemplate>
    </ListBox>

Voila!

Creating a ListBox that Shows All Predefined WPF Colors

In WPF, you can use the Colors class to access a series of predefined colors, defined as static properties of the Colors class.  You reference each color just using the name of the color.

For reference, here’s a little application that shows all colors in a ListBox.  (Credit to casperOne, in a stackoverflow post that shows how to create an object that encapsulates the list of properties in the Colors class).

Here’s the final result.  (Click on the image to see it full-sized).

The XAML for generating this list is pretty straightforward:

<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
	xmlns:local="clr-namespace:WpfApplication1"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    x:Class="WpfApplication1.MainWindow"
	x:Name="Window"
	Title="All Colors"
	Width="640" Height="480" >

    <Window.Resources>
        <ObjectDataProvider MethodName="GetType"
        ObjectType="{x:Type sys:Type}" x:Key="colorsTypeOdp">
            <ObjectDataProvider.MethodParameters>
                <sys:String>System.Windows.Media.Colors, PresentationCore,
                Version=3.0.0.0, Culture=neutral,
                PublicKeyToken=31bf3856ad364e35</sys:String>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>

        <ObjectDataProvider ObjectInstance="{StaticResource colorsTypeOdp}"
        MethodName="GetProperties" x:Key="colorPropertiesOdp">
        </ObjectDataProvider>
    </Window.Resources>

    <ListBox ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
		ScrollViewer.HorizontalScrollBarVisibility="Disabled"
		ScrollViewer.VerticalScrollBarVisibility="Auto" >
    	<ListBox.ItemsPanel>
    		<ItemsPanelTemplate>
    			<WrapPanel />
			</ItemsPanelTemplate>
		</ListBox.ItemsPanel>
		<ListBox.ItemTemplate>
        <DataTemplate>
        	<StackPanel Orientation="Vertical">
                <Rectangle Fill="{Binding Path=Name}" Stroke="Black" Margin="4" StrokeThickness="1" Height="50" Width="81"/>
                <Label Content="{Binding Path=Name}" />
			</StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
    </ListBox>
</Window>

Setting Up a Bare Minimum WPF Development Environment

Microsoft now offers Visual Studio 2010 in an Express edition–which is free.  You’re limited to a single language, but you can develop WPF applications using Express.

In this post, I walk through the exact steps to install and use Visual Studio 2010 Express (C# edition).  The install was done a clean Windows 7 machine.

To start with, go to the Microsoft Express download site.  Click on the link for the development language of your choice (e.g. C#, VB, C++) and then select a language (e.g. English).  The install package will start downloading.

This will download file named vcs_web.exe.  Double-click on the file to start the install process.

Click Yes on the UAC dialog.

Installation files will be extracted:

You’ll get to the first install dialog.  Be a good citizen and agree to send information about your “setup experience” back to Microsoft.  After all, they’re giving you a free product.

Carefully read every word in the license agreement and then agree to it.  (Yes, of course I’m being tongue-in-cheek when I say to read every word).

You can optionally decide to install Silverlight and/or SQL Server Express.  For this example, I’ll install Silverlight only.

Decide what directory to install into.  The default is fine.

Things start downloading and installing.  Sit back and wait–this will take a while.

You may need to restart in the middle of the installation.

After the reboot, the install continues.

Your patience will eventually be rewarded, as the installation completes.

Oh wait–one more restart, just for good measure.

Now you’ll see Visual Studio C# 2010 Express in your Start Menu.

When you start Visual Studio for the first time, you’ll see the Start Page.  You can click on the New Project link to create your first project.  The New Project dialog lists the various types of projects that you can create:

  • Windows Forms Application
  • WPF Application
  • Console Application
  • Class Library
  • WPF Browser Application
  • Empty Project

(Notice that we can’t create Silverlight projects–the Silverlight option that we chose during the install likely just installed the Silverlight runtime, rather than the Silverlight tools for Visual Studio).

At this point, you’re all set.  You have everything that you need, to create Windows client applications–either Windows Forms or WPF.  If you’re building something new, you’ll likely want to start with WPF.

An Application to Let You View WPF Logical Trees

In WPF, a logical tree is just the hierarchy of elements that make up your user interface.  If your user interface is defined in XAML, the logical tree is the set of elements from the XAML, organized into a tree based on their parent/child relationships.

Logical Tree Basics

The logical tree can also be thought of as a model that describes the relationships between objects at runtime.  Knowing the logical tree can be helpful in understanding:

  • Resource lookup
  • Property inheritance
  • Event routing

As an example, the logical tree for the following XAML:

 <Window x:Class="WpfApplication4.MainWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     Title="A window.." Height="350" Width="525">
     <StackPanel>
         <Button Content="Click Me" Height="23" HorizontalAlignment="Left" Width="75" Click="button1_Click" />
         <TextBox />
         <ListBox>
             <ListBoxItem Content="Barley"/>
             <ListBoxItem Content="Oats"/>
         </ListBox>
     </StackPanel>
 </Window>

can be represented by the following logical tree:

You can use the LogicalTreeHelper.GetChildren method to traverse a logical tree and enumerate all of the objects in the tree.

Every element in a logical tree is a DependencyObject, so you pass a top-level object that derives from DependencyObject into the GetChildren method.  The method returns a collection of children of that object.

 // Enumerate each immediate child of main window.  (Does NOT descend down tree)
 foreach (Object obj in LogicalTreeHelper.GetChildren(mainWindow as DependencyObject))
     Debug.WriteLine(obj.ToString());

An Application for Viewing Logical Trees

I wrote a little application that opens a specified .xaml file and then displays the logical tree from that .xaml file in a TreeView.

Here’s what the end result looks like:

To start, you just drag a .xaml file onto the surface of the application’s main window.  It then goes through the following steps:

  • Uses XamlReader.Load method to load the .xaml file into memory, creating objects to represent each element found in the .xaml file
  • Uses LogicalTreeHelper.GetChildren method to traverse the logical tree
  • For each node in the object tree, manually adds type name and (if it exists) object name to a TreeView

Current limitations:

  • Throws an exception if you try to load .xaml that includes an x:Class attribute  (you need to remove the attribute before loading the file)
  • Can’t load .xaml fragments if they don’t include the required XAML vocabulary namespaces
  • Have tested with Window as root element, but no guarantee that it works for other root elements

Where to Find WPF Logical Tree Application

You can find full source code on Codeplex, at wpflogicaltree.codeplex.com.

Under the Covers

The GUI for the application is simple–basically just a Label and a TreeView:

 <Window x:Class="DisplayWpfTrees.MainWindow"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     Title="Drag XAML Files onto This Window to See WPF Logical Tree" Height="350" Width="525" AllowDrop="True"
     Drop="Window_Drop" >
     <ScrollViewer VerticalScrollBarVisibility="Auto">
         <StackPanel Name="spMain" >
             <Label Content="Logical Tree" Height="28" Background="AliceBlue"/>
             <TreeView Name="tvLogical" />
         </StackPanel>
     </ScrollViewer>
 </Window>

You use drag and drop to tell the application to load a .xaml file.  The DragEnter event allows limiting the application to files being dropped.  The Drop event picks up the name of the file being dropped and then calls a helper class to do the loading and initialization of the TreeView.


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xaml;

namespace DisplayWpfTrees
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// DragEnter allows prohibiting disallowed formats on drop
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_DragEnter(object sender, DragEventArgs e)
        {
            if (!e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effects = DragDropEffects.None;
                e.Handled = true;
            }
        }

        /// <summary>
        /// DragEnter allows prohibiting disallowed formats on drop
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_DragOver(object sender, DragEventArgs e)
        {
            if (!e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effects = DragDropEffects.None;
                e.Handled = true;
            }
        }

        /// <summary>
        /// When file is dropped on window, dump its logical tree to our TreeView
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_Drop(object sender, DragEventArgs e)
        {
            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

            string filename = files[0];     // Only deal with first one
            FileInfo fi = new FileInfo(filename);

            if (fi.Extension == ".xaml")
            {
                tvLogical.Items.Clear();

               MyLogicalTreeHelper.LoadAndAddXAMLFile(tvLogical, filename);
            }

            e.Handled = true;
        }

    }
}

Finally, the MyLogicalTreeHelper class includes the method LoadAndAddXAMLFile, which does the work of loading the file and building up the TreeView elements.


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace DisplayWpfTrees
{
    public class MyLogicalTreeHelper
    {
        /// <summary>
        /// Load loose XAML, creating object graph, then dump that graph's logical
        ///   tree to a TreeView.
        /// </summary>
        /// <param name="tv"></param>
        /// <param name="filename"></param>
        public static void LoadAndAddXAMLFile(TreeView tv, string filename)
        {
            try
            {
                // Load loose XAML file
                DependencyObject root = (DependencyObject)LoadXAMLObjectGraphFromFile(filename);

                // Descend through logical tree, adding items to TreeView
                DumpLogicalTreeToTreeView(root, tv.Items);

                // If root object is Window, we need to call Close method to avoid process from
                //   running indefinitely.  ??
                Window win = root as Window;
                if (win != null)
                    win.Close();
            }
            catch (Exception xx)
            {
                MessageBox.Show(string.Format("Error while trying to load .xaml: {0}", xx.Message));
            }
        }

        /// <summary>
        /// Given filename, use XamlReader to construct XAML objects by reading the file
        /// </summary>
        /// <param name="xmlString"></param>
        /// <returns></returns>
        private static object LoadXAMLObjectGraphFromFile(string filename)
        {
            using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
            {
                return System.Windows.Markup.XamlReader.Load(fs);
            }
        }

        /// <summary>
        /// Add children of specified parent to TreeView, under specified node
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="lvi"></param>
        private static void DumpLogicalTreeToTreeView(object parent, ItemCollection tvItems)
        {
            TreeViewItem tvi = new TreeViewItem();
            tvi.Header = XamlElementAsString(parent);
            tvItems.Add(tvi);

            if (!(parent is DependencyObject))
                return;

            foreach (object child in LogicalTreeHelper.GetChildren(parent as DependencyObject))
            {
                DumpLogicalTreeToTreeView(child, tvi.Items);
            }
        }

        /// <summary>
        /// Render a XAML element as a string that contains full type name and optional
        ///   element Name.
        /// </summary>
        /// <param name="theElement"></param>
        /// <returns></returns>
        private static string XamlElementAsString(object theElement)
        {
            string asString;

            // Simple elements (e.g. text content), just convert to string
            if (!(theElement is DependencyObject))
                asString = theElement.ToString();

            else
            {
                // Get full type name
                Type objType = theElement.GetType();
                asString = objType.ToString();

                // Optionally, if the element has a name, append it
                PropertyInfo pi = objType.GetProperty("Name");
                if (pi != null)
                {
                    string name = (string)pi.GetValue(theElement, null);
                    if ((name != null) && (name != ""))
                        asString = string.Format("{0}: ({1})", asString, name);
                }
            }

            return asString;
        }
    }
}

Next Steps

This is all pretty clunky, but it works in a limited fashion.  Obvious next steps include:

  • Data bind the TreeView using HierarchicalDataTemplate, rather than building it up manually
  • Helper class should just build up an object model without having any knowledge of GUI elements (like the TreeView)
  • Automatically strip out x:Class attributes so that files can contain them
  • Better understanding of why process runs indefinitely if you don’t invoke Window.Close
  • Add support for also displaying the visual tree

Building A List of Types That Have Content Properties

For future reference, here’s a little code snippet that iterates through all types in a Silverlight/WPF DLL and generates a list of types that have a XAML content property.  The list is written out to a file.

As an example, we generate the list for PresentationFramework.dll, which is where the Button type resides.

 // Click handler for a System.Windows.Controls.Button
 private void button1_Click(object sender, RoutedEventArgs e)
 {
     ContentPropsInAssembly(button1.GetType().Assembly, "PresentationFramework.txt");
 }

 private void ContentPropsInAssembly(Assembly assem, string filename)
 {
     SortedDictionary<string, string> dictContentProps = new SortedDictionary<string, string>();

     // Iterate through every type in the assembly, recording those
     // that have content properties.
     foreach (Type nextType in assem.GetTypes())
     {
         ContentPropertyOfType(nextType, ref dictContentProps);
     }

     // Serialize
     using (StreamWriter sw = new StreamWriter(filename))
     {
         foreach (KeyValuePair<string, string> entry in dictContentProps)
             sw.WriteLine(string.Format("{0},{1}", entry.Key, entry.Value));
     }
 }

 // For a given type, determine if it has a content property.  If so, add the name
 // of the type and its content property to a dictionary.
 private void ContentPropertyOfType(Type t, ref SortedDictionary<string, string> dict)
 {
     foreach (object att in t.GetCustomAttributes(true))
     {
         Type attType = att.GetType();
         if (string.Equals(attType.Name, "ContentPropertyAttribute"))
         {
             ContentPropertyAttribute cpa = (ContentPropertyAttribute)att;
             dict.Add(t.FullName, cpa.Name);
             break;
         }
     }
 }

Also for the record, here is the full list of types in PresentationFramework.dll that have content properties, of the format [typename],[propertyname]

MS.Internal.Annotations.Component.HighlightComponent,Children
MS.Internal.Annotations.Component.MarkedHighlightComponent,Children
MS.Internal.AppModel.RootBrowserWindow,
MS.Internal.Controls.InkCanvasInnerCanvas,Children
MS.Internal.Documents.DocumentGridContextMenu+EditorMenuItem,Items
MS.Internal.Documents.DocumentGridContextMenu+ViewerContextMenu,Items
MS.Internal.Documents.ReaderPageViewer,Document
MS.Internal.Documents.ReaderScrollViewer,Document
MS.Internal.Documents.ReaderTwoPageViewer,Document
System.Windows.Controls.AccessText,Text
System.Windows.Controls.AdornedElementPlaceholder,Child
System.Windows.Controls.AlternationConverter,Values
System.Windows.Controls.Border,Child
System.Windows.Controls.Button,Content
System.Windows.Controls.Canvas,Children
System.Windows.Controls.CheckBox,Content
System.Windows.Controls.ComboBox,Items
System.Windows.Controls.ComboBoxItem,Content
System.Windows.Controls.ContentControl,Content
System.Windows.Controls.ContentPresenter+DefaultTemplate,VisualTree
System.Windows.Controls.ContentPresenter+UseContentTemplate,VisualTree
System.Windows.Controls.ContextMenu,Items
System.Windows.Controls.ControlTemplate,VisualTree
System.Windows.Controls.DataGrid,Items
System.Windows.Controls.DataGridCell,Content
System.Windows.Controls.DataGridCellsPanel,Children
System.Windows.Controls.DataGridComboBoxColumn+TextBlockComboBox,Items
System.Windows.Controls.Decorator,Child
System.Windows.Controls.DockPanel,Children
System.Windows.Controls.DocumentViewer,Document
System.Windows.Controls.Expander,Content
System.Windows.Controls.FlowDocumentPageViewer,Document
System.Windows.Controls.FlowDocumentReader,Document
System.Windows.Controls.FlowDocumentScrollViewer,Document
System.Windows.Controls.Frame,
System.Windows.Controls.Grid,Children
System.Windows.Controls.GridView,Columns
System.Windows.Controls.GridViewColumn,Header
System.Windows.Controls.GridViewColumnHeader,Content
System.Windows.Controls.GroupBox,Content
System.Windows.Controls.GroupItem,Content
System.Windows.Controls.HeaderedContentControl,Content
System.Windows.Controls.HeaderedItemsControl,Items
System.Windows.Controls.InkCanvas,Children
System.Windows.Controls.InkPresenter,Child
System.Windows.Controls.ItemContainerGenerator+EmptyGroupItem,Content
System.Windows.Controls.ItemsControl,Items
System.Windows.Controls.ItemsPanelTemplate,VisualTree
System.Windows.Controls.Label,Content
System.Windows.Controls.ListBox,Items
System.Windows.Controls.ListBoxItem,Content
System.Windows.Controls.ListView,Items
System.Windows.Controls.ListViewItem,Content
System.Windows.Controls.Menu,Items
System.Windows.Controls.MenuItem,Items
System.Windows.Controls.Page,Content
System.Windows.Controls.Panel,Children
System.Windows.Controls.Primitives.BulletDecorator,Child
System.Windows.Controls.Primitives.ButtonBase,Content
System.Windows.Controls.Primitives.CalendarButton,Content
System.Windows.Controls.Primitives.CalendarDayButton,Content
System.Windows.Controls.Primitives.DataGridCellsPresenter,Items
System.Windows.Controls.Primitives.DataGridColumnHeader,Content
System.Windows.Controls.Primitives.DataGridColumnHeadersPresenter,Items
System.Windows.Controls.Primitives.DataGridRowHeader,Content
System.Windows.Controls.Primitives.DataGridRowsPresenter,Children
System.Windows.Controls.Primitives.DatePickerTextBox,Text
System.Windows.Controls.Primitives.DocumentViewerBase,Document
System.Windows.Controls.Primitives.MenuBase,Items
System.Windows.Controls.Primitives.MultiSelector,Items
System.Windows.Controls.Primitives.Popup,Child
System.Windows.Controls.Primitives.RepeatButton,Content
System.Windows.Controls.Primitives.SelectiveScrollingGrid,Children
System.Windows.Controls.Primitives.Selector,Items
System.Windows.Controls.Primitives.StatusBar,Items
System.Windows.Controls.Primitives.StatusBarItem,Content
System.Windows.Controls.Primitives.TabPanel,Children
System.Windows.Controls.Primitives.ToggleButton,Content
System.Windows.Controls.Primitives.ToolBarOverflowPanel,Children
System.Windows.Controls.Primitives.ToolBarPanel,Children
System.Windows.Controls.Primitives.UniformGrid,Children
System.Windows.Controls.RadioButton,Content
System.Windows.Controls.RichTextBox,Document
System.Windows.Controls.ScrollViewer,Content
System.Windows.Controls.StackPanel,Children
System.Windows.Controls.TabControl,Items
System.Windows.Controls.TabItem,Content
System.Windows.Controls.TextBlock,Inlines
System.Windows.Controls.TextBox,Text
System.Windows.Controls.ToolBar,Items
System.Windows.Controls.ToolBarTray,ToolBars
System.Windows.Controls.ToolTip,Content
System.Windows.Controls.TreeView,Items
System.Windows.Controls.TreeViewItem,Items
System.Windows.Controls.UserControl,Content
System.Windows.Controls.Viewbox,Child
System.Windows.Controls.Viewport3D,Children
System.Windows.Controls.VirtualizingPanel,Children
System.Windows.Controls.VirtualizingStackPanel,Children
System.Windows.Controls.WrapPanel,Children
System.Windows.Data.MultiBinding,Bindings
System.Windows.Data.PriorityBinding,Bindings
System.Windows.Data.XmlDataProvider,XmlSerializer
System.Windows.DataTemplate,VisualTree
System.Windows.DataTrigger,Setters
System.Windows.Documents.AdornerDecorator,Child
System.Windows.Documents.AnchoredBlock,Blocks
System.Windows.Documents.BlockUIContainer,Child
System.Windows.Documents.Bold,Inlines
System.Windows.Documents.DocumentStructures.StoryFragment,BlockElementList
System.Windows.Documents.DocumentStructures.StoryFragments,StoryFragmentList
System.Windows.Documents.Figure,Blocks
System.Windows.Documents.FixedDocument,Pages
System.Windows.Documents.FixedDocumentSequence,References
System.Windows.Documents.FixedPage,Children
System.Windows.Documents.Floater,Blocks
System.Windows.Documents.FlowDocument,Blocks
System.Windows.Documents.Hyperlink,Inlines
System.Windows.Documents.InlineUIContainer,Child
System.Windows.Documents.Italic,Inlines
System.Windows.Documents.List,ListItems
System.Windows.Documents.ListItem,Blocks
System.Windows.Documents.NonLogicalAdornerDecorator,Child
System.Windows.Documents.PageContent,Child
System.Windows.Documents.Paragraph,Inlines
System.Windows.Documents.Run,Text
System.Windows.Documents.Section,Blocks
System.Windows.Documents.Span,Inlines
System.Windows.Documents.Table,RowGroups
System.Windows.Documents.TableCell,Blocks
System.Windows.Documents.TableRow,Cells
System.Windows.Documents.TableRowGroup,Rows
System.Windows.Documents.TextEditorContextMenu+EditorContextMenu,Items
System.Windows.Documents.TextEditorContextMenu+EditorMenuItem,Items
System.Windows.Documents.TextEditorContextMenu+ReconversionMenuItem,Items
System.Windows.Documents.Underline,Inlines
System.Windows.EventTrigger,Actions
System.Windows.FrameworkTemplate,VisualTree
System.Windows.HierarchicalDataTemplate,VisualTree
System.Windows.Media.Animation.BeginStoryboard,Storyboard
System.Windows.Media.Animation.Storyboard,Children
System.Windows.Media.Animation.ThicknessAnimationUsingKeyFrames,KeyFrames
System.Windows.MultiDataTrigger,Setters
System.Windows.MultiTrigger,Setters
System.Windows.Navigation.NavigationWindow,
System.Windows.Navigation.PageFunction`1,Content
System.Windows.Navigation.PageFunctionBase,Content
System.Windows.Shell.JumpList,JumpItems
System.Windows.Style,Setters
System.Windows.Trigger,Setters
System.Windows.VisualState,Storyboard
System.Windows.VisualStateGroup,States
System.Windows.VisualTransition,Storyboard
System.Windows.Window,Content

Session – WPF: Extensible BitmapEffects, Pixel Shaders, and WPF Graphics Futures

PDC 2008, Day #4, Session #4, 1 hr 15 mins

David Teitlebaum
Program Manager
WPF Team

My final session at PDC 2008 was a talk about the improvements in WPF graphics that are available in .NET Framework 3.5 SP1.  David also touched briefly some possible future features (i.e. that would appear in .NET Framework 4.0).

David’s main topic was to walk through the details of the new Shader Effects model, which replaces the old Bitmap Effects feature.

What are Bitmap Effects?

These are effects that are applied to an individual UI element, like a button, to create some desired visual effect.  This includes things like drop shadow, bevels and blur effects.

BitmapEffect

The BitmapEffect object was introduced in Framework 3.0 (the first WPF release).  But there were some problems with it, that led to now replacing it with Shader Effects in 3.5SP1.

Problems with BitmapEffect:

  • They were rendered in software
  • Blur operations were very slow
  • There were various limitations, including no ClearType support, no anisotripic filtering, etc.

New Shader Effects

Basic characteristics in the new Shader Effects include:

  • GPU accelerated
  • Have implemented hardware acceleration of the most popular bitmap effects
    • But did not implement outer glow
  • Can author custom hardware-accelerated bitmap effects using HLSL
  • There is a software-only fallback pipeline that is actually faster than the old Bitmap Effects
  • New Shader Effects run on most video cards
    • Require PixelShader 2.0, which is about 5 years old

How Do You Do Shader Effects?

Here’s an outline of how you use the new Shader Effect model:

  • Derive a custom class from the new ShaderEffect class (which derives from Effect)
  • You write your actual pixel shader code in HLSL, which is used for doing custom hardware-accelerated stuff using Direct3D
    • Language is C-like
    • Compiled to byte-code, consumed by video driver, runs on GPU
  • Some more details about HLSL, as used in WPF
    • DirectX 10 supports HLSL 4.0
    • WPF currently only supports Pixelshader 2.0

So what do pixel shaders really do?  They basically take in a texture (bitmap) as input, do some processing on each point, and return a revised texture as an output.

Basically, you have a main function that accepts the coordinates of the current single pixel to be mapped.  Your code then accesses the original input texture through a register, so it just uses the input parameter (X/Y coordinate) to index into the source texture.  It then does some processing on the pixel in question and returns a color value.  This resultant color value just represents—the resulting RGB color at the specified coordinate.

The final step is to create, in managed code, a class that derives from ShaderEffect and hook it up to the pixel shader code (e.g. xyz.ps file) that you wrote.  You can then apply your shader to any WPF UIElement using XAML.  (By setting the Effect property).

Direct3D Interop

David’s next topic was to talk a bit about interop’ing with Direct3D.  This just means that your WPF application can easily host Direct3D content by using a new class called D3DImage.

This was pretty cool.  David demoed displaying a Direct3D wireframe in the background (WPF 3D subsystem can’t do wireframes), with WPF GUI elements in the foreground, overlaying the background image.

The basic idea is that you create a Direct3D device in unmanaged code and then hook it to a new instance of a WPF D3DImage element, which you include in your visual hierarchy.

WPF Futures

Finally, David touched very briefly on some possible future features.  These are things that may show up in WPF 4.0 (.NET Framework 4.0), or possibly beyond that.

Some of the features likely included in WPF 4.0 include:

  • Increased graphical richness  (e.g. Pixelshader 3.0)
  • Offloading more work to the GPU
  • Better rendering quality
    • Integrate DirectWrite for text clarity
    • Layout rounding

And some of the possible post-4.0 features include:

  • Better exploitation of hardware
  • Vertex shaders
  • Shader groups
  • Shaders in WPF 3D
  • 3D improvements
  • Better media extensibility

References

You can get at David’s PDC08 slide deck for this talk here: http://mschnlnine.vo.llnwd.net/d1/pdc08/PPTX/PC07.pptx

And you can find full video from the session at:  http://mschnlnine.vo.llnwd.net/d1/pdc08/WMV-HQ/PC07.wmv