Welcome to MSDN Blogs Sign in | Join | Help

Silverlight Tips of the Day

General tips of Silverlight including but limited to game programming tips. Also, general Silverlight news and announcements.
Silverlight Tip of the Day #29: Creating a Transparent, Draggable Dialog with Rounded Corners

For this tutorial we will step you through the basics of what you need to do to create a dialog that you can:

  1. Drag/drop.
  2. Add transparency (opacity) to.
  3. Round the corners.

To preview and run this application please visit this link: http://silverlight.services.live.com/invoke/66033/Border%20Demo/iframe.html. Move any of the sliders as well as mouse left click, hold and drag/drop the dialog.

Step 1: Creating a Transparent Dialog with Rounded Corners

For the background of the dialog we will use a Border control. Border controls allow you to set a CornerRadius which is used to round the corners of the border. You can also set the Opacity level which is used to indicate the level of transparency you want for the control.

This is the XAML for our Border:

<Border x:Name="BorderDialog" Opacity="0.5" CornerRadius="10" Width="300" Height="300" BorderBrush="White" Background="Black" BorderThickness="1"></Border>

Opacity goes from 0.0 – 1.0. with floating point increments used to increase the level of Opacity. For our demo, each tick on the slider is represented by a 0.01 change in opacity.

image

Borders go from 0 – 150. At 150, you will have a complete circle. At 0, the dialog will be complete square.

image

To change the values we monitor the Sliders and set the values of the Border control like this:

void _borderSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
    CornerRadius cr = new CornerRadius(_borderSlider.Value);
    _borderDialog.CornerRadius = cr;
    _borderValue.Text = ((int)_borderSlider.Value).ToString();
}
 
void _opacitySlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
    _borderDialog.Opacity = _opacitySlider.Value;
    _opacityValue.Text = Math.Round(_opacitySlider.Value, 2).ToString();
}

 Step 2. Add drag/drop functionality to the dialog using CaptureMouse().

In order to properly handle drag/drop of the dialog we will need to:

  1. Add an event for the control to monitor for MouseLeftButtonDown().
  2. Add an event for the control to monitor for MouseMove().
  3. Add an event for the control to monitor for MouseLeftButtonUp().

In MouseLeftButtonDown() we:

  1. Make a call to CaptureMouse(). This way if the mouse is moved quickly enough to escape the control we will continue to capture the mouse movement.
  2. Get  the last point where the Mouse was clicked. This way when the mouse is moved we can calculate the move difference.
  3. Get the offset into the dialog where the mouse was clicked.
  4. Set _isDragging = true so that we start processing mouse moves.
   1: void DialogSample_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
   2: {
   3:     this.CaptureMouse();
   4:  
   5:     _lastPoint = e.GetPosition(null);
   6:     double cx = (double)this.GetValue(Canvas.LeftProperty);
   7:     double cy = (double)this.GetValue(Canvas.TopProperty);
   8:     _offsetX = _lastPoint.X - cx;
   9:     _offsetY = _lastPoint.Y - cy;
  10:  
  11:     _isDragging = true;
  12:    
  13: }

In MouseMove() we:

  1. Get the current position of the mouse and calculate how far it moved based upon the last point.
  2. Update the dialog to the new position based upon the amount the mouse moved.
  3. Capture the last point we were at.
void DialogSample_MouseMove(object sender, MouseEventArgs e)
{
    if (true == _isDragging)
    {
        Point pt = e.GetPosition(null);
        double x = pt.X - _lastPoint.X;
        double y = pt.Y - _lastPoint.Y;
        double cx = (double) this.GetValue(Canvas.LeftProperty);
        double cy = (double)this.GetValue(Canvas.TopProperty);
        this.SetValue(Canvas.LeftProperty, cx+x);
        this.SetValue(Canvas.TopProperty, cy+y);
        _lastPoint = pt;
    }
}

In MouseLeftButtonUp() we:

  1. Set _isDragging = false to stop monitioring mouse moves.
  2. Release the mouse capture.
void DialogSample_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
   _isDragging = false;
   this.ReleaseMouseCapture();
}

Here is the complete code for this custom dialog control:

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Markup;
 
namespace TransparentDialog
{
    public class DialogSample : Control
    {
 
        private string _toolbarTemplate =
            "<ControlTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"" +
            "                  xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">" +
            "<Canvas x:Name=\"Toolbar\">" +
            "   <Border x:Name=\"BorderDialog\" Opacity=\"0.5\" CornerRadius=\"10\" Canvas.ZIndex=\"1\" Width=\"300\" Height=\"300\" BorderBrush=\"White\"  Background=\"Black\" BorderThickness=\"1\" >" +
            "   </Border>" +
            "   <TextBlock Canvas.ZIndex=\"10000\" Canvas.Top=\"70\"  Canvas.Left=\"50\" Foreground=\"White\">Opacity</TextBlock>" +
            "   <TextBlock Canvas.ZIndex=\"10000\" Canvas.Top=\"100\"  Canvas.Left=\"50\" Foreground=\"White\">Borders</TextBlock>" +
            "   <Slider Canvas.Top=\"70\" Canvas.Left=\"120\"  Canvas.ZIndex=\"10000\" Maximum=\"1.0\" Minimum=\"0.0\" SmallChange=\"0.01\" Value=\"0.5\" x:Name=\"OpacitySilder\" Width=\"100\"></Slider>" +
            "   <Slider Canvas.Top=\"100\" Canvas.Left=\"120\"  Canvas.ZIndex=\"10000\" Maximum=\"150.0\" Minimum=\"0.0\" SmallChange=\"1.0\" Value=\"20\" x:Name=\"BorderSilder\" Width=\"100\"></Slider>" +
            "   <TextBlock Canvas.ZIndex=\"10000\" x:Name=\"OpacityValue\" Canvas.Top=\"70\"  Canvas.Left=\"230\" Foreground=\"White\">Value</TextBlock>" +
            "   <TextBlock Canvas.ZIndex=\"10000\" x:Name=\"BorderValue\" Canvas.Top=\"100\"  Canvas.Left=\"230\" Foreground=\"White\">Value</TextBlock>" +
            "</Canvas>" +
            "</ControlTemplate>";
 
