Using XAML User Controls in XNA

In this article you will learn how to use XAML User Controls in XNA.



As you have read before we have implemented the two technologies in a single solution (Windows-based)

I would like to demonstrate a sample application that will show you what you can do with them.

We will be using "ElementHost" again

Alright then let me show you how it's done. First of all let's create our Silverlight User Control:

Add a new WPF/Silverlight 4 UserControl (Silverlight? Yes, to mention; WPF and Silverlight have common XAML Structure. So it won't be hard to add them. Remember some Silverlight-based controls will give you an error. So make sure to add common controls which are also in WPF) project using .NET Framework 4.0.

xna1.gif

And add some Silverlight controls on your usercontrol:

xna2.gif

I have added this UserControl which can be used in both WPF and Silverlight.

Now let's create our XNA application.

xna3.gif

Add reference to:

  • Presentation Core
  • Presentation Framework
  • UIAutomationProvider
  • WindowsBas
  • WindowsFormsIntegration
  • System.XAML
  • System.Drawing
  • System.Windows.Forms

All right.

Now by using Add->Existing Item add the Silverlight UserControl in your project.

xna4.gif


To begin implementing Silverlight UserControl please add this using statement,

using SilverlightUserControlProject;

Note: "SilverlightUserControlProject" will be replaced by the project name which you have named it when you first created the project.

Now let's create a new instance of our UserControl

SilverlightUserControl suc = new SilverlightUserControl();

And now add these codes on your Initialize() Function

ElementHost elementHost1 = new ElementHost();
elementHost1.Location = new System.Drawing.Point(0, 0);
elementHost1.Name = "elementHost1";
elementHost1.Size = new System.Drawing.Size(415, 375);
elementHost1.TabIndex = 1;
elementHost1.Text = "elementHost1";
elementHost1.Child = this.suc;
Control.FromHandle(Window.Handle).Controls.Add(elementHost1);

Let's run it and see the result:

xna5.gif

As I expected. Since XNA and Silverlight use different UIs we will have to add STAThread() to our application to run it properly.

Update your Program.cs:

[STAThread()]
static void Main(string[] args)
{
  using (Game1 game = new Game1())
  {
    game.Run();
  }
}

Let's run it!

xna6.gif

Great!

We have implemented our Silverlight User Control in XNA.

That was a trick. Because WPF and Silverlight both use XAML. So why not take advantage of ElementHost?

So let's keep going: What you can do with this approach?

Lets make a sample application that works integrated (sending and receiving messages) with XNA.

We will be building a Simple Planet Browser application with XAML based Usercontrols and XNA.

First lets create a class that will provide communication between UserControl and XNA.

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

namespace SLUCinXNA
{
    public static class Infos
    {
        public static string selected_planet="Earth";
    }
}

By default set it to "Earth";

Here is our UserControl that we will be using in our application:

XAML:

<UserControl x:Class="Planets.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="670" d:DesignWidth="282" xmlns:sdk
="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">

    <Grid x:Name="LayoutRoot" Background="Black">
        <Image Height="67" Cursor="Hand" HorizontalAlignment="Left" Margin="12,12,0,0" Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="77" Source="/SLUCinXNA;component/Images/sun.jpg" MouseDown="image1_MouseDown" />
        <Image Height="67" Cursor="Hand" HorizontalAlignment="Left" Margin="12,85,0,0" Name="image2"  Stretch="Fill" VerticalAlignment="Top" Width="77" Source="/SLUCinXNA;component/Images/earth.jpg" MouseDown="image2_MouseDown" />
        <Image Height="67" Cursor="Hand" HorizontalAlignment="Left" Margin="12,158,0,0" Name="image3"  Stretch="Fill" VerticalAlignment="Top" Width="77" Source="/SLUCinXNA;component/Images/jupiter.jpg" MouseDown="image3_MouseDown" />
        <Image Height="67" Cursor="Hand" HorizontalAlignment="Left" Margin="12,231,0,0" Name="image4"  Stretch="Fill" VerticalAlignment="Top" Width="77" Source="/SLUCinXNA;component/Images/mars.jpg" MouseDown="image4_MouseDown" />
        <Image Height="67" Cursor="Hand" HorizontalAlignment="Left" Margin="12,304,0,0" Name="image5"  Stretch="Fill" VerticalAlignment="Top" Width="77" Source="/SLUCinXNA;component/Images/mercury.jpg" MouseDown="image5_MouseDown" />
        <Image Height="67" Cursor="Hand" HorizontalAlignment="Left" Margin="12,375,0,0" Name="image6"  Stretch="Fill" VerticalAlignment="Top" Width="77" Source="/SLUCinXNA;component/Images/neptune.jpg" MouseDown="image6_MouseDown" />
        <Image Height="67" Cursor="Hand" HorizontalAlignment="Left" Margin="12,448,0,0" Name="image7"  Stretch="Fill" VerticalAlignment="Top" Width="77" Source="/SLUCinXNA;component/Images/saturn.jpg" MouseDown="image7_MouseDown" />
        <Image Height="67" Cursor="Hand" HorizontalAlignment="Left" Margin="12,521,0,0" Name="image8"  Stretch="Fill" VerticalAlignment="Top" Width="77" Source="/SLUCinXNA;component/Images/uranus.jpg" MouseDown="image8_MouseDown" />
        <Image Height="67" Cursor="Hand" HorizontalAlignment="Left" Margin="12,594,0,0" Name="image9"  Stretch="Fill" VerticalAlignment="Top" Width="77" Source="/SLUCinXNA;component/Images/venus.jpg" MouseDown="image9_MouseDown" />
        <TextBlock Height="23" HorizontalAlignment="Left" Margin="118,31,0,0" Name="textBlock1" Text="SUN" VerticalAlignment="Top" Foreground="White" FontSize="20" Cursor="Hand" MouseDown="textBlock1_MouseDown" />
        <TextBlock FontSize="20" Foreground="White" Height="23" HorizontalAlignment="Left" Margin="118,106,0,0" Name="textBlock2" Text="EARTH" VerticalAlignment="Top" Cursor="Hand" MouseDown="textBlock2_MouseDown" />
        <TextBlock FontSize="20" Foreground="White" Height="23" HorizontalAlignment="Left" Margin="118,178,0,0" Name="textBlock3" Text="JUPITER" VerticalAlignment="Top" Cursor="Hand" MouseDown="textBlock3_MouseDown" />
        <TextBlock FontSize="20" Foreground="White" Height="23" HorizontalAlignment="Left" Margin="118,249,0,0" Name="textBlock4" Text="MARS" VerticalAlignment="Top" Cursor="Hand" MouseDown="textBlock4_MouseDown" />
        <TextBlock FontSize="20" Foreground="White" Height="23" HorizontalAlignment="Left" Margin="118,323,0,0" Name="textBlock5" Text="MERCURY" VerticalAlignment="Top" Cursor="Hand" MouseDown="textBlock5_MouseDown" />
        <TextBlock FontSize="20" Foreground="White" Height="23" HorizontalAlignment="Left" Margin="118,394,0,0" Name="textBlock6" Text="NEPTUNE" VerticalAlignment="Top" Cursor="Hand" MouseDown="textBlock6_MouseDown" />
        <TextBlock FontSize="20" Foreground="White" Height="23" HorizontalAlignment="Left" Margin="118,466,0,0" Name="textBlock7" Text="SATURN" VerticalAlignment="Top" Cursor="Hand" MouseDown="textBlock7_MouseDown" />
        <TextBlock FontSize="20" Foreground="White" Height="23" HorizontalAlignment="Left" Margin="118,540,0,0" Name="textBlock8" Text="URANUS" VerticalAlignment="Top" Cursor="Hand" MouseDown="textBlock8_MouseDown" />
        <TextBlock FontSize="20" Foreground="White" Height="23" HorizontalAlignment="Left" Margin="118,621,0,0" Name="textBlock9" Text="VENUS" VerticalAlignment="Top" Cursor="Hand" MouseDown="textBlock9_MouseDown" />
    </Grid>
</
UserControl>

xna7.gif

When we click on the Image or Textblock it will show our planet in XNA.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using SLUCinXNA;

namespace Planets
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void image1_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Sun";
        }

        private void textBlock1_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Sun";
        }

        private void image2_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Earth";
        }

        private void textBlock2_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Earth";
        }

        private void image3_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Jupiter";
        }

        private void textBlock3_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Jupiter";
        }

        private void image4_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Mars";
        }

        private void textBlock4_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Mars";
        }

        private void image5_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Mercury";
        }

        private void textBlock5_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Mercury";
        }

        private void image6_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Neptune";
        }
 
        private void textBlock6_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Neptune";
        }

        private void image7_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Saturn";
        }
 
        private void textBlock7_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Saturn";
        }

        private void image8_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Uranus";
        }

        private void textBlock8_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Uranus";
        }

        private void image9_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Venus";
        }

        private void textBlock9_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Infos.selected_planet = "Venus";
        }
    }
}




