Introduction
This article explains how to create a simple Silverlight control dynamically at runtime with XML binding. I have chosen to discuss a simple example to demonstrate the technique. The technique can, of course, be elaborated as per individual requirements and desires.
Background
In this example, we are changing the storyboard target at runtime to bind the animation as we create the dynamic controls, and are using LINQ to bind the controls with XML.
Using the code
We start by creating a Silverlight project with a simple animation where an image just slides back and forth using Expression Blend. We modify page.xaml with the following code:
<UserControl x:Class="BindingXML.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot">
<HyperlinkButton NavigateUri=http://www.google.com TargetName="_blank"
MouseEnter="Link1_MouseEnter" MouseLeave="Link1_MouseLeave"
x:Name="Link1" Foreground="Black" Margin="8,-22,143,0"
VerticalAlignment="Top">
<Canvas>
<Image Canvas.Top="80" Canvas.Left="10"
Source="redarrow.jpg" Width="10" Height="15"
VerticalAlignment="Bottom" x:Name="image4"
RenderTransformOrigin="0.5,0.5"> </Image>
<TextBlock Canvas.Top="80" Canvas.Left="25">AskCNet</TextBlock>
</Canvas>
</HyperlinkButton>
</Grid>
</UserControl>
Then, we open the XAML in Expression Blend to add the desired animation. In this example, we would add animation to the image so that it slides back and forth. Once done with the animation, Expression Blend generates code and adds a story board to the XAML, with additional code attached to the image as follows:
<UserControl x:Class="EurekaHomeLinks.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Resources>
<Storyboard x:Name="Storyboard1" SpeedRatio="3.0">
<PointAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000"
Storyboard.TargetName="Image1"
Storyboard.TargetProperty="(UIElement.RenderTransformOrigin)"
x:Name="samplePointAnim"> <SplinePointKeyFrame
KeyTime="00:00:00" Value="-1,0.6"/>
</PointAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="Image1"
Storyboard.TargetProperty="(UIElement.RenderTransform).
(TransformGroup.Children)[2].(TranslateTransform.X)"
x:Name="sampleDoubleAnim1">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="-9"/>
<SplineDoubleKeyFrame KeyTime="00:00:01" Value="2"/>
<SplineDoubleKeyFrame KeyTime="00:00:02" Value="-1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="Image1"
Storyboard.TargetProperty="(UIElement.RenderTransform).
(TransformGroup.Children)[2].(TranslateTransform.Y)"
x:Name="sampleDoubleAnim2">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:01" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard> </UserControl.Resources>
<Grid x:Name="LayoutRoot">
<HyperlinkButton NavigateUri=http://www.google.com TargetName="_blank"
MouseEnter="Link1_MouseEnter" MouseLeave="Link1_MouseLeave"
x:Name="Link1" Foreground="Black"
Margin="8,-22,143,0" VerticalAlignment="Top">
<Canvas>
<Image Canvas.Top="80" Canvas.Left="10" Source="redarrow.jpg"
Width="10" Height="15" VerticalAlignment="Bottom"
x:Name="image4" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform> <TransformGroup> <ScaleTransform/>
<SkewTransform/> <RotateTransform/> <TranslateTransform/>
</TransformGroup> </Image.RenderTransform>
</Image>
<TextBlock Canvas.Top="80" Canvas.Left="25">AskCNet</TextBlock>
</Canvas>
</HyperlinkButton>
</Grid>UserControl>
The highlighted code above is the part that Expression Blend adds in. I am not getting into the details on how the animation was created in Expression Blend as that is beyond the scope of this article. If interested, please feel free to email me at debdatta.dey@gmail.com.
We did the above steps to retrieve the storyboard code from Expression Blend. Next, we can keep the storyboard and remove the controls within the grid so that our code looks like the one below. We will create the controls dynamically from the code-behind now based upon the XML data. After removing the controls, the page.xaml code would look like the code below.
<UserControl x:Class="BindingXML.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Resources>
<Storyboard x:Name="Storyboard1" SpeedRatio="3.0">
<PointAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:00.0010000" Storyboard.TargetName="Image1"
Storyboard.TargetProperty="(UIElement.RenderTransformOrigin)"
x:Name="samplePointAnim"> <SplinePointKeyFrame
KeyTime="00:00:00" Value="-1,0.6"/>
</PointAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="Image1"
Storyboard.TargetProperty="(UIElement.RenderTransform).
(TransformGroup.Children)[2].(TranslateTransform.X)"
x:Name="sampleDoubleAnim1">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="-9"/>
<SplineDoubleKeyFrame KeyTime="00:00:01" Value="2"/>
<SplineDoubleKeyFrame KeyTime="00:00:02" Value="-1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="Image1"
Storyboard.TargetProperty="(UIElement.RenderTransform).
(TransformGroup.Children)[2].(TranslateTransform.Y)"
x:Name="sampleDoubleAnim2">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:01" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White"> </</Grid>UserControl>
Next, we attempt to bind to the XML file and create the controls at runtime as shown below, from the code-behind in page.xaml.cs.
public Page()
{
InitializeComponent();
LoadXMLFile();
}
private void LoadXMLFile()
{
WebClient xmlClient = new WebClient();
xmlClient.DownloadStringCompleted +=
new DownloadStringCompletedEventHandler(XMLFileLoaded);
xmlClient.DownloadStringAsync(new Uri("MainPageLinks.xml",
UriKind.RelativeOrAbsolute));
}
Once the XML file is loaded, we build the controls binding with the XML data using LINQ, as shown below:
void XMLFileLoaded(object sender, DownloadStringCompletedEventArgs e)
{
double initialTextLeft = 25;
double initialImageLeft = 10;
double initialTop = 40;
Thickness thickButton = new Thickness();
thickButton.Left = 8;
thickButton.Top = -22;
thickButton.Right = 143;
thickButton.Bottom = 0;
Point pointImageTransformOrigin = new Point();
pointImageTransformOrigin.X = 0.5;
pointImageTransformOrigin.Y = 0.5;
if (e.Error == null)
{
string xmlData = e.Result;
XDocument xdoc = XDocument.Parse(xmlData);
var linkItems = from b in xdoc.Descendants() where b.Name == "Link"
select b;
foreach (XElement link in linkItems)
{
string linkText = link.Element("Text").Value;
string URL = link.Element("URL").Value;
string No = link.Attribute("No").Value;
Canvas sampleCanvas = new Canvas();
Image sampleImage = new Image();
sampleImage.Source = new BitmapImage(new Uri("pencil.jpg", UriKind.Relative));
sampleImage.VerticalAlignment = VerticalAlignment.Bottom;
sampleImage.SetValue(Canvas.LeftProperty, initialImageLeft);
sampleImage.SetValue(Canvas.TopProperty, initialTop);
sampleImage.SetValue(NameProperty, "Image" + No);
sampleImage.RenderTransformOrigin = pointImageTransformOrigin;
TransformGroup imgTransGroup = new TransformGroup();
ScaleTransform imgScale= new ScaleTransform();
RotateTransform imgRotate = new RotateTransform();
TranslateTransform imgTranslate = new TranslateTransform();
SkewTransform imgScew = new SkewTransform();
imgTransGroup.Children.Add(imgScale);
imgTransGroup.Children.Add(imgRotate);
imgTransGroup.Children.Add(imgTranslate);
imgTransGroup.Children.Add(imgScew);
sampleImage.RenderTransform = imgTransGroup;
TextBlock sampleText = new TextBlock();
sampleText.Text = linkText;
sampleText.SetValue(Canvas.LeftProperty, initialTextLeft);
sampleText.SetValue(Canvas.TopProperty, initialTop);
sampleCanvas.Children.Add(sampleImage);
sampleCanvas.Children.Add(sampleText);
HyperlinkButton sampleButton = new HyperlinkButton();
sampleButton.TargetName = "_blank";
sampleButton.NavigateUri = new Uri(URL);
sampleButton.VerticalAlignment = VerticalAlignment.Top;
sampleButton.Foreground = new System.Windows.Media.SolidColorBrush(Colors.Black);
sampleButton.MouseEnter += new MouseEventHandler(sampleButton_MouseEnter);
sampleButton.MouseLeave += new MouseEventHandler(sampleButton_MouseLeave);
sampleButton.SetValue(NameProperty, "Button" + No);
sampleButton.Margin = thickButton;
sampleButton.SetValue(Canvas.LeftProperty, initialTextLeft);
sampleButton.SetValue(Canvas.TopProperty, initialTop);
initialTop += 20;
sampleButton.Content = sampleCanvas;
this.LayoutRoot.Children.Add(sampleButton);
}
}
}
In the following code, we start the storyboard OnMouseEnter
of the HyperLinkButton
by attaching the target of the storyboard to the image that is within the HyperLinkButton
. Additionally, we also make the TextBlock
of the HyperLinkButton
that gets the mouse event bold. In OnMouseLeave
, we set the font weight of the TextBlock
back to normal and stop the storyboard.
private void sampleButton_MouseLeave(object sender, MouseEventArgs e)
{
HyperlinkButton linkControl = new HyperlinkButton();
linkControl = (HyperlinkButton)sender;
linkControl.FontWeight = FontWeights.Normal;
Storyboard1.Stop();
}
private void sampleButton_MouseEnter(object sender, MouseEventArgs e)
{
String controlNo = "0";
HyperlinkButton linkControl = new HyperlinkButton();
linkControl = (HyperlinkButton)sender;
linkControl.FontWeight = FontWeights.Bold;
controlNo = linkControl.Name.Substring(6, linkControl.Name.Length - 6);
samplePointAnim.SetValue(Storyboard.TargetNameProperty, "Image" + controlNo);
sampleDoubleAnim1.SetValue(Storyboard.TargetNameProperty, "Image" + controlNo);
sampleDoubleAnim2.SetValue(Storyboard.TargetNameProperty, "Image" + controlNo);
Storyboard1.Begin();
}
Points of interest
You might be wondering why I did not use an image followed by a HyperLinkButton
versus an image and a TextBlock
within the HyperLinkButton
control. I chose this primarily because I did not want the underline that would appear when the mouse is rolled over the HyperLinkButton
. CSS modifications really do not work. Setting the margin of the HyperLinkButton
got rid of the ugly blue box that is created with hyperlinks (margin ="8,-22,143,0" ).
Finally, please let me your thoughts about this article. Thank you!
History
- Just created. None so far.