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应用程序,其有如下几种设置方式:
  • 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方法的使用:

 Silverlight.createObject(
"Page.xaml",
document.getElementById("SilverlightControlHost"),
"mySilverlightControl",
{
width: '300',
height: '300',
inplaceInstallPrompt: false,
background: '#D6D6D6',
isWindowless: 'false',
framerate: '24',
version: '2.0'
},
{
onError:null,
onLoad:null
},
"p1,p2,p3",
null
);

上面代码中指定的XAML源 – Page.xaml很简单,如下所示:

 <UserControl x:Class="SilverlightApplication3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Text="Hello, World!"></TextBlock>
</Grid>
</UserControl>

这样一个最简单的可运行的Silverlight例子就完成了。

下面是实现同样效果的另一种途径 – 使用Object标签。通过<object>标签可以不使用js,直接在页面中以声明方式加载Silverlight控件,同样类似于其它插件的加载,可以使用<param>来设置参数。

使用<object>的一个好处在于,可以在<object>之前放置一段HTML,从而在Silverlight没有正确初始化时在其位置显示这段HTML。如这种情况,在客户端没有Silverlight插件,或版本过低时可以通过这段HTML提供一个下载安装的链接。这种方式可以参考如下的例子:

 <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
<param name="source" value="ClientBin/SilverlightApplication3.xap"/>
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="4.0.50826.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=4.0.50826.0" style="text-decoration:none">
<img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="获取¨Microsoft Silverlight" style="border-style:none"/>
</a>
</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。

 function handleLoad(control, userContext, rootElement) {
//接收并处理自定义参数
var params = control.initParams.Split(",");
for (var i = 0; i < params.Length; i++) {
alert(params[i]);
}
//接收userContext
alert(userContext);
}

处理异常

当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函数的代码:

 //假定这里遇到一个分析器错误
//如前文所述,当我们无法预先知道错误类型时,需要查询errorType属性
function handleError(sender, errorArguments) {
var strError = "Error Details: \n";
strError += "Type: " + errorArguments.errorType + "\n";
strError += "Message: " + errorArguments.errorMessage + "\n";
strError += "Code: " + errorArguments.errorCode + "\n";
strError += "Xaml File: " + errorArguments.xamlFile + "\n";
strError += "Xaml Element: " + errorArguments.xmlElement + "\n";
strError += "Xaml Attribute: " + errorArguments.xmlAttribute + "\n";
strError += "Line: " + errorArguments.lineNumber + "\n";
strError += "Position: " + errorArguments.charPosition + "\n";
alert(strError);
}

这个自定义的错误处理函数中,使用自定义的方式展示了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方法返回一个节点的引用,需要手动把该节点的引用加入控件树中。下面的代码给出示例:

     function handleLoad(control, userContext, sender) {
    var xmalFragment = '<TextBlock Canvas.Top="60" Text="This is Inserted." />';
    var textBlock = control.content.createFromXaml(xmalFragment);
    sender.children.add(textBlock);
    }
  • 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对象的使用方法:

 function handleLoad(control, userContext, sender) {
var downloader = control.createObject("downloader");
downloader.addEventListener("downloadProgressChanged", "handleDLProgress");
downloader.addEventListener("completed", "handleDLComplete");
downloader.open("GET", "z.png");
downloader.send();
}

下面的代码是两个事件处理函数的实现,分别用来反馈下载进度与通知下载完成。

 function handleDLProgress(sender, args) {
var ctrl = sender.getHost(); var t1 = ctrl.content.findName("label");
var v = sender.downloadProgress * 100;
t1.Text = v + "%";
} function handleDLComplete(sender, args) {
alert("Download complete");
}

通过JavaScript操作Silverlight界面元素

最后我们来看一下Silverlight界面元素的编程模型,其中包括如何使用JavaScript来使用这些界面元素暴露的方法与事件。

