Sie sind auf Seite 1von 72

Windows Presentation Foundation (WPF) 3D Tutorial

The purpose of this tutorial is to provide the simplest explanation and examples possible of how to create 3D graphics with Windows Presentation Foundation (WPF) (formerly known as "Avalon"). It is not a comprehensive guide to 3D modeling, but simply a primer for those who have no knowledge of or experience with 3D graphics. 3D or vector-based graphics is a different world from 2D GDI, and it took a long period of guessing, stumbling, and trial and error before I understood what was going on with it. Ultimately I hope that you are able to avoid some of the pains I went through by reading through this tutorial. For your convenience, the tutorial contains inline links to the documentation of all of the .Net 3.0 classes, structures, properties, and methods used in the example code. Click away as you please.

Disclaimer
While I make my best effort to make sure the content and examples in this tutorial are accurate and bug-free, I'm not responsible for anything bad (e.g. crashes, loss of data, any damage) that may happen if you use the information or code in this tutorial. I'll do my best to make sure bugs are fixed and that the content is as accurate as possible. In short, have some common sense and use this at your own risk.

Mike's Version of 3D Graphics Theory


When I started 3D graphics in WPF (back when it was still called Avalon), I had no idea what I was doing. I didn't have a clue what the significance was of a mesh, triangle index, or normal. This was the most painful part about learning 3D modeling, because without a minimal understanding of these things, nothing will show up correctly in WPF (or you'll just get lucky). In short, all you really need to know is what a mesh is and what it is composed of [1].

What is a mesh?
A mesh is basically a representation of a surface. The mesh represents the surface through a system of points and lines. The points describe the high and low areas of the surface, and the lines connect the points to establish how you get from one point to the next. At a minimum, a surface is a flat plane. A flat plane needs three points to define it. Thus, the simplest surface that can be described in a mesh is a single triangle. It turns out that meshes can only be described with triangles. That is because a triangle is the simplest, most granular way to define a surface. A large, complex surface obviously can't be accurately described by one triangle. Instead, it can be approximated by many smaller triangles. You could argue that you could use a rectangle to define a surface, but it's not as granular as a triangle. When you think about it, a rectangle can be broken into two triangles. Two triangles can much more accurately describe a surface than a single rectangle. Ok enough... the point is that a mesh represents a surface through many triangles. A whole mesh is composed of:

Mesh Positions Triangle indeces Triangle normals

Mesh Positions
A mesh position is the location of a single point on a surface. The more dense the points are, the more accurately the mesh describes the surface.

Triangle Indeces
A triangle index is a mesh position that defines one of the three points of a triangle in the mesh. The mesh positions alone cannot describe the mesh triangles. After the positions have been added, you need to define what positions make up which triangles. In WPF, the order in which you add mesh positions is important. A position's index value in a mesh's position collection is used when adding triangle indeces. For example, let's say you have a surface composed of five positions {p0, p1, p2, p3, p4}. If you wanted to define a triangle from p1, p3, and p4, you would add triangle indeces with index values 1, 3, and 4. If the positions were added in a different order {p3, p4, p0, p2, p1} and you wanted a triangle made of the same positions, you would add triangle indeces with index values 4, 0, and 1. The order in which you add triangle indeces is also important. When you define a triangle, you are basically defining the points in either a clockwise or counter-clockwise direction (depending on which side of the triangle you're on, of course). The reason this is important is because it affects which side of the triangle is visible. When I say "side", I don't mean which of the three triangle sides, but which side of the plane. Let's say you're looking straight ahead at the surface of a triangle. If you define its indeces in a clockwise direction, the side you are looking at will be invisible and the opposite side will be visible. If you define its indeces in a counter-clockwise direction, the side you are looking at will be visible and the opposite side will be invisible. You can use the "right hand rule" to remember this. Take your right hand and make a "thumbs up" sign. The direction your fingers curl is counterclockwise and your thumb points up (or out) in the direction the surface would be visible. Exactly why a right-hand-rule applies to index ordering is beyond me. My best guess is that WPF doesn't think it is necessary or efficient to render both sides of the triangle, so you need to pick one of them.

Triangle Normals
After defining positions and triangle indeces, you need to add normals to each position. While the direction in which you add triangle indeces determines which side of the triangle is visible, a normal is used by WPF to know how the surface should be lit by a light source. A normal is a vector that is perpendicular to the surface of the triangle. The normal vector is computed as the "cross product" of two vectors that make up the side of the triangle. If you have a triangle defined by points A, B, and C, you could create the normal by multiplying AB x AC, BC x BA, or CB x CA. All three ways will result in the same normal. However, the right-hand rule still applies. AB x AC will result in a normal in the opposite direction from AC x AB.

In general you'll want your normals to point in the same direction as the visible side of your triangle surface. However, if the normals are angled away from being perpendicular, the surface of the triangle will have a more interesting lighting effect. Each position in the mesh should have a normal assigned to it, and a position can only have one normal. You'll add normals to the mesh in the same order that you added the positions. In other words, the index values of the normals collection in the mesh corresponds to the index values of the positions collection. The more positions you have, the more normals you have. The more normals you have, the more pleasant the lighting and shading will be. In a mesh, a single point may be the index of more than one triangle. In this case, you may want to use multiple points with the same coordinates so that you can have multiple normals at that point. Take the corner of a cube, for example. The corner of a cube is the intersection of three different triangles on the cube. If you only used one position in the mesh to define the common index of those three triangles, you could only use one normal vector for that position. As a result, two of the triangles at that position won't be shaded as well as it could be. It would be better to define that cube corner with three unique positions. Each of the three triangles would use their own unique points, and you could use three normals at that corner instead of just one.

Getting Started With the Code


I'll assume you have a basic understanding of WPF and how to create a basic WPF user interface with XAML. So with that, let's jump in and create a new WPF application in VS.Net 2005. Add the following XAML to the app to create a simple layout with a panel for buttons and a Viewport3D for displaying our 3D stuff:

<Grid> <DockPanel Width="Auto" VerticalAlignment="Stretch" Height="Auto" HorizontalAlignment="Stretch" Grid.ColumnSpan="1" Grid.Column="0" Grid.Row="0" Margin="0,0,0,0" Grid.RowSpan="1"> <StackPanel> <StackPanel.Background> <LinearGradientBrush> <GradientStop Color="White" Offset="0"/> <GradientStop Color="DarkKhaki" Offset=".3"/> <GradientStop Color="DarkKhaki" Offset=".7"/> <GradientStop Color="White" Offset="1"/> </LinearGradientBrush> </StackPanel.Background> <StackPanel Margin="10"> <Button Name="simpleButton" Click="simpleButtonClick">Simple</Button> </StackPanel> </StackPanel>

<Viewport3D Name="mainViewport" ClipToBounds="True"> <Viewport3D.Camera> <PerspectiveCamera FarPlaneDistance="100" LookDirection="-11,-10,-9" UpDirection="0,1,0" NearPlaneDistance="1" Position="11,10,9" FieldOfView="70" /> </Viewport3D.Camera> <ModelVisual3D> <ModelVisual3D.Content> <DirectionalLight Color="White" Direction="-2,-3,-1" /> </ModelVisual3D.Content> </ModelVisual3D> </Viewport3D> </DockPanel> </Grid>

Basically, all of the 3D stuff in WPF happens in Viewport3D controls. That's where we'll be adding our 3D models once we start writing some code. Notice that aPerspectiveCamera has been added to the Viewport3D. The camera is used to allow us to "view" what's in the model from the user interface. Note that the camera is looking at the point {0,0,0} in the model. The model also contains a DirectionalLight light source so that we can view stuff in the model. I encourage you to change the camera's LookDirection and Position while you go through the examples. The screenshots in this tutorial do not necessarily use the LookDirection and Position values in the XAML above. The XAML above has one Button named simpleButton, so we need to hook up its click event to a method named simpleButtonClick in the window's code-behind file.

private void simpleButtonClick(object sender, RoutedEventArgs e)

{ }

Creating a Simple Mesh


When you click the simpleButton button, you're going to create a very simple mesh and add the model to the Viewport3D. But before you do that, you'll need to know a little bit about the types of 3D classes and structures you'll be working with:

GeometryModel3D - a model described by a Mesh (MeshGeometry3D) and a Material (DiffuseMaterial). MeshGeometry3D - a mesh. Has a Positions collection, a TriangleIndeces collection, and a Normals collection. Point3D - the most basic unit of mesh construction. Used to define positions and vectors. Vector3D - used in the calculation of normals. DiffuseMaterial - gives a model color and texture. DirectionalLight - provides light so that objects in the Viewport3D can be seen. Without it, nothing shows up.

Full documentation of all of the System.Windows.Media.Media3D classes can be found at msdn.microsoft.com

Add the code


Before we start digging into the nuts and bolts, make sure you import the using System.Windows.Media.Media3D namespace into the code-behind. That namespace has all of the classes we'll be working with:

using System.Windows.Media.Media3D;

Now let's create a simple mesh and add it to the Viewport3D. We'll start with the simplest mesh possible: a triangle. It'll be located near the origin of the model (remember where the PerspectiveCamera is pointing?) and will have one side along the X-axis (5 units long), one along the Z-axis (5 units long), and a hypotenuse connecting the first two sides. First, create a new MeshGeometry3D:

MeshGeometry3D triangleMesh = new MeshGeometry3D();

Next, define the three points of the triangle:

Point3D point0 = new Point3D(0, 0, 0); Point3D point1 = new Point3D(5, 0, 0);

Point3D point2 = new Point3D(0, 0, 5);

Next, add the three points as positions in the mesh:

triangleMesh.Positions.Add(point0); triangleMesh.Positions.Add(point1); triangleMesh.Positions.Add(point2);

Now we'll add the triangle indeces to define the triangle of the mesh. Since our entire mesh is a triangle, this might seem redundant, but WPF doesn't know how the points connect. Remember the right-hand-rule. We want the top surface of the triangle to be visible, so add the indeces in the appropriate order:

triangleMesh.TriangleIndices.Add(0); triangleMesh.TriangleIndices.Add(2); triangleMesh.TriangleIndices.Add(1);

Next we'll add the normal vectors for the mesh positions. Since this one's easy (the normal will point straight up in the Y direction), we'll just create a normal vector with known dimensions rather than computing a cross product:

Vector3D normal = new Vector3D(0, 1, 0); triangleMesh.Normals.Add(normal); triangleMesh.Normals.Add(normal); triangleMesh.Normals.Add(normal);

Next we'll need to create a DiffuseMaterial for the surface, add the mesh to a model, and add the model to the Viewport3D:

Material material = new DiffuseMaterial( new SolidColorBrush(Colors.DarkKhaki)); GeometryModel3D triangleModel = new GeometryModel3D( triangleMesh, material); ModelVisual3D model = new ModelVisual3D(); model.Content = triangleModel; this.mainViewport.Children.Add(model);

When all is said and done, the simpleButtonClick event handler will look like this:

private void simpleButtonClick(object sender, RoutedEventArgs e) { MeshGeometry3D triangleMesh = new MeshGeometry3D(); Point3D point0 = new Point3D(0, 0, 0); Point3D point1 = new Point3D(5, 0, 0); Point3D point2 = new Point3D(0, 0, 5); triangleMesh.Positions.Add(point0); triangleMesh.Positions.Add(point1); triangleMesh.Positions.Add(point2); triangleMesh.TriangleIndices.Add(0); triangleMesh.TriangleIndices.Add(2); triangleMesh.TriangleIndices.Add(1); Vector3D normal = new Vector3D(0, 1, 0); triangleMesh.Normals.Add(normal); triangleMesh.Normals.Add(normal); triangleMesh.Normals.Add(normal); Material material = new DiffuseMaterial( new SolidColorBrush(Colors.DarkKhaki)); GeometryModel3D triangleModel = new GeometryModel3D( triangleMesh, material); ModelVisual3D model = new ModelVisual3D(); model.Content = triangleModel; this.mainViewport.Children.Add(model); }

That's it! The code produces the following result:

Hardly exciting, I know, but just think - now you understand the building blocks of 3D meshes! Still not excited? Ok, then next we'll move on to a cube.

Creating a Cube
A cube is just an extension of creating a triangle. The differences are:

A cube is composed of 12 triangles instead of one (six sides, each with two triangles) The triangles are oriented in each cardinal direction of the Cartesian coordinate system, which may or may not be easy to visualize at times. It can be particularly tricky when remembering which way would be counter-clockwise for the right-hand-rule.

Let's continue by adding another button to the button panel in the XAML:

<Button Name="cubeButton" Click="cubeButtonClick">Cube</Button>

Then, hook up the cubeButtonClick event handler in the code-behind:

private void cubeButtonClick(object sender, RoutedEventArgs e) { }

We can now do a little refactoring of the code to make life easier. But before we do that we need to talk about the Model3DGroup class. A Model3DGroup is a collection of GeometryModel3D objects. In other words, a Model3DGroup can contain many meshes. On

top of that, Model3DGroup objects can contain otherModel3DGroup objects too, and so on. Last, a Model3DGroup can be added to a Viewport3D. What does all this mean? It provides a means to create small sets of 3D objects and make them a part of other models easily. Think of it as creating a Windows User Control that you can re-use in other User Controls. So, let's start refactoring by abstracting out the creation of a single triangle mesh with normals at its triangle indeces. Add these two methods (CreateTriangleModel() and CalculateNormal() to the code-behind:

private Model3DGroup CreateTriangleModel(Point3D p0, Point3D p1, Point3D p2) { MeshGeometry3D mesh = new MeshGeometry3D(); mesh.Positions.Add(p0); mesh.Positions.Add(p1); mesh.Positions.Add(p2); mesh.TriangleIndices.Add(0); mesh.TriangleIndices.Add(1); mesh.TriangleIndices.Add(2); Vector3D normal = CalculateNormal(p0, p1, p2); mesh.Normals.Add(normal); mesh.Normals.Add(normal); mesh.Normals.Add(normal); Material material = new DiffuseMaterial( new SolidColorBrush(Colors.DarkKhaki)); GeometryModel3D model = new GeometryModel3D( mesh, material); Model3DGroup group = new Model3DGroup(); group.Children.Add(model); return group; } private Vector3D CalculateNormal(Point3D p0, Point3D p1, Point3D p2) { Vector3D v0 = new Vector3D( p1.X - p0.X, p1.Y - p0.Y, p1.Z - p0.Z);

Vector3D v1 = new Vector3D( p2.X - p1.X, p2.Y - p1.Y, p2.Z - p1.Z); return Vector3D.CrossProduct(v0, v1); }

The CreateTriangleModel() method can be used anywhere to create a Model3DGroup that contains a mesh defined by three supplied points. Pretty nifty. TheCalculateNormal() method is used by CreateTriangleModel() to get its normals for its triangle indeces. It does this easily using the CrossProduct method of theVector3D structure. This is really cool because now all you have to do is know the mesh positions and these two methods do the rest for you. Next, let's visualize what the cube will look like in Cartesian space. There are eight unique points, and we'll order them like so:

Now let's create a cube in code. Add this code to the cubeButtonClick() event handler:

private void cubeButtonClick(object sender, RoutedEventArgs e) { Model3DGroup cube = new Model3DGroup();

Point3D p0 = new Point3D(0, 0, 0); Point3D p1 =new Point3D(5, 0, 0); Point3D p2 =new Point3D(5, 0, 5); Point3D p3 =new Point3D(0, 0, 5); Point3D p4 =new Point3D(0, 5, 0); Point3D p5 =new Point3D(5, 5, 0); Point3D p6 =new Point3D(5, 5, 5); Point3D p7 = new Point3D(0, 5, 5); //front side triangles cube.Children.Add(CreateTriangleModel(p3, p2, p6)); cube.Children.Add(CreateTriangleModel(p3, p6, p7)); //right side triangles cube.Children.Add(CreateTriangleModel(p2, p1, p5)); cube.Children.Add(CreateTriangleModel(p2, p5, p6)); //back side triangles cube.Children.Add(CreateTriangleModel(p1, p0, p4)); cube.Children.Add(CreateTriangleModel(p1, p4, p5)); //left side triangles cube.Children.Add(CreateTriangleModel(p0, p3, p7)); cube.Children.Add(CreateTriangleModel(p0, p7, p4)); //top side triangles cube.Children.Add(CreateTriangleModel(p7, p6, p5)); cube.Children.Add(CreateTriangleModel(p7, p5, p4)); //bottom side triangles cube.Children.Add(CreateTriangleModel(p2, p3, p0)); cube.Children.Add(CreateTriangleModel(p2, p0, p1));

ModelVisual3D model = new ModelVisual3D(); model.Content = cube; this.mainViewport.Children.Add(model);

Now when you run the code, you'll see a cube in the app:

Clearing the Viewport


If you've tried clicking both the "Simple" and "Cube" buttons one after another, you've essentially added a cube right on top of the original triangle (or vice-versa). Not a big deal, but kind of annoying if you like keeping things clean. Add this method to the code-behimd to clear out the Viewport3D except for the light source:

private void ClearViewport() { ModelVisual3D m; for (int i = mainViewport.Children.Count - 1; i >= 0; i--) { m = (ModelVisual3D)mainViewport.Children[i]; if (m.Content is DirectionalLight == false) mainViewport.Children.Remove(m); }

Call the ClearViewport() method whenever you want to clean things up (e.g. each time you click a button that adds some stuff).

Controlling the Camera


Let's make another small enhancement that makes it easier to move the camera and where it's looking without having to modify the XAML all the time. Add the following TextBlock and TextBox code to the XAML above the buttons we've already added:

<TextBlock Text="Camera X Position:"/> <TextBox Name="cameraPositionXTextBox" MaxLength="5" HorizontalAlignment="Left" Text="9"/> <TextBlock Text="Camera Y Position:"/> <TextBox Name="cameraPositionYTextBox" MaxLength="5" HorizontalAlignment="Left" Text="8"/> <TextBlock Text="Camera Z Position:"/> <TextBox Name="cameraPositionZTextBox" MaxLength="5" HorizontalAlignment="Left" Text="10"/> <Separator/> <TextBlock Text="Look Direction X:"/> <TextBox Name="lookAtXTextBox" MaxLength="5" HorizontalAlignment="Left" Text="-9"/> <TextBlock Text="Look Direction Y:"/> <TextBox Name="lookAtYTextBox" MaxLength="5" HorizontalAlignment="Left" Text="-8"/> <TextBlock Text="Look Direction Z:"/> <TextBox Name="lookAtZTextBox" MaxLength="5" HorizontalAlignment="Left" Text="-10"/> <Separator/>

<!-- buttons --> <Button Name="simpleButton" Click="simpleButtonClick">Simple</Button>

<Button Name="cubeButton" Click="cubeButtonClick">Cube</Button>

Now, add a new method to the code behind named SetCamera() with this code:

private void SetCamera() { PerspectiveCamera camera = (PerspectiveCamera)mainViewport.Camera; Point3D position = new Point3D( Convert.ToDouble(cameraPositionXTextBox.Text), Convert.ToDouble(cameraPositionYTextBox.Text), Convert.ToDouble(cameraPositionZTextBox.Text) ); Vector3D lookDirection = new Vector3D( Convert.ToDouble(lookAtXTextBox.Text), Convert.ToDouble(lookAtYTextBox.Text), Convert.ToDouble(lookAtZTextBox.Text) ); camera.Position = position; camera.LookDirection = lookDirection; }

The code simply grabs the points entered in the text boxes, creates Point3D objects from the values, and assigns the points to the Position and LookDirectionproperties of the camera. Go ahead and add a call to SetCamera() whenever you click one of the buttons or add something to the Viewport3D. Now you can move the camera around and get a different view of things:

The ScreenSpaceLines3D Class


Back in the beta days of WPF, it used to contain a class named ScreenSpaceLines3D. ScreenSpaceLines3D was used to draw a simple line in 3D space. Unfortunately, in the .Net 3.0 Framework, the ScreenSpaceLines3D class no longer exists, nor does another class exist that replaces its functionality. Fortunately, Dan Lehenbauer has designed a new ScreenSpaceLines3D class for use with .Net 3.0 and WPF. The remainder of this tutorial will use that class, so you'll need to go and download his code from this 3DTools project on CodePlex.com. Extract the files, build the solution, and reference the 3DTools dll in your WPF project. Then add the following using statement in your code:

using _3DTools;

We'll be using ScreenSpaceLines3D to help do interesting things with normals and wireframes, but We won't use it just yet. We'll come back to it in a minute.

Add Normals to the Rendered Model


Wouldn't it be cool to see exactly where the normals are and what direction they're pointing in? Absolutely. Is it necessary? Well, it depends... it can be very handy when building more complex surfaces (like later in this tutorial). We can easily do this by drawing lines with the ScreenSpaceLines3D class. All we really want to do is draw a short little line from the triangle indeces out in the direction of the indeces' normal. To accomplish this, add the following code to the panel containing our controls in the XAML:

<Separator/>

<CheckBox Name="normalsCheckBox">Show Normals</CheckBox> <TextBlock Text="Normal Size:"/> <TextBox Name="normalSizeTextBox" Text="1"/>

The XAML above will allow us to use a check box to toggle whether the normals are shown or not and to set how big the normals are. Next, add this method to the code-behind:

private Model3DGroup BuildNormals( Point3D p0, Point3D p1, Point3D p2, Vector3D normal) { Model3DGroup normalGroup = new Model3DGroup(); Point3D p; ScreenSpaceLines3D normal0Wire = new ScreenSpaceLines3D(); ScreenSpaceLines3D normal1Wire = new ScreenSpaceLines3D(); ScreenSpaceLines3D normal2Wire = new ScreenSpaceLines3D(); Color c = Colors.Blue; int width = 1; normal0Wire.Thickness = width; normal0Wire.Color = c; normal1Wire.Thickness = width; normal1Wire.Color = c; normal2Wire.Thickness = width; normal2Wire.Color = c; double num = 1; double mult = .01; double denom = mult * Convert.ToDouble(normalSizeTextBox.Text); double factor = num / denom;

p = Vector3D.Add(Vector3D.Divide(normal, factor), p0); normal0Wire.Points.Add(p0); normal0Wire.Points.Add(p); p = Vector3D.Add(Vector3D.Divide(normal, factor), p1); normal1Wire.Points.Add(p1); normal1Wire.Points.Add(p); p = Vector3D.Add(Vector3D.Divide(normal, factor), p2); normal2Wire.Points.Add(p2); normal2Wire.Points.Add(p);

//Normal wires are not models, so we can't //add them to the normal group. //to the viewport for now... this.mainViewport.Children.Add(normal0Wire); this.mainViewport.Children.Add(normal1Wire); this.mainViewport.Children.Add(normal2Wire); Just add them

return normalGroup; }

The method above takes three points (the points of a triangle) and a given normal vector. It then draws some lines out from the points in the direction of the normal and adds them to a Model3DGroup. It also scales the normals using the Divide method of the Vector3D structure and the value entered in thenormalSizeTextBox. Pretty simple. Then add this code near the end of the CreateTriangleModel method:

if (normalsCheckBox.IsChecked == true) group.Children.Add(BuildNormals(p0, p1, p2, normal));

Now when you build the cube with normals, you'll see this:

Click to enlarge

Building a Topography
At this point, you know all of the basics of my loose 3D theory and how meshes work in WPF. Let's give it another shot though and create a surface that isn't so orthogonal. Imagine if you will a topography made up of peaks and valleys. A surface where the points are connected by vectors that majestically dance together through space, creating a scene so delightful that you drool on your keyboard. Or, just imagine a topography. First, add another button into the control panel. This new button will be used to create and add the topography to the model:

<Button Name="topographyButton" Click="topographyButtonClick"> Topography </Button>

Essentially, we're going to create a topography that stretches out in the X-Z plane and has peaks and valleys in the Y direction. We'll make it a 10x10 surface. From overhead, the mesh will essentially look like a checkerboard whose squares are divided in two by triangles. Using a nested loop (one loop for the X direction and one for the Z) and some random number generation, we can create the points of the topography:

private Point3D[] GetRandomTopographyPoints() {

//create a 10x10 topography. Point3D[] points = new Point3D[100]; Random r = new Random(); double y; double denom = 1000; int count = 0; for (int z = 0; z < 10; z++) { for (int x = 0; x < 10; x++) { System.Threading.Thread.Sleep(1); y = Convert.ToDouble(r.Next(1, 999)) / denom; points[count] = new Point3D(x, y, z); count += 1; } } return points; }

Once you have the array of points, you can use another simple set of loops to load the points into triangles using our good old CreateTriangleModel() method. Add another button to the XAML in the button panel named topographyButton and hook up an event handler named topographyButtonClick in the code-behind:

private void topographyButtonClick(object sender, RoutedEventArgs e) { ClearViewport(); SetCamera(); Model3DGroup topography = new Model3DGroup(); Point3D[] points = GetRandomTopographyPoints(); for (int z = 0; z <= 80; z = z + 10) { for (int x = 0; x < 9; x++)

{ topography.Children.Add( CreateTriangleModel( points[x + z], points[x + z + 10], points[x + z + 1]) ); topography.Children.Add( CreateTriangleModel( points[x + z + 1], points[x + z + 10], points[x + z + 11]) ); } } ModelVisual3D model = new ModelVisual3D(); model.Content = topography; this.mainViewport.Children.Add(model); }

Basically, the nested loops just zig-zag around the grid and adds points for the triangles. When you run the app, you'll see something like this:

Click to enlarge

With normals turned on: Click to enlarge

Add a Wireframe
Another helpful tool in visualizing a mesh is to see its "wireframe". A wireframe is just a visual representation of the mesh positions and sides of the mesh triangles. It gives a little more definition to the surface so that you can see all of the edges, peaks, and valleys. To draw the wireframe, we'll use theScreenSpaceLines3D class again. All we need to do is enhance the CreateTriangleModel() method. First, add some more XAML to the controls panel in the application so that we can have the option of viewing the wireframe when the model is rendered:

<Separator/> <CheckBox Name="wireframeCheckBox">Show Wireframe</CheckBox>

Next, add the following code at the end of the CreateTriangleModel() method to add the wireframe if the wireframeCheckBox control is checked:

if (wireframeCheckBox.IsChecked == true) { ScreenSpaceLines3D wireframe = new ScreenSpaceLines3D(); wireframe.Points.Add(p0); wireframe.Points.Add(p1); wireframe.Points.Add(p2); wireframe.Points.Add(p0); wireframe.Color = Colors.LightBlue; wireframe.Thickness = 3;

this.mainViewport.Children.Add(wireframe); }

The code above just uses the existing points that have already been defined in the method and draws a path to connect them. The path is then added to the viewport. The final CreateTriangleModel() method will look like this:

private Model3DGroup CreateTriangleModel(Point3D p0, Point3D p1, Point3D p2) { MeshGeometry3D mesh = new MeshGeometry3D(); mesh.Positions.Add(p0); mesh.Positions.Add(p1); mesh.Positions.Add(p2); mesh.TriangleIndices.Add(0); mesh.TriangleIndices.Add(1); mesh.TriangleIndices.Add(2); Vector3D normal = CalculateNormal(p0, p1, p2);

mesh.Normals.Add(normal); mesh.Normals.Add(normal); mesh.Normals.Add(normal); Material material = new DiffuseMaterial( new SolidColorBrush(Colors.DarkKhaki)); GeometryModel3D model = new GeometryModel3D(mesh, material); Model3DGroup group = new Model3DGroup(); group.Children.Add(model);

if (normalsCheckBox.IsChecked == true) group.Children.Add(BuildNormals(p0, p1, p2, normal));

if (wireframeCheckBox.IsChecked == true) { ScreenSpaceLines3D wireframe = new ScreenSpaceLines3D(); wireframe.Points.Add(p0); wireframe.Points.Add(p1); wireframe.Points.Add(p2); wireframe.Points.Add(p0); wireframe.Color = Colors.LightBlue; wireframe.Thickness = 3; this.mainViewport.Children.Add(wireframe); }

return group; }

When you include the wireframe in the model, you'll see this:

Welcome to 3D Land!
That's it! You now have a foundation of the building blocks of meshes (positions, triangle indeces, and normals) and the classes used to create 3D objects in WPF. You should be able to apply these principles to more complex 3D shapes and problems in WPF.

Complex ModelVisual3D Composition and Hit Testing in WPF


In a WPF app I'd worked on, I encountered a scenario where I needed a 3D visual object that I could interact with using the mouse. While mousing-over or clicking the visual, I wanted its appearance to change. I didn't just want its color or size to change - but I actually want its geometry to change. One way to accomplish this is to do hit testing and let your user interface determine what visual model was clicked, and then replace it with another visual model that has the different appearance and geometry. However, a better idea is to encapsulate the behavior of changing the appearance and geometry into a single class that derives from WPF's ModelVisual3D class.

In other words, wouldn't it be great to do this in XAML:

<Viewport3D> <custom:CustomCompositeVisual x:Name="customVisual" /> </Viewport3D>

And then do this in code:

ModelVisual3D hitTestResult = GetHitTestResult(...); if (hitTestResult is CustomCompositeVisual) { CustomCompositeVisual customVisual = hitTestResult as CustomCompositeVisual; customVisual.ChangeAppearance(); }

The main approach to solving this problem is to create a custom class that derives from ModelVisual3D, and then create multiple models or visuals inside of it that it hides or shows as its state changes. Basically you'd create a container that contains child models or visuals that the container will hide and show. You need to make a design choice with this approach. The contents of your containing class can consist of either child GeometryModel3D objects or ModelVisual3D objects. The former is a nonvisual element that gets rendered by your container, and the latter is a visual item that renders itself and can be hit tested on its own. Either approach will work, but depending on how intricate the hit testing of your container needs to be you may need to choose the ModelVisual3D approach. Before I start showing code, let me explain the requirements for my example. I want to create a single ModelVisual3D class that contains two cubes - a large one and a small one. The large one is shown by default. When I click it, it shows the small cube. When I let go of the mouse, it should show the large cube again.

Approach #1: using GeometryModel3D


Let's start with the first approach and use GeometryModel3D objects. Create a new class that inherits from ModelVisual3D, and then create the child GeometryModel3D objects in its constructor. Since the GeometryModel3D objects are not visual elements, they need to be added as the Content property on your class. Your Content property cannot hold children, but it can hold a Model3DGroup object, which can contain children. Add your GeometryModel3D objects to a Model3DGroup, then set the Content property equal to the group: NOTE: You'll need to download the GeometryGenerator.cs file/class first in order for this code to work.

public class CompositeGeometryModelVisual3D : ModelVisual3D

{ GeometryModel3D bigCubeModel; GeometryModel3D smallCubeModel;

public CompositeGeometryModelVisual3D() { bigCubeModel = GeometryGenerator.CreateCubeModel(); smallCubeModel = GeometryGenerator.CreateCubeModel(); smallCubeModel.Transform = new ScaleTransform3D(.3, .3, .3);

Model3DGroup group = new Model3DGroup(); group.Children.Add(bigCubeModel); group.Children.Add(smallCubeModel);

Content = group; } }

To display this custom object in XAML, add an XML namespace to your XAML document that refers to your clr-namespace of the class:

xmlns:local="clr-namespace:YOUR.NAMESPACE.HERE"

Then add the markup for your custom CompositeGeometryModelVisual3D class. Make sure to stick it in the Viewport3D:

<Viewport3D> <!-- make sure to define lighting, etc... --> <local:CompositeGeometryModelVisual3D /> </Viewport3D>

If you run the app, nothing will show up. That's because you haven't given the two child GeometryModel3D objects a Material yet. Add the following methods to the custom CompositeGeometryModelVisual3D class, and call ShowBigModel in the constructor:

public void ShowBigModel() { DiffuseMaterial bigMaterial = new DiffuseMaterial(new SolidColorBrush(Colors.Red)); bigCubeModel.Material = bigMaterial; smallCubeModel.Material = null; }

public void ShowSmallModel() { DiffuseMaterial smallMaterial = new DiffuseMaterial(new SolidColorBrush(Colors.Blue)); smallCubeModel.Material = smallMaterial; bigCubeModel.Material = null; }

Now you should see something like this when you run the app (assuming you've set up the Viewport3D's camera and lighting appropriately:

Now you need to hook up some hit testing to have your custom visual show the small cube when clicked. Give your Viewport3D a name (e.g. mainViewport) and add the following MouseDown and MouseUp event handlers to your Window:

public Window1(){ InitializeComponent(); this.mainViewport.MouseDown += new MouseButtonEventHandler(mainViewport_MouseDown); this.mainViewport.MouseUp += new MouseButtonEventHandler(mainViewport_MouseUp); }

void mainViewport_MouseDown(object sender, MouseButtonEventArgs e) { } void mainViewport_MouseUp(object sender, MouseButtonEventArgs e) { }

Add the following method that will perform a hit test given a Point:

ModelVisual3D GetHitTestResult(Point location) { HitTestResult result = VisualTreeHelper.HitTest(mainViewport, location); if(result != null && result.VisualHit is ModelVisual3D) { ModelVisual3D visual = (ModelVisual3D)result.VisualHit; return visual; }

return null; }

Fill in the implementation of your two mouse event handlers to use the GetHitTestResult method, inspect the result, and then act on the CompositeGeometryModelVisual3D object if it was found:

void mainViewport_MouseUp(object sender, MouseButtonEventArgs e) { Point location = e.GetPosition(mainViewport);

ModelVisual3D result = GetHitTestResult(location); if(result == null) { return; }

if(result is CompositeGeometryModelVisual3D) { ((CompositeGeometryModelVisual3D)result).ShowBigModel(); return; } }

void mainViewport_MouseDown(object sender, MouseButtonEventArgs e) { Point location = e.GetPosition(mainViewport); ModelVisual3D result = GetHitTestResult(location); if(result == null) { return; }

if (result is CompositeGeometryModelVisual3D) { ((CompositeGeometryModelVisual3D)result).ShowSmallModel(); return; } }

Now when you run the code, you should be able to click on the large cube and the small one will be shown. Let go of the mouse (while still hovering over the cube) to show the large one again.

That's design approach #1.

Approach #2, using ModelVisual3D children


For design approach #2, which uses child ModelVisual3D objects rather than child GeometryModel3D objects, your custom class looks a little different. The hit testing works differently too. Create a new class that inherits from ModelVisual3D, but instead uses ModelVisual3D objects:

public class CompositeVisualModelVisual3D : ModelVisual3D { ModelVisual3D bigCubeVisual; ModelVisual3D smallCubeVisual;

public CompositeVisualModelVisual3D() { GeometryModel3D bigCubeModel = GeometryGenerator.CreateCubeModel(); GeometryModel3D smallCubeModel = GeometryGenerator.CreateCubeModel(); smallCubeModel.Transform = new ScaleTransform3D(.3, .3, .3);

bigCubeVisual = new ModelVisual3D(); bigCubeVisual.Content = bigCubeModel; smallCubeVisual = new ModelVisual3D(); smallCubeVisual.Content = smallCubeModel; this.Children.Add(bigCubeVisual); this.Children.Add(smallCubeVisual); ShowBigVisual(); }

public void ShowBigVisual() { DiffuseMaterial bigMaterial = new DiffuseMaterial(new SolidColorBrush(Colors.Green)); GeometryModel3D bigModel = bigCubeVisual.Content as GeometryModel3D; bigModel.Material = bigMaterial; GeometryModel3D smallModel = smallCubeVisual.Content as GeometryModel3D; smallModel.Material = null; }

public void ShowSmallVisual() { DiffuseMaterial smallMaterial = new DiffuseMaterial(new SolidColorBrush(Colors.Goldenrod)); GeometryModel3D bigModel = bigCubeVisual.Content as GeometryModel3D; bigModel.Material = null; GeometryModel3D smallModel = smallCubeVisual.Content as GeometryModel3D; smallModel.Material = smallMaterial; } }

Note what is different this time around. First, you're adding the child ModelVisual3D objects directly to the custom container's Children collection. Second, in order to assign the Material values, you must obtain the internal Content property of each child ModelVisual3D. Then you can cast the Content property (to GeometryModel3D in this case) and assign the Material then. The challenge with hit testing this approach is that your custom CompositeVisualModelVisual3D will not be hit directly by the mouse pointer. Instead, the child ModelVisual3D objects are hit. However, in order to call the ShowBigVisual() and ShowSmallVisual() methods, you want the container class to be produced out of the hit test rather than the child ModelVisual3Ds. How do you do this? Use the VisualTreeHelper class's GetParent() method to find the parent containing object from a visual hit. Modify the two mouse event handler methods in your Window to look like this:

void mainViewport_MouseUp(object sender, MouseButtonEventArgs e) { Point location = e.GetPosition(mainViewport); ModelVisual3D result = GetHitTestResult(location); if(result == null) { return; }

if(result is CompositeGeometryModelVisual3D) { ((CompositeGeometryModelVisual3D)result).ShowBigModel(); return; }

//need to find the visual's parent DependencyObject parent = VisualTreeHelper.GetParent(result); if(parent is CompositeVisualModelVisual3D) { ((CompositeVisualModelVisual3D)parent).ShowBigVisual(); } }

void mainViewport_MouseDown(object sender, MouseButtonEventArgs e)

{ Point location = e.GetPosition(mainViewport); ModelVisual3D result = GetHitTestResult(location); if(result == null) { return; }

if (result is CompositeGeometryModelVisual3D) { ((CompositeGeometryModelVisual3D)result).ShowSmallModel(); return; }

//need to find the visual's parent DependencyObject parent = VisualTreeHelper.GetParent(result); if(parent is CompositeVisualModelVisual3D) { ((CompositeVisualModelVisual3D)parent).ShowSmallVisual(); }

Notice that if the hit result is not of the type CompositeGeometryModelVisual3D (from Approach #1), then you use the VisualTreeHelper to get the parent of the hit result's parent. If the parent container ends up being of the type CompositeVisualModelVisual3D, then you know you've hit a ModelVisual3D contained within the custom container built in Approach #2. You can then call the ShowSmallVisual() and ShowBigVisual() methods on the parent. You can put both of the custom container visuals in your XAML and you'll see tha they both behave the same way even though their implementations are very different:

Building on Approach #2
The main benefit of Approach #2 is that all of the child ModelVisual3D objects in your custom container could be displayed all at the same and hit tested separately. You'll know which child object was clicked. With Approach #1, clicking on any of the "children" will result in a hit test where you don't know which child object was clicked - because the children are technically not visual objects (they are GeometryModel3D objects). So, let's see this benefit in action. Create a new class called Color changer that inherits from ModelVisual3D. This will be another container class that uses Approach #2, but it will display two visuals at the same and will allow interaction between the two:

public class ColorChanger : ModelVisual3D { ModelVisual3D bigCubeVisual; ModelVisual3D smallCubeVisual;

public ColorChanger()

{ GeometryModel3D bigCubeModel = GeometryGenerator.CreateCubeModel(); GeometryModel3D smallCubeModel = GeometryGenerator.CreateCubeModel();

bigCubeModel.Material = new DiffuseMaterial(new SolidColorBrush(Colors.Pink)); smallCubeModel.Material = new DiffuseMaterial(new SolidColorBrush(Colors.Orange));

Transform3DGroup transformGroup = new Transform3DGroup(); transformGroup.Children.Add(new ScaleTransform3D(.3, .3, .3)); transformGroup.Children.Add(new TranslateTransform3D(2.5,0,0)); smallCubeModel.Transform = transformGroup;

bigCubeVisual = new ModelVisual3D(); bigCubeVisual.Content = bigCubeModel;

smallCubeVisual = new ModelVisual3D(); smallCubeVisual.Content = smallCubeModel;

this.Children.Add(bigCubeVisual); this.Children.Add(smallCubeVisual); }

public void SelectChild(ModelVisual3D child){

if(child == bigCubeVisual) { GeometryModel3D model = smallCubeVisual.Content ass GeometryModel3D;

DiffuseMaterial material = model.Material as DiffuseMaterial; SolidColorBrush brush = material.Brush as SolidColorBrush; if(brush.Color == Colors.Orange) { model.Material = new DiffuseMaterial(new SolidColorBrush(Colors.Olive)); } else if(brush.Color == Colors.Olive) { model.Material = new DiffuseMaterial(new SolidColorBrush(Colors.Orange)); } }

if(child == smallCubeVisual) { GeometryModel3D model = bigCubeVisual.Content as GeometryModel3D; DiffuseMaterial material = model.Material as DiffuseMaterial; SolidColorBrush brush = material.Brush as SolidColorBrush; if(brush.Color == Colors.Pink) { model.Material = new DiffuseMaterial(new SolidColorBrush(Colors.Cyan)); } else if(brush.Color == Colors.Cyan) { model.Material = new DiffuseMaterial(new SolidColorBrush(Colors.Pink)); } }

} }

Note that both visuals are being displayed with a material. The SelectChild method allows one of the two objects to be "selected" and will cause the other object to change color. Add the ColorChanger to the XAML inside the viewport:

<local:ColorChanger> <local:ColorChanger.Transform> <TranslateTransform3D OffsetX="-.5" OffsetZ="-4" OffsetY="1"/> </local:ColorChanger.Transform> </local:ColorChanger>

Modify the mouse event handlers in your window so that they know what to do with the ColorChanger container:

DependencyObject parent = VisualTreeHelper.GetParent(result); if(parent is CompositeVisualModelVisual3D) { ((CompositeVisualModelVisual3D)parent).ShowSmallVisual(); } else if(parent is ColorChanger) //NEW CODE STARTS HERE { ((ColorChanger)parent).SelectChild(result); }

Now the Viewport is getting a little full, but when you run the app you'll see the new ColorChanger displayed along with the first two containers you created. Click on one of the ColorChanger's children, and you'll see the other child change its color:

Summary
In general, Approach #2 is more flexible and gives you more power in being able to create a custom "visual" element. With Approach #1, a hit test will return your container object only and will not return children. With Approach #2, the specific child that was clicked inside of your container is returned, and your container can deal with it appropriately. The down side is that you need something that knows how to inspect the visual tree and determine what to do with the parent object found, but it's a valuable trade-off if you need a complex container/child model.

WPF Navigation
Oct 2 2009

In 2001, Microsoft published a paper called Inductive User Interface Guidelines. If you have not read this paper, I would suggest doing so. It is largely technology agnostic, and I know that many of my colleagues at Readify consider it to be a classic. In this article I want to look at building inductive user interfaces with WPF. PS: If you are interested in building WPF Navigation applications, check out Magellan.

Navigation Applications
Navigation applications are applications that are composed of many "pages", and often feel similar to web applications, although not always. A navigation application usually provides some kind of shell in which the pages are hosted, with custom chrome or widgets around it. The pages of a navigation application can usually be broken down into two categories: 1. Standalone, free pages. 2. Pages that make up a step in a process. For example, an online banking application may have a number of standalone pages, such as:

The Welcome page The Account Balance page The Help page

These pages are often read-only, or can be navigated to at any time, and don't really form part of a single "task" that the user performs. Then there are many tasks the user can perform:

Applying for a credit card Paying a bill Requesting to increase their transaction limits

Note that these tasks may comprise of subtasks, which can make our navigation model rather complex. Let's dissect each of these sections and discuss how WPF handles them.

The Shell
The Shell is usually the main window of your application, and it's where your pages are hosted. In WPF, you have two options: 1. If you are building a XAML Browser Application, or XBAP, the shell will normally be the web browser that is hosting your application. It is possible to create your own shell within the browser window, though it may confuse users. 2. If you are building a standalone application, you will need to build your own shell. Building your own shell is as simple as creating a standard WPF Window in a normal WPF application, and using a WPF Frame element. The Frame's job is to declare a section of the window that your pages will be hosted in, and to provide all of the services around navigation. Here is an example:

<Window x:Class="NavigationSample.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300"

> <DockPanel> <Frame x:Name="_mainFrame" /> </DockPanel> </Window>


From code, you can tell the frame to navigate, like so:

_mainFrame.Navigate(new Page1());
Which just so happens to be a helpful shortcut to:

_mainFrame.NavigationService.Navigate(new Page1());

NavigationService
Navigation comprises many functions: going back, going forward, going to a new page, refreshing a page, and so on. Objects in the WPF Navigation ecosystem, like the Frame class, as well as the Page class which I'll describe shortly, can access this functionality by aNavigationService property, which is, surprisingly of type NavigationService. For example:

_mainFrame.NavigationService.GoBack(); _mainFrame.NavigationService.GoForward(); _mainFrame.NavigationService.Refresh();


This same object is made available to every page hosted within the frame, so pages can signal that they wish to navigate backwards or forwards. When navigating, the Navigate method will accept just about anything you want:

_mainFrame.NavigationService.Navigate(new Uri("http://www.google.com/")); _mainFrame.NavigationService.Navigate(new Uri("Page1.xaml", UriKind.Relative)); _mainFrame.NavigationService.Navigate(new Page1()); _mainFrame.NavigationService.Navigate(new Button()); _mainFrame.NavigationService.Navigate("Hello world");
In the first case, it will load a full web browser control with Google within the frame. In the second case, it will resolve a WPF page by looking up the XAML Uri in the application's resources and display the page. In the third case, it will show the same XAML page from a direct object reference. In the fourth, it will display a Button object direct to the screen, without a page around it. And in the last case, it will show a string enclosed within a TextBlock. Frames can show just about anything.

Frames make their content public through the Content dependency property, which makes it easy to bind to. When navigating to a page, for instance, the Content property will give you a Page object. Showing the title of the current page in your shell becomes very trivial: <Window x:Class="NavigationSample.Window1"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" <strong>Title="{Binding Path=Content.Title, ElementName=_mainFrame}"</strong> Height="300" Width="300" > <DockPanel> <Frame x:Name="_mainFrame" /> </DockPanel> </Window>
The NavigationService also provides a number of events you can subscribe to, if you want to control the navigation process:

Navigating, when the frame is about to navigate. Set Cancel to true to stop. Navigated, when navigation has finished but before it is rendered NavigationFailed, when something goes wrong NavigationProgress, when chunks of a remote navigation call are being downloaded. Nice for progress bars. NavigationStopped, when the StopLoading method is called (like clicking "Stop" in IE) or a new Navigate request is made during downloading LoadCompleted, when the page has been rendered

Customizing the Chrome


The Frame control ships with a standard UI that provides back and forward buttons once you have navigated to a second page. Out of the box, it looks very similar to IE 7.0, although it doesn't take into account your current Windows theme:

Fortunately, just like every other WPF control, how the frame looks is completely up to you. Just apply a ControlTemplate and use aContentPresenter to show the content of the page:

<ControlTemplate TargetType="Frame"> <DockPanel Margin="7"> <StackPanel Margin="7" Orientation="Horizontal" DockPanel.Dock="Top" > <Button Content="Avast! Go back!" Command="{x:Static NavigationCommands.BrowseBack}"

IsEnabled="{TemplateBinding CanGoBack}" /> <Button Content="Forward you dogs!" Command="{x:Static NavigationCommands.BrowseForward}" IsEnabled="{TemplateBinding CanGoForward}" /> </StackPanel> <Border BorderBrush="Green" Margin="7" BorderThickness="7" Padding="7" CornerRadius="7" Background="White" > <ContentPresenter /> </Border> </DockPanel> </ControlTemplate>

In that example, TemplateBindings are used because the Frame (being templated) exposes CanGoBack and CanGoForward properties. The NavigationCommands are a set of static routed UI commands that the frame will automatically intercept - there's no need for C# event handlers to call NavigationService.GoBack(). The NavigationService and Frame object expose many other properties and events; these are just the tip of the iceberg, and the ones you'll customize most often when building inductive UI's with WPF. Note: WPF includes a class called NavigationWindow, which is essentially a Window which also doubles as a Frame, by implementing most of the same interfaces. It sounds useful at first, but most of the time you need more control over theWindow, so I've never had any need to use this class. I am just pointing it out for the sake of completeness, though your mileage may vary.

Pages
Pages are the cornerstone of navigation applications, and are the primary surface users interact with. Similar to Windows orUserControls, a Page is a XAML markup file with code behind. If you are working with XBAP's, the Page is the root of the application. Just like a Window or UserControl, a page can only have one child, which you'll need to set to a layout panel to do anything fancy. You can do whatever you like in a WPF Page - 3D, animation, rotation; it's just like any other control.

Using the Navigation Service from a Page


Each Page has a reference to the NavigationService object provided by its container usually a Frame. Note that the Page and its containing Frame will share the same reference to the NavigationService, so the Page won't be able to reference it during it's constructor. The Page can make use of the NavigationService's Navigating event to stop users navigating away from the page, by setting theCancel property on the event arguments. This is useful if the user has unsaved changes on the page, and you want to give them a chance to save them. Don't worry about unsubscribing from the event either - WPF will detect when you navigate away from the page, and automatically removes all event handlers, to avoid any memory leaks (since the NavigationService object lives a lot longer than any particular page would). Since the page is hosted within the Frame in the Visual Tree, you can also make use of some of the NavigationCommands routed commands, since the parent Frame will automatically handle them:

<Page x:Class="NavigationSample.Page2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Page2" > <Grid> <Button Command="{x:Static NavigationCommands.BrowseBack}">Cancel</Button> </Grid> </Page>
In addition, you can of course use the NavigationService to perform any other kind of navigation, such as navigating to a new page or going backwards.

Hyperlinks
One additional feature you can use when building pages is the Hyperlink control, and the automatic navigation it provides. Hyperlinkis an Inline, so it has to be used within a control like

a TextBlock or FlowDocument paragraph. You can use it in other WPF applications, but automatic navigation only works within navigation applications. Here is an example:

<Page x:Class="NavigationSample.Page1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Page1" > <Grid> <TextBlock> <Hyperlink NavigateUri="Page2.xaml">Go to page 2</Hyperlink> </TextBlock> </Grid> </Page>

Passing data between pages


There are two ways you can pass information between pages using WPF. First, I'll show you the way *not *to do it, then I'll show the preferred way to do it :) Although it's not obvious, you can pass query string data to a page, and extract it from the path. For example, your hyperlink could pass a value in the URI:

<TextBlock> <Hyperlink NavigateUri="Page2.xaml?Message=Hello">Go to page 2</Hyperlink> </TextBlock>


When the page is loaded, it can extract the parameters via NavigationService.CurrentSource , which returns a Uri object. It can then examine the Uri to pull apart the values. However, I strongly recommend against this approach except in the most dire of circumstances. A much better approach involves using the overload for NavigationService.Navigate that takes an object for the parameter. You can initialize the object yourself, for example:

Customer selectedCustomer = (Customer)listBox.SelectedItem; this.NavigationService.Navigate(new CustomerDetailsPage(selectedCustomer));


This assumes the page constructor receives a Customer object as a parameter. This allows you to pass much richer information between pages, and without having to parse strings.

Page Lifecycles
Many people have been confused about how long pages live for and how Back/Forward navigation works. The simplest way to demonstrate this is to create a couple of simple

pages, Page1 and Page2, which contain hyperlinks to each other. I am going to write the same tracing code in each page:

public Page1() { InitializeComponent(); Trace.WriteLine("Page 1 constructed"); } private void Button_Click(object sender, RoutedEventArgs e) { Trace.WriteLine("Page 1 navigating to page 2"); this.NavigationService.Navigate(new Uri("Page2.xaml", UriKind.Relative)); } private void Page_Loaded(object sender, RoutedEventArgs e) { Trace.WriteLine("Page 1 loaded"); } private void Page_Unloaded(object sender, RoutedEventArgs e) { Trace.WriteLine("Page 1 unloaded"); } ~Page1() { Trace.WriteLine("Page 1 destroyed"); }
I've also handled the Back and Forward buttons myself, so I can trace when they are clicked, and I've added a button to force the garbage collector to run. Here's what happens:

Page 1 navigating to page 2 Page 2 constructed Page 1 unloaded Page 2 loaded Garbage collector runs Page 1 destroyed Clicked Back Page 1 constructed Page 2 unloaded Page 1 loaded Garbage collector runs Page 2 destroyed
So, the navigation system works as you expect - the page is unloaded, and available for the garbage collector. However, it does become more complicated. Suppose your page required some kind of parameter data to be passed to it:

public Page1(DateTime date) {

InitializeComponent(); Trace.WriteLine("Page 1 constructed " + date.ToString()); } private void Button_Click(object sender, RoutedEventArgs e) { Trace.WriteLine("Page 1 navigating to page 2"); this.NavigationService.Navigate(new Page2(DateTime.Now)); }
When navigating, if you click "Back", WPF can't possibly know what values to pass to the constructor; therefore it must keep the page alive. Here's the trace output:

Page 1 navigating to page 2 Page 2 constructed 5/06/2008 12:23:19 PM Page 1 unloaded Page 2 loaded Garbage collector runs Clicked Back Page 2 unloaded Page 1 loaded
Notice that although the pages are loaded and unloaded many times, it is the same page instance. This means that:

If you navigate using a URI, WPF will create the page by invoking the constructor each time. The navigation history keeps the URI, not the object. If you navigate passing an object directly, WPF will keep the object alive.

This gives you a few things to consider:


Over time, that can amount to some serious memory usage. The Loaded and Unloaded events are called each time the page is shown or disappears, so use them as chances to clear all the data you can in order to minimize memory. The URI navigation mode explained above *can *be useful for these reasons, but don't abuse it :)

Lastly, each page provides a KeepAlive boolean property, which is false by default. However, it only applies when using URI navigation, which explains why many people have tried changing it only to see no difference. Back/forward simply couldn't work if objects were not kept alive, since the navigation service has no way to construct them. An option I'd like to see is being able to pass a lambda or delegate which is used to reload the page.

PageFunctions
WPF Pages serve to allow us to build standalone pages within our application - pages like Welcome, Help, or View Account Balances. However, when it comes to completing tasks, they fall short in a number of ways. Consider the scenario for paying a bill:

Once the task is complete, you need to return to the Home page. Should the Confirm page be written to Navigate directly to the Homepage when a button is clicked? What if the Pay Bill task was triggered from another page? And if the user clicks "Back" upon arriving back at the Home page, should they get to cancel the payment after they already confirmed it? And what if we don't want to allow them to go back? Now consider a more sophisticated example. Maybe the biller they want to pay doesn't appear in their list of existing billers. You might have another task to define a biller - something like this:

This causes us to think carefully about our navigation model:


Will the Verify and Confirm page be hard-coded to transition to Transaction details? When they get to the Transaction Details page, what will clicking "Back" in the shell do? o If it went to Verify and Confirm, we'd be in trouble, since the biller has already been saved

What we are really doing here is interrupting one task to start another - like taking a detour - and then returning to our previous task. After we return, we can then complete the original task. Once a task returns, its navigation journal should be cleared, and it should be as if the detour never happened. This creates a couple of rules:

Within a task, you can click back or forward as much as you like, until it returns. When a task is completed, the browser history "stack" is unwound to where it was when the task began

It's helpful to picture this as code:

PaidBill PayBill() { var biller = SelectBiller(); EnterTransactionDetails(biller); return Confirm(); } Biller SelectBiller() { if (BillerAlreadyExists) return SelectExisting(); else return CreateNew(); }
Where each function represents a task that can branch out, and then return to the original task.

Introducing PageFunctions
Now you can begin to sense where Page Functions get their name. WPF Page Functions are a special type of Page that inherit from aPageFunction<T> class. They are standard Page objects, but with one difference: they provide a Return event which makes it possible to unwind the navigation journal.

Returning
Just like our pseudo code above, and like any other kind of function, when a PageFunction returns it needs to declare a type of object that is being returned, so that the page which triggered the PageFunction can get data back from it. Here's what a the code behind might look like:

public partial class VerifyAndConfirmNewBillerPage : PageFunction<Biller> { public VerifyAndConfirmNewBillerPage(Biller newBiller) { InitializeComponent(); this.DataContext = newBiller; } private void ConfirmButton_Click(object sender, RoutedEventArgs e) { this.OnReturn(new ReturnEventArgs<Biller>((Biller)this.DataContext)); } }
Notice that the class inherits from a generic type. When the user clicks "confirm" on this page, it will raise a Return event to whatever page instantiated it, and returns the Biller that was created.

The page that came before this one would need to subscribe to the event in order to expect it. Here is how that would look:

public partial class DefineNewBillerPage : PageFunction<Biller> { public DefineNewBillerPage() { InitializeComponent(); this.DataContext = new Biller(); } private void SubmitButton_Click(object sender, RoutedEventArgs e) { var nextPage = new VerifyAndConfirmNewBillerPage((Biller)this.DataContext); nextPage.Return += new ReturnEventHandler<Biller>(BillerVerified); this.NavigationService.Navigate(nextPage); } private void BillerVerified(object sender, ReturnEventArgs<Biller> e) { this.OnReturn(e); } }
When the user enters information about their new biller and click "Submit", we navigate to the Verify and Confirm page, and we subscribe to its Return event. On that page, they'll hit Confirm, which will Return control back to our page. Our page will then also return - this is similar to when you make a function call at the last line of an existing function, in that the innermost function's stack is cleared, it returns to the calling function, and then that function returns. Back at the Select Existing Biller page, here's how that code behind would appear:

public partial class SelectExistingBillerPage : PageFunction<PaidBill> { public SelectExisingBillerPage() { InitializeComponent(); this.DataContext = GetAllExisingBillers(); } private void AddNewButton_Click(object sender, RoutedEventArgs e) { var nextPage = new DefineNewBillerPage(); nextPage.Return += new ReturnEventHandler<Biller>(NewBillerAdded); this.NavigationService.Navigate(nextPage); } private void NewBillerAdded(object sender, ReturnEventArgs<Biller> e) { // A biller has been added - we can pluck it from the event args

var nextPage = new EnterTransactionDetailsPage(e.Result); this.NavigationService.Navigate(nextPage); } private void SelectExisingButton_Click(object sender, RoutedEventArgs e) { var nextPage = new EnterTransactionDetailsPage((Biller)listBox1.SelectedItem); this.NavigationService.Navigate(nextPage); } }
Note how in the return event handler our page navigates to the Transaction Details page? Thus, our navigation is actually:

This model allows pages to complete a task, return to whoever called them, and have the previous task continue without any additional knowledge. To users, it would appear as a detour, where in reality the pages operate just like function calls. Since one page returns to the caller, it might be worrying that the previous page will quickly flash up before navigating. Fortunately, this is not the case, as WPF waits for the Return event handler to execute before loading or rendering the previous page. If the Return event handler from the calling page asks to navigate somewhere else, WPF would perform the navigation without showing the page.

Markup
PageFunctions do inherit from the Page base class, and apart from the concept of returning, they operate just like any other Page. Since the base class is generic, the XAML accompanying the PageFunction does look slight different: <PageFunction xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:<strong>domain</strong>="clr-namespace:DomainModel" x:Class="NavigationSample.DefineNewBillerPage" <strong>x:TypeArguments="domain:Biller"</strong> Title="Define a new Biller" >

<Grid> <... /> </Grid> </PageFunction>

PageFunction Lifecycles
When using PageFunctions, you need a reference to the object in order to subscribe to the event and for the handler to be invoked, and so navigation with PageFunctions always involves the PageFunction object being left alive in memory; similar to normal pages. Thus,KeepAlive generally has no effect on PageFunctions. Fortunately, once a PageFunction has returned, it is removed from the navigation journal and can be garbage collected. This means that although the pages that make up a task will consume memory, once the task is finished, the pages are destroyed. In this way, a largely task-focused UI can use the rich navigation model without degrading performance.

Summary
Navigation applications are made up of many pages, which can be categorized as either standalone pages, or pages that contribute towards a step in a user's task. Standalone pages are usually represented as Page objects, and steps within a task are represented asPageFunctions. Pages of any kind can be hosted with a WPF Window using the Frame control, or within an XBAP application hosted by the browser. The NavigationService object shared between a Page and it's host provides many hooks into the navigation system. Page lifecycles can complicate things, but are logical when you think about them. Pay special attention to the lifetime of your pages and find ways to reduce their memory footprint when not in view (remove all data during the Unloaded event, and restore it during the Loaded event, for example). As always, use memory profilers to detect major leaks. Navigation applications make it easy to build user interfaces that are task-focused and geared for ease-of-use over productivity. This style of UI is very difficult to achieve using Windows Forms or other UI systems. Even on the web, which shares similar navigation styles, concepts such as journal unwinding, returning to calling pages and remembering state between pages can be notoriously difficult to implement. Entire frameworks in like Struts in Java exist to enable just these scenarios.

From Zero to Proficient with MEF


By Tim Corey, 21 May 2012 4.99 (73 votes)

Download Example Application Source Code - 31.7 KB

Introduction
Why is it that when we look for a new laptop, we look at the types of ports it has? Not only do we put up with these holes in the sides of our new laptops, we complain if there arent enough of them. The answer, of course, is that these ports allow us to extend our laptop. We can add a second monitor, an external hard drive, or a number of other devices. This doesnt mean that the original laptop is inferior; it just means that different use cases lend themselves to different configurations. So why is it that we insist on making applications without ports?

In .NET 4.0, Microsoft provided us with the Managed Extensibility Framework (MEF). This framework allows us to easily create extensibility points (ports) in our applications. The most obvious use for this is for plug-ins. You could allow a customer to create their own menu items just like they can do in Microsoft Word or Visual Studio. However, there are other uses for MEF as well. For example, if you expect business rules might be changed or expanded in the future (that never happens, right?), you could use MEF to make this process simple. Ill show you how below.

Why Should I Care About MEF


MEF has a lot of similarities to an IoC, but there are some differences. MEF concentrates on bringing parts into your application, even if the implementations are not known or local, whereas IoC is more about loosely coupling known parts. That still leaves the question of when to use MEF. The simplest explanation would be to say that it is for when you want to allow plugins for your application. However, I think there are a lot more reasons to use MEF. As I see it, the major reasons to use MEF are:

To enable users to develop their own plugins for your application. To allow others to extend or modify how your application works without getting access or changing the source. Easy testability (just change where the DLLs are coming from and you could use your app against an entire set of mocks). To loosely couple library projects to your application, especially when they are used by multiple projects. This last reason is a fairly broad one. Let me give you an example. Say you create a standard way to access your database in a safe manner. Maybe you have some custom data access business logic. In the end, you have an Interface that exposes two methods ReadData and WriteData. You want use this DLL in all of your applications that you build internally. You could put the DLL in the GAC of every server or you could attach it to each project. Either solution has its merits but now you have a third option. You could store it centrally and load it via MEF. Then you could easily change it whenever you needed to without a big deployment issue (as long as you didnt change the interface, which you should never do). The bottom line is that there are a lot of reasons to use MEF. As you get comfortable with MEF, you will start to see more and more places in your applications that could be made even better. This is one tool you really want in your toolbox.

Target Audience
When I first started learning how to use MEF, there were a number of resources that helped me. I found practical articles that showed me how to use the framework in a real application and I found technical articles that explained certain features in great length. What I never seemed to find was that one article that was both practical and in-depth.I ended up going from source to source, picking bits and pieces from each. This article is intended to be that in-depth look at what MEF is in a practical manner. I will attempt to walk you throughMEF from start to finish. Along the way I will build example applications so you can see exactly how each feature works.For those of you who are familiar with MEF, this article is broken up by feature so that you can quickly get to just the area you need help with.

Example Application
I have developed an example application that goes over each of the topics below. I have tried to keep the examples simple and easy to follow, yet practical. In the attached code section, you will find a solution that contains eight projects. At the beginning of every section, I will tell you which project to find the examples for that section. Each project also has text at the beginning that tells you what sections it covers. Finally, the parts of the code that deal with a particular section are also labeled with the section name. For the project Example05 (dealing with external libraries), I created two additional projects (Common and ExternalLibrary).

The Theory
MEF is used to design ports in your application. It can create both single-use ports (think VGA port) as well as multi-use ports (think USB ports). It also specifies how to create the plugs that go into those ports. It sounds complicated but it is really not much more than properly using interfaces and a little markup. I could go into a long-winded explanation of how it is designed and what it can do, but that can be hard to follow. Instead, lets dive right into how to use MEF and then we can come back to the theory.

The Basics
There are three basic parts to MEF in your application. If we continue on with our laptop analogy, we can visualize these three parts as the USB port on the laptop, an external hard drive with a USB connector, and the hand that plugs the USB connector into the port. In MEF terms, the port is defined as an [Import] statement. This statement is placed above a property to tell the system that something gets plugged in here. The USB cable is defined as an [Export]statement. This statement is placed above a class, method, or property to indicate that this is an item to be plugged in somewhere. An application could (and probably will) have a lot of these exports and imports. The job of the third piece is to figure out which ports we have and thus which cables we need to plug into them. This is the job of theCompositionContainer. It acts like the hand, plugging in the cables that match up to the appropriate ports. Those cables that dont match up with a port are ignored.

Simple Example
Example Project Name: Example01

Lets look at a simple example. If you have done any research into MEF, you have probably seen this type of example before but we need to crawl before we can walk. This application will have a string that imports a value at runtime.We will export a message to be put into the string and then we will display the string to show it works. To follow along with this example, simply start a new C# project of type Console Application in Visual Studio. When the project is created, you will have a Program.cs file. Inside that file, you should see the following code:
Collapse | Copy Code

static void Main(string[] args) { }

Since this is a static application, we need to create an instance of it instead so that we can play with MEF. Create a new void method called Run and then add code inside the Main method to instantiate the program and call the Run method. Your code should now look like so:
Collapse | Copy Code

static void Main(string[] args) { Program p = new Program(); p.Run(); } void Run() { }

So now we have the plumbing set up. There is nothing MEF-specific in these lines, so I wanted to be sure that this code gets separated from our MEF code for the sake of clarity. Now lets get to the steps needed to get a basic MEFimplementation working. Step 1: You will need to add a reference to System.ComponentModel.Composition in your application. This is where MEF lives. This is dependent on .Net 4.0 framework. In theory you can download it from Codeplex and get it to (mostly) run on .Net 3.5 but that isnt supported or recommended. Step 2: Add a using statement in your Program.cs file that references System.ComponentModel.Composition . This will allow us to use the Import and Export statements directly. It will also allow us to call the ComposeParts method on our container later (for now just remember this information it will make sense later on). Step 3: Add a class-level variable of type string called message. On top of this variable, add a [Import] statement.Your code should look like this:
Collapse | Copy Code

[Import] string message;

This is our port. We are asking our hand to find us a plug that fits into a string variable. Step 4: Add a new class outside the Program class (it can be in a different cs file or in the same one I put ours in the same file just for simplicity). Call it MessageBox. Inside the class, add a string property called MyMessage that returns an example message when the get is called. On top of the property, add [Export]. Your class should look like this:
Collapse | Copy Code

public class MessageBox { [Export()] public string MyMessage { get { return "This is my example message."; }

} }

The MyMessage property is our plug that will be put into the string port we defined above. We have defined the port and the plug, but we arent done yet. If we stopped now, the application would run but nothing would happen.The reason is that we have not yet defined the method that will put the plugs into the ports. That comes next. Step 5: This is the step where people new to MEF can get lost because there are actually a couple steps in one. Im going to walk you through each part of this step so you know exactly what it going on. In the end, we will put all of the code from this step into one helper method called Compose. This will make it easy for our application to hook everything up in a clean manner. To make this easier, we should also add a using statement forSystem.ComponentModel.Composition.Hosting . This will make the code to follow more concise. First, we need to create a catalog of all of the exports and imports. This will simply be an inventory of what is being exported and what needs to be imported. The catalog will need to know where to look. In our case, we will point to the executing assembly. This tells the catalog to inventory our current application. This may seem redundant but dont forget that MEF is designed to load external resources as plug-ins. In a more complex application, you would need to create multiple catalogs. For now, though, we will just create the one catalog like so:
Collapse | Copy Code

AssemblyCatalog catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());

Next, we need to put this catalog into a CompositionContainer. This will give us the methods we need to compose (hook up) our exports and imports from our catalog. The code looks like this:
Collapse | Copy Code

CompositionContainer container = new CompositionContainer(catalog);

Finally, we need to actually hook everything up. This can be accomplished in this case by telling our new container to either compose the parts (ComposeParts) or satisfy the imports once (SatisfyImportsOnce). In either case, we need to pass in the instance that needs to have the imports satisfied on (where the ports are that need to be plugged in). We will do that by passing in the this keyword, which tells the method to look at our current instance of Program for the imports statements. Because we are using such a simple example, either of the composition methods listed would work. The difference is that the SatisfyImportsOnce statement will only hook up the parts once. That means that any changes to our parts catalog (either adding parts or removing them) wont be put into our application automatically. We will get into this more advanced topic later. For now, we are going to use the simplest implementation like so:
Collapse | Copy Code

container.SatisfyImportsOnce(this);

So now we have our helper method. If you have been following along, you should have a method that looks like this:
Collapse | Copy Code

private void Compose() { AssemblyCatalog catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly()); CompositionContainer container = new CompositionContainer(catalog); container.SatisfyImportsOnce(this);

Step 6: The final thing we need to do to get all of this to work is to get up our Run method to call the Composemethod and then display the value of our message variable. Because this is a console application, I will also put aConsole.ReadLine at the end of the method so that the application doesnt close until we hit a key. Here is the completed Run method:
Collapse | Copy Code

void Run() { Compose(); Console.WriteLine(message); Console.ReadKey(); }

That is all it takes to create a simple MEF application. If you run this application right now, you will see the message This is my example message. on the screen. If you got something different than I did or if you want to just see the end results in a working file, see Example 01 in my attached code. To summarize, we created a new console application that had one port of type string called message. We designed a plug of type string that returned the message This is my example message. We then set up a container that took our catalog of parts and hooked them up (once). This allowed us to display the value of our message variable, which was populated with the value pulled from our plug.

Generated Questions
There are a lot of simple MEF demos that wrap things up at this point, but for me this demo generated a lot of questions so before we proceed, lets answer a few of these questions. 1. When I perform the SatisfyImportsOnce or ComposeParts method, am I creating an instance of every item I need to import right away? Yes.
2. Funny. Care to expound? In most cases, creating an instance of every import right away will be fine. However, in the event that you might not use all of the instances or you want to load them later on, you can use lazy load on your imports. We will get into that below. 3. If I export a class and then import it in two locations, do I get two instances of the class? By default, MEF treats each export as a Singleton well, sort of. If all the defaults are on, you get a singleton. See the Object Instancing section for a more detailed description of how the defaults work. 4. Can I create regular instances of classes that are marked for export? Yes, you can. Just note that any import statements that are inside the class will not be satisfied since the instance was created after MEF satisfied the imports. You could change this by passing in the instance reference instead of the this keyword in the linecontainer.SatisfyImportsOnce(this); 5. If Im using MEF, should I use it everywhere in my application? If you had a hammer, would you use that for all of your home repair needs? If so, you probably arent any better at home repair than I am. To directly answer your question, no, MEF is just one tool you can use in your application. It is designed to handle one set of requirements (plug-ins) very well. It can do other things (IoC/DI) but that isnt what it was designed to do. MEF, like a hammer, can do a lot but sometimes it is better to use a different tool. 6. What do I do if I want a bunch of one object? There are a couple ways to handle this, depending on what you want. We could do different interfaces (if you wanted the same object to be used differently in different parts of your application), we could import it into multiple variables (if we

wanted to use the same object in multiple locations in our code) or we could use an ImportMany (if we wanted multiple different objects with the same signature). We will see all of these solutions below.

Allowing null Imports


Example Project Name: Example02 Sometimes you may want to have an import that might or might not have a matching export. In the example of our laptop, we wouldn't want the laptop to violently blow up if we didn't have every USB port filled. Yet, if we don't change any of the defaults, that is exactly what our application will do if we don't fill all of our import statements. We can have as many nonmatching exports as we want, but the first import statement that doesn't have a matching export will throw an exception. If this isn't what you want, there is a way to change the default behavior. On an import statement, you can put named parameters at the end to modify how the import works. One of these parameters is calledAllowDefaults. If you set this to true, MEF will attempt to satisfy the import statement, but failing that, it will put a null value in the variable instead. The import would look like this:
Collapse | Copy Code

[Import(AllowDefault = true)] string message;

In your code, make sure you then test the variable to be sure it isn't null before you use it. Otherwise, you will have just shifted your application-killing exception to a little later in your application's life.

Object Instancing
Example Project Name: Example02 By default, as we said before, MEF creates each export as a Singleton. This is technically true and a lot of descriptions stop there. However, the truth is a bit more complicated. In MEF, there are three PartCreationPolicy values that can be specified. The three values are Shared, NonShared, and Any. By default, Any is what is used on all exports. If the Import statement does not specify the type of PartCreationPolicy it is looking for, the Any policy will translate to Shared. A Shared policy is a Singleton and, of course, Non-Shared indicates that a new instance will be created for the export each time it is imported. Confused yet? How about some code to make this a little easier to understand?Ill put an export and an import statement and then explain what would happen.

Example 1: First, we will look at the simplest example just to get our feet wet:
Collapse | Copy Code

[Export, PartCreationPolicy(CreationPolicy.Any)] public class MyClass {} [Import] MyClass _port1;

[Import] MyClass _port2;

In this example, the _port1 and _port2 variables will get the same Singleton instance of the MyClass object. The export statement is functionally equivalent to just [Export]. I just explicitly set the PartCreationPolicy to show you what was happening by default. Example 2: Next, lets look at how we could change the export statement to create an instance each time we import it:
Collapse | Copy Code

[Export, PartCreationPolicy(CreationPolicy.NonShared)] public class MyClass {} [Import] MyClass _port1; [Import] MyClass _port2;

This time, _port1 and _port2 will get separate instances of the MyClass object. The creation of the instances is done by reflection, which can be a bit costly. There are ways to reduce this cost, but they are outside the scope of this already large article. Example 3: Ive already hinted above that you can specify what type of PartCreationPolicy an import will accept.Lets see how to do that:
Collapse | Copy Code

[Export, PartCreationPolicy(CreationPolicy.Shared)] public class MyClass {} [Import(RequiredCreationPolicy = CreationPolicy.Shared)] MyClass _port1; [Import] MyClass _port2;

In this case, the _port1 variable is requiring that the export be Shared. Because our export is Shared, _port1 will get the Singleton instance of the MyClass object like normal. The _port2 variable will also get the sameSingleton instance. If we changed our export to be NonShared however, we would get an error on the _port1variable because no exports would match the required import. Example 4: This brings us to the question we asked originally about the default export being a Singleton but that the import also had to be default for that to be true. Lets look at an interesting example below:
Collapse | Copy Code

[Export, PartCreationPolicy(CreationPolicy.Any)] public class MyClass {} [Import(RequiredCreationPolicy = CreationPolicy.Shared)] MyClass _port1;

[Import(RequiredCreationPolicy = CreationPolicy.NonShared)] MyClass _port2;

I bet you are wondering what happens here. I specified the Any on the export again just so you could see it. We could have just said [Export]. When the _port1 variable asks for an object with a Shared part creation policy, it gets aSingleton instance of the MyClass object. However, when the _port2 variable asks for an object with a NonSharedpart creation policy, it gets a new instance of the MyClass variable. By specifying a PartCreationPolicy of Any or by not specifying any policy, the export becomes whatever the import wants it to be. If nothing is specified on the import, you will get a Singleton. Make sure you understand this. This is where you could run into trouble down the road if someone decides to specify the RequiredCreationPolicy of NonShared on an import when you were expecting your export to always be a Singleton, even though you only labeled it as [Export].

Specifying Export Types


Example Project Name: Example02

When you mark an object as an export, by default MEF looks at the type of object it is and matches it up to an import that needs that type. Thus when we export a string, it gets matched up to an import of type string. However, this becomes problematic in anything other than an example application, since you may want to export multiple objects of type string. If there are multiple exports that could satisfy an import, MEF will throw an error because it doesnt know which export to use (see Gotchas below). There is a way to put multiple export instances into one import by using ImportMany (see below for more information on this topic) but most likely we want to match up specific exports to specific imports. The way you do this is by describing this export to MEF. You can do that in a number of ways. The first way is to specify a contract name. This can be any name you choose. Here is an example:
Collapse | Copy Code

[Export(MyConfigInfo)] public class MyClass {} [Import(MyConfigInfo)] object _port1;

There are a couple things to note here. First, I used a magic string to identify my export. I needed to use the same magic string to identify the import. If I didnt use the MyConfigInfo on the import statement, MEF would have thrown an error saying it couldnt find the export I was looking for (see Gotchas). Second, I changed the type for_port1 to show that the types dont need to match anymore. Since we are explicitly saying how the import and export should know each other, the type of the variable doesnt matter. That doesnt mean you can put a string into an intvariable. That still throws an error (can I get a collective duh here?). I recommend against using a contract name to identify an export. The reason being that you are reliant on typing everything correctly and you cannot use Intellisense. However, that is just a general rule of thumb I follow. There are cases where it is the best solution. As always, do what is best for your environment.

We can also identify objects by type in our Export/Import. This way does make use of Intellisense and it can be a lot cleaner (in my opinion, but feel free to harass me mercilessly below should you

disagree). When you are exporting class objects, you can make use of Interfaces. Here is an example:
Collapse | Copy Code

[Export(typeof(IConfigInfo))] public class MyClass : IConfigInfo {} [Import(typeof(IConfigInfo))] object _port1;

That is pretty straightforward. Basically, if you are familiar with programming to an interface, you are already set. In your export statement, just specify the type you want to identify the export by and then do the same for the import.

If you really want to get specific, you can specify both a contract name and a contract type in your Export/Import.Basically it takes the two scenarios above and combines them. The end result would look something like this:
Collapse | Copy Code

[Export(MyInfo, typeof(IConfigInfo))] public class MyClass : IConfigInfo {} [Import(MyInfo, typeof(IConfigInfo))] object _port1;

One question that might occur to you is what will happen if you export both the contract name and contract type but only import based upon one of the criteria. The answer is that it wont work. If you are specific in the export, you have to have an exactly matching specificity in the import. The one exception to this is when you dont specify the type in either the export or the import. Since MEF actually implies the type, you can be implicit in one and explicit in the other and the connection will still work. OK, make sense so far? Great, because we are going to make it more complicated. Up until now we have talked about exporting objects like classes and value types. There is one area we havent touched on yet, and that is the area of methods. MEF allows us to export and import methods in the same way we do with entire classes. Just like with everything else, you can specify a contract name and/or contract type. One thing that is a little different here is that we can use a delegate like Action or Func to specify the contract type. Here is an example that uses both a contract name and contract type:
Collapse | Copy Code

[Import("ComplexRule", typeof(Func<string, string>))] public Func<string, string> MyRule{ get; set; } [Export("ComplexRule", typeof(Func<string, string>))] public string ARule(string ruleText) { return string.Format("You passed in {0}", ruleText); }

To call the rule, you would do something like this:


Collapse | Copy Code

Console.WriteLine(MyRule("Hello World"));

I made sure this example was a bit more advanced so you could see exactly how this would work. If you havent used delegates and anonymous types before, you might need to brush up on them before moving forward with this part ofMEF. Trying to learn two technologies at once and using them together is a recipe for a debugging disaster.

So what we are doing here is exporting a method that has one argument (of type string) and an output of typestring. We are importing it into a property and calling it as if it were a local method. We also applied a custom name to our method export so that we can differentiate it from other methods with a similar signature.

Inheriting an Export
Example Project Name: Example03

One feature that really makes MEF powerful is the idea of InheritedExport. With this feature, you can ensure than any class that inherits from the item marked with [InheritedExport] will be exported. This works just like an export statement, so you can have a contract name and/or contract type specified. Before we get into the benefits of this, lets look at an example:
Collapse | Copy Code

[InheritedExport(typeof(IRule))] public interface IRule

public class RuleOne : IRule {} public class RuleTwo : IRule {}

Even though RuleOne and RuleTwo dont have an explicit export statement, they will both be exported as IRuletypes (by the way, read that interface name over and over until you feel better about yourself Im not just a coder, Im also a self-help coach). Now, look over that scenario for a minute and think about the possibilities. First, if you put your Interface in a different assembly, the assembly where RuleOne and RuleTwo were located would not need to know about MEF. You are also guaranteed that the export name and type are correct. Finally, you (mostly) ensure that part of the contract for implementing the interface is that the concrete class gets exported as a part. I say mostly because there is one keyword that can be used to block the export. You can use the [PartNotDiscoverable] tag on the top of your class to specify that this class should not be picked up by the MEF library. The class can still be added explicitly (we will get to that later) but it wont be picked up by the ComposeParts or SatisfyImportsOncemethods.

Importing Multiple Identical Exports


Example Project Name: Example03

If you were to test out the above example, you would probably run into an error that says More than one export was found that matches the constraint (see Gotchas). This is because you have one import statement and more than one export statement that matches your import. In terms of our analogy, we have two VGA cables but only one VGA port.Our hand doesnt know which one to plug in. Sometimes this is a mistake on our part. We define two parts to do the same job. Other times, however, we want this to happen. A common example would be a set of rules that needs to be run. It would be highly unlikely that you would have only one rule for any given scenario. Instead, you would probably have many rules. This is where the [ImportMany] statement comes in. It allows us to import zero or more Exported items that match. Did you catch that? An ImportMany statement wont fail if you don have any matching export statements. It will, however, initialize the collection that you have the ImportMany statement on. That prevents errors where a collection wasnt initialized when it was called. Lets see how this works:

Collapse | Copy Code

[ImportMany(typeof(IRule))] List<IRule> _rules;

This ImportMany statement will work with the above InheritedExport example. Now you can use a foreach on the _rules list and call each rule individually.

Delaying Instance Creation


Example Project Name: Example03 Earlier we talked about the fact that MEF creates instances of every needed import when ComposeParts orSatisfyImportsOnce is run. Normally this should be fine. Newly created instances of classes usually don't take up that much memory. However, there may be times when you want to delay the instantiation of an object or set of objects. To do this, simply use the Lazy<T> for delayed instantiation that Microsoft has already provided us. Nothing changes on the export, only the import needs to be changed like so:
Collapse | Copy Code

[Import] Lazy<MyClass> myImport;

Note that this isn't really MEF technology, but rather MEF using Lazy<T> effectively. It isn't a MEF-specific implementation. Don't forget that when you lazy-load something, you need to access the actual instance you are looking for using the Value property. In our case, that would look like this:
Collapse | Copy Code

myImport.Value.MyMethod()

Using lazy-loading to delay instantiation of certain classes definitely has its place, especially if you are uncertain if all of your imported instances will be used or if certain imports are resourceintensive. Again, just because you found a new tool doesn't mean that it is the best tool for all jobs.

Describing the Export


Example Project Name: Example04 Now that we have learned about how we can use ImportMany to bring a bunch of matching exports into one array and how we can wait to instantiate our imports until we need them, the issue of export decoration comes up. How do we differentiate one export from another if the signatures match without peaking inside them? The answer is metatdata. We can attach metadata to our exports to describe information about the export to the importing application. For instance, say we were going to import a number of business rules. Each rule has the same signature, but deals with how to process a record in a different state. This might be an excellent opportunity to use lazy-loading because we most-likely will not use the rule for every state. It is

also an excellent opportunity to use metadata, because we can describe which state each rule handles. Let's look at an example. Here is our exported class objects:
Collapse | Copy Code

public interface IStateRule { string StateBird(); } [Export(typeof(IStateRule))] [ExportMetadata("StateName","Utah")] [ExportMetadata("ActiveRule", true)] public class UtahRule : IStateRule { public string StateBird() { return "Common American Gull"; } } [Export(typeof(IStateRule))] [ExportMetadata("StateName", "Ohio")] [ExportMetadata("ActiveRule", true)] public class OhioRule : IStateRule { public string StateBird() { return "Cardinal"; } }

I added two pieces of metadata to each export just to show you that you can stack these up. I will only do my filter based upon one piece of data (StateName) but I could filter these exports based upon which were active as well. That way we could have multiple exports for the same state. It would be even better if we put an activation date that we could filter on so we could always get the latest version yet the older versions could be retained in case we needed them to process old records, etc. Here is how we would import those class objects:
Collapse | Copy Code

[ImportMany(typeof(IStateRule))] IEnumerable<Lazy<IStateRule, IDictionary<string, object>>> _stateRules;

Notice that we are using lazy-loading, [ImportMany], and export metadata all against on variable. Whew. That is a lot of different items crashing together in two lines of code. If we hadn't already gone over each of these concepts, it would probably be very confusing but I'm sure it is simple to you now. Just in case it isn't, however, let me explain a bit. We are doing an ImportMany so we can bring in multiple items with the same signature. In our case, the signature is that the export will be of type IStateRule. We are putting this set into an IEnumerable so that we can cycle through the set of imported rules. Next, we are specifying that the items will be Lazy<T> so that they are not loaded right away but rather only when called. Finally, after the type of item to import is specified (IStateRule), we add a parameter to specify an IDictionary set. This is the metadata declaration.

Now that we have set everything up, here is how we would access the metadata. In this example, I am looking to only call the Utah rule:
Collapse | Copy Code

Lazy<IStateRule> _utah = _stateRules.Where(s => (string)s.Metadata["StateName"] == "Utah").FirstOrDefault(); if (_utah != null) { Console.WriteLine(_utah.Value.StateBird()); }

The first line in the above part is a simple Linq query that gets me the first item that matches the criteria specified. In this case, I specified that the "StateName" value should be "Utah". That will put the Lazy<IStateRule> copy into my local variable _utah for use. I did not try to get the actual instance of IStateRule inside the Linq query because if the value isn't found, the system will throw an error. Normally I try to keep examples as simple and focused as possible, so I avoid standard error handling and other plumbing. However, in this case I thought it wisest to include some basic error handling capabilities. Since we are using simple string keys in our Dictionary, there is a very real possibility that we might not have the correct pair listed in every export. What I did here was I made sure to use the FirstOrDefault method so that it won't fail if the query doesn't return any data. I also then check to be sure the _utah variable isn't null before I try to call it. <<hand raises>> I see that hand, but I alread know what you are going to ask: "Why aren't you using [InheritedExport] in this example?" I was hoping you wouldn't ask that, because I don't have a very satisfactory answer. Since you did, however, I'll give you what I have. When you use the [InheritedExport] tag on the IStateRule interface in this example, the metadata does not get exported with the exports. From what I can tell, this is because the metadata needs to be exported at the same time as the export itself and the [InheritedExport] tag seems to be processed at a different time. I have tried multiple tricks to get it to work to no avail. If you know how to get this to work (without a workaround), please let me know. I'll be sure to post your solution here and give you the credit.

Advanced Metadata
Example Project Name: Example04 To make metadata even more useful, instead of using the Dictionary<string, object>, you can specify your own class or interface. The easiest way to do this is on the import side of the application. You simply need to replace theDictionary<string, object> with your interface or class name like so:
Collapse | Copy Code

[ImportMany(typeof(IStateRule))] IEnumerable<Lazy<IStateRule, IStateRuleMetadata>> _stateRules;

My interface looks like this:

Collapse | Copy Code

public interface IStateRuleMetadata { string StateName { get; } }

One thing to note here is that I made my property read-only (only a getter). This is intentional. MEF will throw an error if you try to include a setter. There is no need for a setter, however, so this is fine. To access this metadata, you use the metadata dot property like so:
Collapse | Copy Code

Lazy<IStateRule> _utah = _stateRules.Where(s => (string)s.Metadata.StateName == "Utah").FirstOrDefault();

As you can see, my code on the import side no longer uses strings for the key names, but property names. This allows me to utilize Intellisense at design time, which makes my development easier and it makes it easier to avoid errors that will be difficult to debug. I am sure you are wondering what has changed on the export side. The answer is that nothing has changed. You still specify the metadata properties using strings (no Intellisense). MEF handles how to hook these into your metadata class or interface at runtime. This is especially cool if you use an interface for your metadata like I did, since your interface is never actually implemented in your code. MEF implements it for you.

Loading Other Assemblies


Example Project Name: Example05 So far we have concentrated on how to get the exports from our current assembly to match up with our imports. MEFprovides us with a lot more options than this, which is important. Let's look at the different options we can use.

Assembly Catalog
This is the catalog we have been using up until now. You pass in a specific assembly and MEF handles the discovery of the parts inside that assembly. In our case, we have been passing a reference in to the current assembly (Assembly.GetExecutingAssembly ).

Directory Catalog
This is the fun one. It takes in either a relative or absolute path to a directory. MEF will then scan that directory (but not the sub-directories) for assemblies that are decorated with MEF exports. It will add any parts it finds to the catalog. You can further refine this by adding a parameter that specifies the format for the name of the assembly. For example, you could put "*.dll" to include only dll files or you could put "CompanyName*.dll" to include only dlls that start with your company name. Here is an example of how to set up a directory catalog using a relative path and a filter:
Collapse | Copy Code

DirectoryCatalog catalog = new DirectoryCatalog("Plugins", "*.exe");

I decided to search for an exe file just to show you that it does not have to be a dll. Note that the folder name does not need to include any leading characters like slashes or dots.

Type Catalog
This is one I personally do not use. The purpose of this catalog is to only load exports of a specified type in the catalog. To specify the types, you need to add them directly to the constructor line. For sake of completeness, I will include an example of doing so here:
Collapse | Copy Code

TypeCatalog catalog = new TypeCatalog(typeof(IRule), typeof(IStateRule));

Notice that I added two types, but more could be added as needed.

Aggregate Catalog
When having one catalog isn't enough, you can create an aggregate catalog. This catalog is simply a catalog of catalogs. You can add as many catalogs as you want to this catalog. Simply pass in your catalogs to the constructor and you are set. Most people create their individual catalogs in the constructor line of the aggregate catalog for simplicity and so that they don't have to create individual variables for each catalog. I will show you this technique:
Collapse | Copy Code

AggregateCatalog catalog = new AggregateCatalog(new AssemblyCatalog(Assembly.GetExecutingAssembly()), new DirectoryCatalog(@"C:\Plugins"));

As you can see, I am creating two different types of catalogs (DirectoryCatalog and AssemblyCatalog) inside theAggregateCatalog. This will allow my system to discover the internal parts as well as the parts from all of the assemblies inside the temp folder. Note that I used the string literal character (@) to prevent me from needing to escape the slashes inside my folder path.

Adding New Assemblies at Runtime


Example Project Name: Example05 Now that we know how to load all of the assemblies inside a folder with MEF, the idea of adding assemblies on the fly shouldn't be far behind. This will allow us to drop new dll files into our folder and have our currently-running application pick them up and use them. To accomplish this, there are a few things we need to bring together. First, we are going to need to use theDirectoryCatalog. This is where the changes will be picked up (since we won't be changing our current assembly code on the fly). Next, we will need to create a variable to hold the directory catalog instead of creating a new instance inside the AggregateCatalog constructor. We will also need to be sure to scope this variable in such a

way as to have access to it later on. Finally, we are going to need to have some sort of trigger to tell us when we should tell theDirectoryCatalog to update (and thus trigger a recomposition). In a real-world application, you would most likely put a FileSystemWatcher on your plugins folder and have the events from that trigger the DirectoryCatalog. Doing this is outside the scope of this article. Instead, I will show you the code you need on the MEF side. How you trigger that code is up to you. The actual implementation of recomposition is fairly simple. First, you need to be sure you use the ComposePartsmethod instead of the SatisfyImportsOnce method. Then you need to mark any Import or ImportMany statement that might be getting a new part with the parameter "AllowRecomposition=true". Then you either add something new to the CompositionContainer or you trigger a Refresh() on your DirectoryCatalog. The parts look like this:
Collapse | Copy Code

[Import(AllowDefault = true, AllowRecomposition=true)] string test; dirCatalog.Refresh();

I added the named parameter AllowDefault to my Import as well, just to demonstrate its use. This could also be useful if you are importing a single export and you are going to allow recomposition on that part. If the system doesn't detect a matching export right away, you don't want the system to blow up. By the time you get around to using that variable, the recomposition might have already happened and the part might be populated. Note that when the parts are recomposed, if a part gets added to an ImportMany, all of the parts in that list get rebuilt. That is by design but it means that you need to understand how recomposition will affect your application and make development decisions based upon that fact. Also, the AppDomain will continue to hold a dll in memory until it is restarted. That means that you will need to use ShadowCopy to delete dlls currently in use. If you do, recomposition will remove these parts from your assembly (but still hold a copy of the dll in memory). Your application will work correctly but it isn't totally clean.

Other Features of MEF


We have touched on the mainline features of MEF, but there are still a lot of areas that we haven't covered. Some of these are only rarely used, but there are at least a few that I feel are more useful. Here are a few that I think would be most useful to you (in no particular order).

Constructor Injection
Example Project Name: Example06 MEF provides us with an option on how we bring in an export. Normally, we will decorate a variable with an [Import]tag. However, there is another option for populating the variable and that is via Constructor Injection. The simplest way to do this is to simply add the [ImportingConstructor] over your specialized constructor like so:

Collapse | Copy Code

public class MyClass { [ImportingConstructor] public MyClass(IRule myRule) { _myRule = myRule; } }

I created a specialized constructor for my class that took an interface as an argument. I then decorated the constructor with the ImportingConstructor tag to indicate that I wanted MEF to populate this constructor upon composition.MEF will infer which parts it needs just like it would on an [Import] statement with no modifiers. However, should you want to add modifiers to tell MEF what parts you want specifically, you can do so like this:
Collapse | Copy Code

public class MyClass { [ImportingConstructor] public MyClass([Import(typeof(IRule))] IRule myRule) { _myRule = myRule; } }

This makes the constructor a bit messier but you can do everything you can with an [Import] or [ImportMany] tag in your constructor. So now the question becomes "why?". I believe the best use case for this method is when you want to control what happens when a part is created. For example, you might want to initialize values or otherwise set up your system. For example, if you have a User class that imports a class instance that handles payroll information for the user, you might want to pass in the UserID to the payroll instance right away so it knows who it is working with.

Getting an Export Manually


Example Project Name: Example06 There may be times when you want to get an exported part manually instead of using an import statement. Here are three examples of how to do this:
Collapse | Copy Code

string manual1 = container.GetExportedValue<string>(); string manual2 = container.GetExportedValue<string>("ContainerName"); string manual3 = container.GetExportedValueOrDefault<string>();

The first example just finds a part of type string. The second finds a part of type string with the specified container name. The third shows how you can do the same thing using the GetExportedValueOrDefault method instead so that the system will not throw an exception if the part is not found.

Personally, I shy away from this way of getting values. It gets ugly quick and it makes you expose your container (in some method - don't just make it a global variable) to a wider audience than you otherwise would need to do.

Getting an Alert when MEF is Done


Example Project Name: Example06 There may be times when you need to know when the import process is complete. This is fairly simple to do. First, you need to implement the interface IPartImportsSatisfiedNotification. This interface provides you with the method OnImportsSatisfied(). This method will be fired off when all of the imports have been successfully hooked up.

Shutting Down MEF Early


Example Project Name: Example06 There may be times when you want to explicitly close out MEF and release the resources before the rest of your application is closed. To do this, call the Dispose() method on your container like so:
Collapse | Copy Code

container.Dispose();

Different parts will close out differently, but basically the parts held in the container will be properly closed out and disposed of when the container is disposed.

Gotchas
In the course of working with MEF, you will come across errors and problems. Here are a few of the more common issues and how to resolve them: 1. No valid exports were found that match the constraint This error basically tells you the issue. MEFdidnt find an export to hook into your import. Why this happens can be a bit baffling at times. First, check to see that you are exporting an item of the expected type. Then, make sure the contract name is exactly the same on both ends (if you are using one). Do the same with contract type. Finally, if all else fails and you are sure that everything matches, make sure you are looking in the given assembly for the export. If you are only looking in dll files and forgot to add the current assembly, you may be missing your export. 2. More than one export was found that matches the constraint When you have an Import statement, you can only have one Export that matches. If MEF finds two, it doesnt know which one to use so it throws this error. Either use an ImportMany or modify one of the Export statements to have a different signature. This error is especially common when you are letting MEF assume the contract type. If you export two strings, you will get this error. Instead, try to develop everything towards an Interface. 3. The export is not assignable to type This means that you specified an Import type but you cant put that type into the variable. Just because you call an int a string doesnt make it

so. The other possibility here is that you are trying to do an ImportMany but you accidentally put just Import. In that case, you would be trying to put one item into an item of type IEnumerable, which wont work (it tries to assign it instead of calling the Add method). The reverse is also true - if you put an ImportMany on a variable but the variable cannot accept a set of instances, you will get this error. For more information on MEF errors, MSDN has some good information here: http://msdn.microsoft.com/en-us/library/ff603380.aspx

Ways to Use MEF


MEF can be (and is) used all over the .NET stack. It is common to see it used with WPF and Silverlight (look intoCaliburn.Micro for an excellent implementation of MEF in conjunction with MVVM). You can, however, also use MEFwith ASP.NET to make MVC more extensible. Basically, the technology will work with just about anything. It is more a matter of ensuring that the use case justifies the tool.

The Future of MEF


MEF has been around for a while. Currently the team is working on building a new version of MEF (currently referred to as MEF2 - clever, right?). You can find out more about what the latest build includes by reading the BCL Team blog.

Conclusion
In this article, we have started at the very beginning of the Managed Extensibility Framework and worked our way steadily through the major parts of MEF. We have covered the simple examples, but also the more realistic cases that we are likely to see in a production application. I hope that this has been helpful to you. Please let me know below if there is something that you found unclear. As always, I appreciate your constructive feedback.

Das könnte Ihnen auch gefallen