Tutorial on Silverlight 4 databinding in code-behind, custom user controls, etc.

Introduction

This small tutorial was written to show the students the following aspects of Silverlight:

  • Writing a class that can be used for databinding
  • Perform data-binding through code instead of XAML
  • Creating a custom user control
  • Writing simple data converters

Suppose we are creating a Silverlight game in which each player is represented as a pawn. However, the player class itself is somewhere deep inside the game-engine and we would like the pawn user control to be only loosely coupled to this player class. By doing this, we are able to make a rapid Silverlight prototype and if we later decide that the frontend is pretty lame, we can simply redesign it without too much fuss.

Player class

We create a small class that represents a player, with its name, color and location:

  public class Player
    {
        private string name;
        public string Name {
            get { return name; }
            set { name = value; }
        }

        private Point location;
        public Point Location {
            get { return location; }
            set { location = value; }
        }

        private Color color;
        public Color Color {
            get { return color; }
            set { color = value; }
        }

    }

For two-way databinding to work in Silverlight (and WPF) the Player class needs to implement the INotifyPropertyChanged interface:

  public class Player: INotifyPropertyChanged
    {
        private string name;
        public string Name {
            get { return name; }
            set {
                name = value;
                NotifyPropertyChanged("Name");
            }
        }

        private Point location;
        public Point Location {
            get { return location; }
            set {
                location = value;
                NotifyPropertyChanged("Location");
            }
        }

        private Color color;
        public Color Color {
            get { return color; }
            set {
                color = value;
                NotifyPropertyChanged("Color");
            }
        }

        //Notify
        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs(propertyName));
            }
        }
    }

We can now create a Player object anywhere it’s needed, e.g. :

Player player1 = new Player() {
    Location = new Point(0, 0),
    Name = "Tim",
    Color=Colors.Blue };

Creating a user control

We create a custom user control that will represent the player in the game. Right-click your project and choose “Add new item…”. Next pick Silverlight User Control and give the control a meaningful name, such as pawn.

Set the DesignHeight and DesignWidth to 30 and then insert the following the XAML-code:

    <Grid x:Name="LayoutRoot" Background="{x:Null}" >
        <Ellipse x:Name="playerEllipse"   Stroke="Black" 
                 StrokeThickness="2" Height="30"  Width="30" Fill="#FFFF1717"/>
   </Grid>
  • By defining Background=”{x:Null} we make sure that the background of our control is transparent and thus will blend nicely on the game-board.
  • It is important to explicitly name each element if we wish to be able to bind certain properties to it later on.

Adding the user control to a canvas

Suppose we define a canvas somewhere on our MainPage.xaml:

<Canvas x:Name="playboardCanvas" Background="#FFD7FF07" 
                Width="400" Height="200">

Yeah, it’s a very ugly color, but let’s keep the design to other people.

If we wish to add the newly created user control to this canvas we need to perform the following steps:

1.       Create a new instance of the usercontrol

2.       Define any bindings needed

3.       Add the control to the children of the canvas

This results in:

//Step 1
Pawn pawn = new Pawn();
//Step 2: bindings and datacontext comes here (discussed further on)
//Step 3
playboardCanvas.Children.Add(pawn);

Binding the pawn control to the player class

In order for the pawn to be bound to the player, we  first point the pawns datacontext to the player:

    pawn.DataContext = player1;

We then create a binding object in which we will bind the location of the player to the location of the pawn on the canvas.

    //Bind location.X
    Binding c = new Binding();
    c.Source = player1;
    c.Path = new PropertyPath("Location.X");
    c.Mode = BindingMode.OneWay;
    pawn.SetBinding(Canvas.LeftProperty, c);

We do the same for the Y-coordinate, only this one needs to be bound to the TopProperty of the pawn:

    pawn.SetBinding(Canvas.TopProperty, c);

Writing a convertor

Suppose we defined the Location of our player to be an (x,y)coordinate between (0,0) and (8,8) (for example to define a pawn on a checkerboard). Our previously databound pawn would then be able to move between the (0,0) and (8,8) zone on the canvas…that’s a pretty small canvas.

We’ll write convertor that takes the actual dimensions of the canvas on the screen in account. The convertor will then transform the Location of the player to an equivalent location on the canvas.

