Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Fun With Physics

0.00/5 (No votes)
3 Apr 2008 5  
WPF: A Beginner's Guide series, end exemplar and fun with Physics.

Preface and Thanks

This is the final part to my WPF for Beginners series, and it has been quite a journey even for me. It has taken quite a lot of effort to create this series. And I could not have done it without a few people's help, namely the following

  • Robert Ranck: For creating the VB.NET conversions of my C# projects for all of Part 1 - Part 6 articles that make up this series.
  • Karl Shifflett: For answering some of our dumb VB.NET queries, and for correcting my spelling and occasional syntax cock-up's. Thanks eagled eyed one... nothing wrong with your eyes... you may not sleep enough, but your eyesight is A1. Don't let anyone tell you different!
  • Bea Costa: For allowing me to use her PlanetListbox in Part 6, thanks Bea.
  • Paul Stovell: For his excellent WPF ErrorProvider class which is really cool when you are manually binding expression updates (as we are here)
  • Rubi Grobler: For the Adding Glass Effect to WPF using Attached Properties article. And the code which I used in this article.

Introduction

I have been working on this app on and off since Part 1, and it has kind of become a labour of love. I wanted to tweak this and that. I am finally happy with it, and I really hope you lot like it as much as I do. I have tried really hard to make it use all the stuff I have covered as part of this article series, which is no mean feat let me tell you. On top of that, I wanted to make it look cool... as I like cool things. So naturally, I went for some Physics driven application. Neato!!!

As I say, this article is the last part (the finale app if you like) where I will be using all the stuff we have learnt along the way. Just to remind you, that means we will be covering all of the following:

This article is actually a sort of joint venture (my first, but hopefully not last one) with my old team leader. Ladies and gentlemen, may I present Mr. Fredrik Bornander. Fredrik is not only the best programmer I have ever met, but he is a very cool guy, whom I really get on with. I also enjoy bouncing ideas of him. We have plans for many, many more articles in this and other areas, so watch out for them.

However, we are where we are, so I think the way I am going to do this article is talk about what the app does, give you a video, and then break it down (basically dissect it) and relate each part of the dissected app to one of the original article series parts. That way, you can see which of this article series you need to read if you want to see how something works in more detail.

As this is a joint article, where Fredrik also created some of the application's code, I will mention what Fredrik did as well. In fact, I am going to get Fredrik to write the words for his part. Of course, as Fredrik is Swedish, his spelling will need to checked (actually for anyone that's read any of my articles, it's probably the other way round.. he should probably check my spelling...though at least I can say "vowels" not "whales" hey Fredrik...ha ha).

One thing that I need to mention is that for this one, there will be no VB.NET version published. It's too much work, and I need to move my attention on to other articles now. Sorry!

Anyway, here is what we are going to cover in this article, but only in C#, sorry again:

What the Demo App Does

Essentially, the demo app is very simple. It uses the standard SQL Server Northindwind database, where a number of Customer objects are first retrieved, and then when requested, a Customer's related Order objects are fetched from the database. Both the Customer and Order objects allow the user to edit their details and have some validation performed to ensure that the entered data is valid. That's pretty much it. But, as we will see, this still has plenty of scope to use all the WPF goodness we have learnt along the way. As I say, we will also mix it up with a pinch of Physics to make it move in weird ways... which we like.

Prerequisites

As previously stated, the demo app uses SQL Server (I use SQL Server 2005), but as long as you have the Northwind database installed, it should all be OK whatever version of SQL Server you use. If you don't have the Northwind database, you will need to download and install it from here. Also note that you will need to modify the connection string that the application uses to match your own SQL Server installation. This can be done within the associated app.config file within the "PhysicsHost" project.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
    </configSections>
    <connectionStrings>
        <add name="PhysicsHost.Properties.Settings.NorthwindConnectionString"
            connectionString="Data Source=VISTA01\SQLEXPRESS;
            Initial Catalog=Northwind;Integrated Security=True"
            providerName="System.Data.SqlClient" />
    </connectionStrings>
</configuration>

What Sacha Did

I created the entire application and ported Fredrik's Physics code to WPF. It was WinForms based, so Sacha created the necessary changes to it in order to WPF-ify it. But I can't really take much credit for the Physics code. That's Fredrik's baby all the way. Fredrik is really a frustrated games programmer who starts N-many DirectX games a month, but finishes none of them. Ha ha. At least he'll finish this article with me. So yeah... I ported the Physics code to WPF, but also did all the other WPF elements that make up this demo application. This includes Layout/Resources/Commands and Events/DPs/DataBinding/Styles and Templates, and also LINQ to SQL. Oh, and I also created the trivial DashedOutlineCanvas within the Physics project.

What Fredrik Did

Sacha used to work with Fredrik, and a while back at work, Fredrik started working on this Physics thing (whilst he should have been doing what he was paid to do, which is write boring Sybian C++, but hey), which kind of peaked Sacha's interest. This Physics thing that Fredrik was working on later became this CodeProject article. But Sacha thought this could be used within a WPF app, so Sacha and Fredrik set about making that happen. As a result, the Physics stuff you see in this application is based on the original Physics stuff that Fredrik did for his original CodeProject article. Nice one Fredrik.

A Video of the Demo App

Due to the nature of Physics, the only way that I can do the attached demo application any justice, at all within the scope of this article, is to show you a video which shows it in action. As such, please click on the image below to see a video of the demo application in action:

Click the image or here to view the video.

I would suggest waiting for the entire video to stream, then watch it. It will make most sense that way.

Let the Fun Begin: Dissection

OK, now I have told you what the app does, told you what you need to try it at home, and shown you a video of the demo app in action. I will not talk you through how it was made. Like I say, I think the best way is to dissect the app and relate it back to the individual articles so that if you are lost or maybe are new to this series, you can go back and have a look at the relevant article part.

The application is structured using two projects: the Physics engine and the WPF application. This is shown below:

These two projects will be discussed in detail below. The WPF project has subfolders which contain various files; the folder name gives you an idea of what the files are for.

Physics

This section was written by Fredrik Bornander and proof read and inserted/added to by Sacha Barber.

By using simple Physics to layout controls on a panel, it is easy to get an application to have a very different feel than applications using "normal", static layout of its controls.

The aim with this Physics implementation is to create an easy way for developers without any experience in Physics programming to be able to build cool looking applications.

By creating a control, in this case, a subclass of Canvas (which is called ParticleCanvas) that can be used as any other control in a window, it's simple to add Physics controlled controls to any UI.

Any control added to ParticleCanvas can then be related to a Physics Particle; Springs are then added to constrain the particles in whatever configuration is desired. It should be noted that the controls should really only be added to the ParticleCanvas in the code-behind where they are allocated to a Particle and attached to a Spring. If they are added in XAML, there would still need to be some code added in the code-behind to attach the controls to Particles.

ParticleCanvas Class

ParticleCanvas is the canvas control that owns ParticleSystem (Physics system) It uses a DispatcherTimer to regularly update the state of its internal ParticleSystem (Physics system) so that it can be animated. It does this by telling the internal ParticleSystem (Physics system) to do an integration using the elapsed time; an integration is the operation in which the ParticleSystems (Physics system) next state is computed. This is done once for every timer "tick", and after each integration, all the controls that are related to a Physics Particle are relocated to the Particle's new position. This is all taken care of in the HandleWorldTimerTick method:

/// <summary>
/// This method is hooked to a timer event and is responsible for calling
/// the methods that updates the world state.
/// </summary>
private void HandleWorldTimerTick(object sender, EventArgs e)
{
    Rect constaintsRectancle = 
         new Rect(0, 0, this.ActualWidth, this.ActualHeight);
    lock (ParticleSystem.Particles)
    {
        // TODO: Make sure the actual timestep is configurable, 
        // or better yet, is the actual time elapsed between updates.
        ParticleSystem.DoEulerStep(0.005f, constaintsRectancle);
        foreach (Particle particle in ParticleSystem.Particles)
        {
            // Make sure the Controls are located center on their particles.
            particle.SnapControl();
        }
    }
    this.InvalidateVisual();
}

The final call to this.InvalidateVisual() forces ParticleCanvas to redraw its controls, which snaps the Particles to their new positions.

As the user is able to drag Phyics constrained controls around using the mouse, ParticleCanvas has to keep track of a series of mouse events.

PreviewMouseDown, PreviewMouseMove, and PreviewMouseUp are all used to handle control dragging.

During PreviewMouseDown, the list of Physics Particles are queried for a Particle with the event sender control related to it; i.e., a search is done to see if there is a Physics Particle that is currently related to the control that received the mouse event. If this is the case, that control is brought forward along the Z-Index to make it appear on top of other controls, and the related Physics Particle gets its current velocity cleared and its mass set to positive infinity. The reason for resetting the mass is that Particles with an infinite mass is ignored by the ParticleSystem (Physics system) when the integration is done, and this is important as only the mouse should control the movement of that Particle now.

/// <summary>
/// When the mouse is clicked on a control in the Simulation panel all
/// Particles are searched to find the Particle that has the Control
/// as associated control. Then that Particle is made immovable by setting
/// its mass to infinity and are thus no longer effected by the simulation.
/// Search for control has to go up through the tree as only the top level
/// control is associated with the Particle if a particles associated 
/// Control is a Panel containing more controls.
/// </summary>
public void ParticleCanvas_PreviewMouseDown(object sender, MouseEventArgs e)
{
    if (e.LeftButton == MouseButtonState.Pressed && ownerWindow != null)
    {
        previousAbsoluteMousePosition = Mouse.GetPosition(this);

        Vector mousePosition = previousAbsoluteMousePosition.ToVector();

        var particleWhere = from particle in ParticleSystem.Particles
                            where particle.Control == sender
                            select particle;

        if (particleWhere.Count() > 0)
        {
            Particle particle = particleWhere.First();
            if (selectedParticle != null)
                selectedParticle.Mass = selectedParticleMass;
            selectedParticleMass = particle.Mass;
            selectedParticle = particle;
            selectedParticle.Mass = Single.PositiveInfinity;
            selectedParticle.Velocity = new Vector();
            selectedParticle.Control.SetValue(Canvas.ZIndexProperty, zIndex++);
            return;
        }
    }
}

During PreviewMouseMove, the distance the mouse cursor has travelled is measured, and the Particle is updated with the same movement; the control itself will have its position updated automatically by the next timer tick.

/// <summary>
/// This updates a Particles position when a Control is being dragged.
/// Note that it is not required to move the Control as this is being 
/// fixed in HandleWorldTimerTick when Controls snap to Particles
/// positions.
/// </summary>
public void ParticleCanvas_PreviewMouseMove(object sender, MouseEventArgs e)
{
    if (e.LeftButton == MouseButtonState.Pressed)
    {
        if (selectedParticle != null)
        {
            Point absolutePosition = Mouse.GetPosition(this);
            Rect constaintsRectancle = 
                 new Rect(0, 0, this.ActualWidth, this.ActualHeight);
            selectedParticle.SetPosition(
                new Vector(
                    selectedParticle.Position.X +
                    (absolutePosition.X - previousAbsoluteMousePosition.X),
                    selectedParticle.Position.Y +
                    (absolutePosition.Y - previousAbsoluteMousePosition.Y)),
                    constaintsRectancle
                    );

            previousAbsoluteMousePosition = absolutePosition;
        }
    }
}

When the mouse is released and the PreviewMouseUp event fires, the Particle's original mass is restored; this will (if the original mass wasn't positive infinity) allow the ParticleSystem (Physics system) to again move the particle (and therefore also its related control) around.

/// <summary>
/// If a particle is being dragged this event stops that and fires a 
/// ParticleReleasedEvent to signal this.
/// </summary>
public void ParticleCanvas_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
    if (e.LeftButton == MouseButtonState.Released)
    {
        if (selectedParticle != null)
        {
            selectedParticle.Mass = selectedParticleMass;
            FireParticleReleasedEvent(selectedParticle);
            selectedParticle = null;
        }
    }
}

The ParticleCanvas also (optionally) keeps track when the window it is contained in is being moved so that the Particles can be updated accordingly; this allows for a natural behaviour of the controls suspended by Springs as they'll swing back to their state of rest. The ParticleCanvas does this by moving all the movable (those with mass not equal to positive infinity) Particles by the negative distance the window just moved.

This means that if the window was dragged ten pixels to the left, the Particles would be relocated 10 pixels to the right. Try it for yourself; it looks quite cool.

/// <summary>
/// This method gets called whenever the parent form is moved and 
/// moves the particles accordingly.
/// </summary>
public void HandleOwnerWindowMove(object sender, EventArgs e)
{
    Vector deltaMovement = new Vector(ownerWindowPosition.X - 
        ownerWindow.Left, ownerWindowPosition.Y - ownerWindow.Top);
    ownerWindowPosition = new Point(ownerWindow.Left, ownerWindow.Top);
    foreach (Particle particle in ParticleSystem.Particles)
    {
        particle.MovePosition(deltaMovement);
    }
}

