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

Session – Microsoft .NET Framework: Overview and Applications for Babies

PDC 2008, Day #1, Session #5, 1 hr 15 min.

Scott Hanselman

There’s no way that I was going to miss Hanselman’s talk.  I’m a big fan of his podcasts—Scott is one of the most knowledgeable tech podcasters out there and he can also be pretty entertaining.  I’m always amazed listening to Scott’s podcasts.  Some guest will be talking about the esoterics of some new platform or service and Scott will just “get it”, quickly grokking what the guy is talking about and end up summarizing it in a nice way.

Scott’s talk didn’t disappoint.  I got to the room early and got a front row seat.  Scott also wins the prize for speaking in the most comfy room at the convention center—the talk was in a cozy little theatre with cushy theatre chairs.

The goal of Scott’s talk was to take a spin around some of the newer (3.5 and 4.0) areas of the .NET Framework.  The vehicle was by extending his WPF BabySmash application to use as many features and services as possible.

BabySmash was the little application that Scott used to teach himself WPF.  He’s blogged about his adventures with BabySmash and learning WPF.

I didn’t take notes during Scott’s session, but let’s see if I can remember the different .NET technologies that Scott dabbled in with BabySmash:

  • Silverlight 2
  • Windows Mobile
  • Surface
  • ADO.NET Data Services
  • Entity Framework
  • New ASP.NET charts/graphs

Basically, Scott ported BabySmash to each of these platforms, or made use of the platform to add some new feature to BabySmash.  It was a great way, using a little app like BabySmash, to illustrate how these technologies fit together.

The grand finale was having all of Scott’s Twitter “minions” go to an online BabySmash web page, which collected metrics about which keys were being pressed.  Scott then displayed a live histogram on an ASP.NET page, showing the distribution of the keys pressed.  It’s a testament to Scott’s popularity that the graph grew quickly, with the frequency for some letters quickly moving into the thousands.

What’s even more amazing is that Scott said he hadn’t tested the Twitter app, at least in a broad/distributed way, until the talk.  Very cool.

I can’t really add much to what Scott writes himself.  So do yourself a favor and check him out at hanselman.com.

Session – Microsoft Silverlight, WPF and the Microsoft .NET Framework: Sharing Skills and Code

PDC 2008, Day #1, Session #4, 1 hr 15 min.

Ian Ellison-Taylor

This session focused on sharing code between WPF and Silverlight applications.  How easy is it to take an existing WPF application and run it in the cloud by converting it to Silverlight 2?  Conversely, how easy is it to take a Silverlight 2 application and run it locally as a WPF application?

The bottom line is that it’s really quite easy to run the same application as either a local WPF application or a cloud-based Silverlight 2 app, with just a few modifications.

Ian started with a quick summary of when you’d want to use WPF vs. Silverlight 2:

  • WPF: best for desktop apps needing maximum performance and leveraging 3D graphics on the desktop
  • Silverlight 2: best for RIAs, smaller and lighter, able to run on various platforms and in various browsers

One of the more interesting parts of the talk was Ian’s description of the history of Silverlight 2.  We know that Silverlight 2 uses a smaller version (much smaller) of the .NET Framework, which it deploys via the browser, if a client needs it.

But Ian described how, in the first attempt at a Silverlight 2 framework (WPF/e at the time), they started with the full framework and started pulling stuff out.  They quickly found, however, that it made more sense to start with a clean slate and then only pull in the bits that they needed for Silverlight 2.

Applications written in WPF or Silverlight 2 can be moved to the other platform fairly easily, but Ian said that it was a bit easier to convert Silverlight 2 apps to run as WPF than the other way around.  This makes sense—WPF apps might be using parts of the full .NET framework that aren’t supported in the Silverlight 2 subset.

Also interesting, Ian suggested that developers start by learning Silverlight 2 and then moving to WPF, rather than the other way around.  Things are done in Silverlight 2 in a much simpler way, so the learning curve will likely be shorter.  As an example, he talked about the property system, which is far more complex in WPF.

This was an excellent talk, with some nice demos.  Ian worked simultaneously on a WPF and a Silverlight 2 application, adding features to one and then moving them over to the other platform.  It was an excellent way to highlight some of the differences and the gotchas that developers will run into.  But it also showed off how similar the platforms are and how easy it is to migrate an app from one to the other.

I WPF, Therefore I Blend

If you’re a developer doing WPF development, you really need to be using Expression Blend.

Yes, I know the party line on WPF development runs something like this:

  • Every dev team should have at least 1 developer and 1 designer
  • Developers can’t design decent-looking GUIs to save their soul
  • Designers can’t be trusted with code, or anything close to code (excepting XAML)
  • Devs will open a project in Visual Studio and do all of their work there
  • Designers will open the same project in Blend and do all of their work there
  • Devs wear button-up shirts that don’t match their Dockers
  • Designers wear brand-name labels and artsy little berets

I don’t quite buy into the idea of a simple developer/designer separation, with one tool for each of them.  (I also don’t wear Dockers).

It’s absolutely true that Blend makes it easier for a designer to be part of the team and work directly on the product.  The old model was to have the designers do static mockups in Photoshop and then have your devs painstakingly reproduce the images, working in Visual Studio.  The old model sucks.

The new model, having Blend work directly with XAML and even open the same solution file as Visual Studio, is a huge advancement.  Designers get access to all of the flashy photoshoppy features in Blend, which means that they can do their magic and create something that actually looks great.  And devs will instantly get the new GUI layout when they use Visual Studio to open/run the project.

The problem that I have with the designer/developer divide is as follows.  To achieve an excellent user experience takes more than just independently creating form and function and then marrying the two together.  A designer might create GUI screens that are the most beautiful thing on the planet.  And the dev working with him might write the most efficient and elegant code-behind imaginable.  But this isn’t nearly enough to guarantee a great user experience.

User experience is all about user interaction.  Poorly done user interaction will lead to a failed or unused application far more quickly than either an ugly GUI or poorly performing code.

So what exactly is “user interaction”?  In my opinion, it’s everything in the application except for the code and the GUI.  User interaction is all about how the user uses your application to get her work done (or to create what she wants to create).  Does the application make sense to her?  Does using it feel natural?  Allow her to be efficient?  Are features discoverable?  Does the flow of the application match her existing workflow?

The only way to get user interaction correct is to know your user.  This means truly understanding the problem that your users are trying to solve, as well as what knowledge they have about the problem space.

There is an easy four step process to get at this information: 1) talk to the users; 2) prototype; 3) observe them using the prototype; 4) repeat.

There are a whole host of specific strategies to help you in this process, including things like: use cases, user stories, storyboarding, etc.  The literature is full of good processes and techniques for working early and often with users to get both the right set of functionality and a great user experience.

But let’s get back to designers and developers.  The reason that I don’t buy into the clean GUI/code split (or code + markup, if you’re a Petzold fan) is that good user interaction requires both code and markup.  Somebody needs to be responsible for the user interaction model and it should come first, requiring some code and some markup.

If you do buy into the devs-Studio/designers-Blend party line for WPF development, there are two simplistic approaches that you might be tempted to take, both equally bad:

  • Developer codes up all required functionality, puts API on it and designer creates screens that call into the API
  • Designer mocks up screens and then developers create code behind those screens to get desired functionality

The problem behind both approaches is, of course, that no one is focused on how the user is using the application.  The designer is thinking about the user in aesthetic terms and that’s a huge improvement over a battleship grey GUI.  But it’s not nearly enough–not if your goal is to achieve a great user experience.

If someone needs to be responsible for the user experience, it should be the developer.  If you are lucky enough to be working with a designer, the developer is still the team member that drives the entire process.  The designer is likely working in support of the developer, not the other way around.  (Note: I’m talking here about developing rich WPF client software, rather than web-based sites or applications.  With web-based projects, it’s likely the designer that is driving the project).

My vote is for a process that looks something like the following:

  • Developer initiates requirements gathering through user stories and use cases
  • Developer starts sketching up storyboards, with input from designer
  • Developer builds prototype, using both Visual Studio and Blend
  • Team presents prototype to user, walks through use cases, gets feedback, iterates
    • Important to focus here on how the user works w/application, rather than how it looks
  • As pieces of user interaction solidify
    • Designer begins refining those pieces of GUI for aesthetics, branding, etc.
    • Developer begins fleshing out code behind and full functionality
  • Continue iterating/reviewing with user

You might agree with this process, but say that the developer should work exclusively in Visual Studio to generate the prototypes.  Why is it important for them to use Blend for prototyping and iterating with the user?

The simple truth is that Blend is far superior to Visual Studio for doing basic GUI layout.  Using Visual Studio, you can definitely set property values using the property grid or by entering XAML directly.  But the property editors in Blend make it much easier to quickly set properties and tweak controls.

Given that the developer should be doing the GUI prototyping, I think it makes sense for them to use both Blend and Visual Studio, rather than just Visual Studio alone.

The bottom line is this: the choice of using Blend vs. Visual Studio should be based on the task that you are doing, rather than who is doing that task.  Instead of Blend just being a tool for designers and Visual Studio a tool for developers, it’s more true that Blend is a tool for doing GUI design and Visual Studio a tool for writing/debugging code.  Given that I think the developer should be the person responsible for early prototyping of the GUI, they should be using both Blend and Visual Studio during the early phases of a project.

So if you’re a developer just getting into WPF, don’t write off Blend as an artsy-fartsy tool for designers.  Instead, just think of it as a GUI design tool.  Though you may not be great at putting together beautiful user interfaces, it’s definitely your job to create the early GUI prototypes.  You may not be responsible for the design of the GUI, but you should be responsible for designing the GUI.  So if you WPF, you really ought to Blend.  Who knows?  You might like it so much that you start wearing a beret.

Writing a Screen Saver in WPF

I take my Raindrop Animation from last time and converted it into a screen saver, complete with a Settings dialog, to allow tweaking the various parameters.

Note: Full source code available on Codeplex, at: http://wavesimscrsaver.codeplex.com/

Last time, I created a WPF application that displayed an animated simulation of raindrops falling on water.  It was a little work, but not a huge effort, to convert that application into a Windows screen saver.

A screen saver is mainly just a regular .exe file with a .scr extension that has been copied into your C:\Windows\system32 directory.  In the simplest implementation, your application will just run when the screen saver kicks in.  But a fully functional screen saver in Windows will also support two additional features—running in the little preview window in the Screen Saver dialog and providing a customization GUI that is launched from the Settings button in the Screen Saver dialog.  You’ll also want to tweak the normal runtime behavior so that your application runs maximized, without window borders, and responds to mouse and/or keyboard events to shut down gracefully.

Our existing Raindrops WPF application runs in a WPF window.  We can easily tweak its behavior to run maximized and without a window border.  But we also need to interpret command line parameters so that we can decide which of the three following modes to run in:

  • Normal  (run screen saver maximized)
  • Preview  (run screen saver that is hosted in the little preview window)
  • Settings  (show dialog allowing user to tweak settings)

The first thing that we need to do is to change the main Application object in our WPF application and tell it not to start up a window, but to execute some code.  We remove the StartupUri property (was set to “Window1.xaml”) and replace it with a Startup property that points to an Application_Startup method.

Here is the modified App.xaml code:


<Application x:Class="WaveSim.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Startup="Application_Startup">
    <Application.Resources>

    </Application.Resources>
</Application>

The bulk of our changes will be in the new Application_Startup method.  It’s here that we parse the command line and figure out what mode we should run under.  The Screen Saver mechanism and dialog uses the following API to tell a screen saver how to run:

  • /p handle    Run in preview mode, hosting inside preview window whose handle is passed in
  • /s        Run in normal screen saver mode (full screen)
  • /c        Run in settings (configuration) mode, showing GUI to change settings

Here are the entire contents of App.xaml.cs, with the command line parsing logic:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Windows;
using System.Windows.Interop;
using System.Runtime.InteropServices;
using System.Windows.Media;