        private Canvas _dialogCanvas;
        private Border _borderDialog;
        private Slider _opacitySlider;
        private Slider _borderSlider;
        private bool _isDragging = false;
        private Point _lastPoint;
        private double _offsetX;
        private double _offsetY;
        private TextBlock _opacityValue;
        private TextBlock _borderValue;
 
        public DialogSample()
        {
            Template = (ControlTemplate)XamlReader.Load(_toolbarTemplate);
            ApplyTemplate();
        }
 
        void DialogSample_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
           _isDragging = false;
           this.ReleaseMouseCapture();
        }
 
        void DialogSample_MouseMove(object sender, MouseEventArgs e)
        {
            if (true == _isDragging)
            {
                Point pt = e.GetPosition(null);
                double x = pt.X - _lastPoint.X;
                double y = pt.Y - _lastPoint.Y;
                double cx = (double) this.GetValue(Canvas.LeftProperty);
                double cy = (double)this.GetValue(Canvas.TopProperty);
                this.SetValue(Canvas.LeftProperty, cx+x);
                this.SetValue(Canvas.TopProperty, cy+y);
                _lastPoint = pt;
            }
        }
 
        void DialogSample_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.CaptureMouse();
 
            _lastPoint = e.GetPosition(null);
            double cx = (double)this.GetValue(Canvas.LeftProperty);
            double cy = (double)this.GetValue(Canvas.TopProperty);
            _offsetX = _lastPoint.X - cx;
            _offsetY = _lastPoint.Y - cy;
 
            _isDragging = true;
           
        }
 
        public override void OnApplyTemplate()
        {
            _dialogCanvas = (Canvas)GetTemplateChild("Toolbar");
            _opacitySlider = (Slider)GetTemplateChild("OpacitySilder");
            _borderSlider = (Slider)GetTemplateChild("BorderSilder");
            _borderDialog = (Border)GetTemplateChild("BorderDialog");
            _opacityValue = (TextBlock)GetTemplateChild("OpacityValue");
            _borderValue = (TextBlock)GetTemplateChild("BorderValue");
 
            _borderSlider.Value = 20;
            _opacitySlider.Value = 0.5;
            _borderValue.Text = "20";
            _opacityValue.Text = "0.5";                
            _opacitySlider.ValueChanged += new RoutedPropertyChangedEventHandler<double>(_opacitySlider_ValueChanged);
            _borderSlider.ValueChanged += new RoutedPropertyChangedEventHandler<double>(_borderSlider_ValueChanged);
            
            this.MouseLeftButtonDown += new MouseButtonEventHandler(DialogSample_MouseLeftButtonDown);
            this.MouseMove += new MouseEventHandler(DialogSample_MouseMove);
            this.MouseLeftButtonUp += new MouseButtonEventHandler(DialogSample_MouseLeftButtonUp);
            this.SetValue(Canvas.TopProperty, (double)100);
            this.SetValue(Canvas.LeftProperty, (double)100);
 
        }
 
        void _borderSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            CornerRadius cr = new CornerRadius(_borderSlider.Value);
            _borderDialog.CornerRadius = cr;
            _borderValue.Text = ((int)_borderSlider.Value).ToString();
        }
 
        void _opacitySlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            _borderDialog.Opacity = _opacitySlider.Value;
            _opacityValue.Text = Math.Round(_opacitySlider.Value, 2).ToString();
        }
 
    }
}

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #28: How to Implement a Custom Mouse Cursor

In Tip of the Day #27 we talked about how to change the mouse cursor. But what if you want to have a cursor icon that is not supported by Silverlight? This tutorial will show you how to do it.

For a complete demo of this tip visit: http://silverlight.services.live.com/invoke/66033/Custom%20Cursor%20Demo/iframe.html

The first thing you will want to do is to hide the mouse cursor at the root level of your Silverlight application. In our Page.xaml, we set the Canvas Mouse to “None”:

<UserControl x:Class="CustomCursor.Page"
    xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation 
    xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml 
    Width="400" Height="300">
    <Canvas x:Name="LayoutRoot" Background="White" Cursor="None">
    </Canvas>
</UserControl>

Next, we implement a custom Silverlight control called CustomCursor. The implementation of this class is fairly straight forward. The constructor takes a string which points to the image resource that will represent the cursor (a JPG or PNG file). The template for this custom control is simply a Image. The cursor can be moved by calling MoveTo() with a given point on the screen.

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Markup;
 
namespace CustomCursor
{
    public class CustomCursor : Control
    {
        private const string cursorTemplate =
             "<ControlTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" " +
                      "xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">" +                    
                      "<Image x:Name=\"MyCursor\">" +
                      "</Image>"+
             "</ControlTemplate>";
 
        Image _cursor;
        string _cursorResource;
 
        public CustomCursor(string resource)
        {
            _cursorResource = resource;
            Template = (ControlTemplate)XamlReader.Load(cursorTemplate);
            ApplyTemplate();
        }
 
        public override void OnApplyTemplate()
        {
            _cursor = (Image) GetTemplateChild("MyCursor");
 
            Uri uri = new Uri(_cursorResource, UriKind.Relative);
            ImageSource imgSrc = new System.Windows.Media.Imaging.BitmapImage(uri);
            _cursor.Source = imgSrc;
        }
 
        public void MoveTo(Point pt)
        {
            this.SetValue(Canvas.LeftProperty, pt.X);
            this.SetValue(Canvas.TopProperty, pt.Y);
        }
    }
}

In our Page.xaml.cs we declare our CustomCursor and add it to the canvas. We also add an event handler to monitor for MouseMove events. When the mouse moves, we move the cursor to its new location.

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;
 
