August 2008 - Posts
By default your Silverlight application is aligned to the left of your browser. How cool though would it be if you could keep your Silverlight application centered in the browser?
Run this demo here: http://silverlight.live.com/ContentTestPage.aspx?setName=Keep+me+centered
There are a variety of ways to do this, below is one such technique.
- To start, open your web page that contains your Silverlight control.
- Move your Silverlight control into a table with a single cell or into a div. Add a style to the table setting the margin-left and margin-right to auto.
- You will also need to set the <td> width and height to the size of your Silverlight control otherwise the control will not render.
Example:
<body>
<form id="form1" runat="server" style="height: 100%;">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<table style="margin-left: auto; margin-right: auto;">
<tr>
<td style="width: 600px; height: 300px;">
<asp:Silverlight ID="Xaml1" PluginBackground="Transparent" runat="server" Source="~/ClientBin/SilverlightDev.xap"
MinimumVersion="2.0.30523" Width="100%" Height="100%" />
</td>
</tr>
</table>
</form>
</body>
Alternatively, you can use a div:
<div style="width: 100%; margin: 0">
<div style="margin: auto; width: 600px; height: 300px">
<asp:Silverlight ID="Xaml1" runat="server" Source="~/ClientBin/SilverlightDev.xap" MinimumVersion="2.0.30523" Width="100%" Height="100%" />
</div>
</div>
That’s it! Your Silverlight control will now stay centered in your browser.
Screen Shots:
Thank you,
--Mike Snow
Subscribe in a reader
Let’s say you have a Silverlight application that you want to be scaled to the same width and height of your browser window. This way the application takes up the entire window and not just a fixed sized within the window. To illustrate this I have created a sample application that has a number of random UI elements on it.
You can preview and run this application here: http://silverlight.services.live.com/invoke/66033/Page%20Scaling/iframe.html
Also, the following screen shots show this sample application scaled to different sizes in the browser (tall, normal, wide). As you can see, each UI element in the application is scaled proportionally to the browser size.