namespace WaveSim
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        // Used to host WPF content in preview mode, attach HwndSource to parent Win32 window.
        private HwndSource winWPFContent;
        private Window1 winSaver;

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            // Preview mode--display in little window in Screen Saver dialog
            // (Not invoked with Preview button, which runs Screen Saver in
            // normal /s mode).
            if (e.Args[0].ToLower().StartsWith("/p"))        
            {
                winSaver = new Window1();

                Int32 previewHandle = Convert.ToInt32(e.Args[1]);
                //WindowInteropHelper interopWin1 = new WindowInteropHelper(win);
                //interopWin1.Owner = new IntPtr(previewHandle);

                IntPtr pPreviewHnd = new IntPtr(previewHandle);

                RECT lpRect = new RECT();
                bool bGetRect = Win32API.GetClientRect(pPreviewHnd, ref lpRect);

                HwndSourceParameters sourceParams = new HwndSourceParameters("sourceParams");

                sourceParams.PositionX = 0;
                sourceParams.PositionY = 0;
                sourceParams.Height = lpRect.Bottom - lpRect.Top;
                sourceParams.Width = lpRect.Right - lpRect.Left;
                sourceParams.ParentWindow = pPreviewHnd;
                sourceParams.WindowStyle = (int)(WindowStyles.WS_VISIBLE | WindowStyles.WS_CHILD | WindowStyles.WS_CLIPCHILDREN);

                winWPFContent = new HwndSource(sourceParams);
                winWPFContent.Disposed += new EventHandler(winWPFContent_Disposed);
                winWPFContent.RootVisual = winSaver.grid1;
            }

            // Normal screensaver mode.  Either screen saver kicked in normally,
            // or was launched from Preview button
            else if (e.Args[0].ToLower().StartsWith("/s"))     
            {
                Window1 win = new Window1();
                win.WindowState = WindowState.Maximized;
                win.Show();
            }

            // Config mode, launched from Settings button in screen saver dialog
            else if (e.Args[0].ToLower().StartsWith("/c"))     
            {
                SettingsWindow win = new SettingsWindow();
                win.Show();
            }

            // If not running in one of the sanctioned modes, shut down the app
            // immediately (because we don't have a GUI).
            else
            {
                Application.Current.Shutdown();
            }
        }

        /// <summary>
        /// Event that triggers when parent window is disposed--used when doing
        /// screen saver preview, so that we know when to exit.  If we didn't
        /// do this, Task Manager would get a new .scr instance every time
        /// we opened Screen Saver dialog or switched dropdown to this saver.
        /// </summary>
        ///
<param name="sender"></param>
        ///
<param name="e"></param>
        void winWPFContent_Disposed(object sender, EventArgs e)
        {
            winSaver.Close();
//            Application.Current.Shutdown();
        }
    }
}

The most complicated thing about this code is what we do in preview mode.  We need to basically take our WPF window and host it inside an existing Win32 window—the little preview window on the Screen Saver dialog.  To start with, all we have is the handle of this window.  The trick is to create a new HwndSource object, specifying the desired size and who we want for a parent window.  Then we attach our WPF window by changing the HwndSource.RootVisual property.  We also hook up an event handler so that we know when the window gets disposed.  When the parent window goes away, we need to make sure to shut our application down (or it will continue to run).

Running in normal screen saver mode is the most straightforward of the three options.  We simply instantiate our Window1 window and show it.

For settings/configuration mode, we show a new SettingsWindow window that we’ve created.  This window will display some sliders to let the user change various settings and it will also persist the new settings to an .xml file.

The Raindrop settings are encapsulated in the new RaindropSettings class.  This class just contains public (serializable) properties for the various things we want to tweak, and it includes Save and Load methods that serialize the properties to an .xml file and read them back in.

It’s important that we serialize these properties in an .xml file because the screen saver architecture doesn’t expect to display a settings dialog while the screen saver is running.  Instead, it expects to run the application once to allow the user to change settings and then run again to show the screen saver.

Here is the full code for the RaindropSettings class.  Note that we use auto-implemented properties so that we don’t have to write prop getter/setter code:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Serialization;

namespace WaveSim
{
    /// <summary>
    /// Persist raindrop screen saver settings in memory and provide support
    /// for loading from file and persisting to file.
    /// </summary>
    public class RaindropSettings
    {
        public const string SettingsFile = "Raindrops.xml";

        public double RaindropPeriodInMS { get; set; }  
        public double SplashAmplitude { get; set; }
        public int DropSize { get; set; }
        public double Damping { get; set; }

        /// <summary>
        /// Instantiate the class, loading settings from a specified file.
        /// If the file doesn't exist, use default values.
        /// </summary>
        ///
<param name="sSettingsFilename"></param>
        public RaindropSettings()
        {
            SetDefaults();      // Clean object, start w/defaults
        }

        /// <summary>
        /// Set all values to their defaults
        /// </summary>
        public void SetDefaults()
        {
            RaindropPeriodInMS = 35.0;
            SplashAmplitude = -3.0;
            DropSize = 1;
            Damping = 0.96;
        }

        /// <summary>
        /// Save current settings to external file
        /// </summary>
        ///
<param name="sSettingsFilename"></param>
        public void Save(string sSettingsFilename)
        {
            try
            {
                XmlSerializer serial = new XmlSerializer(typeof(RaindropSettings));

                FileStream fs = new FileStream(sSettingsFilename, FileMode.Create);
                TextWriter writer = new StreamWriter(fs, new UTF8Encoding());
                serial.Serialize(writer, this);
                writer.Close();
            }
            catch { }
        }

        /// <summary>
        /// Attempt to load settings from external file.  If the file doesn't
        /// exist, or if there is a problem, no settings are changed.
        /// </summary>
        ///
<param name="sSettingsFilename"></param>
        public static RaindropSettings Load(string sSettingsFilename)
        {
            RaindropSettings settings = null;

            try
            {
                XmlSerializer serial = new XmlSerializer(typeof(RaindropSettings));
                FileStream fs = new FileStream(sSettingsFilename, FileMode.OpenOrCreate);
                TextReader reader = new StreamReader(fs);
                settings = (RaindropSettings)serial.Deserialize(reader);
            }
            catch {
                // If we can't load, just create a new object, which gets default values
                settings = new RaindropSettings();     
            }

            return settings;
        }
    }
}

Here is the .xaml for our SettingsWindow class.  The window will contain four sliders, one for each setting.  It also includes a button that resets everything back to the default values.  When the user clicks the OK button, all settings are persisted to the RaindropSettings.xml file.  (There is no cancel function).

<Window x:Class="WaveSim.SettingsWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Raindrop Screensaver Settings" Height="300" Width="300">
    <Grid>
        <Button Height="23" Margin="0,0,48,17" Name="btnClose" VerticalAlignment="Bottom" Click="btnClose_Click" HorizontalAlignment="Right" Width="76">OK</Button>
        <Slider Height="21" Margin="0,27,10,0" Name="slidNumDrops" VerticalAlignment="Top" Minimum="1" Maximum="1000" AutoToolTipPlacement="BottomRight" HorizontalAlignment="Right" Width="164" ValueChanged="slidNumDrops_ValueChanged" />
        <Label Height="28" Margin="24,25,0,0" Name="label1" VerticalAlignment="Top" HorizontalAlignment="Left" Width="70">Num Drops</Label>
        <Button Height="23" HorizontalAlignment="Left" Margin="43,0,0,17" Name="btnDefaults" VerticalAlignment="Bottom" Width="76" Click="btnDefaults_Click">Defaults</Button>
        <Label Height="28" HorizontalAlignment="Left" Margin="6,66,0,0" Name="label2" VerticalAlignment="Top" Width="88">Drop Strength</Label>
        <Slider AutoToolTipPlacement="BottomRight" Height="21" Margin="104,70,10,0" Maximum="15" Minimum="0" Name="slidDropStrength" VerticalAlignment="Top" ValueChanged="slidDropStrength_ValueChanged" />
        <Label HorizontalAlignment="Left" Margin="29,111,0,123" Name="label3" Width="61">Drop Size</Label>
        <Slider AutoToolTipPlacement="BottomRight" Margin="104,114,10,127" Maximum="6" Minimum="1" Name="slidDropSize" ValueChanged="slidDropSize_ValueChanged" />
        <Label Height="28" HorizontalAlignment="Left" Margin="30,0,0,79" Name="label4" VerticalAlignment="Bottom" Width="61">Damping</Label>
        <Slider AutoToolTipPlacement="BottomRight" Height="21" Margin="104,0,10,83" Maximum="100" Minimum="50" Name="slidDamping" VerticalAlignment="Bottom" ValueChanged="slidDamping_ValueChanged" SmallChange="0.01" LargeChange="0.1" />
    </Grid>
</Window>

And here is the full code for SettingsWindow.xaml.cs.  When we load the window, we read in settings from the .xml file and change the value of the sliders.  When the user clicks OK, we just save out the current settings to RaindropSettings.xml.

using System;
using System.Collections.Generic;
using System.Linq;
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.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace WaveSim
{
    /// <summary>
    /// Interaction logic for SettingsWindow.xaml
    /// </summary>
    public partial class SettingsWindow : Window
    {
        private RaindropSettings settings;

        public SettingsWindow()
        {
            InitializeComponent();

            // Load settings from file (or just set to default values
            // if file not found)
            settings = RaindropSettings.Load(RaindropSettings.SettingsFile);

            SetSliders();
        }

        private void btnClose_Click(object sender, RoutedEventArgs e)
        {
            settings.Save(RaindropSettings.SettingsFile);
            this.Close();
        }

        /// <summary>
        /// Set all sliders to their default values
        /// </summary>
        ///
<param name="sender"></param>
        ///
<param name="e"></param>
        private void btnDefaults_Click(object sender, RoutedEventArgs e)
        {
            settings.SetDefaults();
            SetSliders();
        }

        private void SetSliders()
        {
            slidNumDrops.Value = 1.0 / (settings.RaindropPeriodInMS / 1000.0);
            slidDropStrength.Value = -1.0 * settings.SplashAmplitude;
            slidDropSize.Value = settings.DropSize;
            slidDamping.Value = settings.Damping * 100;
        }

        private void slidDropStrength_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (settings != null)
            {
                // Slider runs [0,30], so our amplitude runs [-30,0]. 
                // Negative amplitude is desirable because we see little towers of 
                // water as each drop bloops in. 
                settings.SplashAmplitude = -1.0 * slidDropStrength.Value;
            }
        }

        private void slidNumDrops_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (settings != null)
            {
                // Slider runs from [1,1000], with 1000 representing more drops (1 every ms) and 
                // 1 representing fewer (1 ever 1000 ms).  This is to make slider seem natural 
                // to user.  But we need to invert it, to get actual period (ms) 
                settings.RaindropPeriodInMS = (1.0 / slidNumDrops.Value) * 1000.0;
            }
        }

        private void slidDropSize_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (settings != null)
            {
                settings.DropSize = (int)slidDropSize.Value;
            }
        }

        private void slidDamping_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (settings != null)
            {
                settings.Damping = slidDamping.Value / 100;
            }
        }
    }
}

The only remaining thing to be done is to change Window1 to get rid of our earlier sliders and to read in the settings from the .xml file.

Here is the modified Window1.xaml code:

<Window x:Class="WaveSim.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1"
    MouseWheel="Window_MouseWheel" ShowInTaskbar="False" ResizeMode="NoResize" WindowStyle="None"
        MouseDown="Window_MouseDown" KeyDown="Window_KeyDown" Background="Black">
    <Grid Name="grid1">
        <Viewport3D Name="viewport3D1">
            <Viewport3D.Camera>
                <PerspectiveCamera x:Name="camMain" Position="255 38.5 255" LookDirection="-130 -40 -130" FarPlaneDistance="450" UpDirection="0,1,0" NearPlaneDistance="1" FieldOfView="70">

                </PerspectiveCamera>
            </Viewport3D.Camera>
            <ModelVisual3D x:Name="vis3DLighting">
                <ModelVisual3D.Content>
                    <DirectionalLight x:Name="dirLightMain" Direction="2, -2, 0"/>
                </ModelVisual3D.Content>
            </ModelVisual3D>
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <DirectionalLight Direction="0, -2, 2"/>
                </ModelVisual3D.Content>
            </ModelVisual3D>
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <GeometryModel3D x:Name="gmodMain">
                        <GeometryModel3D.Geometry>
                            <MeshGeometry3D x:Name="meshMain" >
                            </MeshGeometry3D>
                        </GeometryModel3D.Geometry>
                        <GeometryModel3D.Material>
                            <MaterialGroup>
                                <DiffuseMaterial x:Name="matDiffuseMain">
                                    <DiffuseMaterial.Brush>
                                        <SolidColorBrush Color="DarkBlue"/>
                                    </DiffuseMaterial.Brush>
                                </DiffuseMaterial>
                                <SpecularMaterial SpecularPower="24">
                                    <SpecularMaterial.Brush>
                                        <SolidColorBrush Color="LightBlue"/>
                                    </SpecularMaterial.Brush>
                                </SpecularMaterial>
                            </MaterialGroup>
                        </GeometryModel3D.Material>
                    </GeometryModel3D>
                </ModelVisual3D.Content>
            </ModelVisual3D>
        </Viewport3D>
    </Grid>
</Window>

And here is the updated Window1.xaml.cs.  Note that we also add event handlers to shut down the application when a mouse or keyboard button is pressed.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
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.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace WaveSim
{
    ///

    /// Interaction logic for Window1.xaml
    ///

    public partial class Window1 : Window
    {
        private Vector3D zoomDelta;

        private WaveGrid _grid;
        private bool _rendering;
        private double _lastTimeRendered;
        private Random _rnd = new Random(1234);

        // Raindrop parameters, from .xml settings file
        private RaindropSettings _settings;

        private double _splashDelta = 1.0;      // Actual splash height is Ampl +/- Delta (random)
        private double _waveHeight = 15.0;

        // Values to try:
        //   GridSize=20, RenderPeriod=125
        //   GridSize=50, RenderPeriod=50
        private const int GridSize = 250; //50;   
        private const double RenderPeriodInMS = 60; //50;

        public Window1()
        {
            InitializeComponent();

            // Read in settings from .xml file
            _settings = RaindropSettings.Load(RaindropSettings.SettingsFile);

            // Set up the grid
            _grid = new WaveGrid(GridSize);
            _grid.Damping = _settings.Damping;
            meshMain.Positions = _grid.Points;
            meshMain.TriangleIndices = _grid.TriangleIndices;

            // On each WheelMouse change, we zoom in/out a particular % of the original distance
            const double ZoomPctEachWheelChange = 0.02;
            zoomDelta = Vector3D.Multiply(ZoomPctEachWheelChange, camMain.LookDirection);

            StartStopRendering();
        }

        private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (e.Delta > 0)
                // Zoom in
                camMain.Position = Point3D.Add(camMain.Position, zoomDelta);
            else
                // Zoom out
                camMain.Position = Point3D.Subtract(camMain.Position, zoomDelta);
        }

        // Start/stop animation
        private void StartStopRendering()
        {
            if (!_rendering)
            {
                //_grid = new WaveGrid(GridSize);        // New grid allows buffer reset
                _grid.FlattenGrid();
                meshMain.Positions = _grid.Points;

                _lastTimeRendered = 0.0;
                CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
                _rendering = true;
            }
            else
            {
                CompositionTarget.Rendering -= new EventHandler(CompositionTarget_Rendering);
                _rendering = false;
            }
        }

        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            RenderingEventArgs rargs = (RenderingEventArgs)e;
            if ((rargs.RenderingTime.TotalMilliseconds – _lastTimeRendered) > RenderPeriodInMS)
            {
                // Unhook Positions collection from our mesh, for performance
                // (see http://blogs.msdn.com/timothyc/archive/2006/08/31/734308.aspx)
                meshMain.Positions = null;

                // Do the next iteration on the water grid, propagating waves
                double NumDropsThisTime = RenderPeriodInMS / _settings.RaindropPeriodInMS;

                // Result at this point for number of drops is something like
                // 2.25.  We’ll induce integer portion (e.g. 2 drops), then
                // 25% chance for 3rd drop.
                int NumDrops = (int)NumDropsThisTime;   // trunc
                for (int i = 0; i < NumDrops; i++)                     _grid.SetRandomPeak(_settings.SplashAmplitude, _splashDelta, _settings.DropSize);                 if ((NumDropsThisTime - NumDrops) > 0)
                {
                    double DropChance = NumDropsThisTime – NumDrops;
                    if (_rnd.NextDouble() <= DropChance)                         _grid.SetRandomPeak(_settings.SplashAmplitude, _splashDelta, _settings.DropSize);                 }                 _grid.ProcessWater();                 // Then update our mesh to use new Z values                 meshMain.Positions = _grid.Points;                 _lastTimeRendered = rargs.RenderingTime.TotalMilliseconds;             }         }         private void Window_MouseDown(object sender, MouseButtonEventArgs e)         {             Application.Current.Shutdown();         }         private void Window_KeyDown(object sender, KeyEventArgs e)         {             Application.Current.Shutdown();         }     } } [/sourcecode] Here is a .zip file containing the entire Raindrops Screen Saver project.  After you build it, you’ll need to:

  • Rename WaveSimScrSaver.exe to WaveSimScrSaver.scr
  • Copy WaveSimScrSaver.scr to C:\Windows\system32

Here’s a screen shot of the screen saver running in Preview mode.  This is very satisfying, since getting this to work properly was the hardest part of the project.

Next Steps

There are a few obvious “next steps” to take in this project, including:

  • Stop screen saver on mouse move (stop on large movement, but not small movement)
  • Run screen saver on multiple monitors/screens
  • Allow user to set the background image
  • Allow user to set an image to get mapped onto the surface of the water

Sources

Here are some of the sources that I used in learning how to create and run a screen saver in WPF:

Raindrop Animation in WPF

I’ve expanded a bit on my earlier example of simulating ripples on water in WPF.  Last time, I started a ripple by inducing a single peak value into a grid of points and then watching the ripples propagate.

Full source code available at:  http://wavesim.codeplex.com

This time, we go much further, inducing peaks at random intervals to simulate raindrops falling on a liquid surface.  The underlying algorithm for propagating the ripples is identical to last time—calculating new height values for every point in a 2D mesh, using a basic filtering/smoothing algorithm.

To see the final result right away, you can download/run the WPF application from here.  As before, you can use the mouse wheel to zoom in/out, while the simulation is running.

I’ve updated the GUI to include a few knobs that you can play with.  The three sliders that control the raindrops are:

  • Num Drops – Controls how fast the drops are falling.  For starters, the average time between raindrops is 35ms.  The slider allows changing the frequency, such that the time between drops ranges from 1ms to 1000ms.  (On average)
  • Drop Strength – Controls how deep the drop falls, which impacts the amplitude of the resulting ripples.  Defaults to creating a drop that goes 3.0 units deep, with a range of [0,15].  (Grid is 250×250 units).
  • Drop Size – The diameter of the drop that comes down.  (Actually, drops are square, so this value is the length of one side of the square).  Defaults to 1, range is [1,6].

To start the animation, with the default values, click on the Start Rain button.  You’ll get a nice/natural animated scene, with raindrops falling on the water.  (On my graphics card, at least, this results in an animation that feels close to real-time—this may not be true on slower/faster cards).

The next thing to try playing with is the Num Drops setting, leaving everything else the same.  The raindrop frequency will increase as you move the slider, and you’ll a much more agitated surface, since the ripples don’t have enough time to damp.

Now try turning the Num Drops setting back down low and turn up the Drop Size setting.  Now you’ll get nice fat drops that create pretty good-size ripples.

Finally, set Drop Size back down again and try playing with the Drop Strength setting.  You’ll simulate stronger drops, as we create much deeper craters for each drop initially.  Also notice the little tower of water the jumps up as the first visual indication of a drop.

You can obviously play with all three of the settings at the same time.  Doing so, you can easily get a pretty crazy bathtub effect, as the waves just get larger and larger.

Use of the Wave button is left as an exercise to the reader.  It basically introduces a deep channel across the entire wave mesh, which results in a fairly large wave that propagates out in both directions.

One interesting thing to note about the wave is that you’ll see the existing ripples bend around the wave and continue propagating outward.  Also note that, because we add all amplitudes to existing point heights, new drops that fall on the wave will be at the proper height, relative to the current wave height.

Ok, I can’t resist.  Here’s a screencap of the Wave in action.

Below is the WPF code that I used for the simulation.  As before, the three parts are: a) the static XAML that sets up the window; b) the code-behind for Window1, which runs the Rendering loop and c) the WaveGrid class, which does the actual simulation and contains the two point buffers.

Here is the XAML code for the main window, nothing too spectacular:

<Window x:Class="WaveSim.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="679.023" Width="812.646"
    MouseWheel="Window_MouseWheel">
    <Grid Name="grid1" Height="618.12" Width="759.015">
        <Grid.RowDefinitions>
            <RowDefinition Height="76*" />
            <RowDefinition Height="542.12*" />
        </Grid.RowDefinitions>
        <Button HorizontalAlignment="Right" Margin="0,11.778,115,0" Name="btnStart" Width="75" Click="btnStart_Click" Height="22.649" VerticalAlignment="Top">Start Rain</Button>
        <Viewport3D Name="viewport3D1" Grid.Row="1">
            <Viewport3D.Camera>
                <PerspectiveCamera x:Name="camMain" Position="255 38.5 255" LookDirection="-130 -40 -130" FarPlaneDistance="450" UpDirection="0,1,0" NearPlaneDistance="1" FieldOfView="70">

                </PerspectiveCamera>
            </Viewport3D.Camera>
            <ModelVisual3D x:Name="vis3DLighting">
                <ModelVisual3D.Content>
                    <DirectionalLight x:Name="dirLightMain" Direction="2, -2, 0"/>
                </ModelVisual3D.Content>
            </ModelVisual3D>
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <DirectionalLight Direction="0, -2, 2"/>
                </ModelVisual3D.Content>
            </ModelVisual3D>
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <GeometryModel3D x:Name="gmodMain">
                        <GeometryModel3D.Geometry>
                            <MeshGeometry3D x:Name="meshMain" >
                            </MeshGeometry3D>
                        </GeometryModel3D.Geometry>
                        <GeometryModel3D.Material>
                            <MaterialGroup>
                                <DiffuseMaterial x:Name="matDiffuseMain">
                                    <DiffuseMaterial.Brush>
                                        <SolidColorBrush Color="DarkBlue"/>
                                    </DiffuseMaterial.Brush>
                                </DiffuseMaterial>
                                <SpecularMaterial SpecularPower="24">
                                    <SpecularMaterial.Brush>
                                        <SolidColorBrush Color="LightBlue"/>
                                    </SpecularMaterial.Brush>
                                </SpecularMaterial>
                            </MaterialGroup>
                        </GeometryModel3D.Material>
                    </GeometryModel3D>
                </ModelVisual3D.Content>
            </ModelVisual3D>
        </Viewport3D>
        <Slider Margin="0,13.596,198,0" Name="slidPeakHeight" ValueChanged="slidPeakHeight_ValueChanged" Minimum="0" Maximum="15" HorizontalAlignment="Right" Width="167.256" Height="20.831" VerticalAlignment="Top" />
        <Label Margin="286,11.964,0,36.083" Name="lblDropDepth" HorizontalAlignment="Left" Width="89.015">Drop Strength</Label>
        <Slider Name="slidNumDrops" HorizontalAlignment="Left" Margin="111,15.452,0,0" Maximum="1000" Minimum="1" Width="167.256" ValueChanged="slidNumDrops_ValueChanged" Height="20.831" VerticalAlignment="Top" />
        <Label HorizontalAlignment="Left" Margin="12,13.596,0,34.451" Name="label1" Width="89">Num Drops</Label>
        <Button HorizontalAlignment="Right" Margin="0,11.963,19,0" Name="btnWave" Width="75" Click="btnWave_Click" Height="22.649" VerticalAlignment="Top">Wave !</Button>
        <Slider Height="20.831" HorizontalAlignment="Left" Margin="111,0,0,5.266" Maximum="6" Minimum="1" Name="slidDropSize" VerticalAlignment="Bottom" Width="167.256" ValueChanged="slidDropSize_ValueChanged"/>
        <Label Height="27.953" HorizontalAlignment="Left" Margin="12,0,0,0" Name="label2" VerticalAlignment="Bottom" Width="89">Drop Size</Label>
    </Grid>
</Window>