namespace CustomCursor
{
    public partial class Page : UserControl
    {
        CustomCursor _cc = new CustomCursor("MyCursor.jpg");
 
        public Page()
        {
            InitializeComponent();
 
            LayoutRoot.Children.Add(_cc);
 
            this.MouseMove += new MouseEventHandler(Page_MouseMove);
        }
 
        void Page_MouseMove(object sender, MouseEventArgs e)
        {
            _cc.MoveTo(e.GetPosition(null));
        }
    }
}

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #27: How to Change the Mouse Cursor

Let’s say you want to change the mouse cursor when hovering over a UI element. You can do this directly in the XAML by setting the Cursor property.
For example, if you want to change the cursor to be the Hand image    cursor when hovering over a button the XAML you would use would be something like this:

<Canvas >
    <Button Cursor="Hand" Width="100" Height="50" Content="Hover over me"></Button>
</Canvas>

When running the app you would see, as in the screen shot below, the mouse cursor change from the arrow to the hand.

image

To do this programmatically you could place a MouseEnter and MouseLeave event handler on the button.

<Grid x:Name="LayoutRoot" Background="White">
    <Canvas >
        <Button x:Name="myButton" MouseEnter="Button_MouseEnter" MouseLeave="Button_MouseLeave" Width="100" Height="50" Content="Hover over me"></Button>
    </Canvas>
</Grid>

Then, in the event handler you could change the cursor this way:

private void Button_MouseEnter(object sender, MouseEventArgs e)
{
    myButton.Cursor = Cursors.Hand;
}
 
private void Button_MouseLeave(object sender, MouseEventArgs e)
{
    myButton.Cursor = Cursors.Arrow;
}

Silverlight provides the following cursors through the Cursor object:

  1. Arrow 
  2. Eraser
  3. Hand 
  4. IBeam
  5. SizeNS
  6. SizeWE 
  7. Stylus
  8. Wait

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #26: How to Change the Output XAP File Name.

Don’t like the default name given to your XAP file? Good news! You can change it but there are two steps you will need to take.

Step #1. Right click on your Silverlight application node in the solutions explorer and choose “Properties”.

image  

Choose the Silverlight tab (default tab opened) and where it says Xap file name enter the new name of the XAP file you want to use.

image

Step #2. Open up the web page that hosts this Silverlight control. In my case, Tip26TestPage.aspx. Change the Source to point to the new name of the XAP.

<asp:Silverlight ID="Xaml1" runat="server" Source="~/ClientBin/NewName.xap" MinimumVersion="2.0.30523" Width="100%" Height="100%" />

That’s it, you can now build and run your application under the new XAP file name.

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #25: How to Build the XAP File to the Debug/Release Folders

By default when you build your Silverlight application you will notice the XAP file is placed in your web sites ClientBin folder:

image

This is regardless of whether you are building under Debug or Release configurations. In Visual Studio 2008 you can choose to build the XAP file to configuration specific folders.

To accomplish this, right click on your web site project node in the solution explorer and choose “Property Pages”.

image

From the Property Pages dialog, select “Silverlight Applications” in the left. Choose your project, and click “Change…” button.

image

Select Yes to the pop-up dialog.

image

If you now go back and build you will notice your XAP file is placed in the correct configuration folder.

Example for Release configuration:

image

Thank you,
--Mike Snow

 Subscribe in a reader

Terrain Tutorial Part 1 – Creating a Scrolling Map

For this tutorial we will be generating a 2D scrolling map. The view is a straight down birds eye view. As shown in Figure 1 below, each terrain tile is represented by a combine upper-left and lower-right polygon. A map can be any width and height of these tiles as shown in Figure 2. Figure 3 and 4 show a section of the map textured, one with a grid overlap toggled on. In Figure 4 you will notice a diagonal “seam”. This is an artifact caused by the way Silverlight deals with anti-aliasing. To avoid the seam, numbers must be integer values with no decimal points. This works fine with straight lines against the X and Y axis, but a diagonal line will have decimal points causing the seam to appear. When we deal with matrix transforms in part 2 of these tutorials we will get rid of the seam by scaling the textures a pixel to overlap with each other, thus hiding the seam.

Here is a demo of what we are creating. Place focus on the control than use the cursor keys to scroll it around.

 image  image

image 

Each tile will be represented by a custom control contained in a class called TerrainTile. Let’s take a look at a few points on this class:

  • Each tile is made up of two polygons.
  • The class constructor takes as parameters the X-position, Y-position, width and height of each tile.
  • Each polygon has one image brush associated with it.

TerrainTile.xaml:

<UserControl x:Class="ScrollingMap.TerrainTile"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <Canvas>
        <Polygon x:Name="UpperLeftPoly">
            <Polygon.Fill>
                <ImageBrush x:Name="UpperImage"></ImageBrush>
            </Polygon.Fill>
        </Polygon>
        <Polygon x:Name="LowerRightPoly">
            <Polygon.Fill>
                <ImageBrush x:Name="LowerImage"></ImageBrush>
            </Polygon.Fill>
        </Polygon>
    </Canvas>
</UserControl>

In the code behind, we create the points for each polygon based upon its location. We also have a function to set the image for the tile.

TerrainTile.xaml.cs

public partial class TerrainTile : UserControl
{
    public TerrainTile(int xPos, int yPos, int width, int height)
    {
        InitializeComponent();
 
        UpperLeftPoly.Points = new PointCollection();
        UpperLeftPoly.Points.Add(new Point(xPos, yPos));
        UpperLeftPoly.Points.Add(new Point(xPos+width, yPos));
        UpperLeftPoly.Points.Add(new Point(xPos, yPos+height));
 
        LowerRightPoly.Points = new PointCollection();
        LowerRightPoly.Points.Add(new Point(xPos+width, yPos));
        LowerRightPoly.Points.Add(new Point(xPos + width, yPos+height));
        LowerRightPoly.Points.Add(new Point(xPos, yPos + height));
        
    }
 
