Live demo
.
A Silverlight control can communicate and interact with other HTML elements of the page on which it is placed.
This is done by using the HtmlPage object (namespace System.Windows.Browser)
By using HtmlPage, you can have access to all HTML elements of the current page and be able to get and set many of their properties and attributes, and, accessorily, call DHTML script functions that may be located there.
This means, among other things, that you can get and set style attributes of the HTML element inside which your own Silverlight control is hosted (the DIV element having the id commonly named "silverlightControlHost")
<div id="silverlightControlHost"
...
This gave me an idea about creating a custom control which would represent this same hosting html DIV element and, through this custom control, interact with the hosting element properties (width, height, location… etc.)
One of the nice features of Silverlight is the Storyboard animations usually used to animate Silverlight controls.
So, what if we try to use a storyboard to animate the Silverlight hosting html element!
Suppose that our animation should move the html host element across the page. From an html viewpoint, this would mean to change its top/left coordinates.
A Silverlight user control doesn’t contain Top / Left properties required to do such a job. So we have to add those properties to our custom control.
I first created an (empty) custom user control:
Xaml:
<UserControl x:Class="SilverHtmlInteraction.HostingDivControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="Transparent"
SizeChanged="UserControl_SizeChanged"
Width="200" Height="200">
<Grid x:Name="LayoutRoot" Background="White">
</Grid>
</UserControl>
The control members:
private HtmlElement m_div = null;
public HtmlElement Div
{
get { return m_div; }
set { m_div = value; }
}
The new Left and Top properties
public double Left
{
get { return attrib = GetDimension("left", "offsetLeft", 0.0); }
set { SetDimentionAttribute("left", value); }
}
public double Top
{
get { return attrib = GetDimension("top", "offsetTop", 0.0); }
set { SetDimentionAttribute("top", value); }
}
How to obtain html element dimensions (width, height, left, top... etc.)
// get the html element’s dimension (example: width, height, left...)
// first try to find the dimension in the element’s style (width, height, left, top...)
// if not found, try to obtain the element’s dimension property (offsetWidth, offsetHeight...)
private double GetDimension(string attrib_name, string property_name, double default_value)
{
double attrib = GetDimensionAttribute(attrib_name, default_value);
// if the style attribute is not present, try to get the dimension property
if (attrib <= 0.0)
return GetDimensionProperty( property_name, default_value);
return attrib;
}
// get the html element’s style attribute (example: width, height, left...)
protected double GetDimensionAttribute(string attrib_name, double default_value)
{
if (m_div == null)
{
debug_display_div_error();
return default_value;
}
// try to get the style attribute’s value
string str_value = m_div.GetStyleAttribute(attrib_name);
if (string.IsNullOrEmpty(str_value))
{
return default_value;
}
// remove the dimension's unit (px)
str_value = str_value.Replace("px", "");
double value = default_value;
// try to convert to numeric value
if (double.TryParse(str_value, out value) == false)
return default_value;
return value;
}
// get the html element’s dimension property (example: offsetHeight, offsetWidth...)
protected double GetDimensionProperty(string property_name, double default_value)
{
if( m_div == null)
{
debug_display_div_error();
return default_value;
}
string str_value = m_div.GetProperty( property_name).ToString();
if (string.IsNullOrEmpty(str_value))
{
return default_value;
}
double value = default_value;
// try to convert to numeric value
if (double.TryParse(str_value, out value) == false)
return default_value;
return value;
}
I then registered the new Left and Top properties with the dependency system:
public static DependencyProperty LeftProperty = DependencyProperty.Register(
"Left", typeof(double), typeof(HostingDivControl),
new PropertyMetadata(
new PropertyChangedCallback(LeftPropertyChanged) ));
public static DependencyProperty TopProperty = DependencyProperty.Register(
"Top", typeof(double), typeof(HostingDivControl),
new PropertyMetadata(
new PropertyChangedCallback(TopPropertyChanged)));
private static void LeftPropertyChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs e)
{
if (obj == null)
return;
if (obj is HostingDivControl != true)
return;
HostingDivControl ctrl = (HostingDivControl)obj;
ctrl.Left = (double)e.NewValue;
}
private static void TopPropertyChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs e)
{
if( obj == null)
return;
if( obj is HostingDivControl !=true)
return;
HostingDivControl ctrl = (HostingDivControl) obj;
ctrl.Top = (double) e.NewValue;
}
The html element accessor should do some work each time this member is changed:
public HtmlElement Div
{
get { return m_div; }
set
{
m_div = value;
if (m_div != null)
{
base.Width = this.Width;
base.Height = this.Height;
Left = GetDimension("left", "offsetLeft", 0.0);
Top = GetDimension("top", "offsetTop", 0.0);
// to be able to change the left and top propertites,
// the html element should have its position style set
// to relative or absolute.
If (string.IsNullOrEmpty( m_div.GetStyleAttribute("position")))
m_div.SetStyleAttribute("position", "relative");
}
else
m_div_id = "";
}
}
We are now ready to use our new custom control to animate the Silverlight control inside its hosting html page.
<Grid x:Name="LayoutRoot" Background="Transparent">
<local:HostingDivControl x:Name="host_div" Margin="0"/>
The animation Storyboard
Let’s create a storyboard that changes the top/left properties of our control (which is the silver light control itself)
Note: I couldn’t use Expression Blend to create the storyboard. Blend seemed confused about the properties to be animated for this ‘special’ control. So I ended up by creating the storyboard by hand!
<UserControl.Resources>
<Storyboard x:Name="Storyboard1">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="host_div" Storyboard.TargetProperty="(Top)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<EasingDoubleKeyFrame KeyTime="00:00:00.50" Value="50"/>
<EasingDoubleKeyFrame KeyTime="00:00:00.80" Value="85"/>
<EasingDoubleKeyFrame KeyTime="00:00:01.00" Value="130"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="host_div" Storyboard.TargetProperty="(Left)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<EasingDoubleKeyFrame KeyTime="00:00:00.50" Value="100"/>
<EasingDoubleKeyFrame KeyTime="00:00:00.80" Value="225"/>
<EasingDoubleKeyFrame KeyTime="00:00:01.00" Value="350"/>
</DoubleAnimationUsingKeyFrames>
...
...
</Storyboard>
</UserControl.Resources>
Live demo
Download the sample code
AnimatedSilverHtmlHost.zip (83.45 kb)