Silverlight Tips of the Day - Blog by Mike Snow

Game Programming with Silverlight

June 2008 - Posts

Silverlight Tip of the Day #13 - How to Get an Images Dimensions in Silverlight.

Since Silverlight is based upon an asynchronized model, getting an image width and height immediately after loading it is not possible. However, you can monitor its download progress and obtain the image dimensions once the progress has reached 100%.

Below is a sample that demonstrates how to do this. Few important notes:

  • BitmapImage requires the namespace System.Windows.Media.Imaging;
  • ActualWidth and ActualHeight are calculated values. The calculation happens after a layout pass. Therefore, the image must be in the tree in order for hits size to be accounted for. Thus my call to GameCanvas.Children.Add(grass);
  • The image you are adding to your canvas must be first added to your Silverlight application project in Visual Studio. Make certain your path to this image is correct.
  • The call to Dispatcher.BeginInvoke(delegate() { … } ); is required before you obtain the Width/Height or they might be intermittently zero.
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.Windows.Media.Imaging;
 
namespace SilverlightApplication12
{
    
    public partial class Page : UserControl
    {
        private Image grass;
 
        public Page()
        {
            InitializeComponent();
            LoadImage("grass.png");
        }
 
        private void LoadImage(string path)
        {
            Uri uri = new Uri(path, UriKind.Relative);
            BitmapImage bitmapImage = new BitmapImage();
            bitmapImage.UriSource = uri;
            bitmapImage.DownloadProgress += 
                new EventHandler<DownloadProgressEventArgs>(bitmapImage_DownloadProgress);
            
            grass = new Image();
            grass.Source = bitmapImage;
            GameCanvas.Children.Add(grass);
        }
 
        void bitmapImage_DownloadProgress(object sender, DownloadProgressEventArgs e)
        {
            if (e.Progress == 100)
            {
                Dispatcher.BeginInvoke(delegate()
                {
                   double height = grass.ActualHeight;
                   double width = grass.ActualWidth;
                });
            }
        }
    }
}

Page.xaml:

<UserControl x:Class="SilverlightApplication12.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <ContentControl x:Name="MyContent">
            <Canvas x:Name="GameCanvas" ></Canvas>
        </ContentControl>                              
    </Grid>
</UserControl>

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #12 - Full Implementation of a Silverlight Policy Server.

In Silverlight Beta 1 you could only use sockets to connect to the same host the Silverlight application was served up from. In Beta 2 a change was made to allow you to connect to any server. However, before the Silverlight application can connect to the server it must first successfully connect to a policy server on that machine in order to proceed with the connection.

Below I will take you through every step you need to create and run your own policy server.

To start, create a new C# console application.  Then, create a new XML file called “clientaccesspolicy.xml” and add it to your project. This is the file your Policy Server will send to a client giving it permission to proceed with the connection.

The contents of the “clientaccesspolicy.xml” are as follows:

<?xml version="1.0" encoding="utf-8" ?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from>
        <domain uri="*"/>
      </allow-from>
      <grant-to>
        <socket-resource port="4502-4534" protocol="tcp"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

Note that with Silverlight, sockets are limited to ports #4502-4534.  Also, the policy server that sends this policy file to the connecting client must be run on port 943.

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
 
namespace PolicyServer
{
    // Encapsulate and manage state for a single connection from a client
    class PolicyConnection
    {
        private Socket _connection;
        private byte[] _buffer; // buffer to receive the request from the client       
        private int _received;
        private byte[] _policy; // the policy to return to the client
 
        // the request that we're expecting from the client
        private static string _policyRequestString = "<policy-file-request/>";
 
        public PolicyConnection(Socket client, byte[] policy)
        {
            _connection = client;
            _policy = policy;
 
            _buffer = new byte[_policyRequestString.Length];
            _received = 0;
 
            try
            {
                // receive the request from the client
                _connection.BeginReceive(_buffer, 0, _policyRequestString.Length, SocketFlags.None,
                    new AsyncCallback(OnReceive), null);
            }
            catch (SocketException)
            {
                _connection.Close();
            }
        }
 
        // Called when we receive data from the client
        private void OnReceive(IAsyncResult res)
        {
            try
            {
                _received += _connection.EndReceive(res);
 
                // if we haven't gotten enough for a full request yet, receive again
                if (_received < _policyRequestString.Length)
                {
                    _connection.BeginReceive(_buffer, _received, _policyRequestString.Length - _received,
                        SocketFlags.None, new AsyncCallback(OnReceive), null);
                    return;
                }
 
                // make sure the request is valid
                string request = System.Text.Encoding.UTF8.GetString(_buffer, 0, _received);
                if (StringComparer.InvariantCultureIgnoreCase.Compare(request, _policyRequestString) != 0)
                {
                    _connection.Close();
                    return;
                }
 
                // send the policy
                Console.Write("Sending policy...\n");
                _connection.BeginSend(_policy, 0, _policy.Length, SocketFlags.None,
                    new AsyncCallback(OnSend), null);
            }
            catch (SocketException)
            {
                _connection.Close();
            }
        }
 