    public void SetImage(string imgResource)
    {
        Uri uri = new Uri(imgResource, UriKind.Relative);
        ImageSource imgSrc = new System.Windows.Media.Imaging.BitmapImage(uri);
        UpperImage.ImageSource = imgSrc;
 
        imgSrc = new System.Windows.Media.Imaging.BitmapImage(uri);
        LowerImage.ImageSource = imgSrc;
    }
}

To manage the different layers of the terrain I have created a class called TerrainManager. This class we:

  • We declare a two dimensional array of tiles to represent the ground layer.
  • Add a function called CreateGroundLayer() to create the ground layer.

TerrainManager.cs

public class TerrainManager
{
    const int TILE_WIDTH = 90;
 
    Canvas _mapCanvas;
    private TerrainTile[,] _groundLayer;
 
    public TerrainManager(Canvas mapCanvas)
    {
        _mapCanvas = mapCanvas;
    }
 
    public void CreateGroundLayer(int width, int height)
    {
        _groundLayer = new TerrainTile[height, width];
 
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                _groundLayer[y, x] = new TerrainTile(x * TILE_WIDTH, y * TILE_WIDTH, TILE_WIDTH, TILE_WIDTH);
                _groundLayer[y, x].SetImage("grass.png");
                _mapCanvas.Children.Add(_groundLayer[y, x]);
            }
        }
    }
}

In our Page.xaml we create a ContentControl that will contain our map Canvas. The map Canvas is what we add the tiles to. The ContentControl is what we will scroll.

Page.xaml

<UserControl x:Class="ScrollingMap.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <Canvas>
        <ContentControl x:Name="MapContent">
            <Canvas x:Name="Map"></Canvas>
        </ContentControl>
    </Canvas>
</UserControl>

In the constructor of Page.xaml.cs we add two event handlers:

  • this.Loaded: We create the terrain manager and initialize a terrain layer once the page is fully loaded.
  • this.KeyDown: We monitor for keyboard events to scroll the map around. In this demo to move you can use the arrow keys, numpad keys or Q, W, E, D, C, X, Z, and A to go NW, North, NE, East, SE, South, SW and West respectively.

Page.xaml.cs

public partial class Page : UserControl
{
    TerrainManager _terrainMgr;
    double _mapOffsetX = 0;
    double _mapOffsetY = 0;
    int _scrollSpeed = 20;
 
    public Page()
    {
        InitializeComponent();
 
        this.Loaded += new RoutedEventHandler(Page_Loaded);
        this.KeyDown += new KeyEventHandler(Page_KeyDown);
    }
 
    void Page_KeyDown(object sender, KeyEventArgs e)
    {
        switch (e.Key)
        {
            case Key.NumPad8:  //North
            case Key.W:
            case Key.Up:
                _mapOffsetY += _scrollSpeed;
                break;
            case Key.NumPad2: // South
            case Key.X:
            case Key.Down:
                _mapOffsetY -= _scrollSpeed;
                break;
            case Key.NumPad6: // East
            case Key.D:
            case Key.Right:
                _mapOffsetX -= _scrollSpeed;
                break;
            case Key.NumPad4: // West
            case Key.A:
            case Key.Left:
                _mapOffsetX += _scrollSpeed;
                break;
            case Key.NumPad7: // NW
            case Key.Q:
                _mapOffsetX += _scrollSpeed;
                _mapOffsetY += _scrollSpeed;
                break;
            case Key.NumPad9: // NE
            case Key.E:
                _mapOffsetX -= _scrollSpeed;
                _mapOffsetY += _scrollSpeed;
                break;
            case Key.NumPad3: // SE
            case Key.C:
                _mapOffsetX -= _scrollSpeed;
                _mapOffsetY -= _scrollSpeed;
                break;
            case Key.NumPad1: // SW
            case Key.Z:
                _mapOffsetX += _scrollSpeed;
                _mapOffsetY -= _scrollSpeed;
                break;
        }
        MapContent.SetValue(Canvas.LeftProperty, _mapOffsetX);
        MapContent.SetValue(Canvas.TopProperty, _mapOffsetY);
    }
 
    void Page_Loaded(object sender, RoutedEventArgs e)
    {
         _terrainMgr = new TerrainManager(Map);
         _terrainMgr.CreateGroundLayer(10, 10);
    }
}

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #24: How to Apply a XAML Template to a Class

Let’s say you have a class where you want to declare elements via a XAML template instead of dynamically creating them.

For example, the XAML to declare an image and add it to a canvas would look like this:

<Canvas x:Name="MyCanvas">
    <Image x:Name="MyImage" Source="Grass.png"></Image>
</Canvas>

To do the same thing programmatically you would need to declare the following code:

Image myImage = new Image();
Uri uri = new Uri("Grass.png", UriKind.Relative);
ImageSource imgSrc = new System.Windows.Media.Imaging.BitmapImage(uri);
myImage.SetValue(Image.SourceProperty, imgSrc);

The way to do this via a template in a class is to first put the XAML into a string like this:

private const string _contentTemplate
       = "<ControlTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"" +
           "                  xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">" +
           "<Image x:Name=\"MyImage\" Source=\"Grass.png\"></Image>" +
           "</ControlTemplate>";

Your class will need to inherit from Control:

public class MyControl : Control

Now, in the constructor of your class you can load this template via the XamlReader.Load() method. You will need to add a using statement first to reference the namespace System.Windows.Markup;

public MyControl()
{
    Template = (ControlTemplate)XamlReader.Load(_contentTemplate);
    ApplyTemplate();
}

ApplyTemplate() is where you can get the elements that are in declared in your XAML code. Override the function OnApplyTemplate like this:

private _myImage;
 
public override void OnApplyTemplate()
{
    _myImage = (Image)GetTemplateChild("MyImage");
}

Complete class:

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Markup;
 
namespace MapEditor
{
    public class MyImage : Control
    {
        Image _myImage = null;
 