Here is the Window1.xaml.cs code.  Some things to take note of:

  • We’re no longer setting peaks in the center of the grid, but calling SetRandomPeak to induce each raindrop
  • As before, we’re using the CompositionTarget_Rendering event handler as our main rendering loop.  During the loop, we induce new raindrops, tell the grid to process the point mesh (propagating waves) and we then reattach the new point grid to our MeshGeometry3D
  • Note that we calculate the number of drops to induce by first calculating how many drops we should drop each time we visit this loop (should be moved outside the loop).  We induce points for the integer portion of this number and then use the fractional part as a % chance of dropping one more point.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
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.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace WaveSim
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        private Vector3D zoomDelta;

        private WaveGrid _grid;
        private bool _rendering;
        private double _lastTimeRendered;
        private Random _rnd = new Random(1234);

        // Raindrop parameters.  Negative amplitude causes little tower of
        // water to jump up vertically in the instant after the drop hits.
        private double _splashAmplitude; // Average height (depth, since negative) of raindrop splashes.
        private double _splashDelta = 1.0;      // Actual splash height is Ampl +/- Delta (random)
        private double _raindropPeriodInMS;
        private double _waveHeight = 15.0;
        private int _dropSize;

        // Values to try:
        //   GridSize=20, RenderPeriod=125
        //   GridSize=50, RenderPeriod=50
        private const int GridSize = 250; //50;
        private const double RenderPeriodInMS = 60; //50;    

        public Window1()
        {
            InitializeComponent();

            _splashAmplitude = -3.0;
            slidPeakHeight.Value = -1.0 * _splashAmplitude;

            _raindropPeriodInMS = 35.0;
            slidNumDrops.Value = 1.0 / (_raindropPeriodInMS / 1000.0);

            _dropSize = 1;
            slidDropSize.Value = _dropSize;

            // Set up the grid
            _grid = new WaveGrid(GridSize);
            meshMain.Positions = _grid.Points;
            meshMain.TriangleIndices = _grid.TriangleIndices;

            // On each WheelMouse change, we zoom in/out a particular % of the original distance
            const double ZoomPctEachWheelChange = 0.02;
            zoomDelta = Vector3D.Multiply(ZoomPctEachWheelChange, camMain.LookDirection);
        }

        private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (e.Delta > 0)
                // Zoom in
                camMain.Position = Point3D.Add(camMain.Position, zoomDelta);
            else
                // Zoom out
                camMain.Position = Point3D.Subtract(camMain.Position, zoomDelta);
        }

        // Start/stop animation
        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            if (!_rendering)
            {
                //_grid = new WaveGrid(GridSize);        // New grid allows buffer reset
                _grid.FlattenGrid();
                meshMain.Positions = _grid.Points;

                _lastTimeRendered = 0.0;
                CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
                btnStart.Content = "Stop";
                _rendering = true;
            }
            else
            {
                CompositionTarget.Rendering -= new EventHandler(CompositionTarget_Rendering);
                btnStart.Content = "Start";
                _rendering = false;
            }
        }

        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            RenderingEventArgs rargs = (RenderingEventArgs)e;
            if ((rargs.RenderingTime.TotalMilliseconds - _lastTimeRendered) > RenderPeriodInMS)
            {
                // Unhook Positions collection from our mesh, for performance
                // (see http://blogs.msdn.com/timothyc/archive/2006/08/31/734308.aspx)
                meshMain.Positions = null;

                // Do the next iteration on the water grid, propagating waves
                double NumDropsThisTime = RenderPeriodInMS / _raindropPeriodInMS;

                // Result at this point for number of drops is something like
                // 2.25.  We'll induce integer portion (e.g. 2 drops), then
                // 25% chance for 3rd drop.
                int NumDrops = (int)NumDropsThisTime;   // trunc
                for (int i = 0; i < NumDrops; i++)
                    _grid.SetRandomPeak(_splashAmplitude, _splashDelta, _dropSize);

                if ((NumDropsThisTime - NumDrops) > 0)
                {
                    double DropChance = NumDropsThisTime - NumDrops;
                    if (_rnd.NextDouble() <= DropChance)
                        _grid.SetRandomPeak(_splashAmplitude, _splashDelta, _dropSize);
                }

                _grid.ProcessWater();

                // Then update our mesh to use new Z values
                meshMain.Positions = _grid.Points;

                _lastTimeRendered = rargs.RenderingTime.TotalMilliseconds;
            }
        }

        private void slidPeakHeight_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            // Slider runs [0,30], so our amplitude runs [-30,0].
            // Negative amplitude is desirable because we see little towers of
            // water as each drop bloops in.
            _splashAmplitude = -1.0 * slidPeakHeight.Value;
        }

        private void slidNumDrops_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            // Slider runs from [1,1000], with 1000 representing more drops (1 every ms) and
            // 1 representing fewer (1 ever 1000 ms).  This is to make slider seem natural
            // to user.  But we need to invert it, to get actual period (ms)
            _raindropPeriodInMS = (1.0 / slidNumDrops.Value) * 1000.0;
        }

        private void btnWave_Click(object sender, RoutedEventArgs e)
        {
            _grid.InduceWave(_waveHeight);
        }

        private void slidDropSize_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            _dropSize = (int)slidDropSize.Value;
        }
    }
}

Finally, here is the updated code for the WaveGrid class.  Things to note:

  • We’ve replaced SetCenterPeak with SetRandomPeak, which does the “dropping”
  • The crazy wave is induced in InduceWave
  • I’ve added a FlattenGrid function, to calm things down

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows.Media.Media3D;

