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 peoples help namely the following
I have been working on this app on and off since Part1 and its kind of become a labour of love. I wanted to tweek 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 him. 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 disect it) and relate each part of the disected 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 also a joint article, where Fredrik also created some of the applications 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 thats read any of my articles, its 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. Its too much work and I need to move my attention on to other articles now. Sorry
Any way here is what we are going to cover in this article but only in C#, sorry again.
Essentially the demo app is very simple. It uses the standard SQL Server Northindwind SQL Server database. Where a number of Customer objects are first retrieved, and then when requested a Customers 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. Thats 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
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 you use. If you dont 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>
Sacha creates the entire application and ported Fredriks Physics code to WPF. It was WinForms based, so Sacha
created the nessecary changes to it in order to WPFify it. But I can't really
take much credit for the Physics code. That's Fredriks baby all the way. Fredrik is really a frustated games programmer that 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....Sacha 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 & Events/DPs/Databinding/Styles & Templates
and also LINQ to SQL. Oh and Sacha also created the trivial DashedOutlineCanvas within the
Physics project
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 Sachas 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 orginal Physics stuff that Fredrik did for his orginal codeproject article. Nice one Fredrik.
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 has streamed then watch it. It will make most sense that way. |
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. So now ill talk you thourgh how it was made. Like I say I think the best way is to disect the app and relate it back to the individual articles so that if you are lost or maybe new to this series, you can go back and have a look at the relevant article part
The application is structured using 2 projects. The Physics engine and the WPF application. This is shown below:

These 2 projects will be discussed in detail below. The WPF project has sub folders which contain various files, the folder name gives you an idea of what the files will be for.
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 it's controls.
The aim with this physics implementation was 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 sub class 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 controls added to that 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 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 code behind to attached the controls to Particles
The ParticleCanvas is the Canvas control that owns the 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 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 Particles 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 the 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,
the ParticleCanvas has to keep track of a series of mouse events.
PreviewMouseDown, PreviewMouseMove and PreviewMouseUp
are all used to keep 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 phyics 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 ZIndex 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 the 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 Particles 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 it's 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's 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);
}
}
The ParticleCanvas's 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 integraton 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 is 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 propotional to the time elapsed. After that the Particle
velocity is updated by simply adding the state velocity to the Particles
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 of 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);
}
}
}
The ParticleSystem class is responsible to calculating positions
and velocities of Particles. Particles are rather
simple classes that holds 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 Particles that Control's position is aligned with
the Particles 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 forces, 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.
Springs are used to constrain two Particles by applying
a forces to the Particles so that the Springs properties
are satisfied. The properties of the Spring are:
Particles are affected by no other forces. Spring Constant; This is a measure of how stiff the Spring
is, the higher value the more "eager" to reach it's Rest Length. Spring
move slower and stabilize the simulation. The forces that the Spring applies to it's 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.
You can refer to Part1 for more information about Layout in WPF
Layout is literally used everywhere within the demo app. From the Windows to the UserControls to the Custom Styles/Templates. Its every where. Probably the best way is to pick a couple of Windows and provide screen shots and the associated layout markup that creates the Windows. Though there are far to many areas where Layout is used to go into it every thing. But this should give you a good idea.
There are 4 WIndows, within the demo app
And there are 2 UserControls, though I wont 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 2 or 3 of these Windows, Say the following ones, we should be able to discuss enough layout I think
Ok so the MainWindow.xaml looks like:
Now if we foget about the ParticleCanvas in the middle (the yellow
boxed area) just for the moment, as Fredrik should have talked about this a bit in the Physics
section, and I will talk about the layout apects 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 the 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>
Ok so the 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>
As the ParticleCanvas inherits from Canvas seveal layout orientated overrides must be performed. These
are as follows :
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
You can refer to Part2 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 3 files as follows
Just to remind ourselves of how to use resoutces. We start by declaring a resource dictionary. Lets 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 the 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). Lets 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 whos 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 dicitionary.
This is typically how the demo app is using resources. Though there are occassions where I am using local Window or Control level Resources, which dont use MergedDictionaries. 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>
You can refer to Part3 for more information about Commands And Events in WPF
The demo application is using 3 routed commands, which are as follows:
The demo application uses the ModelView-ViewModel (MVVM) pattern, so these commands and how they tie up with the overall architecure will be mentioned a bit more in Databinding section of this article. But for now lets just concentrate 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 nesessary to have the command declared/bound and executed all within the main UI, however it is better to have some level of code seperation. By seperating 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. Youll see what I mean.
But anyway back to the commands that this demo app declares. Lets look at them one by one.
There is a command that is created within the CustomerViewModel.cs
that has its command CanExecute/Executed bindings set in the MainWindow
file. And the actual UIElement that triggers the Command 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.cs 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>
Code behind code thats sets the PART_SHowHideOrders button command. By doing this in code behind we are enabling allowing the control to be lookless, where the ens 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. Lets 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 the CustomerUserControl.xaml
has enough Orders. If it does the Show Orders Button will be allowed
to run otherwise it wont. 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 are added to the ParticleCanvas as new Particles
So thats how that command works.
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.

Lets 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 thats that part. So 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. Its 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");
}
}

So whats happening here is within theCustomerViewModel.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.
Works in the same manner as the CustomerViewModel SubmitChangesCommand command,
but is used for Saving Order objects instead of Customer
objects.
You can refer to Part4 for more information about Resources in WPF
I didnt really have a cool idea about how to use DPs or Attached Properties for this article. So I did what any resourcful 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. Rudis 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 alost every WPF book ut there, but the use of the Attached Property is the bit I like. And that is what Rudi did, so thats why I am using it.
So lets see how we do this. I Have posted Rudis original code here also (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 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.
You can refer to Part5 for more information about Databinding in WPF
This is perhaps the most complicated part5 of the demo application. The reason that its complicated is as follows:
So as you can see there is a fair bit to talk about just one this one subject. I think the best bet is to tackle this one thing at a time, in the order described above.
As I am using SQL Server and VS2008, why not use LINQ to SQL I thought. So thats exactly what I do. I use 2 tables Customers/Orders from the Northwind database. I just dragged these 2 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 2 Northwind tables. This class creates the NorthwindDataContext which is the main objects 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 1 instance of the 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 the 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
}
}
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 a N-tier approach where the data flow is something like the following diagram

Where the MainWindow uses the CustomerViewModel to fetch a num
number of Customer objects which are then use 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.
So it can be seen that there is a call made to the CustomerViewModel
to fetch all its Customer objects, this is done using the GetCustomers()
method. Lets 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 is also responsible
for running the business rules. In this case, ther rules are very simple, are the customer has at least 1 associated Order
The final layer is the NorthwindDataContext which as I stated
earlier is standard LINQ to SQL generated code.
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 apps
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 generated classes implement the INotifyPropertyChanged interface. Which is a MUST when dealing
with WPF. So 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.
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 underyling 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 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 eale 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 need to use to update the
bindings with, and then creates the appropriate validation rules for the bindings.
Its 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 its 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 reccommend you go and read Pauls article for youself, its well thought out and described well.
You can refer to Part6 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:
To truly understand these various Styles/Templates I will include a screen shot of the Styled/Templated element and give a listing of the Style/Template that achieved the look in question.
![]()
<!-- 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>


<!-- 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>
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 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>
Recall in Part6 I has a section about lookless controls. So for those
that havent read that section, its 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 doesnt provide a button or a Listbox.
So how 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 override the on to check for the existance 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 the CustomerUserControl and the OrderUserControl. Lets have a look at these shall we.
The CustomerUserControl looks like this by default.

Now this is down to a default Style that I created, which tells
the control what its 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 the 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 either 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
The 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
}
}
There are none, this is all the work of Sacha Barber and his old team leader Fredrik Bornander
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 seperate article. Links to the previous articles are here
23/03/08 : Initial release
| You must Sign In to use this message board. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||