In order to accomplish this, all you have to do is add a RenderTransform of the type ScaleTransform to your Grid or Canvas of your Silverlight control.
For example, add the following code to your Page.xaml file:
<Canvas>
<Canvas.RenderTransform>
<ScaleTransform x:Name="CanvasScale" ScaleX=”1” ScaleY=”1”></ScaleTransform>
</Canvas.RenderTransform>
</Canvas>
Setting ScaleX and ScaleY to “1” is equivelent to a 100% scale. If you set the ScaleX and ScaleY to “0.33” the control would render at 1/3 its original.
Now, as demonstrated in Tip of the Day #9 monitor for your browser resize in your Page.xaml.cs file. Set the CanvasScale ScaleX and ScaleY to be a new percentage of the original width and height:
namespace ScaleTransform
{
public partial class Page : UserControl
{
private int _startingWidth = 800;
private int _startingHeight = 600;
public Page()
{
InitializeComponent();
App.Current.Host.Content.Resized += new EventHandler(Content_Resized);
}
void Content_Resized(object sender, EventArgs e)
{
double height = App.Current.Host.Content.ActualHeight;
double width = App.Current.Host.Content.ActualWidth;
CanvasScale.ScaleX = width / _startingWidth;
CanvasScale.ScaleY = height / _startingHeight;
}
}
}
Thank you,
--Mike Snow
Subscribe in a reader
UserControls are a great way to separate objects into smaller, more manageable chunks of logic. These controls can reused by different applications and are independent from other controls. Each UserControl can contain any amount of content and logic and can be directly added to your Canvas tree (I.e. MyCanvas.Children.Add(myControl)).
Once you have a UserControl created, how do you reference or declare it from another XAML file (such as Page.xaml)? Doing this is relatively straight forward and we will demonstrate how to do this in this tutorial.
Run this application here to preview: http://silverlight.services.live.com/invoke/66033/Custom%20UserControl/iframe.html
To start, let’s create a custom UserControl called “Card”. In Visual Studio 2008, Right click on your Silverlight Application and choose “Add->New Item…”.
In the Add New Item dialog, choose “Silverlight User Control” and change the name to “Card.xaml”.
Add the following two images to your Silverlight application project:
CardDiamond3.png CardHeartAce.png
Now, open up Card.xaml and:
- Add an Image in the UserContol.
- Set the “x:Name” of the Image to “CardImg”.
- Set the source default value to”CardHeartAce.png”
- Remove the controls Width, Height and Grid tags as they are not needed. Your Card.xaml should now look like this:
<UserControl x:Class="UserCtrl.Card"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Image x:Name="CardImg" Source="CardHeartAce.png"></Image>
</UserControl>
Next we want to give users the ability to change the image of the card. Open Card.xaml.cs and add the following code which will allow the user to change the image:
namespace UserCtrl
{
public partial class Card : UserControl
{
public Card()
{
InitializeComponent();
}
public ImageSource CardImage
{
get { return CardImg.Source; }
set
{
this.CardImg.Source = value;
}
}
}
}
Our final step is to show you how to reference this UserControl from a xaml file such as Page.xaml.
In Page.xaml:
- Add a local reference to your Page.xaml UserControl which will allow you to reference objects in your assembly:
<UserControl x:Class="UserCtrl.Page"
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:local="clr-namespace:UserCtrl;assembly=UserCtrl"
Width="480" Height="300" >
- Declare a couple references to your Card control like this:
<UserControl x:Class="UserCtrl.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:UserCtrl;assembly=UserCtrl"
Width="480" Height="300" >
<Canvas x:Name="MyCanvas" Background="Gray">
<local:Card Canvas.Left="50" Canvas.Top="20" x:Name="MyCard" CardImage="CardDiamond3.png" >
</local:Card>
<local:Card Canvas.Left="250" Canvas.Top="20" x:Name="MyCard2" CardImage="CardHeartAce.png" >
</local:Card>
</Canvas>
</UserControl>
- Run the application and you will see two cards are shown!
Thank you,
--Mike Snow
Subscribe in a reader
This tutorial will look into using the Opacity property on the tiles to create smooth, natural looking transitions between tiles. For example, blending a dirt tile into a grass tile, a grass tile into a rock tile, etc.
Run the demo here: http://silverlight.services.live.com/invoke/66033/Terrain%20Transititions/iframe.html
Below, on the left, is a screen shot of a grass and dirt tile without the use of an opacity mask. As you can see, the straight line does not make for a very real looking transition! On the right is the result with an opacity mask applied, making for a much more realistic transition.
No Opacity Mask With Opacity Mask