namespace WaveSim
{
class WaveGrid
{
// Constants
const int MinDimension = 5;
const double Damping = 0.96; // SAVE: 0.96
const double SmoothingFactor = 2.0; // Gives more weight to smoothing than to velocity

// Private member data
private Point3DCollection _ptBuffer1;
private Point3DCollection _ptBuffer2;
private Int32Collection _triangleIndices;
private Random _rnd = new Random(48339);

private int _dimension;

// Pointers to which buffers contain:
// – Current: Most recent data
// – Old: Earlier data
// These two pointers will swap, pointing to ptBuffer1/ptBuffer2 as we cycle the buffers
private Point3DCollection _currBuffer;
private Point3DCollection _oldBuffer;

///

/// Construct new grid of a given dimension
///

/// public WaveGrid(int Dimension)
{
if (Dimension < MinDimension) throw new ApplicationException(string.Format("Dimension must be at least {0}", MinDimension.ToString())); _ptBuffer1 = new Point3DCollection(Dimension * Dimension); _ptBuffer2 = new Point3DCollection(Dimension * Dimension); _triangleIndices = new Int32Collection((Dimension - 1) * (Dimension - 1) * 2); _dimension = Dimension; InitializePointsAndTriangles(); _currBuffer = _ptBuffer2; _oldBuffer = _ptBuffer1; } ///

/// Access to underlying grid data
///

public Point3DCollection Points
{
get { return _currBuffer; }
}

///

/// Access to underlying triangle index collection
///

public Int32Collection TriangleIndices
{
get { return _triangleIndices; }
}

///

/// Dimension of grid–same dimension for both X & Y
///

public int Dimension
{
get { return _dimension; }
}

///

/// Induce new disturbance in grid at random location. Height is
/// PeakValue +/- Delta. (Random value in this range)
///

/// Base height of new peak in grid /// Max amount to add/sub from BasePeakValue to get actual value /// # pixels wide, [1,4] public void SetRandomPeak(double BasePeakValue, double Delta, int PeakWidth)
{
if ((PeakWidth < 1) || (PeakWidth > (_dimension / 2)))
throw new ApplicationException(“WaveGrid.SetRandomPeak: PeakWidth param must be <= half the dimension"); int row = (int)(_rnd.NextDouble() * ((double)_dimension - 1.0)); int col = (int)(_rnd.NextDouble() * ((double)_dimension - 1.0)); // When caller specifies 0.0 peak, we assume always 0.0, so don't add delta if (BasePeakValue == 0.0) Delta = 0.0; double PeakValue = BasePeakValue + (_rnd.NextDouble() * 2 * Delta) - Delta; // row/col will be used for top-left corner. But adjust, if that // puts us out of the grid. if ((row + (PeakWidth - 1)) > (_dimension – 1))
row = _dimension – PeakWidth;
if ((col + (PeakWidth – 1)) > (_dimension – 1))
col = _dimension – PeakWidth;

// Change data
for (int ir = row; ir < (row + PeakWidth); ir++) for (int ic = col; ic < (col + PeakWidth); ic++) { Point3D pt = _oldBuffer[(ir * _dimension) + ic]; pt.Y = pt.Y + (int)PeakValue; _oldBuffer[(ir * _dimension) + ic] = pt; } } ///

/// Induce wave along back edge of grid by creating large
/// wall.
///

/// public void InduceWave(double WaveHeight)
{
if (_dimension >= 15)
{
// Just set height of a few rows of points (in middle of grid)
int NumRows = 20;
//double[] SineCoeffs = new double[10] { 0.156, 0.309, 0.454, 0.588, 0.707, 0.809, 0.891, 0.951, 0.988, 1.0 };

Point3D pt;
int StartRow = _dimension / 2;
for (int i = (StartRow – 1) * _dimension; i < (_dimension * (StartRow + NumRows)); i++) { int RowNum = (i / _dimension) + StartRow; pt = _oldBuffer[i]; //pt.Y = pt.Y + (WaveHeight * SineCoeffs[RowNum]); pt.Y = pt.Y + WaveHeight ; _oldBuffer[i] = pt; } } } ///

/// Leave buffers in place, but change notation of which one is most recent
///

private void SwapBuffers()
{
Point3DCollection temp = _currBuffer;
_currBuffer = _oldBuffer;
_oldBuffer = temp;
}

///

/// Clear out points/triangles and regenerates
///

/// private void InitializePointsAndTriangles()
{
_ptBuffer1.Clear();
_ptBuffer2.Clear();
_triangleIndices.Clear();

int nCurrIndex = 0; // March through 1-D arrays

for (int row = 0; row < _dimension; row++) { for (int col = 0; col < _dimension; col++) { // In grid, X/Y values are just row/col numbers _ptBuffer1.Add(new Point3D(col, 0.0, row)); // Completing new square, add 2 triangles if ((row > 0) && (col > 0))
{
// Triangle 1
_triangleIndices.Add(nCurrIndex – _dimension – 1);
_triangleIndices.Add(nCurrIndex);
_triangleIndices.Add(nCurrIndex – _dimension);

// Triangle 2
_triangleIndices.Add(nCurrIndex – _dimension – 1);
_triangleIndices.Add(nCurrIndex – 1);
_triangleIndices.Add(nCurrIndex);
}

nCurrIndex++;
}
}

// 2nd buffer exists only to have 2nd set of Z values
_ptBuffer2 = _ptBuffer1.Clone();
}

///

/// Set height of all points in mesh to 0.0. Also resets buffers to
/// original state.
///

public void FlattenGrid()
{
Point3D pt;

for (int i = 0; i < (_dimension * _dimension); i++) { pt = _ptBuffer1[i]; pt.Y = 0.0; _ptBuffer1[i] = pt; } _ptBuffer2 = _ptBuffer1.Clone(); _currBuffer = _ptBuffer2; _oldBuffer = _ptBuffer1; } ///

/// Determine next state of entire grid, based on previous two states.
/// This will have the effect of propagating ripples outward.
///

public void ProcessWater()
{
// Note that we write into old buffer, which will then become our
// “current” buffer, and current will become old.
// I.e. What starts out in _currBuffer shifts into _oldBuffer and we
// write new data into _currBuffer. But because we just swap pointers,
// we don’t have to actually move data around.

// When calculating data, we don’t generate data for the cells around
// the edge of the grid, because data smoothing looks at all adjacent
// cells. So instead of running [0,n-1], we run [1,n-2].

double velocity; // Rate of change from old to current
double smoothed; // Smoothed by adjacent cells
double newHeight;
int neighbors;

int nPtIndex = 0; // Index that marches through 1-D point array

// Remember that Y value is the height (the value that we’re animating)
for (int row = 0; row < _dimension; row++) { for (int col = 0; col < _dimension; col++) { velocity = -1.0 * _oldBuffer[nPtIndex].Y; // row, col smoothed = 0.0; neighbors = 0; if (row > 0) // row-1, col
{
smoothed += _currBuffer[nPtIndex – _dimension].Y;
neighbors++;
}

if (row < (_dimension - 1)) // row+1, col { smoothed += _currBuffer[nPtIndex + _dimension].Y; neighbors++; } if (col > 0) // row, col-1
{
smoothed += _currBuffer[nPtIndex – 1].Y;
neighbors++;
}

if (col < (_dimension - 1)) // row, col+1 { smoothed += _currBuffer[nPtIndex + 1].Y; neighbors++; } // Will always have at least 2 neighbors smoothed /= (double)neighbors; // New height is combination of smoothing and velocity newHeight = smoothed * SmoothingFactor + velocity; // Damping newHeight = newHeight * Damping; // We write new data to old buffer Point3D pt = _oldBuffer[nPtIndex]; pt.Y = newHeight; // row, col _oldBuffer[nPtIndex] = pt; nPtIndex++; } } SwapBuffers(); } } } [/sourcecode] That’s basically it.  If anyone is interested in getting the source code, leave a comment and I’ll take the trouble to post it somewhere.

Simple Water Animation in WPF

Many years ago (mid-80s), I was working at a company that had a Silicon Graphics workstation.  Among a handful of demos designed to show off the SGI machine’s high-end graphics was a simulation of wave propagation in a little wireframe mesh.  It was great fun to play with by changing the height of points in the mesh and then letting the simulation run.  And the SGI machine was fast enough that the resulting animation was just mesmerizing.

Recreating this water simulation in WPF seemed like a nice way to learn a little more about 3D graphics in WPF.  (The end result is here).

The first step was to find an algorithm that simulates wave propagation through water.  It turns out that there is a very simple algorithm that achieves the desired effect simply by taking the average height of neighboring points.  The basic algorithm is described in detail in this article on 2D Water.  The same algorithm is also described in The Water Effect Explained.

The next step is to set up the 3D viewport and its constituent elements.  I used two different directional lights, to create more contrast on the surface of the water, as well as defining both diffuse and specular material properties for the surface of the water.

Here is the relevant XAML.  Note that meshMain is the mesh that will contain the surface of the water.

        <Viewport3D Name="viewport3D1" Margin="0,8.181,0,0" Grid.Row="1">
            <Viewport3D.Camera>
                <PerspectiveCamera x:Name="camMain" Position="48 7.8 41" LookDirection="-48 -7.8 -41" FarPlaneDistance="100" UpDirection="0,1,0" NearPlaneDistance="1" FieldOfView="70">

                </PerspectiveCamera>
            </Viewport3D.Camera>
            <ModelVisual3D x:Name="vis3DLighting">
                <ModelVisual3D.Content>
                    <DirectionalLight x:Name="dirLightMain" Direction="2, -2, 0"/>
                </ModelVisual3D.Content>
            </ModelVisual3D>
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <DirectionalLight Direction="0, -2, 2"/>
                </ModelVisual3D.Content>
            </ModelVisual3D>
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <GeometryModel3D x:Name="gmodMain">
                        <GeometryModel3D.Geometry>
                            <MeshGeometry3D x:Name="meshMain" >
                            </MeshGeometry3D>
                        </GeometryModel3D.Geometry>
                        <GeometryModel3D.Material>
                            <MaterialGroup>
                                <DiffuseMaterial x:Name="matDiffuseMain">
                                    <DiffuseMaterial.Brush>
                                        <SolidColorBrush Color="DarkBlue"/>
                                    </DiffuseMaterial.Brush>
                                </DiffuseMaterial>
                                <SpecularMaterial SpecularPower="24">
                                    <SpecularMaterial.Brush>
                                        <SolidColorBrush Color="LightBlue"/>
                                    </SpecularMaterial.Brush>
                                </SpecularMaterial>
                            </MaterialGroup>
                        </GeometryModel3D.Material>
                    </GeometryModel3D>
                </ModelVisual3D.Content>
            </ModelVisual3D>
        </Viewport3D>

Next, we create a WaveGrid class that implements the basic algorithm described above.  The basic idea is that we maintain two separate buffers of mesh data—one representing the current state of the water and one the prior state.  WaveGrid stores this data in two Point3DCollection objects.  As we run the simulation, we alternate which buffer we’re writing into and attach our MeshGeometry3D.Positions property to the most recent buffer.  Note that we’re only changing the vertical height of the points—which is the Y value.

WaveGrid also builds up the triangle indices for the mesh, in an Int32Collection which will also get connected to our MeshGeometry3D.

All of the interesting stuff happens in ProcessWater.  This is where we implement the smoothing algorithm described in the articles.  Since I wanted to fully animate every point in the mesh, I processed not just the internal points that have four neighboring points, but the points along the edge of the mesh, as well.  As we add in height values of neighboring points, we keep track of how many neighbors we found, so that we can do the averaging properly.

The final value for each point is a function of both the smoothing (average height of your neighbors) and the “velocity”, which is basically—how far from equilibrium was the point during the last iteration?  We also then apply a damping factor, since waves will gradually lose their amplitude.

Here’s the complete code for the WaveGrid class:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows.Media.Media3D;

namespace WaveSim
{
    class WaveGrid
    {
        // Constants
        const int MinDimension = 5;    
        const double Damping = 0.96;
        const double SmoothingFactor = 2.0;     // Gives more weight to smoothing than to velocity

        // Private member data
        private Point3DCollection _ptBuffer1;
        private Point3DCollection _ptBuffer2;
        private Int32Collection _triangleIndices;

        private int _dimension;

        // Pointers to which buffers contain:
        //    - Current: Most recent data
        //    - Old: Earlier data
        // These two pointers will swap, pointing to ptBuffer1/ptBuffer2 as we cycle the buffers
        private Point3DCollection _currBuffer;
        private Point3DCollection _oldBuffer;

        /// <summary>
        /// Construct new grid of a given dimension
        /// </summary>
        ///
<param name="Dimension"></param>
        public WaveGrid(int Dimension)
        {
            if (Dimension < MinDimension)
                throw new ApplicationException(string.Format("Dimension must be at least {0}", MinDimension.ToString()));

            _ptBuffer1 = new Point3DCollection(Dimension * Dimension);
            _ptBuffer2 = new Point3DCollection(Dimension * Dimension);
            _triangleIndices = new Int32Collection((Dimension - 1) * (Dimension - 1) * 2);

            _dimension = Dimension;

            InitializePointsAndTriangles();

            _currBuffer = _ptBuffer2;
            _oldBuffer = _ptBuffer1;
        }

        /// <summary>
        /// Access to underlying grid data
        /// </summary>
        public Point3DCollection Points
        {
            get { return _currBuffer; }
        }

        /// <summary>
        /// Access to underlying triangle index collection
        /// </summary>
        public Int32Collection TriangleIndices
        {
            get { return _triangleIndices; }
        }

        /// <summary>
        /// Dimension of grid--same dimension for both X & Y
        /// </summary>
        public int Dimension
        {
            get { return _dimension; }
        }

        /// <summary>
        /// Set center of grid to some peak value (high point).  Leave
        /// rest of grid alone.  Note: If dimension is even, we're not
        /// exactly at the center of the grid--no biggie.
        /// </summary>
        ///
<param name="PeakValue"></param>
        public void SetCenterPeak(double PeakValue)
        {
            int nCenter = (int)_dimension / 2;

            // Change data in oldest buffer, then make newest buffer
            // become oldest by swapping
            Point3D pt = _oldBuffer[(nCenter * _dimension) + nCenter];
            pt.Y = (int)PeakValue;
            _oldBuffer[(nCenter * _dimension) + nCenter] = pt;

            SwapBuffers();
        }

        /// <summary>
        /// Leave buffers in place, but change notation of which one is most recent
        /// </summary>
        private void SwapBuffers()
        {
            Point3DCollection temp = _currBuffer;
            _currBuffer = _oldBuffer;
            _oldBuffer = temp;
        }

        /// <summary>
        /// Clear out points/triangles and regenerates
        /// </summary>
        ///
<param name="grid"></param>
        private void InitializePointsAndTriangles()
        {
            _ptBuffer1.Clear();
            _ptBuffer2.Clear();
            _triangleIndices.Clear();

            int nCurrIndex = 0;     // March through 1-D arrays

            for (int row = 0; row < _dimension; row++)
            {
                for (int col = 0; col < _dimension; col++)
                {
                    // In grid, X/Y values are just row/col numbers
                    _ptBuffer1.Add(new Point3D(col, 0.0, row));

                    // Completing new square, add 2 triangles
                    if ((row > 0) && (col > 0))
                    {
                        // Triangle 1
                        _triangleIndices.Add(nCurrIndex - _dimension - 1);
                        _triangleIndices.Add(nCurrIndex);
                        _triangleIndices.Add(nCurrIndex - _dimension);

                        // Triangle 2
                        _triangleIndices.Add(nCurrIndex - _dimension - 1);
                        _triangleIndices.Add(nCurrIndex - 1);
                        _triangleIndices.Add(nCurrIndex);
                    }

                    nCurrIndex++;
                }
            }

            // 2nd buffer exists only to have 2nd set of Z values
            _ptBuffer2 = _ptBuffer1.Clone();
        }

        /// <summary>
        /// Determine next state of entire grid, based on previous two states.
        /// This will have the effect of propagating ripples outward.
        /// </summary>
        public void ProcessWater()
        {
            // Note that we write into old buffer, which will then become our
            //    "current" buffer, and current will become old. 
            // I.e. What starts out in _currBuffer shifts into _oldBuffer and we
            // write new data into _currBuffer.  But because we just swap pointers,
            // we don't have to actually move data around.

            // When calculating data, we don't generate data for the cells around
            // the edge of the grid, because data smoothing looks at all adjacent
            // cells.  So instead of running [0,n-1], we run [1,n-2].

            double velocity;    // Rate of change from old to current
            double smoothed;    // Smoothed by adjacent cells
            double newHeight;
            int neighbors;

            int nPtIndex = 0;   // Index that marches through 1-D point array

            // Remember that Y value is the height (the value that we're animating)
            for (int row = 0; row < _dimension ; row++)
            {
                for (int col = 0; col < _dimension; col++)
                {
                    velocity = -1.0 * _oldBuffer&#91;nPtIndex&#93;.Y;     // row, col
                    smoothed = 0.0;

                    neighbors = 0;
                    if (row > 0)    // row-1, col
                    {
                        smoothed += _currBuffer[nPtIndex - _dimension].Y;
                        neighbors++;
                    }

                    if (row < (_dimension - 1))   // row+1, col
                    {
                        smoothed += _currBuffer&#91;nPtIndex + _dimension&#93;.Y;
                        neighbors++;
                    }

                    if (col > 0)          // row, col-1
                    {
                        smoothed += _currBuffer[nPtIndex - 1].Y;
                        neighbors++;
                    }

                    if (col < (_dimension - 1))   // row, col+1
                    {
                        smoothed += _currBuffer&#91;nPtIndex + 1&#93;.Y;
                        neighbors++;
                    }

                    // Will always have at least 2 neighbors
                    smoothed /= (double)neighbors;

                    // New height is combination of smoothing and velocity
                    newHeight = smoothed * SmoothingFactor + velocity;

                    // Damping
                    newHeight = newHeight * Damping;

                    // We write new data to old buffer
                    Point3D pt = _oldBuffer&#91;nPtIndex&#93;;
                    pt.Y = newHeight;   // row, col
                    _oldBuffer&#91;nPtIndex&#93; = pt;

                    nPtIndex++;
                }
            }

            SwapBuffers();
        }
    }
}
&#91;/sourcecode&#93;

Finally, we need to hook everything up.  When our main window fires up, we create an instance of <strong>WaveGrid </strong>and set the center point in the grid to some peak value.  When we start the animation, this higher point will fall and trigger the waves.

We do all of the animation in the <strong>CompositionTarget.Rendering </strong>event handler.  This is the recommended spot to do custom animations in WPF, as opposed to doing the animation in some timer Tick event.  (<em>Windows Presentation Foundation Unleashed</em>, Nathan, pg 470).

When you attach a handler to the <strong>Rendering </strong>event, WPF just continues rendering frames indefinitely.  One problem is that the handler will get called for every frame rendered, which turns out to be too fast for our water animation.  To get the water to look right, we keep track of the time that we last rendered a frame and then wait a specified number of milliseconds before rendering another.

Here is the full source code for Window1.xaml.cs:



using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
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.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace WaveSim
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        private Vector3D zoomDelta;

        private WaveGrid _grid;
        private bool _rendering;
        private double _lastTimeRendered;
        private double _firstPeak = 6.5;

        // Values to try:
        //   GridSize=20, RenderPeriod=125
        //   GridSize=50, RenderPeriod=50
        private const int GridSize = 50;   
        private const double RenderPeriodInMS = 50;    

        public Window1()
        {
            InitializeComponent();

            _grid = new WaveGrid(GridSize);        // 10x10 grid
            slidPeakHeight.Value = _firstPeak;
            _grid.SetCenterPeak(_firstPeak);
            meshMain.Positions = _grid.Points;
            meshMain.TriangleIndices = _grid.TriangleIndices;

            // On each WheelMouse change, we zoom in/out a particular % of the original distance
            const double ZoomPctEachWheelChange = 0.02;
            zoomDelta = Vector3D.Multiply(ZoomPctEachWheelChange, camMain.LookDirection);
        }

        private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (e.Delta > 0)
                // Zoom in
                camMain.Position = Point3D.Add(camMain.Position, zoomDelta);
            else
                // Zoom out
                camMain.Position = Point3D.Subtract(camMain.Position, zoomDelta);
            Trace.WriteLine(camMain.Position.ToString());
        }

        // Start/stop animation
        private void btnStart_Click(object sender, RoutedEventArgs e)
        {
            if (!_rendering)
            {
                _grid = new WaveGrid(GridSize);        // 10x10 grid
                _grid.SetCenterPeak(_firstPeak);
                meshMain.Positions = _grid.Points;

                _lastTimeRendered = 0.0;
                CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
                btnStart.Content = "Stop";
                slidPeakHeight.IsEnabled = false;
                _rendering = true;
            }
            else
            {
                CompositionTarget.Rendering -= new EventHandler(CompositionTarget_Rendering);
                btnStart.Content = "Start";
                slidPeakHeight.IsEnabled = true;
                _rendering = false;
            }
        }

        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            RenderingEventArgs rargs = (RenderingEventArgs)e;
            if ((rargs.RenderingTime.TotalMilliseconds - _lastTimeRendered) > RenderPeriodInMS)
            {
                // Unhook Positions collection from our mesh, for performance
                // (see http://blogs.msdn.com/timothyc/archive/2006/08/31/734308.aspx)
                meshMain.Positions = null;

                // Do the next iteration on the water grid, propagating waves
                _grid.ProcessWater();

                // Then update our mesh to use new Z values
                meshMain.Positions = _grid.Points;

                _lastTimeRendered = rargs.RenderingTime.TotalMilliseconds;
            }
        }

        private void slidPeakHeight_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            _firstPeak = slidPeakHeight.Value;
            _grid.SetCenterPeak(_firstPeak);
        }
    }
}

The end result is pretty satisfying—a nice smooth animation of a series of ripples propagating out from the initial disturbance.  You can install and run the simulation by clicking here.  Note that you can zoom in/out using the mouse wheel.

We could extend this example in several different ways:

  • Render the surface of the water in a more lifelike way—e.g. glassy, with reflections.
  • Add simple controls to change the viewpoint or to rotate the mesh itself
  • Add knobs for playing with things like Damping and SmoothingFactor
  • Add ability to “grab” points in the mesh with the mouse and manually move them up/down
  • Raindrop simulation—just add timer that introduces new random peaks, representing raindrops
  • Antialiasing–also consider diagonally adjacent points as neighbors, but adjust by weighting factor when averaging

Drawing a Cube in WPF

It’s time to draw a simple 3D object using WPF.  As a quick introduction to 3D graphics in WPF, let’s just render one of the simplest possible objects—a cube.

In this example, I’ll define everything that we need directly in XAML.  As with everything else in WPF, we could do all this directly in code.  But defining everything in the XAML is a bit cleaner, in that it makes the object hierarchy more obvious.  In a real-world project, you’d obviously do some of this in code, e.g. the creation or loading of the 3D mesh (the object that we want to display).

Let’s start with the final XAML.  Here are the full contents of the Window1.xaml file:

<Window x:Class="SimpleCube.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="398" Width="608"
    <Grid>
        <Viewport3D Name="viewport3D1">
            <Viewport3D.Camera>
                <PerspectiveCamera x:Name="camMain" Position="6 5 4" LookDirection="-6 -5 -4">
                </PerspectiveCamera>
            </Viewport3D.Camera>
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <DirectionalLight x:Name="dirLightMain" Direction="-1,-1,-1">
                    </DirectionalLight>
                </ModelVisual3D.Content>
            </ModelVisual3D>
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <GeometryModel3D>
                        <GeometryModel3D.Geometry>
                            <MeshGeometry3D x:Name="meshMain"
                                Positions="0 0 0  1 0 0  0 1 0  1 1 0  0 0 1  1 0 1  0 1 1  1 1 1"
                                TriangleIndices="2 3 1  2 1 0  7 1 3  7 5 1  6 5 7  6 4 5  6 2 0  2 0 4  2 7 3  2 6 7  0 1 5  0 5 4">
                            </MeshGeometry3D>
                        </GeometryModel3D.Geometry>
                        <GeometryModel3D.Material>
                            <DiffuseMaterial x:Name="matDiffuseMain">
                                <DiffuseMaterial.Brush>
                                    <SolidColorBrush Color="Red"/>
                                </DiffuseMaterial.Brush>
                            </DiffuseMaterial>
                        </GeometryModel3D.Material>
                    </GeometryModel3D>
                </ModelVisual3D.Content>
            </ModelVisual3D>
        </Viewport3D>
    </Grid>
</Window>

The basic idea here is that we need a Viewport3D object that contains everything required to render our cube.  The simplified structure, showing the Viewport3D and its child objects, is:

Viewport3D
    ModelVisual3D   (defines lighting)
        DirectionalLight
    ModelVisual3D   (defines object to render)
        GeometryModel3D
            MeshGeometry3D
            DiffuseMaterial

Here’s what each of these objects is responsible for:

  • Viewport3D – A place to render 3D stuff
  • ModelVisual3D – A 3D object contained by the viewport, either a light or a geometry
  • DirectionalLight – A light shining in a particular direction
  • GeometryModel3D – A 3D geometrical object
  • MeshGeometry3D – The set of triangles that defines a 3D object
  • DiffuseMaterial – Material used to render a 3D object, e.g. a brush

Perhaps the most interesting of these classes is the MeshGeometry3D.  A “mesh” basically consists of a series of triangles, typically all connected to form the 3D object that you want to render.  The MeshGeometry3D object defines a mesh by specifying a series of points and then a collection of triangles.  The collection of points represent all of the vertexes in the mesh and are defined by the Positions property.  The triangles, stored in the TriangleIndices property, are defined in terms of the points, using indexes into the Positions collection.

This seems a bit odd at first.  Why not just define a collection of triangles, each consisting of three points?  Why define the points as a separate collection and then define the triangles by referencing the points?  The answer is that this scheme allows reusing a single point in multiple triangles.

In our case, drawing a cube, we define eight points, for the eight vertexes of the cube.  The image below shows the points numbered from 0-7, matching the order that we add them to Positions.  The back left corner of the cube is located at (0, 0, 0).

After defining the points, we define the 12 triangles that make up the surface cube—two triangles per face.  We define each triangle by just listing the indexes of the three points that make up the triangle.

It’s also important to pay attention to the order that we list the indexes for each triangle.  The order dictates the direction of a vector normal to the triangle, which indicates which side of the triangle that we can see.  The rule is: add vertexes counter-clockwise, as you look at the visible face of the triangle.

In addition to the mesh, we define a material used to render the cube.  In this case, it’s a DiffuseMaterial, which allows painting the surface of the cube with a simple brush.

We also need to add a camera to our scene, by specifying where it is located and what direction it looks in.  In order to see our cube, we put the camera at (6, 5, 4) and then set its LookDirection, a vector, to (-6, -5, -4) so that it looks back towards the origin.

Finally, in order to see the cube, we need lighting.  We define a single light source, which is a DirectionalLight—a light that has no position, but just casts light in a particular direction.

The final result is a simple red cube.

Hello WPF World, part 3 – Forms and Windows

We continue with our basic “hello world” WPF application by adding a button to our main window and then building and running the application.  We also talk about the difference between forms in Windows Forms and windows in WPF, as well as how to add event handlers.

I want to insert a caveat at this point.  These first few “hello world” posts are basic—very, very basic.  Adding a button to a form and having it display a message box is what most of us do in the first five minutes that we spend playing with a new language or framework.  So don’t expect any cosmic secrets here.  I just want to take a little time to throw together a super simple application and then comment a little bit on what I’m seeing.

Form vs. Window

Let’s start by just building our basic wizard-generated application and then running it.  I’ll continue doing parallel stuff in a Windows Forms application, so we can compare the two.  Here’s what we get when we run the applications:

Form vs. Window

Form vs. Window

Nothing too earth-shattering here, although WPF has gotten rid of two old standbys that I’m sick of—the little multi-colored default application icon and the battleship grey form background.  Good riddance to both of them.

In both cases, we get a simple window with the standard window decoration elements.  Nothing appears to have changed here.  But if we look at the type that implements the window in either case, we see that everything is different under the covers.

Win Forms is using a System.Windows.Forms.Form (System.Windows.Forms.dll), while WPF’s main window is a Systems.Windows.Window (PresentationFramework.dll).

I’m curious, so let’s compare the two classes briefly.  (If you don’t already know about it, now is a good time to teach yourself Ctrl-Alt-J in Visual Studio for popping up the Object Browser).

The inheritance tree for a Win Forms Form is:

And the inheritance tree on the WPF side, for the Window, is:

(I included MSDN’s basic description of each class).  We won’t go any deeper than this for now, but the point is that, for WPF, things are very different under the hood.

One difference to note is that WPF does not support MDI (Multiple Document Interface), whereas Windows Forms does.  I could see a case for continuing to support MDI functionality for those who need it, but I can also see why it’s not worth carrying the old MDI framework forward.  It’s rare to see applications that support MDI in exactly the way that Win Forms supported it (windows entirely contained within parent window, etc).  When you do see a parent window containing child windows, the visual interface is likely different from the traditional sizable child windows—e.g. using a series of tabs.  There are so many different ways of doing this that it’s just easier to roll your own mechanism.  Or perhaps we could get some support in WPF in the future for a more updated and customizable implementation of MDI.

Another good way to see what goes on behind the scenes for the main form/window classes is to look at their lifecycle, as described by the events that the classes fire.  I always end up wanting to keep these “window lifetime” event lists for reference purposes, so they’re worth jotting down here.

Forms.Form events (Win Forms)

Loading/opening new form (application startup), events fired are:

Move
LocationChanged
StyleChanged
BindingContextChanged
Load
Layout
VisibleChanged
Activated
Shown
Paint

Closing a Win Forms Form, the events that fire are:

FormClosing
FormClosed
Deactivate

Windows.Window events (WPF)

Loading/opening new window (application startup), events fired are:

Initialized
IsVisibleChanged
SizeChanged
LayoutUpdated
SourceInitialized
Activated
PreviewGotKeyboardFocus
GotKeyboardFocus
LayoutUpdated
Loaded
ContentRendered

Closing a WPF Window, the events that fire are:

Closing
IsVisibleChanged
Deactivated
Closed

Adding a Button

Now let’s add our first control to the WPF window in our application.  We’ll add a button to the window by just dragging it onto the design surface in the XAML designer.

The designer ends up looking something like this:

And the XAML snippet in the bottom window is also updated as soon as we add the button:

Note that everything we do in the designer  is immediately reflected in the XAML.  This is because there is an exact match between what the designer renders and what is stored in the XAML.  You can think of the designer (or design surface) as nothing more than a combination XAML viewer and XAML editor.

We can also demonstrate here that working in the opposite direction works as expected—if you edit the XAML, the designer updates immediately to reflect your changes.  Note that we don’t even have to save the file—the content in the designer changes immediately, as we type!  You can also edit property values in the Properties window that is docked to the right of the designer (under the Solution Explorer).

Let’s take a look now at what happens in our generated code, once we have a couple of controls on the design surface.  I’ll add a CheckBox to the window and then open up Window1.g.cs.  Note that this source file is not updated until we build (since it’s generated from the XAML whenever we build).  If we rebuild the project now and take a look, we’ll see that both controls have been declared at the top of our partial class and that the Connect method includes them in its switch statement:

This code is creating/initializing the controls at runtime, based on the content in the BAML memory stream that was included in our assembly.

Event Handlers

Now it’s time to wire up our first event handler so that we can do something when the button is clicked.

At first glance, something important is missing from Visual Studio.  When we have the WPF Designer open for our main window and have selected our button, the Properties window doesn’t seem to list any events.  Entirely missing is the little event icon that lets us get a list of all events for the currently selected control.

The question then becomes—what designer support do we have for adding event handlers in a WPF application?  The answer is to edit the XAML directly.  If we position the cursor at the end of the attribute list for the Button element in our XAML editor and press space, we see a nice intellisense popup listing all available attributes (properties and events).  Note the presence of the Click event in the image below:

If we select the Click event, or start typing “Click”, the editor adds a new attribute for the Click event and the intellisense window changes to indicate <New Event Handler>.  At this point, we can dbl-click on <New Event Handler>  to generate our event handler, or—better yet—just press the TAB key to generate the handler.

Once we’ve created the default event handler, our XAML looks like this (note the default handler name):

Now we can open our partial class implementation of Window1 in Window1.xaml.cs and we see our empty handler that has been generated for us:

Hello World

We’re finally ready to add some “hello world” code to our handler, which will execute when the Push Me button is clicked:

And—highly satisfying—we can run our program and get one of two message boxes to display, depending on whether the “verbose” checkbox is checked:

Next time, I’ll start looking in more depth at the various controls available in a WPF application, starting with the Button.

Hello WPF World, part 2 – Why XAML?

Let’s continue poking around with a first WPF “hello world” application.  We’ll continue comparing our bare bones wizard-generated WPF project with an equivalent Win Forms application.  And we’ll look at how XAML fits into our application architecture.

Last time, we compared the Win Forms Program class with its parallel in WPF–an App class, which inherits from System.Windows.Application.  The application framework in Win Forms was pretty lightweight–we just had a simple class that instantiated a form and called the Application.Run method.  WPF was just a bit more complicated.  If we count the generated code, we have an App class split across a couple of files, as well as a .xaml file that defines applicaton-level properties (like the startup window).

Now let’s compare the main form in our Win Forms application with the main window generated for us in WPF.  (The fact that WPF calls it a window, rather than a form, hints at the idea that GUI windows aren’t meant to be used just for entering data in business applications).

In Windows Forms, we have two files for each form–the form containing designer-generated code (e.g. Form1.Designer.cs) and the main code file where a user adds their own code (e.g. Form1.cs).  These two source files completely define the form and are all that’s required to build and run your application.  In Windows Forms, the designer renders a form in the IDE simply by reading the Form1.Designer.cs file and reconstructing the layout of the form directly from the code.  (The IDE does create a Form1.resx resource file, but by default your form is not localizable and the resource file contains nothing).

When you think about it, this approach is a bit kludgy.  The designer is inferring the form’s layout and control properties by parsing the code and reconstructing the form.  Form1.Designer.cs is meant to contain only designer-generated code, so with partial classes, we can keep designer-generated code in a single file and it only contains designer code.  But it’s clumsy to use procedural code to define the static layout of a form.

Here’s a picture of how things work in Win Forms:

In this model, the Form1.Designer.cs file contains all the procedural code that is required to render the GUI at runtime–instantiation of controls and setting their properties.  We could dispense with the designer in Visual Studio—it’s just a convenient tool for generating the code.  (I’m ashamed to admit that I’ve worked on projects that broke the designer and everyone worked from that point on only in the code–ugh)!

Now let’s look at WPF.  Here’s a picture of what’s going on:

Note the main difference here is–our designer works with XAML, rather than working with the code.  This is the big benefit of using XAML–that the tools can work from a declarative specification of the GUI, rather than having to parse generated code.  This also means that it’s easier to allow other tools to work with the same file–e.g. Expression Blend, or XamlPad.

Then at build time, instead of just compiling our source code, the build system first generates source code from the XAML file and then compiles the source code.

But this isn’t quite the whole story.  It’s not the case in WPF that the Window1.g.cs file contains everything required to render the GUI at runtime.  If we look at the Window1.g.cs file, we don’t find the familiar lines where we are setting control properties.  Instead, we see a call to Application.LoadComponent, where we pass in a path to the .xaml file.  We also find a very interesting method called Windows.Markup.IComponentConnector.Connect(), which appears to be getting objects passed into it and then wiring them up to private member variables declared for each control.  If we add a single button to our main window, the code looks something like:

But then the obvious question is–what happened to all those control properties?  Where do the property values come from at runtime?

Enter BAML–a binary version of the original XAML that is included with our assembly.  Let’s modify the above picture to more accurately reflect what is going on:

Note the addition–when we build our project, the contents of the XAML file–i.e. a complete definition of the entire GUI–is compiled into a BAML file and stored in our assembly.  Then, at runtime, our code in Window1.g.cs simply loads up the various GUI elements (the logical tree) from the embedded BAML file.  This is done by the Connect method that we saw earlier, in conjunction with a call to Application.LoadComponent:

MSDN documentation tells us, for LoadComponent, that it “loads a XAML file that is located at the specified uniform resource identifier (URI) and converts it to an instance of the object that is specified by the root element of the XAML file”.  When we look at the root element of the XAML file for our application, we discover that it is an object of type Window, with the specific class being HelloWPFWorld.Window1.  Voila!  So we now see that the code in Window1.g.cs which was generated at build time just contains an InitializeComponent method whose purpose it is to reconstitute a Window and all its constitutent controls from the GUI definition in the XAML file.  (Which went along for the ride with the assembly as compiled BAML).

So what is BAML and where is it?  BAML (Binary Application Markup Language) is nothing more than a compiled version of the corresponding XAML.  It’s not procedural code of any sort–it’s just a more compact version of XAML.  The purpose is just to improve runtime performance–the XAML is parsed/compiled at build time into BAML, so that it does not have to be parsed at runtime when loading up the logical tree.

Where does this chunk of BAML live?  If you take a look at our final .exe file in ILDASM, you’ll see it in the manifest as HelloWPFWorld.g.resources.  Going a tiny bit deeper, the Reflector tool shows us that HelloWPFWorld.g.resources contains something called window1.baml, which is of type System.IO.MemoryStream.  (I found something that indicated there was also a BAML decompiler available from the author of Reflector, which would allow you to extract the .baml from an assembly and decompile back to .xaml–but I couldn’t find the tool when I went looking for it).

So there you have it.  We haven’t quite yet finished our “hello world” application, but we’re close.  We’ve now looked in more depth at the structure of the application and learned a bit about where XAML fits into the picture.  Next time, we’ll add a few controls to the form and talk about how things are rendered.

Hello WPF World, part 1

All right, it’s time to create our first “hello world” application in WPF.  Let’s just use the Visual Studio wizard to create an application and then poke around to see what we got.  (Yes, I know I’m a bit late to the WPF game, but let’s just get started).

We’ll start by doing a New Project in Visual Studio 2008.  Under Visual C# (I’m a C# guy), select Windows to see projects related to thick clients.  If you change the targeted .NET Framework to version 3.0 or 3.5, you’ll see the following WPF project types:

  • WPF Application
  • WPF Browser Application
  • WPF Custom Control Library
  • WPF User Control Library

This seems pretty straightforward.  We’re building an application, rather than a control library.  So we want to create a WPF Application. I’ll explore creating WPF controls later.

Now it’s time to see what the project wizard created for us in our project.  As we walk through the solution, let’s compare the pieces with an equivalent “hello world” application in Win Forms, just to see how WPF differs.

AssemblyInfo.cs

For starters, both projects have an AssemblyInfo.cs file that describes metadata for the assembly.  Cracking them open,  they’re pretty similar, as expected.  But there are a couple of differences.

The WPF project includes a couple additional namespaces—System.Resources and System.WindowsSystem.Resources is added for the NeutralResourcesLanguage attribute.

System.Windows is, surprisingly, a new namespace for WPF, containing a lot the high-level WPF classes and types.  In this case, we’re using the ThemeInfo attribute and the ResourceDictionaryLocation enumeration.

The first new chunk of stuff in the WPF file is a commented out instance of the NeutralResourcesLanguage attribute and a comment about adding an <UICulture> tag to your project, if you want your application localizable.  Adding the <UICulture> tag  to your project file will tell the project that it should be localizable, and causes creation of the external satellite DLL.  We’re also instructed to uncomment the NeutralResourcesLanguage attribute, and setting the culture to match the <UICulture> tag—which indicates what our “neutral” language is, i.e. the native language of the assembly itself.  This reportedly speeds performance during the resource fallback process—runtime won’t  bother looking for an external resource DLL if the thread’s CurrentUICulture matches the neutral culture of your assembly.  A little unclear why the attribute is required—possibly just to make sure you set the neutral culture to match the <UICulture> tag.

Next, the WPF AssemblyInfo.cs file contains an instance of the ThemeInfo attribute.  This attribute has to do with defining theme-specific resources for your controls—i.e. you define a set of resources that applies a style to your controls, depending on which Windows theme is active.  Looks like a topic for a future post.

Resources.resx & Resources.Designer.cs

The default resources file created by the project wizard is the same for a WPF application as for a Win Forms application.  We get an empty resource file and an internal class that will be used to contain strong-typed string resources.  (The strongly typed resources were new in VS2005 and offer the huge benefit of being told at compile time that you misspelled a resource name, rather than just having the resource not be found at run time).

Settings.settings & Settings.Designer.cs

The default settings file in WPF is the same as the Win Forms file, with one subtle difference.  The WPF version uses an XML namespace of “uri:settings”, rather than the Win Forms explicit namespace, which is “http://schemas.microsoft.com/VisualStudio/2004/01/settings”.  I’m not enough of an XML or a URI/URN guru to understand the difference here, other than observing that the WPF version is more generic.  It’s also interesting to see that using “uri” for the URI scheme (the part before the colon) is not an official IANA-registered usage.  (See http://en.wikipedia.org/wiki/URI_scheme).

Assembly References

The WPF project references three new assemblies for WPF : PresentationCore, PresentationFramework, and WindowsBase.  These just contain new WPF types, sprinkled across many different namespaces.  (By the way, if you’re curious about the total # types in the Framework, take a look at this post by Brad Abrams: http://blogs.msdn.com/brada/archive/2008/03/17/number-of-types-in-the-net-framework.aspx).

Out of curiosity, I ran NDepend on these WPF assemblies and came up with the following metrics.  PresentationCore – 2,711 types,  PresentationFramework – 2,306, and WindowsBase – 785.  And these are just a subset of the assemblies introduced for WPF in .NET 3.0!

The WPF project does not reference the System.Deployment, System.Drawing or System.Windows.Forms assemblies.  System.Drawing and System.Windows.Forms include GDI+ and Windows Forms functionality, respectively, so it’s obvious why we no longer need them in WPF.  System.Deployment is related to deploying with ClickOnce and it’s not clear why the Win Forms project included it by default.

App.xaml vs. Program.cs

Now we come to the core differences between a Win Forms and a WPF application.  In terms of what you see in the WPF project, the App class couldn’t be simpler—an empty partial class deriving from System.Windows.Application and a mostly empty XAML file:

App.xaml.cs

App.xaml.cs

App.xaml

App.xaml

Wait a minute!  Where’s my Main() function?  In the wizard-generated Win Forms project, we got a Program.cs file with a Main(),  which called System.Windows.Forms.Application.Run, passing it an instance of our main form.  But how does the WPF application start itself up?

The hint is that our App class is declared as a partial class.  If we right-click on the App class and select Go To Definition, we can hunt down the file App.g.i.cs (in the \Debug or \Release folder, if we’ve built our application).  You can also click Show All Files in the Solution Explorer and expand the obj\Debug folder, finding App.g.cs.  (These files appear to be identical—perhaps the i.cs file is generated by Intellisense)?

The magic that creates these generated files at build time comes from the <Generator>MSBuild:Compile</Generator> line in our .csproj file, for the App.xaml file (under ApplicationDefinition tag).  When App.xaml is built, MSBuild generates the actual code that represents what was declared in App.xaml, storing the code in App.g.cs.  The actual code generation magic happens in the Microsoft.Build.Tasks.Windows namespace, which lives in the PresentationBuildTasks assembly.  Sounds like another topic for a future blog.  (I started to get lost in Ildasm).

Now let’s take a look at the App.g.cs file.  It shows that we’re deriving from System.Windows.Application, which is the main WPF application class.  We also see that the InitializeComponent method is pulling stuff in from the XAML file.  In our case, all we have in App.xaml is a value for the StartupUri attribute, which pointed to the XAML file for our main window.  In our code, this maps to setting the StartupUri property of the Application class.  This is basically just—the UI that should be shown when our application starts.

App.g.cs

App.g.cs

The Main function is very similar to what we find in Program.cs for our Win Forms application—we just create an instance of our App class, call InitializeComponent to set stuff up, and call the Application.Run method.  It should be no surprise that the documentation for Run tells us that it creates a System.Windows.Threading.Dispatcher object, which creates a message pump to process windows messages.

Note that we could also call Run and pass it a Window object to indicate the first window to open when the application starts.  Instead, the generated code specifies the first window by setting the StartupUri property.

Next time: Looking at the Window1.xaml, Window1.xaml.cs and Window1.g.cs files, which define the application’s main window.

It’s a WPF World, part 2

Let me continue my ramble about Microsoft technologies leading up to WPF.  Last time, I ended by talking about the .NET technologies and why I think they are so important.  .NET has become the de facto standard for developing applications for the Windows platform (thick clients).  And although ASP.NET likely doesn’t have nearly as big a chunk of market share as Windows Forms, it feels like the WISA stack (Windows, IIS, SQL Server, ASP.NET) is gradually overtaking the LAMP stack (Linux, Apache, MySQL, PHP).  And with the rise of RIAs (Rich Internet Applications), ASP.NET Ajax will likely encourage the continued adoption of the ASP.NET technologies.

Going back to my list of the most important benefits of the .NET world from last time, I realized that I’d like to add a final bullet item.  (In the list–what’s so special about .NET):

  • Programmer Productivity — with things like intellisense and code snippets in Visual Studio, you can be incredibly productive, whether working in Win Forms or ASP.NET

I make the claim about productivity without having had experience with other development environments (at least since doing OWL development with Borland tools).  But the rise in productivity is true even just of Microsoft tools.  They just continue to get better and better.  And though “real programmers” might pooh-pooh all this intellisense nonsense in favor of hand coding in Textpad, I have to believe that even these guys would be more productive if they truly leveraged the tools.

When .NET first came out, I remember reading the marketing materials and being a little misled about where ASP.NET fit into things.  It seemed like Microsoft was touting convergence of Windows vs. web development, by talking up the similarities of the dev experience, working in Windows Forms vs. Web Forms.  Developing with ASP.NET was ostensibly very similar to developing Win Forms applications–you started with an empty design surface, dragged visual controls onto it, and double-clicked to bring up an editor where you wrote your code-behind.  (Nevermind that this model encouraged low quality architectures as people wrote monolithic chunks of event handler code–perpetuating bad habits that we learned with VB6).

But the web model was still very, very different from the thick client model.  On Windows, we were still working with an event-driven model, using the same old Windows message loop that we’d always used.  But users interact with a web-based interface in a completely different way.  We used server-side controls in ASP.NET to hide some of the complexity, but we were still just delivering a big glob of HTML to the client and then waiting for a new HTTP request.

The ASP.NET development environment also felt a bit kludgy.  I remember being a bit dismayed when I wrote my first ASP.NET application.  In the classic Win Forms development environment, there were two separate views of your application, where a developer lived–the design surface and the code behind.  The ASP.NET environment has three–the design surface, the code behind, and the HTML content.  So now instead of a developer jumping back and forth between two views, you end up jumping around in all three.

Spoiler–this web application architecture gets a lot cleaner with Silverlight 2.0.  There seems to be actual convergence between thick vs. thin clients as we use WPF for thick clients, Silverlight 2.0 for thin.  But more on that later.

So along comes WPF (Windows Presentation Foundation) and XAML (Extensible Application Markup Language).

I remember when I first read about XAML and WPF (Avalon at the time).  My first reaction was to be mildly frustrated.  At first glance, XAML seemed to be an arbitrary switch to a new syntax for defining GUIs.  And it seemed cryptic and unnecessary.  In the effort to be able to define a user interface declaratively rather than procedurally, it looked like we were ending up with something far messier and garbled than it needed to be.  It felt much easier to understand an application by reading the procedural code than by trying to make sense of a cryptic pile of angle brackets.

But I’ve come to realize that the switch to defining GUIs declaratively makes a lot of sense.  With XAML, thick clients move a bit closer to web applications, architecturally–the GUI is built from a static declaration of the UI (the XAML), a rendering engine, and some code behind.  And the layout of a user interface (rather than the behavior) is implicitly static, so it makes sense to describe it declaratively.  [As opposed to Windows Installer technology, which converts the dynamics of the installation process to something declarative and ends up being far messier].

Why does it make sense to separate the markup from the code like this?

  • Architecturally cleaner, separating the what (XAML declaraction of GUI) from the how (code-behind).  (Separating the Model from the View)
  • Enables broad tool support–tools can now just read from and write to XAML, since the GUI is now defined separately from the application itself.  (Enabling separate tools for designers/devs, e.g. Expression Blend and Visual Studio).
  • Cleaner for web apps — because we can now serialize a description of the GUI and send it across the wire.  This just extends the ASP.NET paradigm, using a similar architecture, but describing a much richer set of elements.

Another of my earlier reactions to XAML was that it was just a different flavor of external resource (.resx) files.  It looked similar–using angle brackets to describe GUI elements.  But XAML goes far beyond .resx files.  Resource files are used to externalize properties of UI controls that are potentially localizable.  E.g. Control sizes, locations and textual elements.  But the structure is flat because a resource file is nothing more than a big collection of keyword/value pairs.  Nothing can be gleaned about the structure of the UI itself by looking at the .resx file.  XAML, on the other hand, fully describes the UI, hierarchically.  It is far more than a set of properties, but it is logically complete, in that it contains everything required to render the GUI.

XAML is a big part of what makes WPF so powerful.  But there are a number of other key features that differentiate WPF from Windows Forms.

  • Totally new rendering engine for the GUI, based on Direct3D.  This enables better performance in rendering of the GUI, because everything in your GUI is described as 3D objects.  So even apparent 2D user interfaces can take advantage of hardware acceleration on the graphics card.  [The new NVidia GT200 GPU has 1.4 billion transistors, compared with original Core 2 Duo chips, which were in the neighborhood of 300 million].
  • Vector graphics.  The GUI is now entirely defined in vector graphics, as opposed to bitmapped/raster.  This is huge, because it means that you can define the GUI geometry indepedent of the target machine’s screen resolution.  This should mean–no more hair-pulling over trying to test/optimize at various screen resolutions and DPI settings.  (Gack)!
  • Bringing other APIs into the .NET fold.  E.g. 3D, video, and audio are now accessible directly from the .NET Framework, instead of having to use APIs like DirectX.
  • Focus on 3D graphics.  All of the above technologies just make it easier to develop stunning 3D graphical user experiences.  Powerful/cheap graphics hardware has led to 3D paradigms like Apple’s “cover flow” showing up more and more often in the average user interface.  (Battleship Grey, you will not be missed))

So where does WPF fit into the rest of the .NET world?  Is WPF a complete replacement for Windows Forms?

Adopting WPF will not be nearly as big a learning curve as adopting .NET was, originally.  WPF is a full-fledged citizen of the .NET world, existing as a series of .NET namespaces.  So .NET continues to be the premiere Microsoft development technology.

WPF is definitely a replacement for Windows Forms.  It is the new presentation layer, meant to be used for creation of new Windows-based applications (thick clients).  Microsoft is probably hesitant to brand WPF purely as a Windows Forms replacement, not wanting to dismay development shops that have invested a lot in learning .NET and Windows Forms.  But it’s clearly the choice for new Windows-based UI development, especially as the number of WPF controls provided by the tools, and shipped by 3rd party vendors, increases.

WPF is also highly relevant to web development (thin clients).  Silverlight 2 allows a web server to deliver browser-based RIAs containing a subset of the widgets found in WPF.  (Silverlight 2 used to be called Windows Presentation Foundation/Everywhere).  With WPF and Silverlight, Windows and web development are definitely converging.

So we clearly now live in a WPF world.  WPF will rapidly become more widely adopted, as it is used for more and more line-of-business applications, as well as serving as the underlying engine for the new Silverlight 2 RIAs that are starting to appear.  And the best news is that we also still live in a .NET world.  We get all of the .NET goodness that we’ve learned to love, with WPF being the shiniest new tool in our .NET toolbox.

It’s a WPF World, part 1

Has everyone realized that it’s a WPF world out there?  What I mean is this.  Let’s say you’re a classic Windows programmer–meaning that you want to build a basic native Windows application.  You know–just an .exe file that lives on your hard drive, that you run, and that displays a window with some stuff in it.   (Let’s set aside for just a moment Bill’s “Internet Tidal Wave” and Ozzie’s “Device Mesh” and assume that you want to build a good old-fashioned Windows app).  Where are we at today?  Well, WPF is absolutely the world that we live in at the moment.

Let’s think about how we got here.  In the beginning, it was a WinAPI world.  For years, the mantra was, “Thou shalt read Petzold, cover to cover”.  If you don’t know what this means, you’re either too young or you just plain don’t care about what’s under the hood.  With NT and Windows 95, we got to graduate to Win32, but it was still a grand world, working with WinAPI.  (Test yourself–do you get a warm fuzzy feeling, or do you shudder, when you read the following)?

SendMessage(hList,LVM_SETITEM,0,(LPARAM)&LvItem); // Enter text to SubItems

Ahh, memories. Well, after the WinAPI world, we got to graduate to the world of MFC.  (Didn’t it stand for “Millions of Freakin’ Classes”)?  Remember message maps and CCmdTarget?  Actually, aside from unwanted Afx functions in later COM applications and the inevitable bloat, I mostly escaped working with MFC.  I was writing Motif applications for VAX/VMS at the time–but that’s a different story.

Then we come to the glorious days when it was a COM world.  Ahh–David Chappell, Don Box, and even–shudder–Brockschmidt.  I’m still paying off loans taken out to buy tech books during the COM era.  All you really need to know about COM is: a) it’s all about interfaces, baby; b) you need to chant the following in your sleep every night–QueryInterface, AddRef, Release; and c) you’ll learn to love the venerable BSTR.  When I’d worked with COM long enough to finally understand it, my main thought was–holy crap, this stuff is complicated enough that I now truly have job security.

It was, briefly, an ATL world.  ATL was a fluffy breath of fresh air for the beleagured COM programmer.  Or maybe it just felt like a well-earned reward for having truly learned COM.  Sort of like the boy scout who finally gets to use a gas stove after years of making fires by rubbing two sticks together.  Actually, I get a pretty warm fuzzy feeling, remembering ATL–and building out-of-process COM servers in VC6.  Not a bad world at all, really.  I almost miss it.

Still with me as I ramble?  Shall we jump to 2002, when it became a .NET world?

Ok, a brief rant.  A few years back, a fellow colleague (Software Engineer), who’d been a developer for quite a few years, confided to me that he liked programming, but what he absolutely hated is how things kept changing.  Every time a new technology came out, he became infuriated.  I was a little shocked–first of all, because the constant stream of new technologies is what I absolutely love about being a Windows developer.  But mainly, I thought–if you hate change so much, why the hell are you a Windows developer?

On to 2002, when it became a .NET world.  Regardless of when you jumped into .NET, or which Windows technology you came from, you just have to agree that .NET is our Seventh Heaven reward for all of those years of wrestling with these other technologies.  Or is it just Dante’s outermost circle of hell?  In any case, being a C# developer in the .NET world is truly a treat, after the path that we’ve taken to get here.  (Yes, yes, yes–VB.NET is fine language, you do have XML literals while we do not, and you are a person that people like and respect).

If you can’t tell, I’m a C# developer.  Of course, as has been said many times before, the choice of language in .NET is really irrelevant–it’s all about the framework.  The learning curve is obviously in learning thousands of framework classes, rather than arguing about this vs. Me.  I’m definitely not a VB hater–I’ve written a lot of VB6 code over the years.  But I just love working with C#.  It’s C++ with all the dangerous pointy stuff that can hurt you removed.  So no need for political correctness here–I’m going to post examples in C# and get on with it.

What’s so special about .NET?  Some of the standouts for me, in no particular order, are:

  • Language Independence — you can write in your language of choice, since all CLS-compliant languages just resolve to IL and your code can run in the the CLR.
  • Memory Management — (and garbage collection).  No more having to remember to delete every element of an array, as well as the array itself.  Just create your objects and let the CLR worry about having to delete them.
  • CLR as a VM — because your code runs in a virtual machine, we can hypothetically run anywhere and are no longer tied to Windows.  (E.g. Mono).  This is less critical for thick clients but will become more important as we move to Silverlight 2).
  • Reflection — very cool, for debugging, discovery, or just the elegance of having an assembly be self-describing.  Much nicer than the old COM external .tlb file scheme.
  • The .NET Framework — without a doubt, the greatest benefit of .NET.  Wow–thousands of classes that someone else has written that just work.  I’m constantly stumbling on things that I thought I’d have to write, but discover are already present in the framework.

That’s a very crude list of some of the highlights of .NET and why I think it’s so powerful.  There’s no need for me to write yet another high-level overview of what .NET is, or what the parts are.  The book Understanding .NET, by David Chappell is still an excellent introduction/overview.

Well, I’ve run out of room and run out of time.  I’ll save the rest of this particular ramble for later, when I talk just a bit about Win Forms, ASP.NET, and WPF..

[Ok, an editorial comment–this started out being a simple set of notes for creating a first “Hello World” WPF application.  Instead, it sort of turned into a campy litany of old Microsoft technologies.  So I’ll save the Hello World post for next time].