The convertor is pretty straightforward. value will contain the X or Y coordinate of the player, and the extra parameter will contain a reference to the canvas on which the pawn is drawn:

    public class CanvasLocationWidthConvertor : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {

            Canvas canv = (Canvas)parameter;
            return (double)value * (canv.ActualWidth / 5);
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

We now simply add the convertor to the binding object we created earlier, so our binding code now is:

    //Bind location.Y
    Binding c = new Binding();
    c.Source = player1;
    c.Path = new PropertyPath("Location.X");
    c.Mode = BindingMode.OneWay;
    c.Converter = new CanvasLocationWidthConvertor();
    c.ConverterParameter = playboardCanvas;
    pionCanvas.SetBinding(Canvas.LeftProperty, c);

Binding the color

To bind the color of the player object to the pawn, we write the following binding in which the fillproperty of the ellipse is bound to the Color property:

    Binding e = new Binding();
    e.Source = player1;
    e.Path = new PropertyPath("Color");
    e.Mode = BindingMode.OneWay;
    e.Converter = new PlayerColorConvertor();
    pionCanvas.pionEllipse.SetBinding(Ellipse.FillProperty, e);

Since the FillProperty is defined by a SolidColorBrush instead of a Color we have to write a small convertor for that. Again, pretty straightforward:

    public class PlayerColorConvertor : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return new SolidColorBrush((Color)value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

    }

Binding to a grid

The fun thing of databinding in Silverlight (and WPF) is that we kind bind any property of an object to any property of an XAML element. Suppose we defined a 5-by-5 checkerboard grid in xaml (note: make your life easy and write this kind of stuff in the code behind using some loops) :

<Grid x:Name="playGrid" >
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Rectangle Grid.Row="0" Grid.Column="0" Fill="Black"></Rectangle>
            <Rectangle Grid.Row="0" Grid.Column="2" Fill="Black"></Rectangle>
            <Rectangle Grid.Row="0" Grid.Column="4" Fill="Black"></Rectangle>
            <Rectangle Grid.Row="1" Grid.Column="1" Fill="Black"></Rectangle>
            <Rectangle Grid.Row="1" Grid.Column="3" Fill="Black"></Rectangle>
            <Rectangle Grid.Row="2" Grid.Column="0" Fill="Black"></Rectangle>
            <Rectangle Grid.Row="2" Grid.Column="2" Fill="Black"></Rectangle>
            <Rectangle Grid.Row="2" Grid.Column="4" Fill="Black"></Rectangle>
            <Rectangle Grid.Row="3" Grid.Column="1" Fill="Black"></Rectangle>
            <Rectangle Grid.Row="3" Grid.Column="3" Fill="Black"></Rectangle>
            <Rectangle Grid.Row="4" Grid.Column="0" Fill="Black"></Rectangle>
            <Rectangle Grid.Row="4" Grid.Column="2" Fill="Black"></Rectangle>
            <Rectangle Grid.Row="4" Grid.Column="4" Fill="Black"></Rectangle>
</Grid>

Simply bind the X and Y coordinates of the player to the respective Grid.Row and Grid.Column properties of the playGrid object, e.g.:

    //Bind location.X
    Binding c2 = new Binding();
    c2.Source = player1;
    c2.Path = new PropertyPath("Location.X");
    c2.Mode = BindingMode.OneWay;
    playGrid.SetBinding(Grid.RowProperty,c2);

More information

http://msdn.microsoft.com/nl-nl/magazine/cc700358%28en-us%29.aspx
Neathighlighter

4 gedachten over “Tutorial on Silverlight 4 databinding in code-behind, custom user controls, etc.

  1. Hi Tim,

    I can’t get the pawn to render in the correct row or column of the grid for some reason, for example, if I may player is located at point (2,3) …

    Also pionCanvas is this the pawn user control?

    Could you post the full solution code?

    Thanks
    Leon

    Like

    1. “Also pionCanvas is this the pawn user control?”
      Whoops, you just discovered a translationtypo 🙂 This should be playerboardCanvas, which is the canvas where we are placing the ellipses.

      Like

  2. Pingback: DotNetShoutout

Plaats een reactie

Deze site gebruikt Akismet om spam te bestrijden. Ontdek hoe de data van je reactie verwerkt wordt.