        // called after sending the policy to the client; close the connection.
        public void OnSend(IAsyncResult res)
        {
            try
            {
                _connection.EndSend(res);
            }
            finally
            {
                _connection.Close();
            }
        }
    }
 
    // Listens for connections on port 943 and dispatches requests to a PolicyConnection
    class PolicyServer
    {
        private Socket _listener;
        private byte[] _policy;
 
        // pass in the path of an XML file containing the socket policy
        public PolicyServer(string policyFile)
        {
 
            // Load the policy file
            FileStream policyStream = new FileStream(policyFile, FileMode.Open);
 
            _policy = new byte[policyStream.Length];
            policyStream.Read(_policy, 0, _policy.Length);
            policyStream.Close();
 
 
            // Create the Listening Socket
            _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
                ProtocolType.Tcp);
 
            _listener.SetSocketOption(SocketOptionLevel.Tcp, (SocketOptionName)
                SocketOptionName.NoDelay, 0);
 
            _listener.Bind(new IPEndPoint(IPAddress.Any, 943));
            _listener.Listen(10);
 
            _listener.BeginAccept(new AsyncCallback(OnConnection), null);
        }
 
        // Called when we receive a connection from a client
        public void OnConnection(IAsyncResult res)
        {
            Socket client = null;
 
            try
            {
                client = _listener.EndAccept(res);
            }
            catch (SocketException)
            {
                return;
            }
 
            // handle this policy request with a PolicyConnection
            PolicyConnection pc = new PolicyConnection(client, _policy);
 
            // look for more connections
            _listener.BeginAccept(new AsyncCallback(OnConnection), null);
        }
 
        public void Close()
        {
            _listener.Close();
        }
    }
 
    public class Program
    {
        static void Main()
        {
            Console.Write("Starting...\n");
            PolicyServer ps =
                new PolicyServer(@"<path to your file>\clientaccesspolicy.xml");
            System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
        }
    }
}

The code for the policy server itself is as follows:You will need to change where it says "<path to your file>\clientaccesspolicy.xml" to be the actual path to where you saved this file on your server.

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #11 – How to make your Silverlight Control Transparent.

In order to make your Silverlight control transparent to the background of your web site you have to do two things.

From an ASPX page:

  1. Add the tag PluginBackground="Transparent" to your Silverlight control.
  2. Add the tag Windowless="true" to your Silverlight control.

Example:

<asp:Silverlight PluginBackground="Transparent" Windowless="true" ID="Xaml1" runat="server" Source="~/ClientBin/SilverlightApplication4.xap" MinimumVersion="2.0.30523"  />

From an HTML page:

  1. Add the param: <param name="background" value="Transparent" /
  2. Add the param: <param name="windowless" value="true" />

Example:

<object data="data:application/x-silverlight," type="application/x-silverlight-2-b2" >
<param name="source" value="ClientBin/SilverlightApplication4.xap"/>
<param name="onerror" value="onSilverlightError" />
<param name="pluginbackground" value="Transparent" />
<param name="windowless" value="true" /> 
</object>

Notes:

By default your Silverlight control takes up the entire page. This is because the Width and Height are set to 100%. If you want the control to only consume part of the page change these values to represent what you want. For example, to make the control 400x400 pixels on the page you can set it like this:

<asp:Silverlight Width="400px" Height="400px" PluginBackground="Transparent" Windowless="true" ID="Xaml1" runat="server" Source="~/ClientBin/SilverlightApplication4.xap" MinimumVersion="2.0.30523"  />

Thank you,
--Mike Snow

 Subscribe in a reader

Silverlight Tip of the Day #10 – Dynamically loading and displaying images in Beta 2.

In Beta 1 you were able to load an image by directly specifying the path:

Image image = new Image();
image.SetValue(Image.SourceProperty, "images/myImage.png");

However, this has been changed for Beta 2 and you will now need to make use of an ImageSource and Uri object when loading your image. Make certain to add the image to your project or else it will fail to load.

   1: Image image = new Image();
   2: Uri uri = new Uri("images/myImage.png", UriKind.Relative);
   3: ImageSource img = new System.Windows.Media.Imaging.BitmapImage(uri);
   4: image.SetValue(Image.SourceProperty, img)

Now, to display the image you will need to add it to the Children of an element that you have declared in your XAML. For example, let’s say you have created a Canvas object in your Page.xaml under the parent Grid object. Using the x:Name tag, give it the name “Map”: