调用 Silverlight 客户端 APIs
Microsoft Silverlight 插件 支持 一个扩展的编程模型,这包括 托管 和非托管 代码. 非托管 APIs 在 Silverlight 1.0 (formerly WPF/E) Beta release是有效的,同样也存在于Silverlight 1.1 Alpha (May 2007) release. 而 托管 APIs 是在 Silverlight 1.1 Alpha (May 2007) release才支持的. 你可以使用CLR调用 托管 APIs ,这个特性也是在新版本的VS也就是 Visual Studio Code Name "Orcas."中将要集成的, 目前, C# 和 Visual Basic 是可以在1.1中使用的.你也可以使用 dynamic language runtime (DLR). 更多关于 DLR, 请查看 怎么样使用动态语言来开发 Silverlight .
详细的介绍 Siverlight 托管 API 目前不能提供. 然而, 你可以使用一些反射工具来查看 托管 程序集而来了解有些什么 APIs 是可以调用的. 有关此项详细消息, 请查看 怎么样使用Visual Studio Object Browser浏览Silverlight 程序集.
在本篇我们将介绍一些你在开发基于Silverlight的 应用程序的一些常用的使用托管代码的APIs . 在大多数情况下, 对于object model而言,托管 API 一般与对语法和出现的位置都比较严格,这和 非托管 API是一样的. 因此, 你甚至可以在非托管 代码的基础上来进行 托管代码的开发. 然而, 因为object model或命名方式的不一致,这种等价还是需要商榷的.
Silverlight 托管 类库的 层次
很多的托管类依靠Silverlight control 和其UI 特性进行编程,它们都是遵循这些层次关系的:
依赖对象
UIElement
FrameworkElement
如果你曾经有过Windows Presentation Foundation (WPF)开发基础, 那么你就会对这里的层次关系感到熟悉. 然而, 却不像WPF中表面一样, 当在开发 Silverlight程序时,你将自己总结和体会对每个类所充当的在层次关系中的角色. 在Silverlight 中你可以这样认为: FrameworkElement 类是基础 "element" 类,它们包含了很多你在XAML中定义或UI中定义的元素的有用的 APIs.
但是这不代表所有你在 XAML中例示的元素都是 FrameworkElement. 这里有许多元素,它们起了给其它一些UI元素的某些属性赋值的作用, 但是这些属性可能会被精心的包装过. 我们举一个确切的例子, Animation 或 Storyboard的派生类, 它们都不是 FrameworkElement 对象. 事实上, animations Storyboard 经常用来控制一些UI 元素.
浏览 Object Tree 并且 找到已经命名的Objects
一个独立的XAML页的object tree一般都是以一个 XAML root element开始的. 当前存在的 object tree 假设 XAML 页 已经是 Silverlight control的源并且已经解析成功了 . (当然也可以直接创建一个作为resources的 XAML 文件, 或者直接将其载入到object tree中,在这种状况下object tree加入到XAML的 root 里来.)
对于 托管代码, 当你为XAML root定义了一个 x:Class , 这样你就可以轻松的得到XAML root的引用: 你可以直接在后台敲入 "this" 或 "Me". 然而, 某一个object的object tree中的属性并不是都可以引用到的,比如它的构造函数. 如果构造函数被调用,在这种情况下, object tree的大部分还没有构造出来; 你必须等到XAML被解析了.
对于根结点前的一些object对象,你有三种方法来使用它们, 其中有两点是有密切关系的.
你可以顺着object tree的root一直往下, 使用关相的 内容模型所提供的属性来得到所需要的对象引用, 比如 Panel.Children, 或使用泛型集合索引. 这种方法可能并不是你最好的选择, 因为这需要你对XAML文件的结构非常的熟悉. 这通常只是用来进入内容模型,奇怪的是, 目前确没有单个的 API 是提供这项功能的. 一般的,你可能只是使用这种方法来进入内容模型或集合的的某一个层次而已.
你可以给XAML element敲入一个 x:Name属性 . 然后你就可以使用DependencyObject.FindName这个API来得到该对象的引用了.(DependencyObject 是一个很基础的类, 所以你几乎可以在所有的Element中找到这个API.) 如果你使用过 非托管 APIs 来开发过 Microsoft Silverlight 1.0 (formerly WPF/E), 那这个方法你可能就会比较熟悉了. FindName 返回一个类型的 Object, 但FindName 有一个不太方便的就是你必须指定它返回的类型,这样你才能正确有使用它. 这意味你必须了解当前方法所返回的正确的类型.
CS
//using 'this' against the root where this x:Class applies//in this case we know that we are trying to find a TextBlock and can cast TextBlock tb = this.FindName("myTextBlock") as TextBlock;
VB
'using 'this' against the root where this x:Class applies 'in this case we know that we are trying to find a TextBlock and can cast Dim tb As TextBlock = CType(Me.FindName("myTextBlock"), TextBlock)
只要你使用Visual Studio Code Name "Orcas" 和其提供 Silverlight 模板,并使用它的 build过程, 这你就可以有一个更好选择了. 给你的Element一个x:Name 属性. 这样, 你可以不用一定要使用 FindName了, 考虑到 x:Name 其实是和后台的相对应的类是引用关系. 打个比方, 如果你创建了一个element <TextBlock x:Name="myTextBlock"/>, 并且你编程的对象是 x:Class,这样,你只需要要简单的敲入 myTextBlock 就可以得到引用了, 然后你敲入一个点, 相对应的属性,方法或者事件处理事件就能出来了. 通过IntelliSense你可以浏览对象所提供的所有可供调用的内容. 每次生成后台代码的过程可以确保这些自动引用的正确添加. 这个生成的代码文件其实是x:Class的一个partial类,它们将在使用的时候一起编译 . 你可以查看Silverlight project的编译机制: x:Class的partial class定义, XAML root中的 Loaded handler 其实会调用后台文件的 InitializeComponent, 和在obj 目录中生成的文件 (它重定义了 InitializeComponent 以此来在每次加载时,能正确的提供这种引用关系). 你可以发现当你加入一个已经命名的element到 XAML文件中来, 你可能需要预编译,这样才能得到相应的有着最新对象名的 IntelliSense .
生成的后台代码和其引用都是能简单获得的,但你应该注意,你并不是时刻都能有效的使用它的. 比如, 当你想进行 Silverlight control 合并, 你会发现后台文件和XAML文件的关系似乎是反着的, XAML文件总要比后台文件慢一点. 因此, 你可能会觉得在创建自定义控件时使用 FindName 要更频繁一些.
从object tree 向 root element方向遍历, 你可以用 API FrameworkElement.Parent. 当到达 root时, FrameworkElement.Parent将返回 null.
获取或设置一些附加属性
附加属性是XAML 语言定义的,以此来说明怎么样给 elements 的各种特殊属性赋值, 甚至这个属性并不存在于当前element的成员列表中. 举附加属性的一个例子,在 Silverlight 客户端有一个 API 由三个 Canvas 属性组成, 他们可以定位Canvas的子elements的呈现外观. 他们是 Canvas.Left, Canvas.Top, 和 Canvas.ZIndex. 当你想在XAML文件中进行这些属性的设置的时候, 你必须以 Canvas owner的类型来限定并以Owner.Property 形式来调用, 注意,你不要直接在 Canvas中来设置该属性. 相反, 你应该在 Canvas的直接子对象中来设置, 父对象 Canvas 会读入这些属性信息,来控制子对象的呈现效果.
因为附加属性的特殊性,在托管代码中,你不能使用 Object.Property 形式来给其赋值, 因为这总有两个对象在使用 (一个是 XAML 中的对象本身, 还有一个是在属性被设置的时候的对象的实例).
在 托管 代码中,你可以通过调用DependencyObject.GetValue来得到 附加属性值 .第一个参数是指属性的从属者, 它是后台类中的一个成员.你可以通过 DependencyObject.SetValue来设置属性值. 第一个参数同样是指属性的从属者, 第二个是要设置的值. 在每个示例中,都是使用的实例方法, 它们的调用都依靠实例所提供的可以设置的附加属性.
CS
//myTextBlock is a TextBlock from XAML, which is child of a Canvas double currentLeft = (double) myTextBlock.GetValue(Canvas.LeftProperty);if (currentLeft > 400.0) { myTextBlock.SetValue(Canvas.LeftProperty, 400); }
VB
'myTextBlock is a TextBlock from XAML, which is child of a Canvas Dim currentLeft As Double = CType(myTextBlock.GetValue(Canvas.LeftProperty), Double)If (currentLeft > 400.0) ThenmyTextBlock.SetValue(Canvas.LeftProperty, 400)End If
GetValue 和 SetValue 其实有很广的用处.对于 Silverlight client,只要对象提供了相应的属性,你就可以通过这两个方法来取得或者设置它们的值.例如, 你可以调用 myTextBlock.SetValue(TextBlock.TextProperty "hello"). 但是在这些示例中,并没有 "regular" (非附加) 属性, 然而 Instance.Property 形式对于设置或获取属性取来说还是要更直观一些.
高和宽
Height 和 Width 存在于 FrameworkElement. 你可以设置 Height 和 Width 在 Canvas, TextBlock 和各种 Shape 基础类上.
有一小部分的elements (比如 TextBlock) 还存在一个 ActualWidth 和 ActualHeight 属性. 它们是在实际情况下被计算出来的,只读的属性. ActualWidth 和 ActualHeight将受到多种因素的影响而改变,这将帮助你得到实际上显示出来的尺寸. 比如, 当你的 TextBlock里包含一些文字, 文字的大小受 FontSize, FontFamily, FontSpacing, 等的影响, 当然,TextBlock 的实际展示大小也会受这些因素的影响了.
注意
Height 和 Width 对于自定义控件,目前还需要有一个工作区来配合使用, 这取决于你如何使用基础的属性. 想查看详细说明, 点击 怎么样创建一个自定义的 Silverlight Controls.
TextBlock 和 Text APIs
TextBlock 类 包括 一系列的APIs. 在此我们只是简单介绍一下text 对象 , 它们是一些常见的与TEXT相关的属性,可以用来设置TEXT的外观等:
FontFamily: 以一个string对象来对Text的 font family进行设置. Silverlight 默认只支持几种字体(请查看非托管 文档 TextBlock 来了解更详细的内容).你可以调用 TextBlock.SetFontSource方法来取得更多的字体.
FontSize: 以pixels为单位来设置.
FontStretch: 使用 FontStretches 枚举. 你可以在object browser中查看枚举的值, 或是从 Silverlight 1.0 beta documentation来了解更多细节.
FontStyle: 设置成 FontStyles 的枚举, Normal, 或 Italic.
FontWeight: 设置成 FontWeights 的枚举. 你可以在object browser中查看枚举的值, 或者查看非托管 Silverlight 文档来了解更多细节.
Foreground: 一般的, 这将由XAML设置成 solid color , 但也可以设置成任何从 Brush类派生的类 (例如, ImageBrush 或 GradientBrush) 这样可以为其添加更多的特效.
TextDecorations: 设置成 TextDecorations 的枚举, None, 或者 Underline.
TextWrapping: 设置成 TextWrapping 的枚举, NoWrap, 或者 Wrap.
Text
CS
TextBlock tb = new TextBlock();tb.FontFamily = "Arial"; //one of the initial fonts tb.FontSize = 20;tb.FontStretch = FontStretches.SemiExpanded;tb.FontStyle = FontStyles.Italic;tb.FontWeight = FontWeights.DemiBold;SolidColorBrush golden = new SolidColorBrush();golden.Color = Color.FromRgb(211, 147, 12);tb.Foreground = golden;tb.TextDecorations = TextDecorations.Underline;tb.TextWrapping = TextWrapping.Wrap;tb.Text = "I am nondefault!";Run il = new Run();il.FontSize = 40;il.Text = "Hello!";tb.Inlines.Insert(0,il);
VB
Dim tb As TextBlock = New TextBlock()tb.FontFamily = "Arial" 'one of the initial fonts tb.FontSize = 20tb.FontStretch = FontStretches.SemiExpandedtb.FontStyle = FontStyles.Italictb.FontWeight = FontWeights.DemiBoldDim golden As SolidColorBrush = New SolidColorBrush()golden.Color = Color.FromRgb(211, 147, 12)tb.Foreground = goldentb.TextDecorations = TextDecorations.Underlinetb.TextWrapping = TextWrapping.Wraptb.Text = "I am nondefault!"Dim il As Run = New Run()il.FontSize = 40il.Text = "Hello!"tb.Inlines.Insert(0, il)
Colors 和 Brushes
当你在XAML中设置颜色值时,你可以设置基于UNIX X11的已经命名的几乎256种solid colors颜色参数 , 包括有趣的 "MistyRose" 和 "PapayaWhip". 或者, 你可以使用一个#,再加一个字符串量来设置 RGB 或者 ARGB . 在 Silverlight 托管 API中, 预定义的可用的颜色是很少的,只有16种颜色. 因此, 主要的定义 solid color 的方法是调用 静态的 Color.FromArgb, Color.FromScRgb, 或者 Color.FromRgb 方法. 然而, 各种从 Brush 中派生的(比如SolidColorBrush) 只有默认的构造函数. 他们并没有提供方便人们使用的构造函数. 在你调用构造函数后,还必须为其它的一些相关属性进行赋值. 象这些类 RadialGradientBrush 你必须设置每个 GradientStop 并将其添加到 GradientCollection.
CS
Rectangle r = new Rectangle();r.Width = 400;r.Height = 300;SolidColorBrush golden = new SolidColorBrush();golden.Color = Color.FromRgb(211,147,12);r.Fill = golden;
VB
Dim r As Rectangle = New Rectangle()r.Width = 400r.Height = 300Dim golden As SolidColorBrush = New SolidColorBrush()golden.Color = Color.FromRgb(211, 147, 12)r.Fill = golden
这儿包含一些常规的代码, 比如创建一个 brushes 和创建一些用于重用brush的机制. 下面创建brush的代码会有些复杂,但你可以发现这将是有用的:
定义一个brush,并使其保存一个XAML文件, 或者存成一个在XAML中的string(包含根中的xmlns 声明).
把刚才那个文件或者string作为你的应用程序的一个嵌入资源.
以stream方式来访问当前资源, 使用这个stream作为string资源,并调用 XamlReader.Load API (更多关于些 API 的信息将在以后的部分介绍).
把得到的结果应用到 适当的 Brush 类, 并以此来设置相应的 属性.
对于此种方法,你可以使用一些设计工具,比如Microsoft Expression Blend来生成XAML, 使用这些工具你将可以大大减少你的工作量,你不用在对ARGB的值进行麻烦的设置,也不用从别的地方复制strings. Silverlight 托管 API没有包含资源系统, 该系统目前应用在基于 ResourceDictionary-based 的 WPF中,但嵌入资源是目前我们可用的.
鼠标 和 鼠标位置
浏览的客户端 APIs 可以用来得到 鼠标 的位置,这几乎可以在所有支持的浏览器中使用了, 但对于 基于Silverlight的 应用程序这并不够完美.取代以往的用一个service来不断的监视鼠标的动作(这用在 WPF中)的方法,现在我们只在触发某些事件事才返回鼠标座标位置. 然而, 这有一个特殊事件 MouseMove, 这个事件将不断的监视鼠标动作. 极端情况,如果鼠标从不移动, 你也可以得到鼠标的初始位置,这可以使用主 Canvas上的 MouseEnter 事件, 这只要 Canvas 被载入就将触发 (只要鼠标初始位置在该区域内). 你可以通过调用鼠标事件实体中的 MouseEventArgs.GetPosition方法来得到鼠标位置, 与以往得到直接的 X 和 Y 位置参数不同. 这个方法需要UIElement的参数; 你提供给此方法的element参数将用来计算成位置数值. 如果你不设置值,将其为 null, 那么当前座标系统将与 Silverlight 控件内容区域有关. 一般的, 一个典型的用作传入到GetPosition参数的element是这个事件的 sender (你需要给sender转化类型来使其成为一个UIElement).你可以填入任何element, 包括和这次点击事件无关的一些控件, 这可能会导致你从GetPosition得到的 X 和 Y 值为负值.
CS
void el_MouseClick(object sender, MouseEventArgs e) {//sender is a 200px ellipse, the closer you click to its relative center, the more it disappears Ellipse el = sender as Ellipse;double x = e.GetPosition(el).X;el.Opacity = Math.Abs(x - 100)/100;}
VB
Sub el_MouseClick(ByVal sender As Object, ByVal e As MouseEventArgs) Handles HotMouse.MouseLeftButtonUp'sender is a 200px ellipse, the closer you click to its relative center, the more it disappears Dim el As Ellipse = CType(sender, Ellipse)Dim x As Double = e.GetPosition(el).Xel.Opacity = Math.Abs(x - 100) / 100End Sub
最常见的需要得到鼠标 位置的场景分别是 :当 鼠标 点击时, 或当鼠标越过某个边界线的时候.这两个应用已经加入到到 drag-and-drop 应用中了.
XamlReader
在JavaScript中, 不存在构造函数一说, 所以你不能向树中添加你创建的某个element实例对象. 但你可以使用像 DHTML的一些方法, 向已经构造完的 parallel trees和使用 hide-show 方法, 你可使用animations (但只能更改属性值, 而不能添加新的到tree中), 或者你需要调用 Control.content.createFromXAML 来有效的并与转换配合来创建一个构造引擎. 为此,你会发现你会经常在Silverlight 1.0中调用 createFromXAML 来创建你特殊的动态的UI.
在 托管 代码中, 你需要访问构造函数. 因此, 在很多情况下, 调用 XamlReader.Load (它与非托管代码中的 createFromXaml 是一样的) 却并不是很多, 因为你现在已经处于一个 编程模型之中了,你调用构造函数已经是很容易的事情了. 调用构造函数, 你可以将各种属性设置一并添加到控件中,并能动态添加到当前存在的object tree中, 如果你的动态界面改变比较简单,那么这种模式是一个不错的选择.
对于另一种情况, 如果界面的改变比较复杂, 而且你承担了美工和代码两方面的工作,美工方面希望对于两个不同的状态有着两个不同的外观,你可能会想到从XAML中动态的载入内容来刷新 UI. 然而, 在这种情况下, 你不应该直接从一个string里来载入内容到 XAML, 就像 XamlReader.Load 的说明提示的一样. 相反的, 你应该从一个文件或一个保存好的源中来载入string, 你可以使用 System.IO.StreamReader, 这样,对于设计和编码人员来说,他们之间的联系就可以建立在一个文件上,这比一个string明显要好多了吧.
CS
Assembly assembly = this.GetType().Assembly;//BigBrush.xaml is a LinearGradientBrush with half a dozen stops//and perhaps it gets used frequently, from different files Stream s = assembly.GetManifestResourceStream("CallClientAPIs.BigBrush.xaml");StreamReader sr = new StreamReader(s);Brush b = (Brush)XamlReader.Load(sr.ReadToEnd());sr.Close();
VB
Dim assembly As Assembly = Me.GetType().Assembly'BigBrush.xaml is a LinearGradientBrush with half a dozen stops 'and perhaps it gets used frequently, from different files Dim s As Stream = assembly.GetManifestResourceStream("CallClientAPIsVB.BigBrush.xaml")Dim sr As StreamReader = New StreamReader(s)Dim b As Brush = CType(XamlReader.Load(sr.ReadToEnd()), Brush)sr.Close()
无论是JavaScript createFromXAML 和托管的 XamlReader.Load 方法,你还需要考虑这些不同的方法对于不同的UI设计工具产生出来的markup语言的兼容性.
Downloader
在 托管 代码中, 你可以直接使用构造函数来创建 Downloader 对象 (在 非托管 代码中,你需要调用Control.createObject 来从Silverlight control中创建).
在你构造完 Downloader后, 你可能马上会对其添加一个或多个DownloadProgress , DownloadFailed, 或者 Completed 事件. 然后, 你调用 Open, 通过资源的 Uniform Resource Identifier (URI) 来进行下载. 最后, 你调用 Send 来开始你的请求.
注意
如果你要控制下载过程 ,在调用Downloader.Open前请确保设置了 async 参数为 true .
得到客户端浏览器的 Events 和 相关信息
你可以访问浏览器中的 HTML DOM , 你可以通过这个方法来得到一些相关信息 (查看 怎么样通过托管代码来访问 HTML DOM). 然而, 这种方法会存在有一个比较特殊的不容易获取的的信息 : 浏览器的窗口尺寸 (minus toolbars, edges and chrome), 以及用户何时改变了窗口的大小.有一个快捷的方法可以用来得到此信息,这就是使用BrowserHost 类, 它存在于 System.Windows.Interop 命名空间中. 在任意时间, 你可以检查 BrowserHost.ActualWidth 和 BrowserHost.ActualHeight 属性. 你也可以给 Resize 或者 FullScreenChange 加上事件处理,这样你就可以准确的知道当ActualWidth 或者 ActualHeight 改变时的一些数据了, 这些事件是由 user 动作 (resize 或者 转入 fullscreen) 时发生的.
Rectangle 和 Rect
Silverlight 有两个类型名称很象的东西: Rectangle 类, 它的 namespace 在System.Windows.Shapes, 还有一个Rect 结构, 它在 System.Windows中. Rectangle 是一个UI element,它可以用来放在页面上填充空间,可以为其进行颜色等各种设置. 而 Rect 结构 并不是 UI element; 它是一个用来为其它对象存储值的结构体, 并且它并没有一个直接的呈现. 打个比方, Rect 描述了一个RectangleGeometry 或者 Stroke的bounds 值 . Rect 包括 几个实用的方法, 通过这些方法你可以比较 Rect 实例的值, 或者改变已有的 Rect对象的值.