最近做SonySource项目时实现了几个很小的Silverlight程序,分别是Clock、HomePeoplePicker和ManageMentPeoplePicker。实际上这三个silverlight程序都非常简单,主要特点有以下几个方面:
1. Silverlight程序和页面上的HTML元素混合在一起,且在特定事件触发后要动态改变Silverlight程序在页面中占的位置及大小,但给用户的感觉是无缝连接;
2. Javascript和Silverlight相互调用;
3. 简单的探照灯遮照效果;
下面就分别对我认为比较不好处理的地方或者一些我费了很多周折才实现的地方做一简要说明:
一、使Silverlight浮动在Html元素中,并动态改变大小
或许我这个小标题描述得还不是很准确,不能直观表达我的意思。举个例子,假设我们要用Silverlight做个下拉菜单,并将他放在html页面上使用。我们希望这个silverlight菜单所占的大小只是980px宽和30px高,因为在紧挨菜单的上面和下面的地方我们要放置一起其他的html元素。但当用户点击某个菜单项时,这个子菜单就展开,假设子菜单的大小是100px款和200px高,那就要求Siverlight所占的位置至少高为230px。由于Silverlight菜单只有30px高,所以下拉菜单就被截断而不能完整显示。我做的这个项目里三个Silverlight都遇到类似问题。例如PeoplePicker是在一个表格框里显示很多人的图像,当用户点击一个人的图像的时候弹出一个窗口以显示人的详细信息,在某种情况下,这个弹出窗口会超出包含所有人物图像的表格从而部分被截断。在《Silverlight嵌入到HTML之windowless属性及运用AjaxControlToolKit时出现虚线边框的问题》所描述的问题就是基于这种需求。
上述问题是否可以简单的描述为:Silverlight程序在页面上只在指定的Silverlight plug in(<object/>元素)中显示,当超过Silverlight Plug in时就会被截除;当Silverlight程序的宽和高在运行时不确定时,就要求Silverlight Plug in的大小和位置随之改变以使所有silverlight内容都能完整正确的显示出来。
我在这个项目里的解决办法就是基于以上的描述,动态改变Silverlight plug in(object元素)的大小,并时silverlight plug in以绝对定位的方式浮动于其他元素之上,且让silverlight plug in的背景色为透明以不至于让他遮盖所有的底层元素。
首先,我们在页面上定义一个<div>元素,我们的silverlight程序就放在这个<div>里,并以它作为silverlight的定位基准。即正常情况下silverlight和包含它的<div>的位置和大小完全一致。当需要改变silverlight的大小和位置时,也以该<div>为参考。在页面布局时,我们只用关注这个<div>应该放到哪就行了。HTML代码大致如下:
Code
<div id="silverlightHomePeoplePickerHost" style="width:275px;height:324px;background-color:transparent;float:left">
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" style="width:100%;height:100%;position:absolute">
<param name="source" value="../ClientBin/SEL.SonySource.Silverlight.HomePeoplePicker.xap"/>
<param name="onerror" value="onSilverlightError" />
<param name="onload" value="onSilverlightHomePeoplePickerLoaded" />
<param name="background" value="transparent" />
<param name="windowless" value="true" />
<param name="minRuntimeVersion" value="2.0.31005.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=124807" style="text-decoration: none;">
<img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none"/>
a>
object>
<iframe style='visibility:hidden;height:0;width:0;border:0px'>iframe>
div>
那么怎么初始化silverlight的位置和大小呢?就在onload事件里处理:
Code
var silverlightHomePeoplePickerInstance = null;
// When loaded the silverlight HomePeoplePicke in html page
function onSilverlightHomePeoplePickerLoaded(sender, args) {
silverlightHomePeoplePickerInstance = new SilverlightHomePeoplePicker(sender);
}
在onSilverlightHomePeoplePickerLoaded事件中,首先通过参数sender创建一个SilverlightHomePeoplePicker的实例,SilverlightHomePeoplePicker对象的代码大致如下:
Code
// the class of SilverlightHomePeoplePicker
function SilverlightHomePeoplePicker(sender) {
this.slApp = sender;
this.objElement = this.slApp.getHost();
this.divContainer = this.objElement.parentNode;
this.page = this.objElement.Content.
Page;
// the left and top offset to the directly parent element of silverlight object tag
this.leftOffsetToContainer = 0;
this.topOffsetToContainer = 0;
this.page.addEventListener("SilverlightScopeChanged", SilverlightHomePeoplePicker.createDelegate(this, this.handleSilverlightScopeChangedEvent));
addEvent(window, "onresize", SilverlightHomePeoplePicker.createDelegate(this, this.setPosition));
with (this.objElement.style) {
position = "absolute";
zIndex = 999;
width = this.divContainer.offsetWidth + "px";
height = this.divContainer.offsetHeight + "px";
}
this.setPosition();
}
SilverlightHomePeoplePicker.prototype = {
// change scope of silverlight object tag
handleSilverlightScopeChangedEvent: function(s, e) {
this.leftOffsetToContainer = e.Left;
this.topOffsetToContainer = e.Top;
this.setPosition();
with (this.objElement.style) {
width = e.Width + "px";
height = e.Height + "px";
}
},
// set left and top positions
setPosition: function() {
setSilverlightObjectPosition(this.objElement, this.divContainer, this.leftOffsetToContainer, this.topOffsetToContainer);
}
}
SilverlightHomePeoplePicker.createDelegate = function(instance, method) {
return function() {
return method.apply(instance, arguments);
}
}
可以看到,在构造SilverlightHomePeoplePicker的实例时就设置了 <object/>元素的位置和大小。
leftOffsetToContainer和topOffsetToContainer是指silverlight plug in ( object 元素 )左上相对于包含它的<div>的左上角的偏移量,一般正常情况下,这个偏移量为0,即silverlight与包含它的<div>左上角位置重叠。初始化时同时设置了silveright插件的宽和高,即等于包含它的<div>的宽和高。
那么当Silverlight的位置和大小需要改变时怎么办呢?谁来负责处理这个变化呢?首先,在Silverlight程序里应该最先知道这个Silverlight程序的范围是否改变了,是否需要改变silverlight plug in的位置和大小来正确显示整个Silverlight的内容。例如,当弹出或关闭详细信息窗口时,Silverlight程序应该做这个检查,如果需要改变silverlight plugin的位置和大小,就通知javascript程序。
Silverlight的启动xmal文件Page.xaml大概是这个样子:
Code
<UserControl x:Class="SEL.SonySource.Silverlight.HomePeoplePicker.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Width="275" Height="324">
<Grid x:Name="LayoutRoot" RenderTransformOrigin="0,0">
<Grid.RenderTransform>
<TranslateTransform x:Name="LayoutRootTranslate" X="0" Y="0" />
Grid.RenderTransform>
Grid>
UserControl>
然后定义一个SilverlightScopeChangeHandler:
Code
///
/// When the scope of the app changed, call the handler to invoke js to resize the position of the plug-in object
/// Most time, this will be triggered by popup or close the detail window
///
///
///
public delegate void SilverlightScopeChangedHandler(Object sender, ScopeEventArgs e);
///
/// Indicate the scope of the silverlight app
///
public class ScopeEventArgs : EventArgs
{
///
/// Relative to the orginal left position, it will always less than 0
///
[ScriptableMember]
public double Left
{
get;
internal set;
}
///
/// Relative to the orginal top position, it will always less than 0
///