Now let's get to the XNA Part:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using SilverlightUserControlProject;
using System.Windows.Forms.Integration;
using System.Windows.Forms;
using Planets;

namespace SLUCinXNA
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        MainPage mp = new MainPage();
        Model myModel;
        float aspectRatio;
        Vector3 modelPosition = new Vector3(150, 0, 0);
        float modelRotation = 0.0f;
        Vector3 cameraPosition = new Vector3(0.0f, 50.0f, 600.0f);

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            graphics.PreferredBackBufferHeight = 800;
            graphics.PreferredBackBufferWidth = 1200;
            this.IsMouseVisible = true;
        }

        protected override void Initialize()
        {
            base.Initialize();
            ElementHost elementHost1 = new ElementHost();
            elementHost1.Location = new System.Drawing.Point(0, 0);
            elementHost1.Name = "elementHost1";
            elementHost1.Size = new System.Drawing.Size(282, 670);
            elementHost1.TabIndex = 1;
            elementHost1.Text = "elementHost1";
            elementHost1.Child = this.mp;
            Control.FromHandle(Window.Handle).Controls.Add(elementHost1);
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            myModel = Content.Load<Model>("Planets\\earthmodel");
            aspectRatio = graphics.GraphicsDevice.Viewport.AspectRatio;
        }
        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
            modelRotation += (float)gameTime.ElapsedGameTime.TotalMilliseconds *
       MathHelper.ToRadians(0.01f);

            switch (Infos.selected_planet)
            {
                case "Earth":
                    myModel = Content.Load<Model>("Planets\\earthmodel");
                    break;
                case "Sun":
                    myModel = Content.Load<Model>("Planets\\sunmodel");
                    break;
                case "Jupiter":
                    myModel = Content.Load<Model>("Planets\\jupitermodel");
                    break;
                case "Mars":
                    myModel = Content.Load<Model>("Planets\\marsmodel");
                    break;
                case "Mercury":
                    myModel = Content.Load<Model>("Planets\\mercurymodel");
                    break;
                case "Neptune":
                    myModel = Content.Load<Model>("Planets\\neptunemodel");
                    break;
                case "Saturn":
                    myModel = Content.Load<Model>("Planets\\saturnmodel");
                    break;
                case "Uranus":
                    myModel = Content.Load<Model>("Planets\\uranusmodel");
                    break;
                case "Venus":
                    myModel = Content.Load<Model>("Planets\\venusmodel");
                    break;             

            }
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);
            Matrix[] transforms = new Matrix[myModel.Bones.Count];
            myModel.CopyAbsoluteBoneTransformsTo(transforms);

            foreach (ModelMesh mesh in myModel.Meshes)
            {             
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.PreferPerPixelLighting = true;
                    effect.World = transforms[mesh.ParentBone.Index] *
                        Matrix.CreateRotationY(modelRotation)
                        * Matrix.CreateTranslation(modelPosition);
                    effect.View = Matrix.CreateLookAt(cameraPosition,
                        Vector3.Zero, Vector3.Up);
                    effect.Projection = Matrix.CreatePerspectiveFieldOfView(
                        MathHelper.ToRadians(45.0f), aspectRatio,
                        1.0f, 10000.0f);
                }
                mesh.Draw();
            }
 
            base.Draw(gameTime);
        }
    }
}

I have preferred PerPixelLighting instead of DefaultLighting in this demonstration. And I haven't even added effects on the planets. You can add Glow HLSL effect for them.

We're controlling whether selectedvalue has changed or not in Update Function.

Now let's run it and see a demonstration.

xna8.gif

xna9.gif

Then I clicked Jupiter and here it is. Spinning around.

I know! The planets' models are lame :) But this is for Demonstration. Not a real application.

You might write some text about the selected planet:

For example:

xna10.gif

It's up to your imagination and creativity to make applications in Windows-based systems using XNA and UserControls together.

I've used textures from JHT's Planetary Pixel Emporium: http://planetpixelemporium.com/planets.html

Note: If you face some problems regarding .fbx files, they use relative paths. You might have to change it by opening it with a notepad and search for ".jpg" in the file and change its path. This caused because I used an old version of a 3D design application.