ParticleSystem Class

The ParticleCanvas' internal Physics simulation is maintained by a ParticleSystem instance. The ParticleSystem is a class which owns all Particles and Springs, holds all world properties (such as gravity and drag), and computes the integration. The integration is calculated in two major steps:

Step 1: Calculate the Derivatives for All Particles

This means calculating the difference in velocity and position, which is acceleration and velocity. First, all existing forces that are currently acting upon a Particle are cleared so that the Particle is completely unaffected by force. Then the "world" forces are applied; this is the force of gravity and the drag which is kind of like wind resistance. The drag is important to simple simulations as this one, as it puts a "damper" on the system which stabilizes it. After that, the forces that the Springs add to the Particles are applied to the Particles, and after that, the just calculated "state" is stored away in the Particle as a ParticleState.

/// <summary>
/// Calculates the derivative for the all entities in the simulation.
/// </summary>
public void CalculateDerivative()
{
    foreach (Particle particle in Particles)
    {
        // Clear all existing forces acting on the particle
        particle.ResetForce();

        // Add a gravity force
        particle.AddForce(Gravity);

        // Add world drag
        Vector drag = particle.Velocity * -dragFactor;
        particle.AddForce(drag);
    }

    foreach (Spring spring in Springs)
    {
        // Apply what ever forces this spring holds
        spring.Apply();
    }

    foreach (Particle particle in Particles)
    {
        particle.State = new ParticleState(particle.Velocity, 
            particle.Force * (particle.OneOverMass));
    }
}

Step 2: Update the Position with the Particle State

First, the Particle state is scaled down by the time factor so that the update will be proportional to the time elapsed. After that, the Particle velocity is updated by simply adding the state velocity to the Particle's current velocity. The same normally applies for position as well, but as the Particles can be constrained to the ParticleCanvas's rectangle, a few calculations have to be done to make sure it bounces off the edges if it is moving off the screen.

/// <summary>
/// This method is called once per "frame" and is responsible for 
/// calculating the next state of the simulation. That is the 
/// velocities and positions for all the particles.
/// </summary>
/// <param name="deltaTime"></param>
public void DoEulerStep(double deltaTime, Rect constaintsRectancle)
{
    CalculateDerivative();

    foreach (Particle particle in Particles)
    {
        particle.State.Position *= deltaTime;
        particle.State.Velocity *= deltaTime;

        particle.Velocity = particle.Velocity + particle.State.Velocity;

        Vector newPosition = particle.Position + particle.State.Position;
        
        // If the particle is supposed to be constrained
        // to the canvas "visible" area
        // do collision detection and figure out new position and velocity
        if (particle.ConstrainedToCanvas && 
                !constaintsRectancle.Contains(newPosition.ToPoint()))
        {
            double x = particle.Velocity.X;
            double y = particle.Velocity.Y;

            // If particle is moving left and
            // is to the left of the left canvas boundry
            // clamp position and reverse velocity
            // and damp velocity by wall friction
            // This is repeated for each of the four borders.
            if (particle.Velocity.X < 0 && newPosition.X 
                    < constaintsRectancle.Left)
            {
                newPosition.X = constaintsRectancle.Left;
                x *= -(1.0 - wallFriction);
            }

            if (particle.Velocity.X > 0 && newPosition.X 
                    > constaintsRectancle.Right)
            {
                newPosition.X = constaintsRectancle.Right;
                x *= -(1.0 - wallFriction);
            }

            if (particle.Velocity.Y < 0 && newPosition.Y 
                    < constaintsRectancle.Top)
            {
                newPosition.Y = constaintsRectancle.Top;
                y *= -(1.0 - wallFriction);
            }

            if (particle.Velocity.Y > 0 && newPosition.Y 
                    > constaintsRectancle.Bottom)
            {
                newPosition.Y = constaintsRectancle.Bottom;
                y *= -(1.0 - wallFriction);
            }
            particle.Velocity = new Vector(x, y);
        }

        particle.Position = newPosition;
    }
}

The ParticleSystem class also exposes a method for rendering the Springs.

public void Render(System.Windows.Media.DrawingContext dc)
{
    lock(Springs)
    {
        foreach (Spring spring in Springs)
        {
            spring.Render(dc);
        }
    }
}

Particle Class

The ParticleSystem class is responsible for calculating positions and velocities of Particles. Particles are rather simple classes that hold position, velocity, force, and mass used when doing the simulation, and also, optionally, a relation to a Control. If a Control is related to a Particle, that Control's position is aligned with the Particle when the method SnapControl is called.

public void SnapControl()
{
    // If a Control is associated with this Particle then snap 
    // the Controls location so that it centers around the Particle
    if (Control != null)
    {
        Control.SetValue(Canvas.LeftProperty, 
                        (double)Position.X - Control.ActualWidth / 2.0);
        Control.SetValue(Canvas.TopProperty, 
                        (double)Position.Y - Control.ActualHeight / 2.0);
        Control.Arrange(new Rect(Position.ToPoint(), Control.DesiredSize));
    }
}

Other than that, the Particle doesn't hold much logic; it's all calculated by the ParticleSystem class. If a Particle is assigned a mass of positive infinity, it is not affected by any force; this is useful when creating anchor points in the simulation. Note that even if the Particle has positive infinity mass, it can still be moved by dragging it with the mouse.

Spring Class

Springs are used to constrain two Particles by applying a force to the Particles so that the Spring's properties are satisfied. The properties of the Spring are:

  1. Rest length: This is the distance the spring will eventually end up in if the Particles are affected by no other forces.
  2. Spring constant: This is a measure of how stiff the Spring is; the higher the value, the more "eager" to reach its Rest length.
  3. Damping constant: This is a damper that is used to make the Spring move slower and stabilize the simulation.

The forces that the Spring applies to its two Particles are calculated by a method that might look a bit complicated, but really is quite simple:

/// <summary>
/// This method "applies" the springs forces by calculating the force
/// and applying it to it's two <code>Particle</code>s.
/// </summary>
public void Apply()
{
    Vector deltaX = From.Position - To.Position;
    Vector deltaV = From.Velocity - To.Velocity;

    double term1 = SpringConstant * (deltaX.Length - RestLength);
    double term2 = DampingConstant * 
                    Vector.AngleBetween(deltaV, deltaX) / deltaX.Length;

    double leftMultiplicant = -(term1 + term2);
    Vector force = deltaX;

    // FIXME: Should do something about zero-length springs here as the
    // simulation will brake on zero-length springs...
    force *= 1.0f / deltaX.Length;
    force *= leftMultiplicant;

    From.Force += force;
    To.Force -= force;
}

Calculate the direction the force should have, this is always in the direction of the other Particle; apply the Spring constant to the difference in distance between Particles and the desired distance. Figure out the amount of damping needed, this is relative to how much or how little the Particles are moving away from each other; the more they're moving away, the more the damping. Calculate the force to apply to the first particle, negate the force, and apply it to the second Particle.

The Spring can also render itself using a ISpringRenderer; this is just a way to get different looks for a Spring.

How We Used Layout (Part 1)

You can refer to Part 1 for more information about Layouts in WPF.

Layout is literally used everywhere within the demo app. From the Windows to the UserControls to the Custom Styles/Templates. It's everywhere. Probably the best way is to pick a couple of Windows and provide screenshots and the associated layout markup that creates the Windows. Though, there are far too many areas where Layout is used to go into everything. But this should give you a good idea.

There are four Windows within the demo app:

And there are two User Controls, though I won't discuss the layout for these just yet, as the discussion about the UserControls is more suited to Templates/Styles and Lookless controls.

If we pick on two or three of these Windows, say the following ones, we should be able to discuss enough layout I think:

  • MainWindow.xaml
  • EditOrderWindow.xaml
  • AboutWindow.xaml

OK, so MainWindow.xaml looks like:

Now we will forget about the ParticleCanvas in the middle (the yellow boxed area) just for a moment, as Fredrik should have talked about this a bit in the Physics section, and I will talk about the layout aspects a bit more below. Once we gloss over the ParticleCanvas, the layout is failry trivial (I have removed some markup for clarity).

<Window x:Class="PhysicsHost.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:physics="clr-namespace:BarberBornander.UI.Physics;
                   assembly=BarberBornander.UI.Physics" 
    xmlns:models="clr-namespace:PhysicsHost.ViewModel" 
    xmlns:local="clr-namespace:PhysicsHost"
    local:GlassEffect.IsEnabled="true"    
    WindowState="Maximized" 
    WindowStartupLocation="CenterScreen"
    Title="Particles" Height="800" Width="600" 
    Icon="../Images/logo.png"
    Loaded="MainWindow_Loaded" 
    SizeChanged="Window_SizeChanged">

    <Window.Resources>
    ....
    ....
    </Window.Resources>

    <Window.ContextMenu>
        <ContextMenu>
            <MenuItem Tag="../Images/anchor.png" 
                 Header="Reset Anchor To Start Position"
                 Template="{StaticResource contentMenuItemTemplate}" 
                 Click="MenuItem_Click" />
        </ContextMenu>
    </Window.ContextMenu>
    
    <Window.CommandBindings>
    ....
    ....
    </Window.CommandBindings>

    <!-- START OF LAYOUT -->
    <Grid x:Name="LayoutRoot" Background="Black">

        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>

        <!-- Footer Banner -->
        <physics:DashedOutlineCanvas Margin="20,0,0,20" 
                Background="#FFFF9900" Grid.Column="0" 
                Grid.Row="1" 
                MouseDown="DashedOutlineCanvas_MouseDown"  
                VerticalAlignment="Center" 
                HorizontalAlignment="Left" 
                Width="400" Height="20">
            <Label FontFamily="Arial" FontSize="10" 
               FontWeight="Bold" Foreground="Black" 
               Content="FileInfo:// A WPF particle system 
                        by Sacha Barber + Fredrik Bornander"/>
        </physics:DashedOutlineCanvas>


        <DockPanel Background="Black" 
               LastChildFill="True" Grid.Column="0" 
               Grid.Row="0" Margin="0,0,0,10">
            <!-- Top Banner -->
            <Border DockPanel.Dock="Top" 
                 CornerRadius="10,10,0,0" 
                 Height="120" Margin="10,10,10,0"
                 Background="{StaticResource orangeGradientBrush2Stops}">
                <Image Source="../Images/header.png" 
                   HorizontalAlignment="Left" 
                   VerticalAlignment="Top" Width="480" 
                   Height="90" Margin="15,15"/>

            </Border>
            <!-- Particle Canvas -->
            <Border DockPanel.Dock="Bottom" 
                    CornerRadius="0,0,0,0" Margin="10,0,10,0">
                <Border.Background>
                    <LinearGradientBrush 
                             EndPoint="0.484,0.338" 
                             StartPoint="0.484,0.01">
                        <GradientStop Color="#FFFF9900" Offset="0"/>
                        <GradientStop Color="#FF000000" Offset="1"/>
                    </LinearGradientBrush>
                </Border.Background>
                <physics:ParticleCanvas DockPanel.Dock="Bottom" 
                       x:Name="particleCanvasSimulation" 
                       Margin="10,10,10,10" 
                       Width="Auto" Height="Auto">
                    <TextBlock x:Name="txtRemoveOrders" 
                        FontSize="14" FontStyle="Italic" 
                        FontWeight="Bold" Foreground="White" 
                        Canvas.Left="10" Canvas.Top="10" 
                        TextDecorations="Underline" 
                        Text="Remove All Orders" 
                        Visibility="Hidden" 
                        MouseDown="txtRemoveOrders_MouseDown"/>
                </physics:ParticleCanvas>
            </Border>
        </DockPanel>
    </Grid>
</Window>

OK, so EditOrderWindow.xaml looks like:

Here is the layout for this Window (again, I've removed certain markup for clarity):

<Window x:Class="PhysicsHost.EditOrderWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:models="clr-namespace:PhysicsHost.ViewModel"  
    xmlns:validation="clr-namespace:PaulStovell.Samples.WpfValidation"  
    xmlns:local="clr-namespace:PhysicsHost"
    local:GlassEffect.IsEnabled="true"    
    Icon="../Images/logo.png"
    Title="Particles" Height="360" Width="500"
    ResizeMode="NoResize"
    Background="Black" TextElement.Foreground="White">

    <Window.Resources>
    ....
    ....
    </Window.Resources>

    <Window.CommandBindings>
    ....
    ....
    </Window.CommandBindings>

    <!-- START OF LAYOUT -->
    <DockPanel LastChildFill="True">

        <Canvas DockPanel.Dock="Top" Height="50" 
                Background="{StaticResource orangeGradientBrush2Stops}">
            <Image Source="../Images/order.png" Width="40" 
                Height="40" Canvas.Left="5" Canvas.Top="5"/>
            <Label Canvas.Left="50" Canvas.Top="10" 
              Width="auto" Height="auto" Content="EDIT ORDER" 
              FontSize="18" FontWeight="Bold"/>
        </Canvas>

        <DockPanel Margin="5" 
           DockPanel.Dock="Bottom" LastChildFill="True">

            <StackPanel Orientation="Horizontal" 
                     DockPanel.Dock="Bottom" Margin="6">

                <Button x:Name="btnSave" Content="Save" 
                    Height="auto" Width="auto" Margin="5"
                    FontFamily="Arial" Foreground="White"
                    Template="{StaticResource bordereredButtonTemplate}" 
                    Command="{x:Static models:OrderViewModel.SubmitChangesCommand}" />

                <Button x:Name="btnCancel" Content="Cancel" 
                    Height="auto" Width="auto" Margin="5"
                    FontFamily="Arial" Foreground="White"
                    Template="{StaticResource bordereredButtonTemplate}" 
                    Click="btnCancel_Click"/>
            </StackPanel>

            <ScrollViewer ScrollViewer.HorizontalScrollBarVisibility="Hidden" 
                      ScrollViewer.VerticalScrollBarVisibility="Auto" 
                      DockPanel.Dock="Top">
                <!-- Paul Stovells Excellent ErrorProvider-->
                <validation:ErrorProvider x:Name="errorProvider">
                
                    <StackPanel Orientation="Vertical" Margin="5">
                        
                        <!--OrderID-->
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="2*"/>
                            </Grid.ColumnDefinitions>

                            <TextBlock Grid.Row="0" 
                               Grid.Column="0" Text="OrderID"  />
                            <TextBox x:Name="txtOrderID" 
                               Grid.Row="0" Grid.Column="1" 
                               Margin="3"  
                               Text="{Binding OrderID}" 
                               HorizontalAlignment="Stretch" 
                               IsReadOnly="True"/>
                        </Grid>

                        <!--ShipName-->
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="2*"/>
                            </Grid.ColumnDefinitions>

                            <TextBlock Grid.Row="0" 
                               Grid.Column="0" Text="ShipName" />
                            <TextBox x:Name="txtShipName"
                               Grid.Row="0" Grid.Column="1"  Margin="3"  
                               Text="{Binding ShipName, UpdateSourceTrigger=Explicit, 
                               ValidatesOnDataErrors=True}"  
                               Style="{StaticResource textStyleTextBox}" 
                               MaxWidth="{Binding Path=ActualWidth,ElementName=txtOrderID}" 
                               HorizontalAlignment="Stretch"  />
                        </Grid>

                        <!--ShipAddress-->
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="2*"/>
                            </Grid.ColumnDefinitions>

                            <TextBlock Grid.Row="0" 
                               Grid.Column="0" Text="ShipAddress" />
                            <TextBox x:Name="txtShipAddress" 
                               Grid.Row="0" Grid.Column="1"  Margin="3"  
                               Text="{Binding ShipAddress, UpdateSourceTrigger=Explicit, 
                               ValidatesOnDataErrors=True}"  
                               Style="{StaticResource textStyleTextBox}" 
                               HorizontalAlignment="Stretch" Height="50" 
                               MaxWidth="{Binding Path=ActualWidth,ElementName=txtOrderID}" 
                               MinLines="1" MaxLines="2"  />
                        </Grid>

                        <!--ShipCity-->
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="2*"/>
                            </Grid.ColumnDefinitions>

                            <TextBlock Grid.Row="0" 
                              Grid.Column="0" Text="ShipCity" />
                            <TextBox x:Name="txtShipCity" 
                               Grid.Row="0" 
                               Grid.Column="1"  Margin="3"  
                               Text="{Binding ShipCity, UpdateSourceTrigger=Explicit, 
                               ValidatesOnDataErrors=True}"  
                               Style="{StaticResource textStyleTextBox}" 
                               MaxWidth="{Binding Path=ActualWidth,ElementName=txtOrderID}" 
                               HorizontalAlignment="Stretch"  />
                        </Grid>

                        <!--ShipRegion-->
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="2*"/>
                            </Grid.ColumnDefinitions>

                            <TextBlock Grid.Row="0" 
                              Grid.Column="0" Text="ShipRegion" />
                            <TextBox x:Name="txtShipRegion"
                                Grid.Row="0" 
                                Grid.Column="1"  Margin="3"  
                                Text="{Binding ShipRegion, UpdateSourceTrigger=Explicit, 
                                ValidatesOnDataErrors=True}"  
                                Style="{StaticResource textStyleTextBox}" 
                                MaxWidth="{Binding Path=ActualWidth,ElementName=txtOrderID}" 
                                HorizontalAlignment="Stretch"  />
                        </Grid>

                        <!--ShipPostalCode-->
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="2*"/>
                            </Grid.ColumnDefinitions>

                            <TextBlock Grid.Row="0" 
                              Grid.Column="0" Text="ShipPostalCode" />
                            <TextBox x:Name="txtShipPostalCode" 
                               Grid.Row="0" Grid.Column="1"  Margin="3"  
                               Text="{Binding ShipPostalCode, UpdateSourceTrigger=Explicit, 
                               ValidatesOnDataErrors=True}"  
                               Style="{StaticResource textStyleTextBox}" 
                               MaxWidth="{Binding Path=ActualWidth,ElementName=txtOrderID}" 
                               HorizontalAlignment="Stretch"  />
                        </Grid>

                        <!--ShipCountry-->
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="2*"/>
                            </Grid.ColumnDefinitions>

                            <TextBlock Grid.Row="0" 
                               Grid.Column="0" Text="ShipCountry" />
                            <TextBox x:Name="txtShipCountry" 
                               Grid.Row="0" Grid.Column="1"  Margin="3"  
                               Text="{Binding ShipCountry, UpdateSourceTrigger=Explicit, 
                                     ValidatesOnDataErrors=True}"
                               Style="{StaticResource textStyleTextBox}" 
                               MaxWidth="{Binding Path=ActualWidth,ElementName=txtOrderID}" 
                               HorizontalAlignment="Stretch"  />
                        </Grid>
                    </StackPanel>
               </validation:ErrorProvider>         
            </ScrollViewer>
        </DockPanel>
    </DockPanel>
</Window>

AboutWindow.xaml looks like:

Here is the layout for this Window (again, I've removed certain markup for clarity):

<Window x:Class="PhysicsHost.AboutWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:PhysicsHost"
    local:GlassEffect.IsEnabled="true"    
    Title="Particles"
    Icon="../Images/logo.png"
    ResizeMode="NoResize"
    Width="500" Height="350" 
    Background="#FF000000">

    <Window.Resources>
        <Storyboard x:Key="OnMouseEnterSachas">
        ....
        ....
        </Storyboard>

        <Storyboard x:Key="OnMouseEnterFredriks">
        ....
        ....
        </Storyboard>
    </Window.Resources>

    <Window.Triggers>
        ....
        ....
    </Window.Triggers>

    <!-- START OF LAYOUT -->
    <DockPanel Width="Auto" Height="Auto" 
          LastChildFill="True" Background="#FF000000">
        <Canvas Width="Auto" Height="49" 
               Background="#FFFF9900" DockPanel.Dock="Top">
            <Image Width="200" Height="50" 
                    Source="../Images/aboutHeader.png"/>
        </Canvas>
        <Grid Width="Auto" Height="Auto" 
               Background="#FF000000" DockPanel.Dock="Top">
            <Grid.RowDefinitions>
                <RowDefinition Height="25"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>

            <Canvas Grid.Row="0" Grid.Column="0" 
                    Width="Auto" Background="White" 
                    HorizontalAlignment="Stretch" 
                    VerticalAlignment="Top" Height="25">
                <Path x:Name="pthSachas" Fill="Black" 
                   Stretch="Fill" Stroke="Black" Width="10" 
                   Height="10" Data="M0,0 L 0,10 L 5,5" 
                   Canvas.Left="20" 
                   Canvas.Top="8" Visibility="Visible"/>
                <Label x:Name="lblSachasBit" Width="133" 
                    Height="auto" FontFamily="Aharoni" 
                    Foreground="Black" Canvas.Left="31" 
                    Content="What Sacha did" 
                    Canvas.Top="4" />
                <Path x:Name="pthFredriks" Fill="Black" 
                  Stretch="Fill" Stroke="Black" Width="10" 
                  Height="10" Data="M0,0 L 0,10 L 5,5" 
                  Canvas.Left="239" Canvas.Top="8" 
                  Visibility="Hidden"/>
                <Label x:Name="lblFredriksBit" 
                    Width="133" Height="auto" 
                    FontFamily="Aharoni" 
                    Foreground="Black" Canvas.Left="250" 
                    Content="What Fredrik did" 
                    Canvas.Top="4" />
            </Canvas>

            <Canvas Grid.Row="1" Grid.Column="0" >
                <!-- Sachas bit words -->
                <TextBlock  x:Name="tbSachas" Width="215" 
                Text="Sacha is responsible for converting 
                      Fredriks Physics classes from a Winforms 
                      environment into WPF. Sacha also created 
                      this application, and the underlying classes 
                      that support the application. Fredrik 
                      and Sacha used to work together. Fredrik was 
                      Sachas team leader. Sacha really wants 
                      Fredrik to come and work at Sachas new job, 
                      where they can share their love 
                      of http://icanhascheezburger.com/" 
                TextWrapping="Wrap" 
                RenderTransformOrigin="0.5,0.5" Background="Black" 
                Canvas.Left="16" Foreground="#FFFFFFFF" 
                HorizontalAlignment="Left" Height="180" 
                VerticalAlignment="Stretch" Canvas.Top="0">
                    <TextBlock.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform ScaleX="1" ScaleY="1"/>
                            <SkewTransform AngleX="0" AngleY="0"/>
                            <RotateTransform Angle="0"/>
                            <TranslateTransform X="0" Y="23"/>
                        </TransformGroup>
                    </TextBlock.RenderTransform>
                </TextBlock>
                <!-- Fredriks bit words -->
                <TextBlock x:Name="tbFredriks" Width="215" 
                  Text="Fredrik is a Swedish chap that knows what's what when 
                        it comes to programming. He used to be Sachas team leader, 
                        but Sacha had to leave to pursue his WPF interest. 
                        Fredrik can program anything (apart from WPF), 
                        but is most happy writing games in 
                        DirectX that he never finishes. He wrote the original 
                        Physics for this application. Basically he's smart. 
                        The best you'll ever meet. I once saw him write a 3D screen saver
                        in about 2 hours without needing to look anything up. He rocks" 
                  TextWrapping="Wrap" 
                  RenderTransformOrigin="0.5,0.5" 
                  Background="Black" 
                  Canvas.Left="250" Foreground="#FFFFFFFF" 
                  HorizontalAlignment="Left" Height="1" 
                  VerticalAlignment="Stretch" Canvas.Top="0">
                    <TextBlock.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform ScaleX="1" ScaleY="1"/>
                            <SkewTransform AngleX="0" AngleY="0"/>
                            <RotateTransform Angle="0"/>
                            <TranslateTransform X="0" Y="23"/>
                        </TransformGroup>
                    </TextBlock.RenderTransform>
                </TextBlock>
            </Canvas>
        </Grid>
    </DockPanel>
</Window>

ParticleCanvas - Advanced Layout

As ParticleCanvas inherits from Canvas, several layout oriented overrides must be performed. These are as follows:

  • ArrangeOverride: When overridden in a derived class, positions child elements and determines a size for a FrameworkElement derived class.
  • MeasureOverride: When overridden in a derived class, measures the size in layout required for child elements and determines a size for the FrameworkElement-derived class.

The ParticleCanvas overrides these methods as follows:

/// <summary>
/// Any custom Panel must override ArrangeOverride and MeasureOverride
/// </summary>

protected override Size ArrangeOverride(Size arrangeSize)
{
    foreach (UIElement element in base.InternalChildren)
    {
        double x;
        double y;
        double left = Canvas.GetLeft(element);
        double top = Canvas.GetTop(element);
        x = double.IsNaN(left) ? 0 : left;
        y = double.IsNaN(top) ? 0 : top;

        element.Arrange(new Rect(new Point(x, y), element.DesiredSize));
    }
    return arrangeSize;
}


protected override Size MeasureOverride(Size constraint)
{
    Size size = new Size(double.PositiveInfinity, double.PositiveInfinity);
    foreach (UIElement element in base.InternalChildren)
    {
        element.Measure(size);
    }
    return new Size();
}

Where each child is given as much space as they want.

How We Used Resources (Part 2)

You can refer to Part 2 for more information about Resources in WPF.

As with Layout, the demo app uses resources all over the place. Though, one thing I should point out is that they are all static resources. There are none that change once assigned, so there is no need for any DynamicResource allocation. I have partitioned most resources (there are still a few Window level resources around) into three files, as follows:

  • StylesAndTemplatesCommon.xaml: Used by 1 or 2 common areas
  • StylesAndTemplatesGlobal.xaml: Used by most demo application items
  • StylesAndTemplatesValidation.xaml: Used for data validation purposes

Just to remind ourselves of how to use resources: we start by declaring a resource dictionary. Let's take the the StylesAndTemplatesValidation.xaml resource dictionary as an example.

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:models="clr-namespace:PhysicsHost.ViewModel" 
    xmlns:local="clr-namespace:PhysicsHost"
    local:GlassEffect.IsEnabled="true"    

    <!-- Resource dictionary entries should be defined here. -->

    <!-- Brushes -->
    <SolidColorBrush x:Key="SolidRedBrush" Color="Red" />
    <SolidColorBrush x:Key="SolidBorderBrush" Color="#888" />

    <!-- Exception/ValidationRule ToolTip Style -->
    <Style x:Key="{x:Type ToolTip}" TargetType="ToolTip">
        <Setter Property="OverridesDefaultStyle" Value="true"/>
        <Setter Property="HasDropShadow" Value="True"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ToolTip">
                    <Border Name="Border"
                          Background="{StaticResource SolidRedBrush}"
                          BorderBrush="{StaticResource SolidBorderBrush}"
                          BorderThickness="1"
                          Width="{TemplateBinding Width}"
                          Height="{TemplateBinding Height}">
                        <ContentPresenter
                            TextElement.Foreground="White"
                            Margin="4" 
                            HorizontalAlignment="Left"
                            VerticalAlignment="Top" />
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="HasDropShadow" 
                                      Value="true">
                            <Setter TargetName="Border" 
                             Property="CornerRadius" 
                             Value="4"/>
                            <Setter TargetName="Border" 
                             Property="SnapsToDevicePixels" 
                             Value="true"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <!-- Exception/ValidationRule Based Validitaion TextBox Style -->
    <Style x:Key="validationStyleTextBox" TargetType="TextBox">
        <Setter Property="Foreground" Value="#333333" />
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip"
                        Value="{Binding RelativeSource={RelativeSource Self},
                        Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>

</ResourceDictionary>

Then in the area where we want to use this resource, say on EditCustomWindow.xaml (where databinding is used), we simply need to reference the Resource Dictionary. I am using a MergedDictionary, but there are other ways (like code). Let's see:

<Window x:Class="PhysicsHost.EditCustomerWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:models="clr-namespace:PhysicsHost.ViewModel"  
    xmlns:validation="clr-namespace:PaulStovell.Samples.WpfValidation" 
    xmlns:local="clr-namespace:PhysicsHost"
    local:GlassEffect.IsEnabled="true"     
    Icon="../Images/logo.png"
    Title="Particles" Height="360" Width="500"
    ResizeMode="NoResize"
    Background="Black" 
    TextElement.Foreground="White">

    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary 
                   Source="../Resources/StylesAndTemplatesCommon.xaml"/>
                <ResourceDictionary 
                   Source="../Resources/StylesAndTemplatesValidation.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

    <Window.CommandBindings>
        ...
        ...
    </Window.CommandBindings>


    <DockPanel LastChildFill="True">
        ...
        ...

        <TextBlock Grid.Row="0" Grid.Column="0" 
                      Text="ContactName" />
                <TextBox x:Name="txtContactName" 
                    Grid.Row="0" Grid.Column="1" 
                    Margin="3"  
                    Text="{Binding ContactName, 
                          UpdateSourceTrigger=Explicit, ValidatesOnDataErrors=True}"  
                    Style="{StaticResource validationStyleTextBox}" 
                    MaxWidth="{Binding Path=ActualWidth, 
                              ElementName=txtCustomerID}" 
                    HorizontalAlignment="Stretch"/>
             ...
            ... 

        </DockPanel>

    </DockPanel>
</Window>

We can see that there is a MergedDictionary declared on the EditCustomWindow Window, and that there is a TextBox on this Window that uses a StaticResource called "validationStyleTextBox" which is using the resource whose Key is "validationStyleTextBox" from within the resource file that has been referenced through the use of the MergedDictionary. This resource "validationStyleTextBox" is contained within the StylesAndTemplatesValidation.xaml resource dictionary.

This is typically how the demo app is using resources. Though, there are occasions where I am using local Window or Control level resources, which don't use MergedDictionarys. These are declared as follows:

<Window.Resources>
    <Storyboard x:Key="OnMouseEnterSachas">
        <!-- Expand Sachas text, and shrink Fredriks -->
        <DoubleAnimation To="240" Storyboard.TargetName="tbSachas" 
        Storyboard.TargetProperty="(FrameworkElement.Height)" Duration="0:0:001"/>
        <DoubleAnimation To="1" Storyboard.TargetName="tbFredriks" 
        Storyboard.TargetProperty="(FrameworkElement.Height)" Duration="0:0:001"/>

        <!-- Show Sachas arrow, and hide Fredriks -->
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="pthFredriks"
                       Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0:0:00" 
            Value="{x:Static Visibility.Hidden}" />
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="pthSachas"
             Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0:0:00" 
            Value="{x:Static Visibility.Visible}" />
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>

    <Storyboard x:Key="OnMouseEnterFredriks">
        <!-- Expand Fredriks text, and shrink Sachas -->
        <DoubleAnimation To="240" 
          Storyboard.TargetName="tbFredriks" 
          Storyboard.TargetProperty="(FrameworkElement.Height)" 
          Duration="0:0:001"/>
        <DoubleAnimation To="1"
           Storyboard.TargetName="tbSachas" 
           Storyboard.TargetProperty="(FrameworkElement.Height)" 
           Duration="0:0:001"/>
        <!-- Show Fredriks arrow, and hide Sachas -->
        <ObjectAnimationUsingKeyFrames 
                Storyboard.TargetName="pthSachas"
                Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0:0:00" 
            Value="{x:Static Visibility.Hidden}" />
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames 
               Storyboard.TargetName="pthFredriks"
               Storyboard.TargetProperty="Visibility">
            <DiscreteObjectKeyFrame KeyTime="0:0:00" 
               Value="{x:Static Visibility.Visible}" />
        </ObjectAnimationUsingKeyFrames>

    </Storyboard>

</Window.Resources>

How We Used Commands and Events (Part 3)

You can refer to Part 3 for more information about Commands and Events in WPF.

The demo application uses three Routed Commands, which are as follows:

  • CustomerViewModel -> ShowHideOrdersForCustomerCommand, for viewing the Orders for a specific Customer
  • CustomerViewModel -> SubmitChangesCommand, for saving a single Customer
  • OrderViewModel -> SubmitChangesCommand, for saving a single Order

The demo application uses the ModelView-ViewModel (MVVM) pattern, so these commands and how they tie up with the overall architecture will be mentioned a bit more in the Databinding section of this article. But for now, let's just concentrate on how the commands are defined and used.

The reason that Routed Commands are good is that we can have a layer of abstraction between the UI and where the command is actually declared. Of course, sometimes it is convenient and necessary to have the command declared/bound and executed all within the main UI; however, it is better to have some level of code separation. By separating the command from the UI, we can offer the potential for the UI VisualTree to be replaced. And as long as the new UI VisualTree contains the relevant command bindings, the application will still work. Josh Smith refers to this as structural skinning in his podder article series. Have a look. You'll see what I mean.

But anyway, back to the commands that this demo app declares. Let's look at them one by one.

CustomerViewModel: ShowHideOrdersForCustomerCommand

There is a command that is created within CustomerViewModel.cs that has its command CanExecute/Executed bindings set in the MainWindow file. The actual UIElement that triggers the Command is used on the PART_SHowHideOrders button within the CustomerUserControl. As the CustomerUserControls are generated within the MainWindow window, the correct command bindings exist, so when the button is pressed on the CustomerUserControl button, due to the routed nature of Routed Commands, the notification flows back to the MainWindow file which runs the Executed method, which was set within the MainWindow command bindings.

So we have some code to declare the command within the CustomerViewModel.cs file.

public static readonly RoutedCommand ShowHideOrdersForCustomerCommand
    = new RoutedCommand("ShowHideOrdersForCustomerCommand", typeof(CustomerViewModel));

And then we have a StaticResource which provides a default Style for the CustomerUserControl which contains the PART_SHowHideOrders button which uses this command. The command is actually set in code. This is shown below. I have removed anything that is not required to illustrate this point.

<!-- CustomerUserControl -->
<Style x:Key="defaultCustomerControlStyle" 
        TargetType="{x:Type local:CustomerUserControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate 
                 TargetType="{x:Type local:CustomerUserControl}">
                .....
                .....
                <Button x:Name="PART_ShowHideOrders" 
                   Template="{StaticResource bordereredButtonTemplate}" 
                   Margin="5,0,0,0" Padding="4" 
                   Width="auto" Height="auto" 
                   HorizontalAlignment="Left"
                   FontFamily="Arial" FontSize="9" 
                   Foreground="White" Content="Show My Orders"
                   VerticalAlignment="Center"/>
                .....
                .....
                <ControlTemplate.Triggers>
                  .....
                  .....
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

This is the code-behind that sets the PART_SHowHideOrders button command. By doing this in code behind, we are allowing the control to be lookless, where the end user can totally restyle the control. But we will talk more about this later in the Styles/Templates/Lookless Controls section.

PART_ShowHideOrders.Command = CustomerViewModel.ShowHideOrdersForCustomerCommand;

And the final part is the actual command bindings within the MainWindow.xaml file. Let's see that, starting with the XAML:

<Window.CommandBindings>
    <CommandBinding Command="{x:Static models:CustomerViewModel.
        ShowHideOrdersForCustomerCommand}" 
        CanExecute="ShowHideOrdersForCustomerCommand_CanExecute" 
        Executed="ShowHideOrdersForCustomerCommand_Executed"/>
</Window.CommandBindings>

And now the actual code-behind for the commands:

#region Command Sinks

/// <summary>
/// Only allow the 
/// <see cref="PhysicsHost.ViewModel.
/// CustomerViewModel.ShowHideOrdersForCustomerCommand">
/// ShowHideOrdersForCustomerCommand </see>command to
/// execute if the current Customer has enough orders
/// to show
/// </summary>
private void ShowHideOrdersForCustomerCommand_CanExecute(
    object sender, CanExecuteRoutedEventArgs e)
{
    currentCustomerUserControl = 
        (e.OriginalSource as Button).Tag as CustomerUserControl;
    if (currentCustomerUserControl != null)
    {
        currentCustomer = 
            currentCustomerUserControl.DataContext as Customer;
        e.CanExecute = 
            customerViewModel.CustomerHasEnoughOrders(
            currentCustomer.CustomerID);
    }
    else
        e.CanExecute = false;
}

/// <summary>
/// Shows Orders for selected Customer
/// </summary>
private void ShowHideOrdersForCustomerCommand_Executed(
    object sender, ExecutedRoutedEventArgs e)
{
    //hide shown Customer Orders
    RemoveOrdersFromContainer();
    //show Orders for Customer selected
    foreach (Particle particle in 
        particleCanvasSimulation.ParticleSystem.Particles)
    {
        if (particle.Control.Equals(currentCustomerUserControl))
        {
            currentParticleForCustomer = particle;
            break;
        }
    }
    //show orders for Customer
    InitialiseOrders(currentCustomer.CustomerID, 
        currentParticleForCustomer);
}
#endregion

This command actually checks whether the current Customer (LINQ to SQL object, more on this later) associated with CustomerUserControl.xaml has enough Orders. If it does, the Show Orders button will be allowed to run; otherwise, it won't. See the button grayed out in one of the CustomerUserControls below.

When the command is run N-many (configurable in App.cs) Orders associated with it are added to ParticleCanvas as new Particles.

So that's how that command works.

CustomerViewModel: SubmitChangesCommand

There is also another command that is declared within the CustomerViewModel.cs that has its command CanExecute binding set to be always true within the actual CustomerViewModel.cs file and the Executed bindings set within the EditCustomerWindow.xaml window.

Let's see how this command is declared and has its CanExecute binding to return true constantly. This is shown below, and is within the CustomerViewModel.cs file.

public static readonly RoutedCommand SubmitChangesCommand
    = new RoutedCommand("SubmitChangesCommand", typeof(CustomerViewModel));