So how does this work? What we do is we put down a grass layer followed by a layer of dirt on the same tile location. We then apply an Opacity mask to the dirt image which tells Silverlight what level of rendering to do on each pixel within the dirt image.
Example:
Layer 1 Layer 2 Opacity Mask Result
+
+
=
The checker boxes in the opacity image (image #3) above represent the areas that are transparent in the PNG opacity mask. In this area, the image will not be drawn allowing any image below it show through.
Here’s how it looks in our XAML code:
<Image Source="grass.png"></Image>
<Image Source="dirt.png" >
<Image.OpacityMask >
<ImageBrush ImageSource="opacityMask.png"></ImageBrush>
</Image.OpacityMask>
</Image>
Using a PNG image file, transparency is represented by the alpha value only. All the other colors in this image have no impact. Each color is represented by “#AARRGGBB” where AA=alpha hex value, RR=red hex value, GG=green hex value and BB= blue hex value. Transparency in a pixel increases starting at #00000000 (completely non-transparent) to #FF000000 (completly transparent.
If you do not want to use an image for your opacity mask, you can also use a Brush. For example, this tile shows a transition using a LinearImageBrush:
Layer 1 Layer 2 LinearGradientBrush Result
+
+
=
Here is the XAML code:
<Image Source="grass.png"></Image>
<Image Source="dirt.png">
<Image.OpacityMask >
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#FF000000" Offset="0.0" />
<GradientStop Color="#00000000" Offset="1.0" />
</LinearGradientBrush>
</Image.OpacityMask>
</Image>
Using other brush types, such as the RadialGradientBrush, you can do a lot of other cool effects.
Example:
+
+
=
XAML Code:
<Image Source="grass.png"></Image>
<Image Source="dirt.png">
<Image.OpacityMask >
<RadialGradientBrush GradientOrigin="1,0">
<GradientStop Color="#FF000000" Offset="0.0" />
<GradientStop Color="#FF000000" Offset="0.25" />
<GradientStop Color="#AE000000" Offset="0.75" />
<GradientStop Color="#00000000" Offset="1.0" />
</RadialGradientBrush>
</Image.OpacityMask>
</Image>
Thank you,
--Mike Snow
Subscribe in a reader
When clicking on your Silverlight application how do you know if the <Alt>, <Shift>, <Ctrl>, <Windows> and/or <Apple> key is down as well?
To do this, you simply need to check the Keyboard.Modifiers member which returns a ModifierKeys object.
The following code below in our Page.xaml.cs shows how this is done.
Run and preview this app here: http://silverlight.services.live.com/invoke/66033/Left%20Mouse%20Down/iframe.html
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 ShiftMouseClick
{
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
this.MouseLeftButtonDown += new MouseButtonEventHandler(Page_MouseLeftButtonDown);
}
void Page_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Data.Text = String.Empty;
ModifierKeys keys = Keyboard.Modifiers;
bool shiftKey = (keys & ModifierKeys.Shift) != 0;
bool altKey = (keys & ModifierKeys.Alt) != 0;
bool appleKey = (keys & ModifierKeys.Apple) != 0;
bool controlKey = (keys & ModifierKeys.Control) != 0;
bool windowsKey = (keys & ModifierKeys.Windows) != 0;
if (true == shiftKey)
Data.Text += "<shift>";
if (true == altKey)
Data.Text += "<alt>";
if (true == appleKey)
Data.Text += "<apple>";
if (true == controlKey)
Data.Text += "<ctrl>";
if (true == windowsKey)
Data.Text += "<windows>";
Data.Text += " Left Clicked";
}
}
}
And our Page.xaml:
<UserControl x:Class="ShiftMouseClick.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">
<TextBlock x:Name="Data">Click here (also use alt,ctrl,windows,apple,shift keys)</TextBlock>
</Grid>
</UserControl>
Thank you,
--Mike Snow
Subscribe in a reader
For this tutorial I will be demonstrating how to create a fast, optimized Sprite animation class. In my demo, you will be able to:
- Increase the speed of the sprites.
- Increase the count of the sprites.
To stress the sprites further, I have added random movement (bouncing off walls), rotational transforms and opacity/transparency shifting.
Run this application now by clicking on this link: http://silverlight.services.live.com/invoke/66033/Fast%20Sprites/iframe.html. If you increase the speed and the number of sprites, you should notice the animation continues to run smoothly despite the large number and speed.
Preview Screen Shot (left = 100 sprites, right = 1000 sprites)
Each sprite is encapsulated in its own class I have called Sprite. Let’s take a look at the complete class for a Sprite below. In summary:
- The Sprite class inherits from Control. This way you can directly add your Sprite to the Canvas tree. The XAML that represents the sprite is put into a template (_spriteTemplate) and which is applied to the class by calling ApplyTemplate();
- The Sprite constructor takes the width and height of the sprite. It also loads and applies the template.
- To set the image of the Sprite you must call SetImage() passing the file name of the image resource which must be included as part of your project.
- The rest of the routines in this class control the movement, position and rotation of the Sprite.
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 FastAnimation
{
public class Sprite : Control
{
private Image _spriteImage;
private RotateTransform _rotateTransform;
private int _width;
private int _height;
private double _posX = 0;
private double _posY = 0;
private double _xInc = 0;
private double _yInc = 0;
private int _opacityDir = 1;
private string _spriteTemplate =
"<ControlTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"" +
" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">" +
"<Image x:Name=\"SpriteImage\">" +
" <Image.RenderTransform>" +
" <RotateTransform x:Name=\"ImageTransform\">" +
" </RotateTransform>" +
" </Image.RenderTransform>" +
"</Image>" +
"</ControlTemplate>";
public Sprite(int width, int height)
{
_width = width;
_height = height;
Template = (ControlTemplate)XamlReader.Load(_spriteTemplate);
ApplyTemplate();
}
public override void OnApplyTemplate()
{
_spriteImage = (Image)GetTemplateChild("SpriteImage");
_rotateTransform = (RotateTransform)GetTemplateChild("ImageTransform");
_rotateTransform.CenterX = _width / 2;
_rotateTransform.CenterY = _height / 2;
}
public void SetImage(string resource)
{
Uri uri = new Uri(resource, UriKind.Relative);
ImageSource imgSrc = new System.Windows.Media.Imaging.BitmapImage(uri);
_spriteImage.Source = imgSrc;
}
public double YInc
{
set { _yInc = value; }
}
public double XInc
{
set { _xInc = value; }
}
public double PosX
{
get { return _posX; }
set
{
_posX = value;
this.SetValue(Canvas.LeftProperty, _posX);
}
}
public double PosY
{
get { return _posY; }
set
{
_posY = value;
this.SetValue(Canvas.TopProperty, _posY);
}
}
public bool Step(int speed)
{
if (_xInc * speed + _posX > 800 || _xInc * speed + _posX < 0)
return false;
if (_yInc * speed + PosY > 600 || _yInc * speed + PosY < 0)
return false;
PosX += _xInc * speed;
PosY += _yInc * speed;
if (_opacityDir == 1)
_spriteImage.Opacity += 0.01;
else
_spriteImage.Opacity -= 0.01;
if (_spriteImage.Opacity >= 1)
_opacityDir = 0;
else if (_spriteImage.Opacity <= 0)
_opacityDir = 1;
return true;
}
public void Rotate()
{
_rotateTransform.Angle += 1;
_rotateTransform.Transform(new Point(32, 24));
}
}
}
In Page.xaml, we declare our UI elements that allow us to increase the speed and count of sprites:
<UserControl x:Class="FastAnimation.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="800" Height="600" Background="Black">
<Grid x:Name="LayoutRoot" Background="Black">
<Canvas Background="Black" x:Name="MyCanvas">
<TextBlock Canvas.ZIndex="10000" Foreground="White">Speed</TextBlock>
<Slider Canvas.ZIndex="10000" x:Name="SliderSpeed" Minimum="0" Maximum="50" Width="100" Canvas.Left="50"></Slider>
<TextBlock Canvas.ZIndex="10000" x:Name="SpeedValue" Canvas.Left="150" Foreground="White">1</TextBlock>
<TextBlock Canvas.ZIndex="10000" Canvas.Top="20" Foreground="White">Count</TextBlock>
<Slider Canvas.ZIndex="10000" x:Name="SliderCount" Canvas.Top="20" Minimum="0" Maximum="1000" Value="100" Width="100" Canvas.Left="50"></Slider>
<TextBlock Canvas.ZIndex="10000" x:Name="CountValue" Canvas.Left="150" Canvas.Top="20" Foreground="White">100</TextBlock>
</Canvas>
</Grid>
</UserControl>
In Page.xaml.cs we:
- Create an array of sprites (100 by default).
- Setup our game loop timer using the Storyboard discussed in Tip of the Day #16. In this loop, we move and rotate the sprites.
- Monitor events for the Speed and Sprite Count slider controls.
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 FastAnimation
{
public partial class Page : UserControl
{
private Sprite[] _sprites;
private Storyboard _gameLoop = new Storyboard();
private int _spriteCount = 0;
private int _speed = 1;
public Page()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Page_Loaded);
}
void Page_Loaded(object sender, RoutedEventArgs e)
{
SliderCount.ValueChanged +=new RoutedPropertyChangedEventHandler<double>(SliderCount_ValueChanged);
SliderSpeed.ValueChanged +=new RoutedPropertyChangedEventHandler<double>(SliderSpeed_ValueChanged);
CreateSprites(100);
_gameLoop.Duration = TimeSpan.FromMilliseconds(0);
_gameLoop.Completed += new EventHandler(MainGameLoop);
_gameLoop.Begin();
}
private void CleanUp()
{
for (int i = 0; i < _spriteCount; i++)
{
MyCanvas.Children.Remove(_sprites
);
}
}
private void CreateSprites(int spriteCount)
{
lock (this)
{
CleanUp();
_spriteCount = spriteCount;
_sprites = new Sprite[_spriteCount];
Random rand = new Random();
for (int i = 0; i < _spriteCount; i++)
{
Sprite sprite = new Sprite(64, 48);
double x = (double)rand.Next(800);
double y = (double)rand.Next(600);
sprite.SetImage("fireballlogo.png");
sprite.PosX = x;
sprite.PosY = y;
ChangeDirections(sprite);
MyCanvas.Children.Add(sprite);
_sprites
= sprite;
}
}
}
private void ChangeDirections(Sprite sprite)
{
Random rand = new Random();
double xi = rand.Next(1, 100);
double yi = rand.Next(1, 100);
xi *= 0.01;
yi *= 0.01;
int n1 = rand.Next(0, 2);
int n2 = rand.Next(0, 2);
if (n1 == 0)
xi *= -1;
if (n2 == 0)
yi *= -1;
sprite.XInc = xi;
sprite.YInc = yi;
}
private void MoveSprites()
{
for (int i = 0; i < _spriteCount; i++)
{
Sprite sprite = _sprites
;
double x = sprite.PosX;
double y = sprite.PosY;
if (false == sprite.Step(_speed))
{
ChangeDirections(sprite);
}
}
}
private void RotateSprites()
{
for (int i = 0; i < _spriteCount; i++)
_sprites
.Rotate();
}
private void MainGameLoop(object sender, EventArgs e)
{
MoveSprites();
RotateSprites();
_gameLoop.Begin();
}
private void SliderCount_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
int count = (int) SliderCount.Value;
CountValue.Text = count.ToString();
CreateSprites(count);
}
private void SliderSpeed_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
_speed = (int) SliderSpeed.Value;
SpeedValue.Text = _speed.ToString();
}
}
}
For this tutorial we will step you through the basics of what you need to do to create a dialog that you can:
- Drag/drop.
- Add transparency (opacity) to.
- 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.
Borders go from 0 – 150. At 150, you will have a complete circle. At 0, the dialog will be complete square.
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:
- Add an event for the control to monitor for MouseLeftButtonDown().
- Add an event for the control to monitor for MouseMove().
- Add an event for the control to monitor for MouseLeftButtonUp().
In MouseLeftButtonDown() we:
- 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.
- Get the last point where the Mouse was clicked. This way when the mouse is moved we can calculate the move difference.
- Get the offset into the dialog where the mouse was clicked.
- 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:
- Get the current position of the mouse and calculate how far it moved based upon the last point.
- Update the dialog to the new position based upon the amount the mouse moved.
- 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:
- Set _isDragging = false to stop monitioring mouse moves.
- 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
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.
Demo (Silverlight 3 Beta 1 required):
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”:
MainPage.xaml:
<UserControl x:Class="CustomCursor.MainPage"
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" Cursor="None" Background="White">
</Canvas>
</UserControl>
Next, create a customer Silverlight user control called CustomCursor. The only thing this control need to contain is a Image control.
Cursor.XAML:
<UserControl x:Class="CustomCursor.CustomCursor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Image x:Name="MyCursor"></Image>
</UserControl>
In the code behind for the CustomCursor class add a method to set the image and another method to position the control.
Cursor.XAML.cs:
namespace CustomCursor
{
public partial class CustomCursor : UserControl
{
public CustomCursor()
{
InitializeComponent();
}
public void SetCursor(string resource)
{
Uri uri = new Uri(resource, UriKind.Relative);
MyCursor.Source = new System.Windows.Media.Imaging.BitmapImage(uri);
}
public void MoveTo(Point pt)
{
this.SetValue(Canvas.LeftProperty, pt.X);
this.SetValue(Canvas.TopProperty, pt.Y);
}
}
}
Finally, go back to MainPage.xaml.cs and add an instance of your CustomCursor to your Canvas. Also, monitor for the MouseMove event so you can position the cursor to be at the location of your mouse.
namespace CustomCursor
{
public partial class MainPage : UserControl
{
CustomCursor customCursor = new CustomCursor();
public MainPage()
{
InitializeComponent();
customCursor.SetCursor("fireball.png");
LayoutRoot.Children.Add(customCursor);
this.MouseMove += new MouseEventHandler(MainPage_MouseMove);
}
void MainPage_MouseMove(object sender, MouseEventArgs e)
{
customCursor.MoveTo(e.GetPosition(null));
}
}
}
Thank you,
--Mike Snow
Subscribe in a reader
For this tutorial we will make use of matrix transforms to skew and scale an image in a terrain tile while moving the individual vertices of the terrain tiles. This will give the terrain a 3D look and feel. Make certain to read Part 1 of before reading this tutorial.
For a preview of the final app from this tutorial, see: http://silverlight.services.live.com/invoke/66033/Terrain%20Transform/iframe.html. Grab any terrain vertex (black dot) by holding down the left mouse button so that the vertex turns red. Move the mouse and you will see the vertex is also moved.
Here is an example of something you can make from this site above:
To illustrate what we are doing, take a look at these 3 steps. I have put some numbers on a grass tile so you can better see the expected result in skewing/scaling.
Step 1: One terrain tile before the vertices are changed. Notice the seam.
Step 2: Upper right vertex moved to the upper right but the image is not transformed/skewed to fit change.
Step 3: Matrix scale and skew applied to the terrain tile. Notice the seam is gone due to the scaling and the numbers follow the border on the top of the image due to the transform.
So how did we do this? First, let’s change the template we declared in Part 1 of these terrain tutorials. We are adding both a ScaleTransform and and a MatrixTransform to our Path. To our ImageBrush we are adding just a MatrixTransform. More on these soon.
private const string _imageTriangleContentTemplate
= "<ControlTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"" +
" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">" +
"<Canvas><Path Stroke=\"Blue\" x:Name=\"PathElementUpperLeft\" " +
" Width=\"100\" Height=\"100\"" +
" Data=\"M 0,0 100,0 0,100\">" +
" <Path.RenderTransform>" +
" <TransformGroup>" +
" <ScaleTransform x:Name=\"PathScaleUpperLeft\" ScaleX=\"1.01\" CenterX=\"25\" CenterY=\"25\" />" +
" <MatrixTransform x:Name=\"PathTransformUpperLeft\" />" +
" </TransformGroup>" +
" </Path.RenderTransform>" +
" <Path.Fill>" +
" <ImageBrush x:Name=\"TileImageUpperLeft\" " +
" AlignmentX=\"Left\" AlignmentY=\"Top\" Stretch=\"Fill\">" +
" <ImageBrush.RelativeTransform>" +
" <MatrixTransform x:Name=\"BrushTransformUpperLeft\"/>" +
" </ImageBrush.RelativeTransform>" +
" </ImageBrush>" +
" </Path.Fill>" +
"</Path>" +
"<Path x:Name=\"PathElementLowerRight\" " +
" Width=\"100\" Height=\"100\"" +
" Data=\"M 0,0 100,0 0,100\">" +
" <Path.RenderTransform>" +
" <TransformGroup>" +
" <ScaleTransform x:Name=\"PathScaleLowerRight\" ScaleX=\"1.01\" CenterX=\"25\" CenterY=\"25\" />" +
" <MatrixTransform x:Name=\"PathTransformLowerRight\" />" +
" </TransformGroup>" +
" </Path.RenderTransform>" +
" <Path.Fill>" +
" <ImageBrush x:Name=\"TileImageLowerRight\" " +
" AlignmentX=\"Left\" AlignmentY=\"Top\" Stretch=\"Fill\">" +
" <ImageBrush.RelativeTransform>" +
" <MatrixTransform x:Name=\"BrushTransformLowerRight\"/>" +
" </ImageBrush.RelativeTransform>" +
" </ImageBrush>" +
" </Path.Fill>" +
"</Path></Canvas>" +
"</ControlTemplate>";
In our OnApplyTemplate() function we can now get a references to these transform objects:
public override void OnApplyTemplate()
{
_pathScaleUpperLeft = (ScaleTransform)GetTemplateChild("PathScaleUpperLeft");
_pathTransformUpperLeft = (MatrixTransform)GetTemplateChild("PathTransformUpperLeft");
_brushTransformUpperLeft = (MatrixTransform)GetTemplateChild("BrushTransformUpperLeft");
_imageBrushUpperLeft = (ImageBrush)GetTemplateChild("TileImageUpperLeft");
_tilePathUpperLeft = (Path)GetTemplateChild("PathElementUpperLeft");
_pathScaleLowerRight = (ScaleTransform)GetTemplateChild("PathScaleLowerRight");
_pathTransformLowerRight = (MatrixTransform)GetTemplateChild("PathTransformLowerRight");
_brushTransformLowerRight = (MatrixTransform)GetTemplateChild("BrushTransformLowerRight");
_imageBrushLowerRight = (ImageBrush)GetTemplateChild("TileImageLowerRight");
_tilePathLowerRight = (Path)GetTemplateChild("PathElementLowerRight");
UpdateTextureCoordinates(new Point(0, 0), new Point(1, 0), new Point(0, 1), _brushTransformUpperLeft);
UpdateTextureCoordinates(new Point(1, 0), new Point(1, 1), new Point(0, 1), _brushTransformLowerRight);
}
Our references are declared as such:
private Path _tilePathUpperLeft;
private ScaleTransform _pathScaleUpperLeft;
private MatrixTransform _pathTransformUpperLeft;
private MatrixTransform _brushTransformUpperLeft;
private ImageBrush _imageBrushUpperLeft;
private ScaleTransform _pathScaleLowerRight;
private MatrixTransform _pathTransformLowerRight;
private MatrixTransform _brushTransformLowerRight;
private ImageBrush _imageBrushLowerRight;
private Path _tilePathLowerRight;
You will notice above in OnApplyTemplate() we call UpdateTextureCoordinates(). This is required to ensure the image maps in the right direction within the triangle. We pass our ImageBrush transform matrix to this function:
private void UpdateTextureCoordinates(Point p1, Point p2, Point p3, MatrixTransform transform)
{
Double m11 = (p2.X - p1.X);
Double m12 = (p2.Y - p1.Y);
Double m21 = (p3.X - p1.X);
Double m22 = (p3.Y - p1.Y);
Double ox = p1.X;
Double oy = p1.Y;
transform.Matrix = new Matrix(m11, m12, m21, m22, ox, oy).Invert(); ;
}
For the upper left triangle we would set the following texture coordinates:
- UpdateTextureCoordinates(new Point(0, 0), new Point(1, 0), new Point(0, 1), _brushTransformUpperLeft);
For the lower right triangle we would set the following texture coordinates:
- UpdateTextureCoordinates(new Point(1, 0), new Point(1, 1), new Point(0, 1), _brushTransformLowerRight);
Each triangle by default is set with coordinates 0,0, 100,0, 0,100 and a scale of 100x100. We then transform these coordinates to whatever coordinates we want using this scale. Anytime we want to change a triangle we call UpdateCorners() with the transform and scale matrices for that triangle. The first section of code performs the transformation on the new coordinates. The second part of the code scales the image slightly to get rid of the seam. I have found setting _antiSeamOverScale = 2 gets rid of the seam.
private void UpdateCorners(Point p1, Point p2, Point p3, MatrixTransform transform, ScaleTransform scale)
{
Double m11 = (p2.X - p1.X) * 0.01d;
Double m12 = (p2.Y - p1.Y) * 0.01d;
Double m21 = (p3.X - p1.X) * 0.01d;
Double m22 = (p3.Y - p1.Y) * 0.01d;
Double ox = p1.X;
Double oy = p1.Y;
transform.Matrix = new Matrix(m11, m12, m21, m22, ox, oy);
Rect rect = new Rect(p1, p2);
rect.Union(p3);
Double w = rect.Width, h = rect.Height;
scale.ScaleX = w > 0 ? (w + _antiSeamOverscale) / w : 0;
scale.ScaleY = h > 0 ? (h + _antiSeamOverscale) / h : 0;
}
With that, here is a complete loop to create a map of tiles.
public partial class Page : UserControl
{
TerrainTile[,] _tiles = new TerrainTile[10, 10];
public Page()
{
InitializeComponent();
for (int y = 0; y < 10; y++)
{
for (int x = 0; x < 10; x++)
{
TerrainTile tile = new TerrainTile();
tile.SetUpperLeftPoints(new Point(x * 100, y * 100), new Point(x * 100 + 100, y * 100), new Point(x * 100, y * 100 + 100));
tile.SetLowerRightPoints(new Point(x * 100 + 100, y * 100), new Point(x * 100 + 100, y * 100 + 100), new Point(x * 100, y * 100 + 100));
tile.SetImage("earth2.png");
MyCanvas.Children.Add(tile);
_tiles[y, x] = tile;
}
}
}
}
For more info on matrix transforms see: http://www.senocular.com/flash/tutorials/transformmatrix/
Other resources: http://www.pacem.it/CMerighi/Posts/71,en-US/Triangle_Texturing,_Theory_and_Silverlight_App.aspx
Special thanks to Florian Kruesch for answering some questions. See his sample here: http://www.codeproject.com/KB/silverlight/silverlight_triangle.aspx
Thank you,
--Mike Snow
Subscribe in a reader
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
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.
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:
- Arrow
- Eraser
- Hand
- IBeam
- SizeNS
- SizeWE
- Stylus
- Wait
Thank you,
--Mike Snow
Subscribe in a reader
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”.
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.
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
By default when you build your Silverlight application you will notice the XAP file is placed in your web sites ClientBin folder:
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”.
From the Property Pages dialog, select “Silverlight Applications” in the left. Choose your project, and click “Change…” button.
Select Yes to the pop-up dialog.
If you now go back and build you will notice your XAP file is placed in the correct configuration folder.
Example for Release configuration:
Thank you,
--Mike Snow
Subscribe in a reader
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.
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
My next set of blogs will be about creating realistic terrain for games that have a birds eye perspective on the world.
To start, I will be creating this tutorials:
- Creating a scrolling map.
In this tutorial we will go over the basics needed to create a 2D scrolling map.
See: http://silverlight.services.live.com/invoke/66033/Terrain%20Transform/iframe.html
- Using Transform matrices to Create 3D Looking Terrain.
This tutorial will take the terrain to the next level adding matrix transformations to give the terrain a 3D look. We will apply transforms to the individual tiles on the map so that you can specify texture coordinates and skew textures within the triangles.
See: http://silverlight.services.live.com/invoke/66033/Terrain%20Transform/iframe.html
- Creating Smooth Tile Transitions using Opacity
This tutorial will look into using the Opacity property on the tiles to create smooth transitions between tiles. For example, blending a dirt tile into a grass tile, a grass tile into a rock tile, etc.
I will add more to the list as I get more ideas. Feel free to send me suggestions!!
Thank you,
--Mike Snow
Subscribe in a reader
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