Silverlight界面元素包括Canvas,Image,MediaElement及各类Shape,Path等,它们都为JavaScript的访问暴露了一些方法。下面逐一介绍这些方法:

  • AddEventListener/RemoveEventListener方法:

    AddEventListener方法主要用于给界面元素添加一个事件侦听器,而相反RemoveEventListener用来删除一个界面元素与事件的关联。后着通过前者返回的整数值或事件处理程序的名称来完成删除。
    下面的代码展示了AddEventListener事件的订阅与事件处理函数的实现:

 function handleLoad(control, userContext, sender) {
sender.addEventListener("mouseLeftButtonDown", handleMouse);
}
function handleMouse(sender, mouseEventArgs) {
alert(mouseEventArgs.getPosition(null).x + ":" + mouseEventArgs.getPosition(null).y);
}
  • 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:

 function handleLoad(control, userContext, sender) {
var tb1 = sender.findName("tb1");
tb1.Text = "Hello World Updated";
}
  • GetHost方法

    在界面元素上调用这个方法,可以得到包含这些界面元素的Silverlight控件的引用,这样可以获得Silverlight控件的版本号。

  • getParent方法

    通过findName方法成功得到一个界面元素的引用后,可以调用在这个界面元素上调用getParent方法来返回其父元素的引用,同样如果找不到会返回null。

  • getValue/setValue方法

    这两个方法用于访问与设置Silverlight中的附加属性。通过下面的例子可以对这两个方法的使用一目了然:

     var tb1 = sender.findName("tb1");
    tb1.setValue("Canvas.Top", 20);

    提示:

    也可以通过"[]"这样索引的形式访问或修改附加属性的值,如上面的代码等价于下面的代码: tb1["Canvas.Top"] = 20;

  • setFontSource方法

    这个方法是TextBlock独有的,通过这个方法可以给TextBlock应用新字体。如可以使用Downloader对象下载你想要设置的字体,在下载成功完成后将Downloader对象传递给TextBlock的SetFontSource方法,这样就可以将新字体应用到TextBlock。另外注意,你需要有分发这种字体的权利。下面的代码完整的展示了上文所述的场景:

     function handleIt(sender, eventArgs) {
    var control = sender.getHost(); var downloader = control.createObject("downloader");
    downloader.addEventListener("Completed", "onCompleted");
    downloader.open("GET", "NewFont.TTF");
    downloader.send();
    } function onCompleted(sender, evnetArgs) {
    var myTextBlock = sender.findName("myTextBlock");
    myTextBlock.setFontSource(sender);
    myTextBlock.fontFamily = "Simhei";
    }

界面元素也支持许多事件,可以通过前文介绍的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订阅了一些事件:

 <Canvas Height="400" Width="400">
<Ellipse Canvas.Top="0" Height="10" Width="10" Fill="Black"
MouseLeftButtonDown="onMouseDown"
MouseLeftButtonUp="onMouseUp"
MouseMove="onMouseMove" />
<Ellipse Canvas.Top="20" Height="10" Width="10" Fill="Black"
MouseLeftButtonDown="onMouseDown"
MouseLeftButtonUp="onMouseUp"
MouseMove="onMouseMove"/>
<Ellipse Canvas.Top="40" Height="10" Width="10" Fill="Black"
MouseLeftButtonDown="onMouseDown"
MouseLeftButtonUp="onMouseUp"
MouseMove="onMouseMove"/>
<Ellipse Canvas.Top="60" Height="10" Width="10" Fill="Black"
MouseLeftButtonDown="onMouseDown"
MouseLeftButtonUp="onMouseUp"
MouseMove="onMouseMove"/>
</Canvas>

下面是事件处理函数:

 var beginX;
var beginY;
var isMouseDown = false;
function onMouseDown(sender, mouseEventArgs)
{
beginX = mouseEventArgs.getPosition(null).x;
beginY = mouseEventArgs.getPosition(null).y;
isMouseDown = true;
sender.captureMouse();
}
function onMouseMove(sender, mouseEventArgs)
{
if (isMouseDown == true)
{
var currX = mouseEventArgs.getPosition(null).x;
var currY = mouseEventArgs.getPosition(null).y;
sender["Canvas.Left"] += currX - beginX;
sender["Canvas.Top"] += currY - beginY;
beginX = currX;
beginY = currY;
}
}
function onMouseUp(sender, mouseEventArgs)
{
isMouseDown = false;
sender.releaseMouseCapture();
}

其中CaptureMouse方法用于在拖动某个对象过程中,让该对象一直拥有鼠标事件,MouseMove的处理函数中通过参数传入的值持续改变被拖动对象的位置。我们还使用isMouseDown变量来保证只处理鼠标按下(即拖动过程中)时触发的MoveMouse事件。

下面给出用C#实现相同功能的代码,值得注意的是由于Canvas.Left和Canvas.Top属性是依赖属性,无法像JavaScript中那样直接访问,而需要使用Canvas.GetLeft和Canvas.GetTop这样的方法来访问:

 private double beginX, beginY;
private bool isMouseDown = false; private void mouseInit(object sender, MouseButtonEventArgs e)
{
Ellipse el = sender as Ellipse;
el.CaptureMouse();
beginX = Canvas.GetLeft(el);
beginY = Canvas.GetTop(el);
isMouseDown = true;
} private void mouseMove(object sender, MouseEventArgs e)
{
if (isMouseDown)
{
Ellipse el = sender as Ellipse;
var x = e.GetPosition(null).X;
var y = e.GetPosition(null).Y;
Canvas.SetLeft(el, Canvas.GetLeft(el) + x - beginX);
Canvas.SetTop(el, Canvas.GetTop(el) + y - beginY);
beginX = x;
beginY = y;
}
} private void mouseRelease(object sender,MouseButtonEventArgs e)
{
Ellipse el = sender as Ellipse;
el.ReleaseMouseCapture();
isMouseDown = false;
}
 

本文完

上一篇:Python网络爬虫Scrapy框架研究


下一篇:【原/转】UITableview性能优化总结