        private const string _contentTemplate
               = "<ControlTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"" +
                   "                  xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">" +
                   "<Image x:Name=\"MyImage\" Source=\"Grass.png\"></Image>" +
                   "</ControlTemplate>";
 
        public override void OnApplyTemplate()
        {
            _myImage = (Image)GetTemplateChild("MyImage");
        }
 
        public MyImage()
        {
            Template = (ControlTemplate)XamlReader.Load(_contentTemplate);
            ApplyTemplate();
        }
 
    }
}

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #23 – How to Capture the Mouse Wheel Event

Silverlight currently does not support mouse wheel events. However, you can attach an event to capture the mouse wheel movement through the HtmlPage object. This tutorial will show you how to do it for IE, Opera, Mozilla and Safari browsers.

To start, we declare three events to capture the mouse event in order to cover all the possibilities for all browsers. For example, the DOMMouseScroll event is used by Mozilla.

public Page()
{
    InitializeComponent();
 
    HtmlPage.Window.AttachEvent("DOMMouseScroll", OnMouseWheel);
    HtmlPage.Window.AttachEvent("onmousewheel", OnMouseWheel);
    HtmlPage.Document.AttachEvent("onmousewheel", OnMouseWheel);
}

In our event we can get the delta change in the wheel. The way we do this in Mozilla and Safari browsers is different than how we do it in IE or Opera.

  1. Mozilla/Safari: Check the property called “detail”
  2. IE/Opera: Check the property called “wheeldelta”

Here is the complete code:

private void OnMouseWheel(object sender, HtmlEventArgs args)
{
    double mouseDelta = 0;
    ScriptObject e = args.EventObject;
 
    // Mozilla and Safari
    if (e.GetProperty("detail") != null) 
    {
        mouseDelta = ((double)e.GetProperty("detail"));
    }
    // IE and Opera
    else if (e.GetProperty("wheelDelta") != null) 
        mouseDelta = ((double)e.GetProperty("wheelDelta"));
 
    mouseDelta = Math.Sign(mouseDelta);
}

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #22 – How to add Sound Effects, Music and Video to your Silverlight App.

No game is complete without a great sound track and sound effects! This tutorial will show you how to do both with a very small amount of code.

Demo:

(Silverlight 2 RTW required).

Silverlight currently supports the following formats:

Video

  1. WMV1-3 (Windows Media Video 7, 8 & 9 respectively).
  2. WMVA (Windows Media Video Advanced Profile, non VC-1).
  3. WMVC1 (Windows Media Video Advanced Profile, VC-1).

Audio

  1. MP3 – ISO/MPEG Layer-3
    • Sampling frequencies: 8, 11.025, 12, 16, 22.05, 24, 32, 44.1, and 48 kHz
    • Bit rates: 8-320 kbps, variable bit rate.
  2. WMA 7 (Windows Media Audio 7)
  3. WMA 8 (Windows Media Audio 8)
  4. WMA 9 (Windows Media Audio 9)

To add sound, music or video you will need to declare a MediaElement. Each media file you reference must be added to your project. Select each file and change the Build Action = “Resource” in the Properties window as seen in Figure 22.1. This will ensure the media file gets copied to your ClientBin folder during execution.

image 
Figure 22.1. Properties for the Media File

Let’s start by adding three buttons and three MediaElements to our Page.xaml. One button for playing music, one for sound, and the other for video.

<Canvas Background="Black">
    <Button Click="Button_Click_Music" Canvas.Left="10" Canvas.Top="10" Width="80" Height="30" Content="Play Music"></Button>
    <Button Click="Button_Click_Sound" Canvas.Left="100" Canvas.Top="10" Width="80" Height="30" Content="Play Sound"></Button>
    <Button Click="Button_Click_Video" Canvas.Left="200" Canvas.Top="10" Width="80" Height="30" Content="Play Video"></Button>
    <MediaElement x:Name="SoundFile" Source="Boom.mp3" AutoPlay="False"></MediaElement>
    <MediaElement x:Name="MusicFile" Source="Sharon.mp3" AutoPlay="False"></MediaElement>
    <MediaElement Width="300" Height="300" Canvas.Top="100" x:Name="VideoFile" AutoPlay="False"  Source="MyVideo.wmv"></MediaElement>
</Canvas>

For our code behind:

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;
 
namespace Tip22
{
    public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();
        }
 
        private void StopAll()
        {
            MusicFile.Stop();
            SoundFile.Stop();
            VideoFile.Stop();
        }
        private void Button_Click_Music(object sender, RoutedEventArgs e)
        {
            StopAll();
            MusicFile.Play();
        }
 
        private void Button_Click_Sound(object sender, RoutedEventArgs e)
        {
            StopAll();
            SoundFile.Play();
        }
 
        private void Button_Click_Video(object sender, RoutedEventArgs e)
        {
            StopAll();
            VideoFile.Play();
        }
    }
}

Few things to notice:

  1. We set AutoPlay=”False” to prevent the media element from playing as soon as the app starts.
  2. We use the Source property to point to the name of the file we want to play.
  3. We use the x:Name attribute to identify the sound element. We can then use this ID to play the sound file. Example: VideoFile.Play().

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #21 – How to work with Shapes, Brushes and Gradients

Shapes, gradients and brushes provide the user with a lot of power to make custom controls that look beautiful. For example, gradients can be made to look like shadows or lighting for stunning visual effects. For this demo, we are going to make the beginnings of what could end up being a clock, or taken further, a gauge. The screen shot below is what we will be achieving in this tutorial.

image

Before we make this clock, let’s review the properties that will make these controls.

Shapes Elements

Silverlight supports what is called vector graphics by providing the following basic shapes:

  • Ellipse – Describes an oval or circle.
  • Rectangle – Describes a rectangle or a square (can have rounded corners).
  • Line – Describes a line that connects two points.
  • Polygon – Describes a closed shape with an arbitrary number of sides.
  • Polyline – Descries a series of connected lines that may or may not form a closed shape.
  • Path – Describes complex shapes that include curves and arcs.

Color

Color can be specified in one of the following formats:

  • Its name such as “Red”, “Blue”, “Black”, etc.
  • A 6 digit RGB (red, green, blue) notation #rrggbb where rr, gg and bb are the two digit hexadecimal value that describes the amount of color for red, green and blue respectively. Example: #FF0000 would be a bright red color.
  • A 8 digit ARGB (alpha, red, green, blue) notation with two extra values that describe the alpha value (opacity) of the color. For example, #FFFF0000 would be bright red with no opacity.

Fill & Fill

Shapes consist of two parts, the outer outline (border) and the inner part. The color of these parts are controlled through the Stroke and Fill properties. Some shapes such as a Line only have a stroke. Specifying a Fill for a line would have no affect.

image

Brushes

Using <Ellipse.Fill> we can fill this ellipse using one of the following brush options:

  • A linear gradient brush – Paints a gradient along a line. The line is diagonal and stretches from the upper left to the lower right corner by default. The properties StartPoint and EndPoint can change these positions.
  • A Image brush – Paints with an image.
  • A radial gradient brush – Paints a gradient along a circle. By default, the circle is centered on the area being painted.
  • A solid color brush – Paints an area with a solid color.
  • A video brush – Paints an area with live streaming video.

Here is an example of each:

image

As appears in source code:

<MediaElement Canvas.Top="300" x:Name="MyMedia" Source="MyVideo.wmv" Width="300" Height="300" />
 
<Ellipse Canvas.Top="20" Canvas.Left="5" Width="100" Height="100" StrokeThickness="2">
    <Ellipse.Fill>
        <LinearGradientBrush
        StartPoint='0.1,0.06'
        EndPoint='0.5,0.6'>
            <GradientStop Color='#FFFFFFFF' Offset='0'/>
            <GradientStop Color='#FF000000' Offset='1'/>
        </LinearGradientBrush>
    </Ellipse.Fill>
</Ellipse>
 
<Ellipse  Canvas.Top="20" Canvas.Left="105" Width="100" Height="100" StrokeThickness="2">
    <Ellipse.Fill>
        <ImageBrush ImageSource="clock.png" Stretch="Uniform"></ImageBrush>
    </Ellipse.Fill>
</Ellipse>
 
<Ellipse Canvas.Top="20" Canvas.Left="205" Width="100" Height="100" StrokeThickness="2">
    <Ellipse.Fill>
        <RadialGradientBrush>
            <GradientStop Color="Yellow" Offset="0.0" />
            <GradientStop Color="Red" Offset="1.0" />
        </RadialGradientBrush>
    </Ellipse.Fill>
</Ellipse>
 
<Ellipse Canvas.Top="20" Canvas.Left="305" Width="100" Height="100" StrokeThickness="2">
    <Ellipse.Fill>
        <SolidColorBrush Color="Blue"/>
    </Ellipse.Fill>
</Ellipse>
 
<Ellipse Canvas.Top="20" Canvas.Left="405" Width="100" Height="100" StrokeThickness="2">
    <Ellipse.Fill>
        <VideoBrush SourceName="MyMedia" />
    </Ellipse.Fill>
</Ellipse>

For the Video brush, make certain the video file is placed in your web sites ClientBin folder.

On to making the clock! We start with a simple ellipse in a <Canvas> object setting it’s Width and Height to 200. For the Fill property, we will apply the LinearGradientBrush

<Grid x:Name="LayoutRoot" Background="Gray">
    <Canvas x:Name="Clock">
        <Ellipse Canvas.Top="150" Canvas.Left="5" Width="200" Height="200" StrokeThickness="2">
            <Ellipse.Fill>
                <LinearGradientBrush StartPoint='0.1,0.06' EndPoint='0.5,0.6'>
                    <GradientStop Color='#FFFFFFFF' Offset='0'/>
                    <GradientStop Color='#FF000000' Offset='1'/>
                </LinearGradientBrush>
            </Ellipse.Fill>
        </Ellipse>
    </Canvas>
</Grid>

 

This code will generate the following circle:

image

Let’s add another small circle in the middle plus 2 sets of 3 red lines to make the the hour and minute hands for the clock.

            <Line X1="100"  X2="25"  Y1="246" Y2="247" Stroke="Red" StrokeThickness="1" />
            <Line X1="100"  X2="25"  Y1="247" Y2="247" Stroke="Red" StrokeThickness="1" />
            <Line X1="100"  X2="25"  Y1="248" Y2="247" Stroke="Red" StrokeThickness="1" />
 
            <Line X1="106"  X2="60"  Y1="246" Y2="290" Stroke="Red" StrokeThickness="1" />
            <Line X1="106"  X2="60"  Y1="247" Y2="290" Stroke="Red" StrokeThickness="1" />
            <Line X1="106"  X2="60"  Y1="248" Y2="290" Stroke="Red" StrokeThickness="1" />
 
            <Ellipse Width="15" Height="15" Canvas.Left="100" Canvas.Top="240">
                <Ellipse.Fill>
                    <LinearGradientBrush
                        StartPoint='0.1,0.06'
                        EndPoint='0.5,0.6'>
                        <GradientStop Color='#FFFFFFFF' Offset='0'/>
                        <GradientStop Color='#FF000000' Offset='1'/>
                    </LinearGradientBrush>
                </Ellipse.Fill>
            </Ellipse>

image

Finally, for the numbers we will programmatically generate and add them as Textblocks. We do this by calculating the X & Y location along a circle with a radius of 85 pixels. Each number is separated by 30 degrees. We start at 1:00 which is located at an angle of 300 degrees.

   1: private void AddNumbers()
   2: {
   3:     double angle = 300;
   4:     for (int i = 1; i < 13; i++)
   5:     {
   6:         TextBlock tb = new TextBlock();
   7:         tb.Foreground = new SolidColorBrush(Colors.White);
   8:         tb.Text = i.ToString();
   9:  
  10:         double radians = angle * (Math.PI / 180);
  11:         double radius = 85;
  12:         int x = (int)(radius * Math.Cos(radians));
  13:         int y = (int)(radius * Math.Sin(radians));
  14:         tb.SetValue(Canvas.LeftProperty, (double) x+97);
  15:         tb.SetValue(Canvas.TopProperty, (double) y+239);
  16:  
  17:         angle += 30;
  18:         Clock.Children.Add(tb);
  19:     }
  20: }

Here is our final result:

image

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #20 – How to Increase your Isolated Storage Quota.

Each application by default is given 1 MB of storage space through Isolated Storage where the server is able to store client specific data on the clients machine. So what if you need more than 1 MB? Fortunately, the IsolatedStorageFile object provides a method called IncreaseQuotaTo() that allows a server to prompt the user for permission to increase the amount of storage.

You can see the list of applications that are using IsolatedStorage on your box. To do this:

  1. Right click on any Silverlight Application and from the context menu choose the only option: “Silverlight Configuration”.

SC

  1. This will bring up the Silverlight Configuration Dialog: 

    image
  2. Click on the last tab “Application Storage” and you will see a list of all web sites that are using Isolated Storage on this machine. The current amount of space each app is using as well as the quota allowed per app are also listed. 

    image

For our demo we will be displaying 3 pieces of information:

  1. Current Spaced Used
  2. Current Space Available
  3. Current Quota

I have added a text box where you can specify the number of bytes you wish to increase your quota by. By clicking on the button, this will then make the necessary call to IncreaseQuotaTo() in order to increase the quota.

***Note: You must call this function from a user event such as a button click. For security reasons, calling IncreaseQuotaTo() directly will automatically return false since the user did not instantiate the call.

Below is a screen shot of the test application we are creating:

3

When you enter a value and click on the button you will be prompted with this dialog:

4

If you click yes, you will see the new results:

image

Now, let’s look at our XAML code in Page.xaml that we use to set up this interface:

<UserControl x:Class="IncreaseIsolatedStorage.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="800" Height="600">
<Grid x:Name="LayoutRoot" Background="White">
    <Canvas Canvas.Left="10" Canvas.Top="10">
        <TextBlock Canvas.Left="10" x:Name="SpacedUsed" >Current Spaced Used=</TextBlock>
        <TextBlock Canvas.Left="10" x:Name="SpaceAvaiable" Canvas.Top="20">Current Space Available=</TextBlock>
        <TextBlock Canvas.Left="10" x:Name="CurrentQuota" Canvas.Top="40">Current Quota=</TextBlock>
        <TextBlock Canvas.Left="10" x:Name="NewSpace" Canvas.Top="70">New space (in bytes) to request=</TextBlock>
        <TextBox Canvas.Left="255" Canvas.Top="70" Width="100" x:Name="SpaceRequest"></TextBox>
        <TextBlock Canvas.Left="365" Canvas.Top="70" Width="60">(1048576 = 1 MB)</TextBlock>
        <Button Canvas.Left="10" Content="Increase Storage" Canvas.Top="100" 
                Width="100" Height="50" Click="Button_Click">                
        </Button>
        <TextBlock Canvas.Left="10" Canvas.Top="160" x:Name="Results"></TextBlock>
    </Canvas>
</Grid>
</UserControl>

And our code behind:

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 System.IO.IsolatedStorage;
 
namespace SilverlightApplication11
{
    public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();
 
            SetStorageData();
        }
 
        private void SetStorageData()
        {
            using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
            {
                SpacedUsed.Text     = "Current Spaced Used = "+(isf.Quota - isf.AvailableFreeSpace).ToString() +" bytes";
                SpaceAvaiable.Text  = "Current Space Available=" + isf.AvailableFreeSpace.ToString() + " bytes";
                CurrentQuota.Text   = "Current Quota=" + isf.Quota.ToString() + " bytes";
            }
        }
        
        private void IncreaseStorage(long spaceRequest)
        {
            using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
            {
                long newSpace = isf.Quota + spaceRequest; 
                try
                {
                    if (true == isf.IncreaseQuotaTo(newSpace))
                    {
                        Results.Text = "Quota successfully increased.";
                    }
                    else
                    {
                        Results.Text = "Quota increase was unsuccessfull.";
                    }
                }
                catch (Exception e)
                {
                    Results.Text = "An error occured: "+e.Message;
                }
                SetStorageData();
            }
        }
 
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                long spaceRequest = Convert.ToInt64(SpaceRequest.Text);
                IncreaseStorage(spaceRequest);
            }
            catch { // User put bad data in text box }
        }
    }
}

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #19: Using Isolated Storage

Silverlight uses Isolated Storage as a virtual file system to store data in a hidden folder on your machine. It breaks up the data into two separate sections: Section #1 contains administrative information such as disk quota and section #2 contains the actual data. Each Silverlight application is allocated its own portion of the storage with the current quota set to be 1 MB per application.

