XPF: Getting Started

Updated 2010-08-09: Brought into line with changes in Build 10+
Updated 2010-09-28: Brought into line with changes in Build 24+

In XPF, the RootElement control is the main host for all your controls, it manages the renderer, user input and is the target for Update/Draw calls.  The RootElement should be thought of akin to the Window control in WPF.  Although it’s possible to create more than one RootElement in an XNA application, it’s more advisable to use one that has a viewport equal to the entire screen and layout our elements within it dynamically.  XPF won’t physically render anything in empty space, so there is no performance overhead to having a large RootElement.

Let’s say we want to build an awesome game and we need a few things dotted around the screen.  We want a player’s score displayed top left, a high score top right and the number of lives remaining bottom left as in the scamp below:

Getting Started

Your XNA instincts might be telling you to think about working out the explicit position for each of the 3 main elements.  In XPF you don’t need to, instead you can use the layout controls to create a logical layout without a vector in sight.

Renderer, Adapters & Primitives Service

Instantiating a RootElement, requires that we pass it an IRenderer.  XPF comes with an out of the box Renderer that implements the required IRenderer interface, which we’ll use.  Its constructor requires ISpriteBatch and IPrimitivesService.

In order to reduce dependencies on XNA and increase the testability of XPF, we don’t directly use any XNA classes, but rather use interfaces that describe the specific XNA functionality we require.  Unfortunately XNA doesn’t implement very many useful interfaces we can leverage, so how do we get an actual SpriteBatch that implements ISpriteBatch?  One answer is to use the the Adapter pattern which essentially wraps SpriteBatch in a new class that does implement ISpriteBatch and delegates the calls down to the real SpriteBatch.  Adapters allow you to bridge the gap between one architecture that isn’t interface driven and one that is.  Creating Adapters is a fairly tedious task, so thankfully XPF comes with a bunch out of the box for you to use.

In the case of SpriteBatchAdapter, it inherits from SpriteBatch directly and implements ISpriteBatch meaning it’s easy to create and behaves just as a normal SpriteBatch object would (because it is one!).

Next up is IPrimitivesService, the only thing it exposes is a single pixel texture that XPF can use to draw borders and backgrounds.  XPF also comes with a pre-baked IPrimitivesService implementation which we can use.

With our SpriteBatchAdapter and PrimitivesService in hand, we can instantiate our Renderer:

public class MyComponent : DrawableGameComponent{
    private SpriteBatchAdapter spriteBatchAdapter;

    public MyComponent(Game game)
        : base(game)
    {
    }

    protected override void LoadContent()
    {
        this.spriteBatchAdapter = new SpriteBatchAdapter(new SpriteBatch(this.GraphicsDevice));
        var primitivesService = new PrimitivesService(this.GraphicsDevice);
        var renderer = new Renderer(this.spriteBatchAdapter, primitivesService);
    }
}

RootElement

We’re now ready to create our RootElement – when doing so you stipulate the viewport you want XPF to use i.e. what it should consider top left and how much width/height it has to layout it’s children.  In our case we’re going to give it the whole screen, so we can use the handy Viewport.ToRect() extension method in XPF:

protected override void LoadContent()
{
    this.spriteBatchAdapter = new SpriteBatchAdapter(new SpriteBatch(this.GraphicsDevice));
    var primitivesService = new PrimitivesService(this.GraphicsDevice);
    var renderer = new Renderer(this.spriteBatchAdapter, primitivesService);

    this.rootElement = new RootElement(this.GraphicsDevice.Viewport.ToRect(), renderer);
}

RootElement can only contain a single child via it’s Content property.  Typically we would use a layout control such as the Grid to host all our other controls.  Right now, let’s set the RootElement.Content property to a TextBlock so we can see something rendering.

TextBlock

Those familiar with WPF/Silverlight will be familiar with the TextBlock – you want to display text on the screen?  You want a TextBlock.  Creating a TextBlock requires the use of another adapter – the SpriteFontAdapter, which allows us to instantiate TextBlock with an ISpriteFont.

var spriteFont = this.Game.Content.Load<SpriteFont>("MySpriteFont");
var spriteFontAdapter = new SpriteFontAdapter(spriteFont);

var textBlock = new TextBlock(spriteFontAdapter)
    {
        Text = "Hello World",
        Background = new SolidColorBrush(Colors.Red) 
    };

this.rootElement.Content = textBlock;

You’ll notice that I’ve also set the Background of the TextBlock to Red, which a) shows you how easy it is to give something a background in XPF; and b) will let us see where it appears and the space it occupies a little more clearly.  If we were to run our game now, we wouldn’t see much (besides CornflowerBlue) – we need to instruct XPF to Update and Draw and then run our game:

public override void Update(GameTime gameTime)
{
    this.rootElement.Update();
    base.Update(gameTime);
}

public override void Draw(GameTime gameTime)
{
    this.rootElement.Draw();
    base.Draw(gameTime);
}

01 Hello World

Alignment

Pretty exciting – but why so much red?  All controls have horizontal and vertical alignment properties which default to stretch – these attempt to “stretch” the control to fill the area given to it by its parent – in this case, the RootElement and so the whole screen.  Stretch doesn’t mean that a control’s contents are physically stretched (as in transformed), but rather it tries to take up the maximum amount of space its parent has allocated it and then uses that to lay itself out.

If we don’t want our TextBlock to stretch itself, we just change the horizontal/vertical alignment properties to something other than stretch.

var textBlock = new TextBlock(spriteFontAdapter)
{
   Text = "Hello World",
   Background = new SolidColorBrush(Colors.Red),
   HorizontalAlignment = HorizontalAlignment.Left,
   VerticalAlignment = VerticalAlignment.Top
};

02 Hellow World

Text Wrapping, Margins & Padding

TextBlock can also handle dynamically wrapping text.  If we swap our “Hello World” text for some information about Red Badger we’ll unsurprisingly see it run off the screen.

03 Hellow World

But if we turn on text wrapping, add a 10px margin on all sides (and just to show off) add 10px padding on all sides as well, we get something much more pleasing to the eye:

var textBlock = new TextBlock(spriteFontAdapter)
{
    Text =
        "Red Badger is a product and service consultancy, specialising in bespoke software projects, developer tools and platforms on the Microsoft technology stack.", 
    Background = new SolidColorBrush(Colors.Red), 
    HorizontalAlignment = HorizontalAlignment.Left, 
    VerticalAlignment = VerticalAlignment.Top, 
    Wrapping = TextWrapping.Wrap, 
    Margin = new Thickness(10),
    Padding = new Thickness(10)
};  

04 Hello World

StackPanel

Whilst the TextBlock is pretty powerful, it doesn’t make an ideal candidate as the content for a RootElement, because it can only contain text and no other controls.  Rather we need something that supports multiple children; a panel.

The StackPanel is one of the simplest panels – it stacks its children, one after the other, in either a vertical or horizontal direction.  Because all controls ultimately inherit from UIElement, StackPanel has many of the same properties as TextBlock; it can have horizontal/vertical alignment, margins and backgrounds, but it also has a children property which allows us to add multiple controls.  Let’s change our RootElement to contain a StackPanel, set the panel’s background (so we can see the space it occupies) and and add 3 TextBlock controls to the StackPanel:

var stackPanel = new StackPanel
    {
        Background = new SolidColorBrush(Colors.Red),
        Children =
            {
                new TextBlock(spriteFontAdapter) { Text = "Item 1", Margin = new Thickness(10) },
                new TextBlock(spriteFontAdapter) { Text = "Item 2", Margin = new Thickness(10) },
                new TextBlock(spriteFontAdapter) { Text = "Item 3", Margin = new Thickness(10) }
            }
    };
this.rootElement.Content = stackPanel;

01 StackPanel

You’ll note that StackPanel has a default orientation of vertical, so the elements are laid out down the screen.  Notice too all the red (again) – as with the TextBlock, the StackPanel default alignment is stretch in both directions.

Let’s change the orientation of the StackPanel to horizontal, which should lay the child elements out across the screen (rather than down it).  We’ll also change the horizontal/vertical alignment to centre the StackPanel (instead of stretch it).

var stackPanel = new StackPanel
{
    Background = new SolidColorBrush(Colors.Red), 
    Children =
        {
            new TextBlock(spriteFontAdapter) { Text = "Item 1", Margin = new Thickness(10) }, 
            new TextBlock(spriteFontAdapter) { Text = "Item 2", Margin = new Thickness(10) }, 
            new TextBlock(spriteFontAdapter) { Text = "Item 3", Margin = new Thickness(10) }
        }, 
    Orientation = Orientation.Horizontal, 
    HorizontalAlignment = HorizontalAlignment.Center, 
    VerticalAlignment = VerticalAlignment.Center
};

02 StackPanel

Grid

The StackPanel got us a little closer to our awesome game scamp in that it could handle more than one child – but for the layout we require, the StackPanel isn’t going to cut it.  Instead we turn to one of the most powerful layout panels, the Grid.

When you create a Grid, you do so with row and column definitions that define its structure.  Let’s create a 2 by 2 Grid and tell RootElement to use it as the main layout container.

var grid = new Grid
    {
        RowDefinitions =
            {
                new RowDefinition(),
                new RowDefinition()
            },
        ColumnDefinitions =
            {
                new ColumnDefinition(),
                new ColumnDefinition()
            }
    };

this.rootElement.Content = grid;

Grid has no visual properties (besides background), so as it stands nothing will render.  We need to put content into the grid.  Grid inherits from the same base class as StackPanel, so it also has a children property which we can add content to.  Let’s do that and see what happens:

var topLeftTextBlock = new TextBlock(spriteFontAdapter) { Text = "Top Left", Margin = new Thickness(10) };
grid.Children.Add(topLeftTextBlock);

var topRightTextBlock = new TextBlock(spriteFontAdapter) { Text = "Top Right", Margin = new Thickness(10) };
grid.Children.Add(topRightTextBlock);

var bottomLeftTextBlock = new TextBlock(spriteFontAdapter) { Text = "Bottom Left", Margin = new Thickness(10) };
grid.Children.Add(bottomLeftTextBlock);

var bottomRightTextBlock = new TextBlock(spriteFontAdapter) { Text = "Bottom Right", Margin = new Thickness(10) };
grid.Children.Add(bottomRightTextBlock);

Grid 01

The astute amongst you will have noticed that we haven’t given the Grid anyway of knowing which row or column each of the child controls should be placed in, so at the moment they’re all being placed into the first cell, on top of each other.  Whilst this isn’t what we want, it does highlight one of the Grid’s special abilities; to stack content along the z-axis.

Attached Properties

So that the Grid can figure out which cell it should place each child into, we use attached properties - another WPF/Silverlight concept which is supported in XPF.  If you’ve never heard of attached properties before they’re fairly self describing; they’re a mechanism for “attaching” a property to an object that doesn’t physically have that property.  In this case it negates the need for every control to have row and column properties just in case they’re contained within a Grid.  Instead Grid owns the attached property and allows it to be attached to anything that’s valid content for a Grid.  Let’s re-write the previous code, but using attached properties to define their row/column placements:

var topLeftTextBlock = new TextBlock(spriteFontAdapter) { Text = "Top Left", Margin = new Thickness(10) };
Grid.SetRow(topLeftTextBlock, 0);
Grid.SetColumn(topLeftTextBlock, 0);
grid.Children.Add(topLeftTextBlock);

var topRightTextBlock = new TextBlock(spriteFontAdapter) { Text = "Top Right", Margin = new Thickness(10) };
Grid.SetRow(topRightTextBlock, 0);
Grid.SetColumn(topRightTextBlock, 1);
grid.Children.Add(topRightTextBlock);

var bottomLeftTextBlock = new TextBlock(spriteFontAdapter) { Text = "Bottom Left", Margin = new Thickness(10) };
Grid.SetRow(bottomLeftTextBlock, 1);
Grid.SetColumn(bottomLeftTextBlock, 0);
grid.Children.Add(bottomLeftTextBlock);

var bottomRightTextBlock = new TextBlock(spriteFontAdapter) { Text = "Bottom Right", Margin = new Thickness(10) };
Grid.SetRow(bottomRightTextBlock, 1);
Grid.SetColumn(bottomRightTextBlock, 1);
grid.Children.Add(bottomRightTextBlock);

Grid 02

Pretty snazzy right?  Next step is to get our Grid cells to expand to the entire screen, so the “Bottom Right” TextBlock, really is bottom right.  In order to do this, we need to return to the row/column definitions we created earlier.

Row and Column Definitions

When creating a RowDefinition you can optionally specify a height property.  Height can either be set to “auto”, which sizes the height of the row automatically to contain the cells’ contents.  “Pixel”, which allows for an explicit pixel height.  Or “star”, which allows for percentage style values.  The same is true for ColumnDefinition, but in relation to its width property.

At time of writing the XPF beta doesn’t support star heights/widths, which should be the default and allow for truly fluid layouts, but it will do shortly.  Right now the default is auto, which explains why the Grid cells are just big enough to contain their contents and are bunched up in the left hand corner.

For the time being then, we need to alter our row/column definitions to use explicit pixel values:

var grid = new Grid
{
    RowDefinitions =
        {
            new RowDefinition { Height = new GridLength(210) },
            new RowDefinition { Height = new GridLength(210) }
        },
    ColumnDefinitions =
        {
            new ColumnDefinition { Width = new GridLength(200) },
            new ColumnDefinition { Width = new GridLength(200) }
        }
};

Grid 03

Now to get things hugging the side of the screen, we just need to change the alignment properties of each TextBlock.  Because they’re contained within a Grid, the alignment properties only affect their alignment within their cell:

var topLeftTextBlock = new TextBlock(spriteFontAdapter)
    {
        Text = "Top Left",
        Margin = new Thickness(10)
    };
Grid.SetRow(topLeftTextBlock, 0);
Grid.SetColumn(topLeftTextBlock, 0);
grid.Children.Add(topLeftTextBlock);

var topRightTextBlock = new TextBlock(spriteFontAdapter)
    {
        Text = "Top Right",
        Margin = new Thickness(10),
        HorizontalAlignment = HorizontalAlignment.Right
    };
Grid.SetRow(topRightTextBlock, 0);
Grid.SetColumn(topRightTextBlock, 1);
grid.Children.Add(topRightTextBlock);

var bottomLeftTextBlock = new TextBlock(spriteFontAdapter)
    {
        Text = "Bottom Left",
        Margin = new Thickness(10),
        VerticalAlignment = VerticalAlignment.Bottom
    };
Grid.SetRow(bottomLeftTextBlock, 1);
Grid.SetColumn(bottomLeftTextBlock, 0);
grid.Children.Add(bottomLeftTextBlock);

var bottomRightTextBlock = new TextBlock(spriteFontAdapter)
    {
        Text = "Bottom Right",
        Margin = new Thickness(10),
        HorizontalAlignment = HorizontalAlignment.Right,
        VerticalAlignment = VerticalAlignment.Bottom
    };
Grid.SetRow(bottomRightTextBlock, 1);
Grid.SetColumn(bottomRightTextBlock, 1);
grid.Children.Add(bottomRightTextBlock);

Grid 04

Border

Next up we need to get some horizontal lines under the top two TextBlock controls and above the bottom two, we also want the bottom row to have a green background.  To do that let’s use the Border control.  Border has the same base class as RootElement and as such can only contain a single element.  It allows us to place borders on 1, 2, 3 or all 4 sides of the object it contains.  We’re going to create a Border control for each TextBlock, place the TextBlock within it and make each Border the immediate child of the Grid.  The top two borders will only specify a border on the bottom giving a horizontal line that will run across the whole screen.  The bottom two borders will only specify a border on the top giving the same effect and use our pea green colour for the background:

var topLeftBorder = new Border
    {
        BorderBrush = new SolidColorBrush(Colors.Black),
        BorderThickness = new Thickness(0, 0, 0, 2),
        Child = new TextBlock(spriteFontAdapter)
            {
                Text = "Top Left",
                Margin = new Thickness(10)
            }
    };
Grid.SetRow(topLeftBorder, 0);
Grid.SetColumn(topLeftBorder, 0);
grid.Children.Add(topLeftBorder);

var topRightBorder = new Border
{
    BorderBrush = new SolidColorBrush(Colors.Black),
    BorderThickness = new Thickness(0, 0, 0, 2),
    Child = new TextBlock(spriteFontAdapter)
    {
        Text = "Top Right",
        Margin = new Thickness(10),
        HorizontalAlignment = HorizontalAlignment.Right
    }
};
Grid.SetRow(topRightBorder, 0);
Grid.SetColumn(topRightBorder, 1);
grid.Children.Add(topRightBorder);

var bottomLeftBorder = new Border
{
    BorderBrush = new SolidColorBrush(Colors.Black),
    BorderThickness = new Thickness(0, 2, 0, 0),
    Background = new SolidColorBrush(new Color(106, 168, 79, 255)),
    Child = new TextBlock(spriteFontAdapter)
    {
        Text = "Bottom Left",
        Margin = new Thickness(10),
        VerticalAlignment = VerticalAlignment.Bottom
    }
};
Grid.SetRow(bottomLeftBorder, 1);
Grid.SetColumn(bottomLeftBorder, 0);
grid.Children.Add(bottomLeftBorder);

var bottomRightBorder = new Border
{
    BorderBrush = new SolidColorBrush(Colors.Black),
    BorderThickness = new Thickness(0, 2, 0, 0),
    Background = new SolidColorBrush(new Color(106, 168, 79, 255)),
    Child = new TextBlock(spriteFontAdapter)
    {
        Text = "Bottom Right",
        Margin = new Thickness(10),
        HorizontalAlignment = HorizontalAlignment.Right,
        VerticalAlignment = VerticalAlignment.Bottom
    }
};
Grid.SetRow(bottomRightBorder, 1);
Grid.SetColumn(bottomRightBorder, 1);
grid.Children.Add(bottomRightBorder);

Grid 05

So close.  Let’s add another row into the mix for the main content of our game, which will push the top and bottom rows apart giving us the layout we’re after.  We’ll make the [new] middle row large enough that we don’t need to specify heights on the top and bottom rows, they can revert to auto.  We’ll also change the Grid background to white and swap in the correct text:

var grid = new Grid
{
    Background = new SolidColorBrush(Colors.White),
    RowDefinitions =
        {
            new RowDefinition(),
            new RowDefinition { Height = new GridLength(320) },
            new RowDefinition()
        },
    ColumnDefinitions =
        {
            new ColumnDefinition { Width = new GridLength(200) },
            new ColumnDefinition { Width = new GridLength(200) }
        }
};

Grid 06

Finally our design has been achieved, and all without using a single vector!

As you can see, if you’re coming from a background of WPF or Silverlight you’ll find using XPF a breeze.  If on the other hand you’ve never touched either of those technologies, your learning curve maybe slightly steeper, but hopefully you’ll appreciate that XPF allows you to describe the layout you want to achieve using language and constructs that directly relate to the problem domain, creating more elegant and manageable code.

You can download the code for this solution here: Xpf.Samples.zip – but it won’t build until you’ve also downloaded the latest nightly build to go with it and updated the references.  (I know that’s a bit of a pain – more info in the readme).

In future posts in this series we’ll be discussing some of the more powerful features of XPF including the ScrollViewer, ItemsControl, user input, data binding, animation and some of the upcoming features still being developed.

David Wynne
More from David