public CustomerViewModel()
{
    CommandManager.RegisterClassCommandBinding(typeof(CustomerViewModel),
        new CommandBinding(CustomerViewModel.SubmitChangesCommand,null,
            delegate(object sender, CanExecuteRoutedEventArgs e) {
                e.CanExecute = true;
            }));
}

So that's that part. That just leaves the Executed command binding. Which is within the EditCustomerWindow Window, as shown below:.

<Window.CommandBindings>
    <CommandBinding Command="{x:Static models:CustomerViewModel.SubmitChangesCommand}" 
        Executed="CustomerViewModelSubmitChangesCommand_Executed"/>
</Window.CommandBindings>

Now there must be a button that uses this command, and there is. It's the btnSave button on the EditCustomerWindow window, which is shown below:

<Window.CommandBindings>
    <CommandBinding Command="{x:Static models:CustomerViewModel.SubmitChangesCommand}" 
        Executed="CustomerViewModelSubmitChangesCommand_Executed"/>
</Window.CommandBindings>

And the code-behind is as follows:

/// <summary>
/// Sumbit the bound data changes back into the underlying
/// <see cref="Customer">Customer data object</see> and see
/// if we are able to update the Database
/// </summary>
private void CustomerViewModelSubmitChangesCommand_Executed(
    object sender, ExecutedRoutedEventArgs e)
{
    try
    {
        if (UpdateBindings())
        {
            this.customerViewModel.SubmitChanges();
            this.Close();
            MessageBoxHelper.ShowMessageBox(
                "Successfully updated Customer", 
                "Customer updated");
        }
        else
        {
            MessageBoxHelper.ShowErrorBox(
                "Error updating Customer", 
                "Customer error");
        }
    }
    catch (BindingException bex)
    {
        MessageBoxHelper.ShowErrorBox(
            "Binding error occurred\r\n" + bex.Message,
            "Binding error");
    }
    catch (Exception ex)
    {
        MessageBoxHelper.ShowErrorBox(
            "An Error occurred trying to update the database\r\n" 
            + ex.Message,"Database save error");
    }

}

What is happening here is, within the CustomerViewModel.cs file, the command is declared and is set to always be enabled. And the Executed command binding method simply updates the bound Customer (LINQ to SQL, more on this later) object. And that's it for this command.

OrderViewModel: SubmitChangesCommand

Works in the same manner as the CustomerViewModel SubmitChangesCommand command, but is used for saving Order objects instead of Customer objects.

How We Used Dependency Properties (Part 4)

You can refer to Part 4 for more information about Resources in WPF.

I didn't really have a cool idea about how to use DPs or Attached Properties for this article. So I did what any resourceful developer should do... steal some code, but quote the original source. To this end, I am using the code as posted by CodeProject user Rudi Grobler. Rudi's code uses Attached Properties (my favourite of all the DP family) to extend glass into your Window (which happens in Vista, by default) using P/Invoke. This technique is in a every WPF book out there, but the use of the Attached Property is the bit I like. And that is what Rudi did, so that's why I am using it.

Let's see how we do this. I have posted Rudi's original code here (I have just renamed the namespace). But you can get the full brief at his original article post, which is right here.

The important part is this class which provides the Attached Property and the P/Invoke stuff:

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.Navigation;
using System.Windows.Shapes;
using System.Runtime.InteropServices;
using System.Windows.Interop;

namespace PhysicsHost
{
    [StructLayout(LayoutKind.Sequential)]
    public struct MARGINS
    {
        public int cxLeftWidth;
        public int cxRightWidth;
        public int cyTopHeight;
        public int cyBottomHeight;
    };

    public class GlassEffect
    {
        [DllImport("DwmApi.dll")]
        public static extern int 
            DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS pMarInset);

        [DllImport("dwmapi.dll", PreserveSig = false)]
        static extern bool DwmIsCompositionEnabled();

        public static readonly DependencyProperty IsEnabledProperty =
            DependencyProperty.RegisterAttached("IsEnabled",
                typeof(Boolean),
                typeof(GlassEffect),
                new FrameworkPropertyMetadata(OnIsEnabledChanged));

        public static void SetIsEnabled(DependencyObject element, Boolean value)
        {
            element.SetValue(IsEnabledProperty, value);
        }
        public static Boolean GetIsEnabled(DependencyObject element)
        {
            return (Boolean)element.GetValue(IsEnabledProperty);
        }

        public static void OnIsEnabledChanged(DependencyObject obj, 
                DependencyPropertyChangedEventArgs args)
        {
            if ((bool)args.NewValue == true)
            {
                Window wnd = (Window)obj;
                wnd.Loaded += new RoutedEventHandler(wnd_Loaded);
            }
        }

        static void wnd_Loaded(object sender, RoutedEventArgs e)
        {
            Window wnd = (Window)sender;
            Brush originalBackground = wnd.Background;
            wnd.Background = Brushes.Transparent;
            try
            {
                IntPtr mainWindowPtr = new WindowInteropHelper(wnd).Handle;
                HwndSource mainWindowSrc = HwndSource.FromHwnd(mainWindowPtr);
                mainWindowSrc.CompositionTarget.BackgroundColor = 
                                           Color.FromArgb(0, 0, 0, 0);

                MARGINS margins = new MARGINS();
                margins.cxLeftWidth = -1;
                margins.cxRightWidth = -1;
                margins.cyTopHeight = -1;
                margins.cyBottomHeight = -1;

                DwmExtendFrameIntoClientArea(mainWindowSrc.Handle, ref margins);
            }
            catch (DllNotFoundException)
            {
                wnd.Background = originalBackground;
            }
        }
    }
}

This means we can now use this Attached Property using one line in one of our Windows, which will Glassify the Window, thanks to the Attached Properties and some P/Invoke magic.

<Window x:Class="PhysicsHost.EditOrderWindow"
    ....
    .... 
    xmlns:local="clr-namespace:PhysicsHost"
    local:GlassEffect.IsEnabled="true"
    ....
    ....
</Window">

DPs and especially Attached Properties are the bomb. They are so good, the possibilities are endless.

How We Used DataBinding (Part 5)

You can refer to Part 5 for more information about DataBinding in WPF.

This is perhaps the most complicated part of the demo application. The reason that it's complicated is as follows:

  1. The demo app is using LINQ to SQL for the database interaction
  2. The demo app is using an N-Tier approach to working with the database
  3. The demo app is using validation through the .NET 3.5 IDataErrorInfo interface
  4. The demo app is using manually updatable bindings (which was not discussed in Part 5)

As you can see, there is a fair bit to talk about just on this one subject. I think the best bet is to tackle this one thing at a time, in the order described above.

LINQ to SQL

As I am using SQL Server and VS2008, why not use LINQ to SQL I thought. So that's exactly what I dis. I used two tables, Customers/Orders, from the Northwind database. I just dragged these two tables onto the LINQ to SQL Designer.

This is all very standard stuff. This LINQ to SQL designer creates the file Northwind.Designer.cs which holds all the table mapping and objects required to communicate with these two Northwind tables. This class creates the NorthwindDataContext, which is the main object that should be used to query/update the database against. As I say, this is all very standard.

The only issue was that I wanted to only have one instance of NorthwindDataContext in the demo app. Luckily, due to the wonderful Partial Class feature, it was trivial to just create another partial class which provides the singleton instance for NorthwindDataContext. This is shown below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PhysicsHost.DataAccess
{

    /// <summary>
    /// This class is another part (partial) to the 
    /// NorthwindDataContext LINQ to SQL generated
    /// classes. This part provides a thread safe
    /// singleton of the NorthwindDataContext class
    /// </summary>
    partial class NorthwindDataContext
    {
        #region Data
        private static NorthwindDataContext instance = null;
        private static readonly object padlock = new object();
        #endregion

        #region Singleton Instance
        public static NorthwindDataContext Instance
        {
            get
            {
                lock (padlock)
                {
                    if (instance == null)
                    {
                        instance = new NorthwindDataContext();
                    }
                    return instance;
                }
            }
        }
        #endregion
    }
}

N-Tier approach

I wanted to make the demo application as easy to understand as I could, and I think a major part of this is structure. As such, the demo application uses an N-tier approach where the data flow is something like in the following diagram:

Where MainWindow uses the CustomerViewModel to fetch a number of Customer objects which are then used as DataContext values for individual CustomerUserControl objects. The Customer objects are supplied by calling the CustomerBAL object, which in turn gets the required Customers from the actual Northwind database by using the NorthwindDataContext.

This may be a little clearer if we look at the code.

Starting at the top where the individual CustomerUserControl objects are created, this is done within the MainWindow.Xaml.cs file.

/// <summary>
/// Create the Customer Particles
/// </summary>
private void InitialiseCustomers()
{
    try
    {
        Customer[] custs = customerViewModel.GetCustomers().ToArray();
        Particle[] particles = new Particle[custs.Count()];
        int startPos = 100;

        //setup Usercontrol particles
        for (int i = 0; i < custs.Count(); i++)
        {
            if (i == 0)
            {
                particles[i] = new Particle(1.0f, 
                    new Vector(startPos, startPos), true);
            }
            else
            {
                startPos += 200;
                particles[i] = new Particle(1.0f, 
                    new Vector(startPos, startPos), true);
            }
            particles[i].Control = getCustomerUserControl(custs[i]);
            particleCanvasSimulation.ParticleSystem.Particles.Add(particles[i]);
        }

        //setup anchor
        anchor = new Particle(float.PositiveInfinity, 
            new Vector((double)(
                this.particleCanvasSimulation.ActualWidth / 2), 40),true);
        anchor.Control = getAnchorButton();
        particleCanvasSimulation.ParticleSystem.Particles.Add(anchor);
        //now generate the pyhsics for these new Particles
        GeneratePhysicsForParticles(particles, 
            anchor, false, 840.0f, 260.0f, 60.0f);
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine(ex.Message);
        MessageBoxHelper.ShowMessageBox(ex.Message, "An error occurred");
    }
}

private CustomerUserControl getCustomerUserControl(Customer cust)
{
    CustomerUserControl control = new CustomerUserControl();
    control.DataContext = cust;
    particleCanvasSimulation.Children.Add(control);
    control.PreviewMouseUp += new MouseButtonEventHandler(
        particleCanvasSimulation.ParticleCanvas_PreviewMouseUp);
    control.PreviewMouseMove += new MouseEventHandler(
        particleCanvasSimulation.ParticleCanvas_PreviewMouseMove);
    control.PreviewMouseDown += new MouseButtonEventHandler(
        particleCanvasSimulation.ParticleCanvas_PreviewMouseDown);
    control.MouseEnter += new MouseEventHandler(
        particleCanvasSimulation.ParticleCanvas_MouseEnter);
    Style defaultCustomerControlStyle = 
        this.TryFindResource("defaultCustomerControlStyle") as Style;
    if (defaultCustomerControlStyle != null)
        control.Style = defaultCustomerControlStyle;
    return control;
}

There is a little bit of the Physics code in here (sorry), but Fredrik has explained that, so it should be clear enough I hope.

It can be seen that there is a call made to CustomerViewModel to fetch all its Customer objects; this is done using the GetCustomers() method. Let's see the CustomerViewModel class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;

using PhysicsHost.DataAccess;

namespace PhysicsHost.ViewModel
{
    /// <summary>
    /// Custom View Model, used to bind to within the 
    /// <see cref="MainWindow">MainWindow</see>
    /// </summary>
    public class CustomerViewModel
    {
        #region Data
        CustomerBAL customerBAL = new CustomerBAL();
        #endregion

        #region Commands
        public static readonly RoutedCommand 
            ShowHideOrdersForCustomerCommand
            = new RoutedCommand("ShowHideOrdersForCustomerCommand", 
            typeof(CustomerViewModel));

        public static readonly RoutedCommand SubmitChangesCommand
            = new RoutedCommand("SubmitChangesCommand", 
            typeof(CustomerViewModel));
        #endregion

        #region Ctor
        public CustomerViewModel()
        {
            CommandManager.RegisterClassCommandBinding(
                typeof(CustomerViewModel),
                new CommandBinding(CustomerViewModel.SubmitChangesCommand,null,
                    delegate(object sender, CanExecuteRoutedEventArgs e) {
                        e.CanExecute = true;
                    }));
        }
        #endregion

        #region Public Methods
        public IEnumerable<Customer> GetCustomers()
        {
            return customerBAL.GetCustomers();
        }

        public bool CustomerHasEnoughOrders(string CustomerID)
        {
            return customerBAL.CustomerHasEnoughOrders(CustomerID);
        }

        public bool SubmitChanges()
        {
            return customerBAL.SubmitChanges();
        }
        #endregion
    }
}

And it can be seen that this now calls the CustomerBAL object. So examining that class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Linq;
using System.Text;
using System.Runtime.CompilerServices;

namespace PhysicsHost.DataAccess
{
    /// <summary>
    /// Customer Business Layer
    /// </summary>
    public class CustomerBAL
    {
        #region Ctor
        public CustomerBAL()
        {

        }
        #endregion

        #region Public Methods

        [MethodImpl(MethodImplOptions.Synchronized)]
        public IEnumerable<Customer> GetCustomers()
        {
            int maxToShow = 
              int.Parse(App.Current.Properties["MAX_CUSTOMERS"].ToString());

            NorthwindDataContext db = NorthwindDataContext.Instance;

            var customers = 
               (from c in db.Customers where c.Orders.Count > 0 select c);
            int maxExistingCustomerWithOrders = customers.Count();
            int numOfCustomerToSelect = maxExistingCustomerWithOrders > maxToShow ?
                                        maxToShow : maxExistingCustomerWithOrders;

            return customers.Take(numOfCustomerToSelect).OrderBy(c => c.CustomerID);
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public bool CustomerHasEnoughOrders(string CustomerID)
        {
            int maxToShow = 
              int.Parse(App.Current.Properties["MAX_ORDERS"].ToString());

            NorthwindDataContext db = NorthwindDataContext.Instance;
            return (from o in db.Orders 
                    where o.CustomerID ==  
                    CustomerID select o).Count() > maxToShow;
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public bool SubmitChanges() 
        {
            //As LINQ to SQL is dead clever we can simply call 
            //db.SubmitChanges, and as its been tracking changes automatically
            //this is all we need to do
            NorthwindDataContext db = NorthwindDataContext.Instance;
            db.SubmitChanges(ConflictMode.FailOnFirstConflict);
            return true;
        }
        #endregion
    }
}

This class is responsible for not only interacting with the NorthwindDataContext, but also for running the business rules. In this case, the rules are very simple, does the customer has at least one associated Order.

The final layer is the NorthwindDataContext which, as I stated earlier, is standard LINQ to SQL generated code.

IDataErrorInfo Interface Validation

As we are dealing with bound data, when we edit either a Customer or Order object, we must ensure that there is a way to validate the data entered. I have chosen to use the new .NET 3.5 method which is by the use of the IDataErrorInfo interface.

This interface must be used on the binding source object. In the demo app's case, this is either a Customer or a Order object coming from the auto generated LINQ to SQL code. When I first thought about this, I though this could be quite a pain as I would have to manually change auto generated classes. As luck would have it, Partial Classes to the rescue again. Looking at the auto generated stuff LINQ to SQL produces, say for Customer, we can see the following:

[Table(Name="dbo.Customers")]
public partial class Customer : INotifyPropertyChanging, INotifyPropertyChanged
{
    ....
    ....
    ....
}

Not only is the class partial (which means we can extend it in a different source code file... one of the best things MSFT ever did are Partial Classes), but the LINQ to SQL Designer makes all the generated classes implement the INotifyPropertyChanged interface. Which is a must when dealing with WPF. This is excellent news. All that had to be done was to create an extra Partial Class for Customer and one for Order. This is shown below for Customer:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace PhysicsHost.DataAccess
{
    /// <summary>
    /// This class is another part (partial) to the 
    /// LINQ to SQL generated class within the 
    /// Northwind.designer.cs file. This class
    /// provides the validation that is used
    /// by the WPF databindings
    /// </summary>
    public partial class Customer : IDataErrorInfo
    {
        #region Data
        private StringBuilder combinedError = new StringBuilder(500);
        #endregion

        #region IDataErrorInfo Members

        /// <summary>
        /// Return the full list of validation 
        /// errors for this object
        /// </summary>
        public string Error
        {
            get
            {
                return combinedError.ToString();
            }
        }

        /// <summary>
        /// Validates a particular column, and returns a 
        /// string representing the current error
        /// </summary>
        /// <param name="columnName">
        ///       The property name to validate</param>
        /// <returns>A string representing the current error</returns>
        public string this[string columnName]
        {
            get
            {
                string result = null;
                combinedError = new StringBuilder(500);

                //basically we need a case for each property you wish to validate
                switch (columnName)
                {

                    case "ContactName":
                        if (string.IsNullOrEmpty(this.ContactName))
                        {
                            result = "ContactName cant be empty";
                            combinedError.Append(result + "\r\n");
                        }
                        if (!string.IsNullOrEmpty(this.ContactName) &&
                            this.ContactName.Length >= 15)
                        {
                            result = "ContactName should be <= 15 chars";
                            combinedError.Append(result + "\r\n");
                        }
                        break;

                    case "ContactTitle":
                        if (string.IsNullOrEmpty(this.ContactTitle))
                        {
                            result = "ContactTitle cant be empty";
                            combinedError.Append(result + "\r\n");
                        }
                        if (!string.IsNullOrEmpty(this.ContactTitle) &&
                            this.ContactTitle.Length >= 15)
                        {
                            result = "ContactTitle should be <= 15 chars";
                            combinedError.Append(result + "\r\n");
                        }
                        break;

                    case "Address":
                        if (string.IsNullOrEmpty(this.Address))
                        {
                            result = "Address cant be empty";
                            combinedError.Append(result + "\r\n");
                        }
                        if (!string.IsNullOrEmpty(this.Address) &&
                            this.Address.Length >= 30)
                        {
                            result = "Address should be <= 30 chars";
                            combinedError.Append(result + "\r\n");
                        }
                        break;

                    case "City":
                        if (string.IsNullOrEmpty(this.City))
                        {
                            result = "City cant be empty";
                            combinedError.Append(result + "\r\n");
                        }
                        if (!string.IsNullOrEmpty(this.City) &&
                            this.City.Length >= 10)
                        {
                            result = "City should be <= 10 chars";
                            combinedError.Append(result + "\r\n");
                        }
                        break;

                    case "Region":
                        if (string.IsNullOrEmpty(this.Region))
                        {
                            result = "Region cant be empty";
                            combinedError.Append(result + "\r\n");
                        }
                        if (!string.IsNullOrEmpty(this.Region) && 
                            this.Region.Length >= 15)
                        {
                            result = "Region should be <= 15 chars";
                            combinedError.Append(result + "\r\n");
                        }
                        break;

                    case "PostalCode":
                        if (string.IsNullOrEmpty(this.PostalCode))
                        {
                            result = "PostalCode cant be empty";
                            combinedError.Append(result + "\r\n");
                        }
                        if (!string.IsNullOrEmpty(this.PostalCode) &&
                            this.PostalCode.Length > 10)
                        {
                            result = "PostalCode should be <= 10 chars";
                            combinedError.Append(result + "\r\n");
                        }
                        break;

                    case "Country":
                        if (string.IsNullOrEmpty(this.Country))
                        {
                            result = "Country cant be empty";
                            combinedError.Append(result + "\r\n");
                        }
                        if (!string.IsNullOrEmpty(this.Country) &&
                            this.Country.Length > 10)
                        {
                            result = "Country should be <= 10 chars";
                            combinedError.Append(result + "\r\n");
                        }
                        break;

                    case "Phone":
                        if (string.IsNullOrEmpty(this.Phone))
                        {
                            result = "Phone cant be empty";
                            combinedError.Append(result + "\r\n");
                        }
                        if (!string.IsNullOrEmpty(this.Phone) &&
                            this.Phone.Length > 15)
                        {
                            result = "Phone should be <= 15 chars";
                            combinedError.Append(result + "\r\n");
                        }
                        break;

                    case "Fax":
                        if (string.IsNullOrEmpty(this.Fax))
                        {
                            result = "Fax cant be empty";
                            combinedError.Append(result + "\r\n");
                        }
                        if (!string.IsNullOrEmpty(this.Fax) &&
                            this.Fax.Length > 15)
                        {
                            result = "Fax should be <= 15 chars";
                            combinedError.Append(result + "\r\n");
                        }
                        break;

                }
                return result;
            }
        }
        #endregion
    }
}

And now, we have all the bits we need to be able to perform validation using the information provided by the use of the IDataErrorInfo interface. Basically, what we do is associate a Style with a TextBox which uses the error message generated by the IDataErrorInfo interface implementation. This Style is described a bit more below.

Manually Updatable Bindings

One of the things that I wanted was the ability for a user to be able to either update a bound field or to cancel the edit entirely. Within WPF, this is pretty easy to do; you just need to need to set the UpdateSourceTrigger property of the binding to Explicit. And then, you have to manually update the binding yourself in code. If we stick to just examining how the Customer object works, the same is true for Order objects.

There is a Window (EditCustomerWindw) for editing a single instance of a Customer object. Basically, the DataContext of the EditCustomerWindw is set to a single Customer object, and then the EditCustomerWindw has various markup that binds to this single Customer object. But all updating to the bindings are set such that their UpdateSourceTrigger properties are set to Explicit. So there is some code that has to be done to update the underlying object from the bound values.

<Window x:Class="PhysicsHost.EditCustomerWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:models="clr-namespace:PhysicsHost.ViewModel"  
    xmlns:validation="clr-namespace:PaulStovell.Samples.WpfValidation"  
    xmlns:local="clr-namespace:PhysicsHost"
    local:GlassEffect.IsEnabled="true"        
    Icon="../Images/logo.png"
    Title="Particles" Height="360" Width="500"
    ResizeMode="NoResize"
    Background="Black" TextElement.Foreground="White">

    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary 
                  Source="../Resources/StylesAndTemplatesCommon.xaml"/>
                <ResourceDictionary 
                  Source="../Resources/StylesAndTemplatesValidation.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

    <Window.CommandBindings>
        <CommandBinding 
            Command="{x:Static models:CustomerViewModel.SubmitChangesCommand}" 
            Executed="CustomerViewModelSubmitChangesCommand_Executed"/>
    </Window.CommandBindings>

    <DockPanel LastChildFill="True">

        <Canvas DockPanel.Dock="Top" Height="50" 
               Background="{StaticResource orangeGradientBrush2Stops}">
            <Image Source="../Images/customer.png" 
                Width="40" Height="40" 
                Canvas.Left="5" Canvas.Top="5"/>
            <Label Canvas.Left="50" Canvas.Top="10" 
               Width="auto" Height="auto" 
               Content="EDIT CUSTOMER" 
               FontSize="18" FontWeight="Bold"/>
        </Canvas>

        <DockPanel Margin="5" 
                DockPanel.Dock="Bottom" LastChildFill="True">

            <StackPanel Orientation="Horizontal" 
                     DockPanel.Dock="Bottom" Margin="6">

                <Button x:Name="btnSave" Content="Save" 
                    Height="auto" Width="auto" Margin="5"
                    FontFamily="Arial" Foreground="White"
                    Template="{StaticResource bordereredButtonTemplate}" 
                    Command="{x:Static models:CustomerViewModel.
                             SubmitChangesCommand}" />

                <Button x:Name="btnCancel" 
                    Content="Cancel" Height="auto" 
                    Width="auto" Margin="5"
                    FontFamily="Arial" Foreground="White"
                    Template="{StaticResource bordereredButtonTemplate}" 
                    Click="btnCancel_Click"/>
            </StackPanel>

            <ScrollViewer 
                      ScrollViewer.HorizontalScrollBarVisibility="Hidden" 
                      ScrollViewer.VerticalScrollBarVisibility="Auto" 
                      DockPanel.Dock="Top">
                
                <validation:ErrorProvider x:Name="errorProvider">
                
                    <StackPanel Orientation="Vertical" Margin="5">
                        <!--CustomerID-->
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="2*"/>
                            </Grid.ColumnDefinitions>

                            <TextBlock Grid.Row="0" 
                                Grid.Column="0" Text="CustomerID"  />
                            <TextBox x:Name="txtCustomerID"  
                              Grid.Row="0" 
                              Grid.Column="1" Margin="3"  
                              Text="{Binding CustomerID}" 
                              HorizontalAlignment="Stretch" 
                              IsReadOnly="True"/>
                        </Grid>
                        <!--ContactName-->
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="2*"/>
                            </Grid.ColumnDefinitions>

                            <TextBlock Grid.Row="0" 
                                Grid.Column="0" Text="ContactName" />
                            <TextBox x:Name="txtContactName" 
                              Grid.Row="0" 
                              Grid.Column="1"  Margin="3"  
                              Text="{Binding ContactName, UpdateSourceTrigger=Explicit, 
                              ValidatesOnDataErrors=True}"  
                              Style="{StaticResource validationStyleTextBox}" 
                              MaxWidth="{Binding Path=ActualWidth,
                                        ElementName=txtCustomerID}" 
                              HorizontalAlignment="Stretch"/>
                        </Grid>
                        <!--ContactTitle-->
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="2*"/>
                            </Grid.ColumnDefinitions>

                            <TextBlock Grid.Row="0" 
                               Grid.Column="0" Text="ContactTitle" />
                            <TextBox x:Name="txtContactTitle" 
                              Grid.Row="0" 
                              Grid.Column="1"  Margin="3"  
                              Text="{Binding ContactTitle, UpdateSourceTrigger=Explicit, 
                              ValidatesOnDataErrors=True}"  
                              Style="{StaticResource validationStyleTextBox}" 
                              MaxWidth="{Binding Path=ActualWidth,
                                        ElementName=txtCustomerID}" 
                              HorizontalAlignment="Stretch"/>
                        </Grid>
                         .......
                         .......
                         .......
                         .......

                    </StackPanel>
                        
               </validation:ErrorProvider>         
            </ScrollViewer>
        </DockPanel>
    </DockPanel>
</Window>

And in the C# code-behind, there is the following code:

/// Update the changes back into the underlying
/// <see cref="Customer">Customer data object</see>
/// <returns>True if all binding values were valis. This
/// is done by using Paul Stovells <see cref="ErrorProvider">
/// ErrorProvider</see> class</returns>
private bool UpdateBindings()
{
    try
    {
        UpdateSingleBinding(txtContactName, TextBox.TextProperty);
        UpdateSingleBinding(txtContactTitle, TextBox.TextProperty);
        UpdateSingleBinding(txtAddress, TextBox.TextProperty);
        UpdateSingleBinding(txtCity, TextBox.TextProperty);
        UpdateSingleBinding(txtRegion, TextBox.TextProperty);
        UpdateSingleBinding(txtPostalCode, TextBox.TextProperty);
        UpdateSingleBinding(txtCountry, TextBox.TextProperty);
        UpdateSingleBinding(txtPhone, TextBox.TextProperty);
        UpdateSingleBinding(txtFax, TextBox.TextProperty);

        //now validate the binding values
        return errorProvider.Validate();
    }
    catch
    {
        throw new BindingException(string.Format(
            "There was a problem updating the Bindings for Customer {0}",
            (this.DataContext as Customer).CustomerID));
    }
}

/// <summary>
/// Updates a single TextBox binding
/// </summary>
private void UpdateSingleBinding(DependencyObject target, DependencyProperty dp)
{
    BindingExpression bindingExpression =
        BindingOperations.GetBindingExpression(
        target, dp);
    bindingExpression.UpdateSource();
}

Some more eagle eyed amongst you may notice that there is call to an object called errorProvider. Well, just going back to the XAML for a second:

<Window x:Class="PhysicsHost.EditCustomerWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:models="clr-namespace:PhysicsHost.ViewModel"  
    xmlns:validation="clr-namespace:PaulStovell.Samples.WpfValidation"  
    xmlns:local="clr-namespace:PhysicsHost"
    local:GlassEffect.IsEnabled="true"        
    Icon="../Images/logo.png"
    Title="Particles" Height="360" Width="500"
    ResizeMode="NoResize"
    Background="Black" TextElement.Foreground="White">

    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary 
                  Source="../Resources/StylesAndTemplatesCommon.xaml"/>
                <ResourceDictionary 
                  Source="../Resources/StylesAndTemplatesValidation.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

    <Window.CommandBindings>
        <CommandBinding 
          Command="{x:Static models:CustomerViewModel.SubmitChangesCommand}" 
          Executed="CustomerViewModelSubmitChangesCommand_Executed"/>
    </Window.CommandBindings>

    <DockPanel LastChildFill="True">

        <Canvas DockPanel.Dock="Top" Height="50" 
                  Background="{StaticResource orangeGradientBrush2Stops}">
            <Image Source="../Images/customer.png" 
                  Width="40" Height="40" 
                  Canvas.Left="5" Canvas.Top="5"/>
            <Label Canvas.Left="50" Canvas.Top="10" 
               Width="auto" Height="auto" 
               Content="EDIT CUSTOMER" 
               FontSize="18" FontWeight="Bold"/>
        </Canvas>

        <DockPanel Margin="5" 
               DockPanel.Dock="Bottom" LastChildFill="True">

            <StackPanel Orientation="Horizontal" 
                     DockPanel.Dock="Bottom" Margin="6">

                <Button x:Name="btnSave" Content="Save" 
                    Height="auto" Width="auto" Margin="5"
                    FontFamily="Arial" Foreground="White"
                    Template="{StaticResource bordereredButtonTemplate}" 
                    Command="{x:Static models:CustomerViewModel.
                             SubmitChangesCommand}" />

                <Button x:Name="btnCancel" Content="Cancel" 
                    Height="auto" Width="auto" Margin="5"
                    FontFamily="Arial" Foreground="White"
                    Template="{StaticResource bordereredButtonTemplate}" 
                    Click="btnCancel_Click"/>
            </StackPanel>

            <ScrollViewer ScrollViewer.HorizontalScrollBarVisibility="Hidden" 
                      ScrollViewer.VerticalScrollBarVisibility="Auto" 
                      DockPanel.Dock="Top">
                
                <validation:ErrorProvider x:Name="errorProvider">
                
                    <StackPanel Orientation="Vertical" Margin="5">
                    ......
                    ......
                    </StackPanel>
               </validation:ErrorProvider>         
            </ScrollViewer>
        </DockPanel>
    </DockPanel>
</Window>

Notice the use of the <validation:ErrorProvider x:Name="errorProvider"> element. This is a special element that I use to wrap my manually updatable bindings. As I am not setting the validation rules in the XAML and manually updating the bindings, I needed a way to create the validation rules at the point where the user chose to update the underlying data source. Basically, clicking the Save button. What this class does is use Reflection to get a list of the objects that have bindings, then get the DPs that it needs to use to update the bindings with, and then creates the appropriate validation rules for the bindings. It's cool. This comes from another MSFT MVP called Paul Stovell, and can be found right here.

Here is the relevant code for the validate() method. Notice the use of the IDataErrorInfo that was discussed earlier. See how it's all starting to fit together.

/// <summary>
/// Validates all properties on the current data source.
/// </summary>
/// <returns>True if there are no errors displayed, otherwise false.</returns>
/// <remarks>
/// Note that only errors on properties that are displayed are included. 
/// Other errors, such as errors for properties that are not displayed, 
/// will not be validated by this method.
/// </remarks>
public bool Validate()
{
    bool isValid = true;
    _firstInvalidElement = null;

    if (this.DataContext is IDataErrorInfo)
    {
        List<Binding> allKnownBindings = ClearInternal();

        // Now show all errors
        foreach (Binding knownBinding in allKnownBindings)
        {
            string errorMessage = 
                ((IDataErrorInfo)this.DataContext)[knownBinding.Path.Path];
            if (errorMessage != null && errorMessage.Length > 0)
            {
                isValid = false;

                // Display the error on any elements bound to the property
                FindBindingsRecursively(
                this.Parent,
                delegate(FrameworkElement element, Binding binding, 
                DependencyProperty dp)
                {
                    if (knownBinding.Path.Path == binding.Path.Path)
                    {
                        BindingExpression expression = 
                            element.GetBindingExpression(dp);
                        ValidationError error = new 
                            ValidationError(new ExceptionValidationRule(), 
                            expression, errorMessage, null);
                        System.Windows.Controls.
                            Validation.MarkInvalid(expression, error);

                        if (_firstInvalidElement == null)
                        {
                            _firstInvalidElement = element;
                        }
                        return;

                    }
                });
            }
        }
    }
    return isValid;
}

But I would recommend you go and read Paul's article for yourself; it's well thought out and described well.

How We Used Styles/Templates/Lookless Controls (Part 6)

You can refer to Part 6 for more information about Styles/Templates/Lookless Controls in WPF.

The demo application uses Styles/Templates all over the place. For instance, there are Styles/Templates for the following items:

  • Button
  • Tooltip
  • TextBox with validation
  • MenuItem
  • CustomerUserControl
  • OrderUserControl

To truly understand these various Styles/Templates, I will include a screenshot of the Styled/Templated element and give a listing of the Style/Template that achieved the look in question.

Button ControlTemplates

<!-- Anchor button template -->
<ControlTemplate x:Key="anchorButtonTemplate" 
             TargetType="{x:Type Button}">
    <ContentPresenter Margin="0" 
          Content="{TemplateBinding Content}" 
          Width="auto" Height="auto">
        <ContentPresenter.ToolTip>
                <StackPanel Margin="5,5,5,5" 
                      Orientation="Vertical" 
                      Background="Black">
                    <Label Height="auto" 
                        FontSize="16" FontWeight="Bold" 
                        Width="auto" 
                        Background="Black" Foreground="White" 
                        Content="Search Root"/>
                    <Label Height="auto" FontSize="10"  
                           Width="auto" Foreground="White"
                           Content="Drag me around to see what happens"/>
                </StackPanel>
        </ContentPresenter.ToolTip>
    </ContentPresenter>
</ControlTemplate>

<!-- Global Buttons -->
<ControlTemplate x:Key="bordereredButtonTemplate" 
                      TargetType="{x:Type Button}">
    <Border x:Name="border" 
            CornerRadius="3" Background="Transparent" 
            BorderBrush="White" BorderThickness="2" 
            Width="auto" Visibility="Visible">
        <ContentPresenter  Margin="3" 
           Content="{TemplateBinding Content}" 
           Width="auto" Height="auto"/>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="IsEnabled" Value="false">
            <Setter TargetName="border" 
                   Property="Opacity" Value="0.4"/>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Tooltip Style

<!-- Specialized ToolTip Style -->
<Style TargetType="ToolTip">
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="HasDropShadow" Value="True"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ToolTip">
                <Border CornerRadius="10" 
                        Background="{StaticResource orangeGradientBrush}"
                        BorderBrush="White" BorderThickness="1">
                    <ContentPresenter Width="auto" Height="auto" />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Validation Styles

Starting with the textbox Style that is used to show a validation fault on a TextBox:

<!-- Exception/ValidationRule Based Validitaion TextBox Style -->
<Style x:Key="validationStyleTextBox" TargetType="TextBox">
    <Setter Property="Foreground" Value="#333333" />
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip"
                    Value="{Binding RelativeSource={RelativeSource Self},
                    Path=(Validation.Errors)[0].ErrorContent}"/>
        </Trigger>
    </Style.Triggers>
</Style>

And now the Validation Tooltip Style:

<!-- Exception/ValidationRule ToolTip Style -->
<Style x:Key="{x:Type ToolTip}" TargetType="ToolTip">
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="HasDropShadow" Value="True"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ToolTip">
                <Border Name="Border"
                      Background="{StaticResource SolidRedBrush}"
                      BorderBrush="{StaticResource SolidBorderBrush}"
                      BorderThickness="1"
                      Width="{TemplateBinding Width}"
                      Height="{TemplateBinding Height}">
                    <ContentPresenter
                        TextElement.Foreground="White"
                        Margin="4" 
                        HorizontalAlignment="Left"
                        VerticalAlignment="Top" />
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasDropShadow" 
                                  Value="true">
                        <Setter TargetName="Border" 
                            Property="CornerRadius" Value="4"/>
                        <Setter TargetName="Border" 
                            Property="SnapsToDevicePixels" 
                            Value="true"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

MenuItem ControlTemplate

<!-- MenuItem -->
<ControlTemplate x:Key="contentMenuItemTemplate" 
           TargetType="{x:Type MenuItem}">
    <StackPanel Orientation="Horizontal">
        <Image 
           Source="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
                   AncestorType={x:Type MenuItem}},Path=Tag}" 
           Width="25" Height="25" />
        <Label 
           Content="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
                    AncestorType={x:Type MenuItem}},Path=Header}" 
           Height="25" />
    </StackPanel>
</ControlTemplate>

Lookless Controls

Recall in Part 6, I had a section about lookless controls. For those that haven't read that section, it's basically like this. Using Styles/Templates, a designer/developer is able to totally swap out the entire visual tree for a class. For example, I could have a UserControl that I want to contain a button and a listbox. But the designer or another programmer decides to create a new Style for my UserControl that doesn't provide a button or a listbox.

How can we cope with that? Well, what we can do is decorate our objects with the PartTempateAttribue to relay our intentions of what the control should contain within its visual tree in order for it to work correctly. And we can also check for the existence of the required visual elements that are required in order for the templated control to work.

This is what is done within the demo app for both CustomerUserControl and the OrderUserControl. Let's have a look at these, shall we?

CustomerUserControl

The CustomerUserControl looks like this by default:

Now, this is down to a default Style that I created, which tells the control what it's visual tree should be. This default Style is shown below:

<!-- CustomerUserControl -->
<Style x:Key="defaultCustomerControlStyle" 
         TargetType="{x:Type local:CustomerUserControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate 
                   TargetType="{x:Type local:CustomerUserControl}">
                <!-- Control Layout -->
                <Grid x:Name="LayoutRoot" Height="70" 
                          RenderTransformOrigin="0.5,0.5">
                    <Grid.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform ScaleX="1" ScaleY="1"/>
                            <SkewTransform AngleX="0" AngleY="0"/>
                            <RotateTransform Angle="0"/>
                            <TranslateTransform X="0" Y="0"/>
                        </TransformGroup>
                    </Grid.RenderTransform>
                    <Border Margin="0,0,0,0" 
                            Background="#FF303030" BorderBrush="#FFFFFFFF" 
                            BorderThickness="2,2,2,2" 
                            CornerRadius="3,3,3,3">
                        <DockPanel Width="Auto" 
                                 Height="Auto" LastChildFill="True">
                            <Border Height="20" DockPanel.Dock="Top" 
                                  Background="#FFFFFFFF" 
                                  CornerRadius="0,0,0,0" Margin="0,-2,0,0">
                                <Label Margin="45,-5,0,0" 
                                    Width="auto" Height="auto" 
                                    Content="{Binding Path=CustomerID}" 
                                    FontSize="14" FontWeight="Bold"/>
                            </Border>
                            <Canvas>
                                <Image Margin="-40,-40,0,0" 
                                            Width="50" Height="50" 
                                            Canvas.Left="18" 
                                            Canvas.Top="8" 
                                            Source="../images/customer.png"/>
                                <StackPanel Canvas.Left="0" 
                                         Canvas.Top="16" 
                                         Orientation="Vertical">
                                    <StackPanel Orientation="Horizontal" 
                                              Margin="0,5,0,0">
                                        <!-- Show Orders Controls -->
                                        <Button x:Name="PART_ShowHideOrders" 
                                          Template="{StaticResource 
                                                    bordereredButtonTemplate}" 
                                          Margin="5,0,0,0" Padding="4" 
                                          Width="auto" Height="auto" 
                                          HorizontalAlignment="Left"
                                          FontFamily="Arial" 
                                          FontSize="9" Foreground="White" 
                                          Content="Show My Orders"
                                        VerticalAlignment="Center"/>
                                        <!-- Edit -->
                                        <Button x:Name="PART_Edit" 
                                          Template="{StaticResource 
                                                    bordereredButtonTemplate}" 
                                          Margin="5,0,0,0" Padding="4" 
                                          Width="auto" Height="auto" 
                                          HorizontalAlignment="Left"
                                          FontFamily="Arial" 
                                          FontSize="9" Foreground="White" 
                                          Content="Edit Me"
                                          VerticalAlignment="Center"/>
                                    </StackPanel>
                                </StackPanel>
                            </Canvas>
                        </DockPanel>
                    </Border>
                </Grid>
                <ControlTemplate.Triggers>
                    <EventTrigger RoutedEvent="Mouse.MouseEnter" 
                               SourceName="LayoutRoot">
                        <BeginStoryboard 
                           Storyboard="{StaticResource OnMouseEnterGrow}"/>
                    </EventTrigger>
                    <EventTrigger RoutedEvent="Mouse.MouseLeave" 
                                    SourceName="LayoutRoot">
                        <BeginStoryboard 
                           Storyboard="{StaticResource OnMouseLeaveShrink}"/>
                    </EventTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Notice the use of some strange named elements:

<!-- Show Orders Controls -->
<Button x:Name="PART_ShowHideOrders" 
  Template="{StaticResource bordereredButtonTemplate}" 
  Margin="5,0,0,0" Padding="4" 
  Width="auto" Height="auto" 
  HorizontalAlignment="Left"
  FontFamily="Arial" FontSize="9" 
  Foreground="White" Content="Show My Orders"
  VerticalAlignment="Center"/>

<!-- Edit -->
<Button x:Name="PART_Edit" 
   Template="{StaticResource bordereredButtonTemplate}" 
   Margin="5,0,0,0" Padding="4" 
   Width="auto" Height="auto" 
   HorizontalAlignment="Left"
   FontFamily="Arial" FontSize="9" 
   Foreground="White" Content="Edit Me"
   VerticalAlignment="Center"/>

These are the important parts that allow the control to work as it was intended to work. To understand this a bit better, examine the code-behind:

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.Navigation;
using System.Windows.Shapes;

using PhysicsHost.DataAccess;
using PhysicsHost.ViewModel;

namespace PhysicsHost
{
    /// <summary>
    /// Represents a Customer from the Northwind database.
    /// This is lookless control, and as such as Style
    /// can be applied, but there are expected to be 2
    /// PARTs called
    /// <list type="bullet">
    /// <item>PART_ShowHideOrders, Button</item>
    /// <item>PART_Edit, Button</item>
    /// </list>
    /// Which are required for the control to work correctly
    /// </summary>
    [TemplatePart(Name = "PART_ShowHideOrders", Type = typeof(Button))]
    [TemplatePart(Name = "PART_Edit", Type = typeof(Button))] 
    public partial class CustomerUserControl : UserControl
    {
        #region Ctor
        public CustomerUserControl()
        {
            InitializeComponent();
        }
        #endregion

        #region OnApplyTemplate
        /// <summary>
        /// Find the required parts from the applied template
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            //show hide button
            Button PART_ShowHideOrders = 
                base.GetTemplateChild("PART_ShowHideOrders") as Button;
            if (PART_ShowHideOrders != null)
            {
                PART_ShowHideOrders.Tag = this;
                PART_ShowHideOrders.Command = 
                CustomerViewModel.ShowHideOrdersForCustomerCommand;
            }
            //edit button
            Button PART_EditButton = 
                base.GetTemplateChild("PART_Edit") as Button;
            if (PART_EditButton != null)
            {
                PART_EditButton.Tag = this;
                PART_EditButton.Click += 
                    new RoutedEventHandler(PART_EditButton_Click);
            }
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Show EditCustomerWindow
        /// </summary>
        private void PART_EditButton_Click(object sender, RoutedEventArgs e)
        {
            EditCustomerWindow ec = new EditCustomerWindow();
            ec.DataContext = this.DataContext;
            ec.Owner = MainWindow.GetWindow(this);
            ec.ShowInTaskbar = false;
            ec.WindowStartupLocation = WindowStartupLocation.CenterOwner;
            ec.ShowDialog();
        }
        #endregion
    }
}

Notice at the top of the class, I use the PartTempateAttribue to relay my intentions of what elements were expected and what they should be called and what type of elements are expected.

The other thing to note is the OnApplyTemplate() method, which is where the expected elements are looked for, and if found, have their required events wired up.

In the above example, I am also using a Command which is assigned to one of the expected buttons ("PART_ShowHideOrders"). The usage of Commands within the demo app was discussed in the Commands and Events section.

OrderUserControl

OrderUserControl looks like this by default, and works in a very simliar manner to that described above:

<!-- OrderUserControl -->
<Style x:Key="defaultOrderControlStyle" 
              TargetType="{x:Type local:OrderUserControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:OrderUserControl}">
                <!-- Control Layout -->
                <Grid x:Name="LayoutRoot" Height="90" 
                              RenderTransformOrigin="0.5,0.5">
                    <Grid.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform ScaleX="1" ScaleY="1"/>
                            <SkewTransform AngleX="0" AngleY="0"/>
                            <RotateTransform Angle="0"/>
                            <TranslateTransform X="0" Y="0"/>
                        </TransformGroup>
                    </Grid.RenderTransform>
                    <Border Margin="0,0,0,0" 
                          Background="#FF303030" 
                          BorderBrush="#FFFFFFFF" 
                          BorderThickness="2,2,2,2" 
                          CornerRadius="3,3,3,3">
                        <DockPanel Width="Auto" 
                                    Height="Auto" LastChildFill="True">
                            <Border Height="20" DockPanel.Dock="Top" 
                                Background="#FFFFFFFF" 
                                CornerRadius="0,0,0,0" Margin="0,-2,0,0">
                                <Label Margin="45,-5,0,0" 
                                Width="auto" Height="auto" 
                                Content="{Binding Path=OrderID}" 
                                FontSize="14" FontWeight="Bold"/>
                            </Border>
                            <Canvas>
                                <Image Margin="-40,-40,0,0"
                                    Width="50" Height="50" 
                                    Canvas.Left="18" 
                                    Canvas.Top="8" 
                                    Source="../images/order.png"/>
                                <StackPanel Canvas.Left="0" 
                                      Canvas.Top="16" 
                                      Orientation="Vertical">
                                    <StackPanel Orientation="Horizontal">
                                      <Label  Width="auto" 
                                        Height="auto" Content="Order Date:" 
                                        FontSize="9" FontWeight="Bold" 
                                        Foreground="#FFFFFFFF"/>
                                      <Label Width="auto" Height="auto" 
                                        FontSize="9" Foreground="#FFFFFFFF" 
                                        Content="{Binding OrderDate,
                                                 Converter={StaticResource dateConv}}" />
                                    </StackPanel>

                                    <StackPanel Orientation="Horizontal" 
                                                          Margin="0,5,0,0">
                                        <!-- Edit -->
                                        <Button x:Name="PART_Edit" 
                                        Template="{StaticResource bordereredButtonTemplate}" 
                                        Margin="5,0,0,0" Padding="4" 
                                        Width="auto" Height="auto" 
                                        HorizontalAlignment="Left"
                                        FontFamily="Arial" 
                                        FontSize="9" Foreground="White" 
                                        Content="Edit Me"
                                        VerticalAlignment="Center"/>
                                    </StackPanel>
                                </StackPanel>
                            </Canvas>
                        </DockPanel>
                    </Border>
                </Grid>
                <ControlTemplate.Triggers>
                    <EventTrigger RoutedEvent="Mouse.MouseEnter" 
                        SourceName="LayoutRoot">
                        <BeginStoryboard 
                            Storyboard="{StaticResource OnMouseEnterGrow}"/>
                    </EventTrigger>
                    <EventTrigger RoutedEvent="Mouse.MouseLeave" 
                        SourceName="LayoutRoot">
                        <BeginStoryboard 
                            Storyboard="{StaticResource OnMouseLeaveShrink}"/>
                    </EventTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

This time, there is only one required element that the control must have in order to work correctly:

<!-- Edit -->
<Button x:Name="PART_Edit" 
   Template="{StaticResource bordereredButtonTemplate}" 
   Margin="5,0,0,0" Padding="4" 
   Width="auto" Height="auto" 
   HorizontalAlignment="Left"
   FontFamily="Arial" FontSize="9" 
   Foreground="White" Content="Edit Me"
   VerticalAlignment="Center"/>

And here is the code-behind:

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.Navigation;
using System.Windows.Shapes;

using PhysicsHost.DataAccess;

namespace PhysicsHost
{

    /// <summary>
    /// Represents a Order from the Northwind database.
    /// This is lookless control, and as such as Style
    /// can be applied, but there are expected to be 1
    /// PART called
    /// <list type="bullet">
    /// <item>PART_Edit, Button</item>
    /// </list>
    /// Which are required for the control to work correctly
    /// </summary>
    [TemplatePart(Name = "PART_Edit", Type = typeof(Button))] 
    public partial class OrderUserControl : UserControl
    {
        #region Ctor
        public OrderUserControl()
        {
            InitializeComponent();
        }
        #endregion

        #region OnApplyTemplate
        /// <summary>
        /// Find the required parts from the applied template
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            //edit button
            Button PART_EditButton = 
                base.GetTemplateChild("PART_Edit") as Button;
            if (PART_EditButton != null)
            {
                PART_EditButton.Tag = this;
                PART_EditButton.Click += 
                    new RoutedEventHandler(PART_EditButton_Click);
            }
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Show EditOrderWindow
        /// </summary>
        private void PART_EditButton_Click(object sender, RoutedEventArgs e)
        {
            EditOrderWindow eo = new EditOrderWindow();
            eo.DataContext = this.DataContext;
            eo.Owner = MainWindow.GetWindow(this);
            eo.ShowInTaskbar = false;
            eo.WindowStartupLocation = 
                WindowStartupLocation.CenterOwner;
            eo.ShowDialog();
        }
        #endregion
    }
}

References

There are none, this is all the work of Sacha Barber and his old team leader Fredrik Bornander.

Other Good Sources

Various sources were used along the way; these were all included in the individual articles, so you should use those for a list of resources that pertain to the individual subject of the separate article. Links to the previous articles are here:

  1. Part 1: Layout
  2. Part 2: Resources
  3. Part 3: Commands and Events
  4. Part 4: Dependency Properties
  5. Part 5: DataBinding
  6. Part 6: Styles/Templates

History

  • 23/03/08: Initial release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here