Advantages:

  1. Isolated Storage is a great alterative to using cookies (as discussed in Tip of the Day #18) especially if you are working with large sets of data. Examples of use include undo functionality for your app, shopping cart items, window settings and any other setting your application can call up the next time it loads.
  2. Isolated storage stores by user allowing server applications to dedicate unique settings per individual user.

Possible Pitfalls:

  1. Administrators can set disk quota per user and assembly which means there is no guarantee on space available. For this reason, it is important to add exception handling to your code.
  2. Even though Isolated Storage is placed in a hidden folder it is possible, with a bit of effort, to find the folder. Therefore the data stored is not completely secure as users can change or remove files. It should be noted though that you can use the cryptography classes to the encrypt data stored in isolated storage preventing users from changing it.
  3. Machines can be locked down by administrative security policies preventing applications from writing to the IsolatedStorage. More specifically, code must have the IsolatedStorageFilePermission to work with isolated storage.

All that said, let’s take a look at how we save and load data. Note that you will need to add a using statement to reference the namespace System.IO.IsolatedStorage as well as System.IO.

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 System.IO.IsolatedStorage;
using System.IO;
 
namespace SilverlightApplication10
{
    public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();
            SaveData("Hello There", "MyData.txt");
            string test = LoadData("MyData.txt");
        }
 
        private void SaveData(string data, string fileName)
        {
            using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
            {
                using (IsolatedStorageFileStream isfs = new IsolatedStorageFileStream(fileName, FileMode.Create, isf))
                {
                    using (StreamWriter sw = new StreamWriter(isfs))
                    {
                        sw.Write(data);
                        sw.Close();
                    }
                }
            }
        }
 
        private string LoadData(string fileName)
        {
            string data = String.Empty;
            using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
            {
                using (IsolatedStorageFileStream isfs = new IsolatedStorageFileStream(fileName, FileMode.Open, isf))
                {
                    using (StreamReader sr = new StreamReader(isfs))
                    {
                        string lineOfData = String.Empty;
                        while ((lineOfData = sr.ReadLine()) != null)
                            data += lineOfData;
                    }
                }
            }
            return data;
        }
    }
}

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #18: How to Set Browser Cookies.

Cookies are strings of text that the server can store on the client side. These cookies can then be sent back by the client to the server each time the client accesses that server again. Cookies are commonly used for session tracking, authentication, site preferences and maintaining specific information about users.  For example, items a client stores in a shopping cart can be stored on the client side as cookies so that they can leave the online store and return later to check out.

So, how do we set cookies from within a Silverlight application? To accomplish this we turn again to the HtmlPage.Document object. To use this object you must add a using statement to reference the System.Windows.Browser namespace.

To set a cookie we need to call SetProperty() with a string in the following format: “Key=Value;expires=ExpireDate.”

For example:

private void SetCookie(string key, string value)
{
    // Expire in 7 days
    DateTime expireDate = DateTime.Now + TimeSpan.FromDays(7);
 
    string newCookie = key + "=" + value + ";expires=" + expireDate.ToString("R");
    HtmlPage.Document.SetProperty("cookie", newCookie);
}

Now, to get the cookie we split up and iterate through all the cookies returned through the property HtmlPage.Document.Cookies.

For example:

private string GetCookie(string key)
{
    string[] cookies = HtmlPage.Document.Cookies.Split(';');
 
    foreach (string cookie in cookies)
    {
        string [] keyValue = cookie.Split('=');
        if (keyValue.Length == 2)
        {
            if(keyValue[0].ToString() == key)
                return keyValue[1];
        }
    }
    return null;
}

For more details on additional properties you can set when creating cookies please see MSDN.

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #17: How to Animate a Rotating Image

Each Silverlight element exposes a property called RenderTransform that is used to set the transform information that affects the rendering position of the element. I will be demo’ing a non-stop circular transform rotation of an image as seen here below:

  (Silverlight 2 RTM required).

First, let’s declare the the image in our Page.xaml. Make certain to add the image your are setting the source to here to your project in VS. Since we are rotating around the center of the image, we set the CenterX and CetnerY to be the center coordinates of the image which. In my case the image I am using is 64x48 pixels so the center is set at CenterX=32, CenterY=24.

In Page.XAML replace <Grid></Grid> with the following:

<Canvas Background="Black">
    <Image x:Name="FireballLogo" Source="images/Fireballlogo.png">
        <Image.RenderTransform>
            <RotateTransform x:Name="FireballTransform" CenterX="32" CenterY="24"></RotateTransform>
        </Image.RenderTransform>
    </Image>
</Canvas>

Next, let’s setup our game loop timer using CompositionTarget.Rendering as our looping event. We perform the transform around the center of the image which is 32, 24 since the image is 64x48 in size. For each frame, we increment the angle by one.

namespace Tip17
{
    public partial class Page : UserControl
    {
        public Page()
        {
            InitializeComponent();
 
            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
        }
 
        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            FireballTransform.Angle += 1; 
            FireballTransform.Transform(new Point(32, 24)); 
        }
    }
}

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #16 - StoryBoard versus DispatcherTimer for Animation and Game Loops.

In Tip of the Day #5 I discussed how to use the DispatcherTimer for your main game loop. However, a better approach may be to use the StoryBoard timer as discussed here or the CompositionTarget.Rendering event which was recently added to Silverlight 2. Check out Tip of the Day #50 for more info on using the CompositionTarget.Rendering event for your main game loop.

From my research, the reasons the StoryboardTimer is better than the DispatcherTimer is as follows:

  1. The StoryBoard is handled on a separate thread that is not affected by the UI thread which the DispatcherTimer is on.
  2. The DispatcherTimer is a lower resolution timer than the timer behind the Storyboard class, which causes loss in fidelity.
  3. The Storyboard execution is more stable across the different supported OS’s and web browsers.

Given that, let’s take a look at how it can be done. In the example below, we create a StoryBoard timer and increment and display a count variable that represents the number of times the MainGameLoop was called. I have set my duration between calls to be zero milliseconds but you will want to change this to whatever best fits your animation story.

Page.xaml.cs:

namespace SilverlightApplication8
{
    public partial class Page : UserControl
    {
        Storyboard _gameLoop = new Storyboard();
        int count = 0;
 
        public Page()
        {
            InitializeComponent();
            _gameLoop.Duration = TimeSpan.FromMilliseconds(0);
            _gameLoop.Completed += new EventHandler(MainGameLoop);
            _gameLoop.Begin();
        }
 
        void MainGameLoop(object sender, EventArgs e)
        {
            // Add any game logic/animation here.
            // Example:
            myTextbox.Text = count.ToString();
            count++;
 
            // Continue storyboard timer
            _gameLoop.Begin();
        }
    }
}
Page.xaml: