WPF,Silverlight与XAML读书笔记第四十七 - Silverlight与浏览器
说明:本系列基本上是《WPF揭秘》的读书笔记。在结构安排与文章内容上参照《WPF揭秘》的编排,对内容进行了总结并加入一些个人理解。
这部分内容主要介绍Silverlight与浏览器的交互,包括使用JavaScript来控制Silverlight。这其中起主要作用的是Silverlight插件与Silverlight.js这个文件。
首先我们要展示怎样在浏览器中嵌入Silverlight对象,有两种方式实现这个目的,第一种是使用JavaScript加载Silverlight控件,这涉及到上文提到的一个很重要的文件 – Silverlight.js,其在Silverlight插件与Silverlight内容之间建立起桥梁。
Silverlight文件包含两个初始化Silverlight控件的方法createObject和createObjectEx。它们的区别在于createObjectEx将参数序列化为JSON。
在页面引用了Silverlight.js后,我们就开始调用createObject方法初始化Silverlight。在这之前我们先了解一下createObject方法的参数。
-
Source: 用来设置控件要显示的XAML代码或XAP应用程序,其有如下几种设置方式:
-
文件引用,如source.xap
-
某个div中嵌入式的xaml代码。假如这个div对象名为xmaldiv,则设置source属性为#xamldiv来引用。
-
-
parentElement: 网页中用于包含Silverlight控件的div的id
-
ID: Silverlight控件的唯一标识
-
这个属性是一个属性数组,其中包含如下这些属性
-
width: Silverlight控件的宽度,接受像素值或百分比
-
height: Silverlight控件的高度,接受像素值或百分比
-
backgroud: 控件的背景颜色,接受ARGB或颜色名
-
framerate: 动画的最大帧率,默认值24
-
isWindowless: 布尔值,设置HTML内容是否可以显示到Silverlight上面,默认值为false。
-
enableHtmlAccess: 设置Silverlight的内容是否可以访问浏览器的DOM模型,默认值为true。
-
inplaceInstallPrompt:布尔值设置Silverlight的安装模式,true表示直接安装,false为间接安装
-
直接安装:用户无需离开访问的页面,只需接受一个软件许可协议,控件就会被自动下载安装。
-
间接安装:用户被导航到微软网站,在特定的页面中接受协议,下载并安装控件。
-
version: Silverlight控件向前兼容的最低版本
-
-
-
这个参数是一个事件数组
-
onLoad: 控件加载完毕后触发的事件
-
onError: 遇到异常时触发的事件
-
onFullScreenChange: Silverlight中指示全屏状态的FullScreen改变时触发
-
onResize: 当Silverlight的ActualWidth或ActualHeight改变时触发
-
-
initParams:定义加载控件时,传入的用户自定义参数的集合,该参数为字符串类型。这样我们可以把所要传递的参数使用逗号分隔,组成一个字符串传递过去。在接收并使用参数时,可以通过Js的split方法将字符串转化为一个数组以使用。参数的处理一般位于onLoad事件的处理程序,后文有详细介绍。
-
userContext: 该参数用于为控件设置一个唯一标识。这个标识主要用于传递给onLoad事件处理函数用以区分同一页面中不同的Silverlight控件。这个参数的行为与initParams相同,所以也可以用它来传递参数,虽然这不是推荐做法。具体使用可见下文代码。
上面介绍了这么多,下面我们通过一段代码演示createObject方法的使用:
1 Silverlight.createObject( 2 "Page.xaml", 3 document.getElementById("SilverlightControlHost"), 4 "mySilverlightControl", 5 { 6 width: '300', 7 height: '300', 8 inplaceInstallPrompt: false, 9 background: '#D6D6D6', 10 isWindowless: 'false', 11 framerate: '24', 12 version: '2.0' 13 }, 14 { 15 onError:null, 16 onLoad:null 17 }, 18 "p1,p2,p3", 19 null 20 );
上面代码中指定的XAML源 – Page.xaml很简单,如下所示:
1 <UserControl x:Class="SilverlightApplication3.MainPage" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 mc:Ignorable="d" 7 d:DesignHeight="300" d:DesignWidth="400"> 8 <Grid x:Name="LayoutRoot" Background="White"> 9 <TextBlock Text="Hello, World!"></TextBlock> 10 </Grid> 11 </UserControl>
这样一个最简单的可运行的Silverlight例子就完成了。
下面是实现同样效果的另一种途径 – 使用Object标签。通过<object>标签可以不使用js,直接在页面中以声明方式加载Silverlight控件,同样类似于其它插件的加载,可以使用<param>来设置参数。
使用<object>的一个好处在于,可以在<object>之前放置一段HTML,从而在Silverlight没有正确初始化时在其位置显示这段HTML。如这种情况,在客户端没有Silverlight插件,或版本过低时可以通过这段HTML提供一个下载安装的链接。这种方式可以参考如下的例子:
1 <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%"> 2 <param name="source" value="ClientBin/SilverlightApplication3.xap"/> 3 <param name="onError" value="onSilverlightError" /> 4 <param name="background" value="white" /> 5 <param name="minRuntimeVersion" value="4.0.50826.0" /> 6 <param name="autoUpgrade" value="true" /> 7 <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=4.0.50826.0" style="text-decoration:none"> 8 <img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="获取¨Microsoft Silverlight" style="border-style:none"/> 9 </a> 10 </object>
注意:<object>标签中data属性的值:"application/x-silverlight-2",这是Silverlight的MINE类型。Silverlight插件在加载Silverlight内容时会检查该值,如果是一个不支持的MINE类型,则无法正确加载(这是那段HTML代码就起作用了:))
使用这种方式加载Silverlight控件的另一个好处在于可以在不接受js内容的地方,如第三方博客服务中,加载Silverlight内容。
说完了两种加载Silverlight内容的方式。接下来介绍处理控件加载事件。
我们可以定义一个函数来响应控件的onLoad事件。这个函数应符合如下格式的声明:
Function handleLoad(control, userCotext, rootElement);
这三个参数分别为:
-
控件的引用,我们前文介绍的createObject的initParams函数传递的值就存储于control.initParams属性中。
-
上下文信息,这是createObject方法userContext方法传递的参数。
-
XAML根元素的引用
注意:Silverlight中加载顺序
XAML UI元素中定义的任何loaded事件会在Silverlight控件的onLoad事件之前触发。Silverlight有一个布尔类型的只读属性 – IsLoaded。该属性默认值为false,它在Silverlight控件的onLoad事件发生前被设置,通过它可以判断控件是否加载完成。
下面的示例代码,我们分别演示了处理initParams参数,接收userContext。
1 function handleLoad(control, userContext, rootElement) { 2 //接收并处理自定义参数 3 var params = control.initParams.Split(","); 4 for (var i = 0; i < params.Length; i++) { 5 alert(params[i]); 6 } 7 //接收userContext 8 alert(userContext); 9 }
处理异常
当Silverlight遇到错误,如XAML解析器无法完成解析,加载未正确完成时,在createObject()创建Silverlight控件时定义的onError事件处理程序就会被调用。
如果onError参数没有被设置,或设置为null,则默认的错误处理函数会被调用并以alert()的方式给出错误信息。
自定义的onError事件处理函数,我们称其为handleError,接收两个参数 – sender – 表示引发错误的对象,errorArguments - 为错误事件参数。errorArguments为ErrorEventArgs类型,其中有一个errorType属性表明具体的错误类型,可能的值为RuntimeError或ParserError。这两个类型都是ErrorEventArgs的子类,当通过errorType确定具体错误类型后,就可以把errorArgments作为具体子类的对象对待并访问其中特定的属性。
ParserErrorArgs中的一些属性:
-
charposition属性,包含了出错字符串的位置信息。
-
linenumber属性,包含出错的行号信息
-
xamlFile,包含出错文件的信息
-
xmlAttribute包含出错的xml属性信息
-
xmlElement包含出错元素的信息
RuntimeErrorEventArgs包含的属性:
-
charPosition属性,包含了出错字符串的位置信息。
-
lineNumber属性,包含出错的行号信息
-
methodName包含出错方法的信息
最后我们来看一段handleError函数的代码:
1 //假定这里遇到一个分析器错误 2 //如前文所述,当我们无法预先知道错误类型时,需要查询errorType属性 3 function handleError(sender, errorArguments) { 4 var strError = "Error Details: \n"; 5 strError += "Type: " + errorArguments.errorType + "\n"; 6 strError += "Message: " + errorArguments.errorMessage + "\n"; 7 strError += "Code: " + errorArguments.errorCode + "\n"; 8 strError += "Xaml File: " + errorArguments.xamlFile + "\n"; 9 strError += "Xaml Element: " + errorArguments.xmlElement + "\n"; 10 strError += "Xaml Attribute: " + errorArguments.xmlAttribute + "\n"; 11 strError += "Line: " + errorArguments.lineNumber + "\n"; 12 strError += "Position: " + errorArguments.charPosition + "\n"; 13 alert(strError); 14 }
这个自定义的错误处理函数中,使用自定义的方式展示了Silverlight中发生的错误。
Silverlight控件的属性
接下来这一部分我们介绍Silverlight插件所支持的属性,方法与事件模型。另外也介绍Silverlight控件对载入与错误事件的处理,包括Silverlight提供的默认的错误处理函数以及如何重写这个函数,如何传递自定义参数和上下文信息给Silverlight控件。
控件的属性主要分三类:直接访问类,内容访问类,设置访问类
直接访问类的属性可以使用"Control.Propertyname"的语法直接访问。内容访问类与设置访问类分别使用"control.content.propertyname"与"control.settings.propertyname"的语法访问。
直接访问类属性
-
initParams:前文有介绍,只能在初始化Silverlight控件时设置。
-
isLoaded:只读布尔属性,表明控件是否加载完
-
source:指定要显示的XAML内容源。其设置方式在介绍createObject()方法参数部分有详细介绍
内容访问类
-
actualHeight:返回Silverlight控件区域的高度,以像素为单位。
这个属性的值有几种可能,当初始化时使用绝对值(像素)设置,这个属性的值就与设置值相同,如果初始化时使用百分比设置,这个属性会随着浏览器窗口的改变而改变,当控件在全屏模式下时,它的值与系统桌面的高度相同。
-
actualWidth:返回控件实际显示宽度,决定这个属性的因素与actualHeight相同。
-
fullScreen:控制Silverlight控件是标准模式还是全屏模式。默认值false即标准模式
设置访问类
-
background: 设置Silverlight控件的背景颜色,其接受多种格式,包括颜色名,有或没有alpha值的8位或16位RGB格式。
-
enableFrameRateContent: 是否在浏览器状态栏中显示Silverlight当前的帧率,默认值为false。
-
enableHtmlAccess: 是否允许XAML内容访问浏览器DOM对象默认值为true。
-
enableRedrawRegious: 是否让Silverlight控件在每一帧重画每个区域。默认值为false,当设置为true时有利于提高程序性能。
-
maxFrameRate: 设置Silverlight动画的最大帧率,默认值为24,可设最大值为64。
-
version: 当前Silverlight插件的版本号。这是一个由逗号分隔的四个数字组成的字符串,四个数字分别表示主版本,次版本,编译号和修订号,其中前两个号是必须的。
-
windowsless: 该属性被设置为true时,控件为非窗口模式,Silverlight的内容可以高效率的显示在HTML内容的下面。
Silverlight控件的方法
Silverlight控件拥有一个直接访问类方法和三种内容访问类方法。直接访问方法为createObject,其用来创建实现特定功能的可清除对象。在Silverlight中这样的对象只有Downloader对象(后文会详细这个对象),下面介绍的3个方法是内容访问方法,应使用control.content.methodName()来调用。
-
createFromXmal方法
该方法将定义好的XAML内容动态地加入Silverlight控件中。该方法接收两个参数,第一个是包含要添加XAML内容的字符串,第二个参数指示是否创建命名空间,当为true时,会在引入的XAML中添加一个有唯一值的x:Name属性以与现有XAML元素名称区分。
添加的内容有一个限制,即只能有一个根节点,否则该把这些元素套在一个<Canvas>标签内。
另外CreateFromXaml创建的对象并没有立刻被添加到Silverlight控件中,CreateFromXaml方法返回一个节点的引用,需要手动把该节点的引用加入控件树中。下面的代码给出示例:
1 function handleLoad(control, userContext, sender) { 2 var xmalFragment = '<TextBlock Canvas.Top="60" Text="This is Inserted." />'; 3 var textBlock = control.content.createFromXaml(xmalFragment); 4 sender.children.add(textBlock); 5 }
-
CreateFromXamlDownloader方法
这个方法结合下文介绍的Downloader对象一起使用。该方法接收两个参数,第一个是用于下载XAML代码的Downloader对象,第二个参数用于指定使用Downloader对象包的哪一部分,如下载的是一个压缩包,该属性就设置为压缩包中XAML代码的文件名,如果下载的不是压缩包,该参数传入空字符串即可。
-
fullName方法
使用该方法通过x:Name查找指定的节点,如果找到则返回该节点的引用,否则返回null。
Downloader对象
接下来,介绍Silverlight中的Downloader对象,包括如何使用Downloader对象动态的向Silverlight程序中添加内容。Downloader对象用于异步下载额外内容,如包括单个资源的文件或多个资源的压缩包。Downloader对象通过JavaScript访问网络资源,Downloader对象只能从Silverlight程序所在的站点上下载内容,如果试图从别的站点上下载文件会抛出异常。
Downloader对象的属性
-
downloadProgress: 该属性为0到1间的值,代表当前下载进度,1表示下载完毕。
-
status: 该属性提供当前下载进程的HTTP状态值,这是一个标准的HTTP状态。如404表示文件未找到,200表示成功。
-
statusText: 该属性表示当前下载状态的文本信息,它与status属性保持一致,对于成功完成的HTTP请求,status值为"200",StatusText值为"OK"。更多状态码与对应文本可参见W3C对于HTTP协议的描述。
-
uri: 要下载的目标对象的地址
Downloader对象的方法
-
abort: 该方法用于取消当前下载,并将所有属性设为默认值。
-
getResponseText: 该方法返回下载内容字符串形式的结果,该方法有一个可选参数,用来指定需要返回其内容的压缩包中的文件的文件名。
-
open: 该方法用于初始化下载,其接受两个参数,第一个是下载的请求方式,目前Silverlight只支持GET方式请求。
-
send: 在使用Open方法初始化过的Downloader对象上调用该方法来执行下载请求。
Downloader对象的事件
-
completed: 下载完毕时触发该事件。此事件的处理函数接收两个参数。第一个是事件触发者,此处即Downloader对象,第二个是传入事件处理函数的一组参数。
-
downloadProgressChanged: 在内容下载过程中触发该事件。如当下载进程达到5%时会触发该事件,当进程达到100%时,在触发此事件的同时会触发completed事件。
下面我们通过代码来看一下Downloader对象的使用方法:
1 function handleLoad(control, userContext, sender) { 2 var downloader = control.createObject("downloader"); 3 downloader.addEventListener("downloadProgressChanged", "handleDLProgress"); 4 downloader.addEventListener("completed", "handleDLComplete"); 5 downloader.open("GET", "z.png"); 6 downloader.send(); 7 }
下面的代码是两个事件处理函数的实现,分别用来反馈下载进度与通知下载完成。
1 function handleDLProgress(sender, args) { 2 var ctrl = sender.getHost(); 3 4 var t1 = ctrl.content.findName("label"); 5 var v = sender.downloadProgress * 100; 6 t1.Text = v + "%"; 7 } 8 9 function handleDLComplete(sender, args) { 10 alert("Download complete"); 11 }
通过JavaScript操作Silverlight界面元素
最后我们来看一下Silverlight界面元素的编程模型,其中包括如何使用JavaScript来使用这些界面元素暴露的方法与事件。
Silverlight界面元素包括Canvas,Image,MediaElement及各类Shape,Path等,它们都为JavaScript的访问暴露了一些方法。下面逐一介绍这些方法:
-
AddEventListener/RemoveEventListener方法:
AddEventListener方法主要用于给界面元素添加一个事件侦听器,而相反RemoveEventListener用来删除一个界面元素与事件的关联。后着通过前者返回的整数值或事件处理程序的名称来完成删除。
下面的代码展示了AddEventListener事件的订阅与事件处理函数的实现:
1 function handleLoad(control, userContext, sender) { 2 sender.addEventListener("mouseLeftButtonDown", handleMouse); 3 } 4 function handleMouse(sender, mouseEventArgs) { 5 alert(mouseEventArgs.getPosition(null).x + ":" + mouseEventArgs.getPosition(null).y); 6 }
-
findName方法
此方法通过搜索XAML树查找指定的对象。如果找到会返回该对象的引用;反之返回空(null),同样下面给出一个示例:
XAML:
1 <Grid x:Name="LayoutRoot" Background="White">
2 <TextBlock x:Name="tb1" Text="Hello, World!"></TextBlock>
3 <TextBlock x:Name="tb2" Text="Hello, World 2!"></TextBlock>
4 <TextBlock x:Name="tb3" Text="Hello, World 3!"></TextBlock>
5 </Grid>
JavaScript:
1 function handleLoad(control, userContext, sender) { 2 var tb1 = sender.findName("tb1"); 3 tb1.Text = "Hello World Updated"; 4 }
-
GetHost方法
在界面元素上调用这个方法,可以得到包含这些界面元素的Silverlight控件的引用,这样可以获得Silverlight控件的版本号。
-
getParent方法
通过findName方法成功得到一个界面元素的引用后,可以调用在这个界面元素上调用getParent方法来返回其父元素的引用,同样如果找不到会返回null。
-
getValue/setValue方法
这两个方法用于访问与设置Silverlight中的附加属性。通过下面的例子可以对这两个方法的使用一目了然:
1 var tb1 = sender.findName("tb1"); 2 tb1.setValue("Canvas.Top", 20);
提示:
也可以通过"[]"这样索引的形式访问或修改附加属性的值,如上面的代码等价于下面的代码: tb1["Canvas.Top"] = 20;
-
setFontSource方法
这个方法是TextBlock独有的,通过这个方法可以给TextBlock应用新字体。如可以使用Downloader对象下载你想要设置的字体,在下载成功完成后将Downloader对象传递给TextBlock的SetFontSource方法,这样就可以将新字体应用到TextBlock。另外注意,你需要有分发这种字体的权利。下面的代码完整的展示了上文所述的场景:
1 function handleIt(sender, eventArgs) { 2 var control = sender.getHost(); 3 4 var downloader = control.createObject("downloader"); 5 downloader.addEventListener("Completed", "onCompleted"); 6 downloader.open("GET", "NewFont.TTF"); 7 downloader.send(); 8 } 9 10 function onCompleted(sender, evnetArgs) { 11 var myTextBlock = sender.findName("myTextBlock"); 12 myTextBlock.setFontSource(sender); 13 myTextBlock.fontFamily = "Simhei"; 14 }
界面元素也支持许多事件,可以通过前文介绍的JavaScript中的AddEventListener方法订阅这些事件,也可以直接在XAML中添加这些事件的处理方法(这是更常见的做法),下面依次介绍这些事件:
-
GotFocus:当界面元素得到鼠标焦点的时候触发。
-
LostFocus:该事件与GotFocus事件相反,在对象失去焦点时触发。
-
keyDown:该事件在界面元素拥有焦点,并在按下键盘键时触发,该事件包含两个参数,第一个是事件发送者的引用,第二个是KeyEventArgs对象,这个对象包含多个属性:
-
key: 一个整数,代表哪一个键被按下,具体值定义于Silverlight SDK中。
-
platfromKeyCode:这个值是由操作系统决定的
-
shift:布尔型,表示shift键是否被按下
-
ctrl:布尔型,表示Ctrl是否被按下
-
-
keyUp:该事件在某个对象拥有焦点并释放按键的时候触发,此事件与KeyDown事件拥有相同的参数。
注意:KeyDown与KeyUp事件在Silverlight全屏模式下不会触发
-
Loaded:该事件在对象被解析并加入到Silverlight控件之后,显示之前触发。
-
MouseEnter:鼠标进入对象的区域时触发。
-
MouseLeave:与MouseEnter相反,在鼠标离开对象区域时触发。
-
MouseleftButtonDown:该事件在当用户在某个对象上按下鼠标左键时触发。
-
MouseLeftButtonUp:该事件在松开鼠标左键的时候触发。
-
MouseMove:该事件在鼠标在界面元素上移动时触发。
最后我们看一个使用JavaScript操作界面元素的例子 – 实现鼠标拖拽界面元素。
首先是XAML,其中包含了要拖动的元素,并使用XAML订阅了一些事件:
1 <Canvas Height="400" Width="400"> 2 <Ellipse Canvas.Top="0" Height="10" Width="10" Fill="Black" 3 MouseLeftButtonDown="onMouseDown" 4 MouseLeftButtonUp="onMouseUp" 5 MouseMove="onMouseMove" /> 6 <Ellipse Canvas.Top="20" Height="10" Width="10" Fill="Black" 7 MouseLeftButtonDown="onMouseDown" 8 MouseLeftButtonUp="onMouseUp" 9 MouseMove="onMouseMove"/> 10 <Ellipse Canvas.Top="40" Height="10" Width="10" Fill="Black" 11 MouseLeftButtonDown="onMouseDown" 12 MouseLeftButtonUp="onMouseUp" 13 MouseMove="onMouseMove"/> 14 <Ellipse Canvas.Top="60" Height="10" Width="10" Fill="Black" 15 MouseLeftButtonDown="onMouseDown" 16 MouseLeftButtonUp="onMouseUp" 17 MouseMove="onMouseMove"/> 18 </Canvas>
下面是事件处理函数:
1 var beginX; 2 var beginY; 3 var isMouseDown = false; 4 function onMouseDown(sender, mouseEventArgs) 5 { 6 beginX = mouseEventArgs.getPosition(null).x; 7 beginY = mouseEventArgs.getPosition(null).y; 8 isMouseDown = true; 9 sender.captureMouse(); 10 } 11 function onMouseMove(sender, mouseEventArgs) 12 { 13 if (isMouseDown == true) 14 { 15 var currX = mouseEventArgs.getPosition(null).x; 16 var currY = mouseEventArgs.getPosition(null).y; 17 sender["Canvas.Left"] += currX - beginX; 18 sender["Canvas.Top"] += currY - beginY; 19 beginX = currX; 20 beginY = currY; 21 } 22 } 23 function onMouseUp(sender, mouseEventArgs) 24 { 25 isMouseDown = false; 26 sender.releaseMouseCapture(); 27 }
其中CaptureMouse方法用于在拖动某个对象过程中,让该对象一直拥有鼠标事件,MouseMove的处理函数中通过参数传入的值持续改变被拖动对象的位置。我们还使用isMouseDown变量来保证只处理鼠标按下(即拖动过程中)时触发的MoveMouse事件。
下面给出用C#实现相同功能的代码,值得注意的是由于Canvas.Left和Canvas.Top属性是依赖属性,无法像JavaScript中那样直接访问,而需要使用Canvas.GetLeft和Canvas.GetTop这样的方法来访问:
1 private double beginX, beginY; 2 private bool isMouseDown = false; 3 4 private void mouseInit(object sender, MouseButtonEventArgs e) 5 { 6 Ellipse el = sender as Ellipse; 7 el.CaptureMouse(); 8 beginX = Canvas.GetLeft(el); 9 beginY = Canvas.GetTop(el); 10 isMouseDown = true; 11 } 12 13 private void mouseMove(object sender, MouseEventArgs e) 14 { 15 if (isMouseDown) 16 { 17 Ellipse el = sender as Ellipse; 18 var x = e.GetPosition(null).X; 19 var y = e.GetPosition(null).Y; 20 Canvas.SetLeft(el, Canvas.GetLeft(el) + x - beginX); 21 Canvas.SetTop(el, Canvas.GetTop(el) + y - beginY); 22 beginX = x; 23 beginY = y; 24 } 25 } 26 27 private void mouseRelease(object sender,MouseButtonEventArgs e) 28 { 29 Ellipse el = sender as Ellipse; 30 el.ReleaseMouseCapture(); 31 isMouseDown = false; 32 }
本文完