2008年10月,写了一篇《动态加载XAML文件》,其中按照Silverlight MSDN的“资源文件”一节的介绍,按照“Build Action”(生成操作)的三种不同方式Resource、Content、None,分别加载了三个XAML文件,以示不同。 这两天经过实践,发现并不像文档中说的“Build Action”仅能取三者之一,比如俺博客中也提到了默认的“Page”也可以,和“Resource”基本上一样。重要的是,这两天发现把“Build Action”设为“Embedded Resource”也可以动态加载,不过原来的统一LoadXaml(string file)方法不再适用, 需要用到“反射”。 把《动态加载XAML文件》一文中的工程,再添加一个btnEmbedded.xaml文件,内容和btn1等类似,设置属性的“Build Action”为“Embedded Resource”。在Page_Loaded中则用下述反射方法生成Button,文件名字需要“namespace+所在的位置”,typeof(Page)获取到该文件所在的程序集。 //从Build Action为“Embeded Resource”的XAML文件读取。//异常捕捉省略。 String name = "LoadXaml.btns.btnEmbedded.xaml"; System.IO.Stream stream = typeof(Page).Assembly.GetManifestResourceStream(name); String xaml = new System.IO.StreamReader(stream).ReadToEnd(); Button btnEmb = (Button)System.Windows.Markup.XamlReader.Load(xaml); 把生成的XAP解压缩,反编译后可以看到,btnEmbedded.xaml文件位于Resources目录下,和LoadXaml.g.resources同级;而Build Action为Resource或Page的Xaml文件,则位于LoadXaml.g.resources之中。这就是为什么两者需要用不同的方法来加载。 ![]()
然而,上述两者同样最终都是在LoadXaml.dll程序集内,不像Build Action为“Content”的在Xap包内而在该程序集外,也不像Build Action为“None”的就根本不在Xap包内。只是提醒你复习下《Silverlight Resource 概览》。 |
在Silverlight中,Size、Rect、Rectangle、RectangleGeometry有点相像,有点关联,零星散落用到一个是一个,不如放在一块比较比较,联系联系,加深理解。 Size其实是个结构体,只有一个构造函数,需要两个参数,描述对象的高度和宽度。示意如下: public struct Size { public Size(double width, double height) { if ((width < 0.0) || (height < 0.0)) { throw new ArgumentException(); } this._width = width; this._height = height; } } 尽管宽和高是double,但尽量还应使用整数值。(PS:用double是为了和WPF兼容,此乃后话。)同时宽和高不能为负值,上限不是double.PositiveInfinity,而是一个由Silverlight本机的代码强制实施的较低数字,约为1,000,000. Rect和Size非常类似,也是结构体,但除了宽高之外,Rect还指定了原点(X,Y)。一般Rect用于与较低级别的图形表现相关的API,而Size用于与UI表现和布局相关的API。有三个构造函数,示意如下: public struct Rect { public Rect(double x, double y, double width, double height) { if (width < 0.0) { throw new ArgumentException("", "width"); } if (height < 0.0) { throw new ArgumentException("", "height"); } this._x = x; this._y = y; this._width = width; this._height = height; } public Rect(Point point1, Point point2); public Rect(Point location, Size size); } 宽高情况与Size类似,(即不可以为负值,帮助文档中宽高可以为负值是错误的。)原点可以为负值,表示起点在布局之外。 可见Rect本质就是一个值,这个值是个矩形区域。由于命名原因,常与Rectangle相混淆,其实这两兄弟基本就没联系。Rectangle是一个UI元素,隶属于Shape,仍在XAML中,出来的就是个矩形,还可以对这个矩形Shape填充颜色、增加事件等。代码示意: public sealed class Rectangle : Shape { } Rect、Rectangle哥俩还有一个胞弟叫RectangleGeometry,看名字就知道它与Rectangle的区别就在于Geometry和Shape的差异:Shape类有Fill、Stroke、Opacity等呈现属性,而Geometry类只能定义几何图形,无法呈现。Geometry最常用于两个用途:1,作为Path形状的Data属性,用来描绘内容。2,用于Clip其他UI元素。 而RectangleGeometry有一个名字为Rect的属性,改属性的类型为Rect结构体。示意如下: public sealed class RectangleGeometry : Geometry { public Rect Rect { get; set; } } 总结一下: Size和Rect,都是结构体,宽高定义类似,但后者多个原点定义。 Rect和Rectangle,除了名字类似外,几乎不会用到一块。 Rect和RectangleGeometry,Rect作为一个值,可以赋给RectangleGeometry的Rect属性。 Rectangle和RectangleGeometry,两个都是密封类,由于继承自不同的父类Shape和Geometry,差异也一并显现。 ![]() 示例Rect和RectangleGeometry,有图片元素正常如左,被一RectangleGeometry剪辑,结果如右,示例代码: img.Clip = new RectangleGeometry { Rect = new Rect(40, 20, 100, 100)}; |
学习Silverlight的控件,不管是Core Controls,抑或是Toolkit Controls,甚至是第三方控件,除了掌握最基本的用法,常见属性、事件等,对控件的外观进行美化也非常必要。我原来习惯是直接对Templates 和 VSM进行手写修改,生产力低下。曾经简单试过Expression Blend,这两天又重新学习下,至少掌握怎么修改控件的Template,比如为某个状态变化加个动画什么的。
一:认识“交互”面板。在Blend中创建动画,必须先熟悉下“交互”面板上常用按钮的基本作用。下图是来自Blend帮助文档WPF示例,和Silverlight基本一样,只注意“对象和时间线”里的几个重要按钮即可,更多的参考MSDN《“交互”面板》。
![]()
⑷是Storyboard选项,初次点击是添加,之后可以删除、复制等。 ⑾是播放指针,指示动画所在的时间点。可以在时间线中拖动播放指针,设置时间点,同时也可预览动画。 ⑿关键帧,关键帧具有不同的级别:简单关键帧、复合关键帧、对象级关键帧等,以椭圆的大小区分。在关键帧上右键,可以删除,设置一些选项等。 ⑺是记录关键帧 ,记录选定对象的属性在当前时间点的快照。也即点击一下添加一个关键帧,然后为这个关键帧选定相关属性的变化值。 二:创建简单动画。在Expression Blend 中,动画由时间线组成,可在时间线上记录关键帧,以表示属性更改的时间设置。相关的几个按钮也已介绍,按照MSDN《创建简单动画》的步骤,清晰明了很快学会。动画的概念、分类不再本主题之列。
三:修改控件模板。在Blend选中某个控件,右键选择“编辑控件部件(模板)”,然后单击“编辑副本”(修改默认模板)或“创建空项”(从空白创建)。出现 “创建样式资源”对话框,输入模板的名称。其中“定义位置”的三个选项代表着Style的应用域,应用程序所有文档中同样控件均可使用该模板、本文档中同样控件可使用、只有该文档中的该控件使用该模板。
![]()
确定后即可开始修改模板。本小节更详尽的过程见MSDN《创建系统控件的可重用模板》。 四:修改模板的可选项。对于一个控件的原有模板,有哪些可以修改,或者自定义的呢?修改不同状态下控件的外观,修改控件状态改变的过渡时间等。一般步骤就是选中某状态/状态变化,设置属性/时间的变化。具体可参考MSDN。
![]()
还有就是为状态添加动画,使控件过渡到该状态后加以显示。有了上述基础,这也不是难事了,可参考《添加状态改变后显示的动画》。 写得有点虎头蛇尾,每个小节在MSDN上都有相关内容,只不过它分门别类的组织方式,不适合就某一个问题的线性解决,我把它们串了起来。学会修改系统控件的模板后,还可以把这个模板应用到其他控件上,同时自定义控件的模板创建也会方便得多。 当然,Blend可学的内容也很多,比如单是设计可以成就《蛇蝎美女》,设计复杂的动画可以参考Lawrence的《使用XAML和Blend创建动画》,甚至构建3D动画都没问题。 |
枚举Key指定不特定于操作系统的可移植键代码。一般用于KeyDown和KeyUp事件的KeyEventArgs参数提供的Key值。(KeyEventArgs还包含以下属性,特定于操作系统的PlatformKeyCode,Handled和OriginalSource。) Key是一般的枚举,尽管号称“简易而不简单”,用起来注重简单即可,无需深究太多。KEYNONE为0;BACKSPACE为1到DELETE为19的特殊键;DIGIT0到DIGIT9,20到29;A到Z,30到55;F1到F12,56到67;剩下的是小键盘从NUMPAD0到DIVIDE,68到82;最后是个KEYUNKOWN,255。 和Key枚举一样,在System.Windows.Input命名空间下,有一个KeyBoard类,这个类比较特殊,只有一个成员,是个属性,名称是Modifiers,返回值是枚举ModifierKeys。枚举ModifierKeys指定修改键集合,具有FlagsAttribute特性,即位标识,其成员值按位组合,每个都是2的某次方。俺就不再多解释位标记,简单说点技巧。ModifierKeys的定义大概如下: [FlagsAttribute]public enum ModifierKeys { None=0; //没有按下任何修饰符 Alt=1; //按下Alt键 Control=2; //按下Ctrl键 Shift=4; //按下Shift键 Windows=8; //按下Windows徽标键 Apple=8; //按下Apple键(与Windows键不会同时存在?) } 所谓位标记,就是按照二进制来看,每个bit代表了一个值,每个值其实也只有一位上是1。因此某个值与其他值按位与(&)的结果都是0,而不同值之间的按位或则可代表这几种值的组合。 ModifierKeys.Shift & ModifierKeys.Control,即0010 & 0100 为 0;ModifierKeys.Shift | ModifierKeys.Control,即 0010 | 0100为0110,代表两个键都按下。 而KeyBoard.Modifers代表目前所按下的修改键,若当前按下Shift键和Ctrl键,则KeyBoard.Modifers 的值为ModifierKeys.Shift | ModifierKeys.Shift。此时, KeyBoard.Modifers & ModifierKeys.Control == ModifierKeys.Control,即0110 & 0010 == 0010, 用这个等式可判断Ctrl键是否被按下,同理有语句: bool shiftDown = KeyBoard.Modifers & ModifierKeys.Shift == ModifierKeys.Shift;bool shiftDown = KeyBoard.Modifers & ModifierKeys.Shift != 0; 若shiftDown为true,则表面Shift键被按下。(但并不一定是只有Shift键被按下。) 若要判断只有某修改键被按下,更为简单,只要判别则KeyBoard.Modifers的值即可: bool onlyCtrlDown = KeyBoard.Modifers == ModifierKeys.Control; 举一反三,判断某对修改键组合被按下,判断只有这对修改键组合被按下。(略) 学习他人的代码,判断Ctrl键是否按下,这两者交替出现,搞得我不知道用哪个才好。要是有人讲解枚举时,拿Key和ModifierKeys做例子多么一举两得。 Silverlight文档:修改键始终生成自己的键事件,可以选择处理这些事件,也可以选择跟踪自己的修改键状态,(PS:一般是在KeyDown事件和KeyUp事件里放置一个自定义的bool标记符跟踪。)但用Modifiers通常更为方便。 附送一个在名称为tb的TextBox只能输入数字的代码片段,加深理解。 private void tb_KeyDown(object sender, KeyEventArgs e) |
本文对应于Silverlight 2帮助文档中“Application Model”章节,微软的介绍顺序或许着眼于宏观架构,而本文则属于问题导向型的介绍。在实际中遇到初始化化页面、加载其他xap中的应用程序、调用资源文件等问题,在解决过程,不断的参考阅读了这部分内容。尽管单一逐个地解决了问题,但重新组织,并整理下自己的思路,理解将会更加深刻。
Application类封装了一个Silverlight应用程序(单例模式),它主要表示应用程序代码在 Silverlight 插件生命周期中的入口点(插件激活过程可参考下图)。提供若干服务:应用程序生存期管理、 应用程序资源文件加载、 未处理的异常处理和Web 宿主集成等。(“Application Service”)
Application类有三个事件成员:Startup、Exit和UnhandledException。在新创建Silverlight应用程序时,在自动生成的App.xaml.cs会看到相应事件的处理方法。Exit和UnhandledExeption可查看帮助文档,暂时只关注Startup事件。
Startup用来在应用程序启动时进行一些初始化操作,最基本的指定RootVisual属性来显示主页面。其次就是检索传进来的参数StartupEventArgs 对象的 InitParams 属性,进行相应的处理,比如如何确定不同的起始主页面,参考TerryLee的《在Silverlight 2应用程序中切换用户控件》。还可以通过HtmlDocument.QueryString从宿主网页中检索URL参数,通过IsolatedStorageSettings类检索先前应用程序的会话数据等。
此外,Startup中也可以检索其他资源,比如用WebClinet等异步下载其他资源文件或程序集。若文件为Zip或xap,则要利用到Application.GetResourceStream方法。若文件为程序集,则要创建一个新的AssemblyPart对象,并调用它的Load方法来进行加载。当然,调用其他资源文件,也可不必Startup中进行。比如可以在某页面中点击按钮来触发事件,调用其他xap中的某个程序集。
理解调用外部xap中程序集的过程,首先需要了解“Application Structure”,查看并理解AppManifest.xaml文件。需要注意的是,beta2中用Deployment对象来获取AppManifest.xaml文件中的元素已不再有效,可以利用Linq方式读取。可参考《Dynamic Load & Invoke Other Xap For SilverLight 2.0》,内容是中文的,这篇文章有改进之处,比如把这个过程抽象为一个方法,可供以后直接调用。
Application类处理上面提到的Application.GetResourceStream方法外,还有一个常见的方法就是Application.LoadComponent。查看page等页面构造函数中InitializeComponent()方法即可看到它的出现。该方法也常用来调用“Build Action”为“Page”的xaml文件,参考《动态加载XAML文件》。
Application还有其他方法,不再逐一介绍。最后看一下Application的属性,Current、Host、Resource和RootVisual。
Application.Current用来提供对当前应用程序的 Application 实例的共享访问。由于一般系统自动生成的是从Appliction继承的App类,因此常见形式为:App currentApp = (App)Application.Current; Host,获取有关 Silverlight 应用程序的主机的各种详细信息。比如txtBlock.Text = App.Current.Host.Background; Application.Resource常出现在App.xaml文件中,指应用于整个应用程序的资源。相对的就是单一页面域的资源和inline式资源。 RootVisual就来获取或指定应用程序的起始页面。
更复杂的是覆盖自动生成的继承自Applicaitonde的App类,自定义实现一个继承自Application的类,略
2008年10月22日 星期三 22:28
2008年10月15日 星期三 17:03
2008年10月12日 星期日 11:01
2008年10月10日 星期五 10:48
2008年09月26日 星期五 11:05
|
出于安全问题考虑,在Silverlight应用程序中从其他地方获取图片、音频、视频,以及XAML文件、字体文件等资源时,所使用的URL会有些限制。这些URLs分为三类: Cross-scheme URL:譬如HTTP下的Silverlight应用程序试图获取HTTPS下的资源是不被允许的。 Cross-domain URL:这个在Ajax等地方比较常见,就是从一个服务器获取另一个服务器上的资源,这时需要看安全策略文件是否允许。 Cross-zone URL:在IE的工具菜单下的安全选项卡上,我们可以看到分四个区域:Internet, Local intranet, Trusted sites(可信站点)和Restricted sites(受限站点)。此外,the local machine(本地机器)也被看做是一个区域。Cross-zone主要防止Internet上的程序来访问Local intranet的资源,当然本地的程序访问互联网的资源肯定不受限制,只要别人允许即可。因为zone是在IE上的划分,因此这个概念在苹果机上暂不支持。 下表摘要性的描述了在Silverlight中通过WebClient或 HTTP类以及其他组件访问URL的限制规则,原图和原文(这篇博客差不多就是简要翻译):URL Access Restrictions in Silverlight 2。 ![]()
后记:我在编写小程序时,请求自己机子上的图片,使用http://localhost/test.png。后来发给局域网的同学测试,发现不能正常出图。Google了下,发现他人类似的错误是由于Cross-scheme。到我这里的错误具体应该是Cross-zone,当同学运行我的程序,访问地址是http://liongg/silverlight/default.aspx,此时在程序内部,向localhost,即同学的机子请求图片,一来他机子上可能没相应图片,最重要的这是不允许的:互联网、局域网的应用程序试图获取你自己电脑上的资源! 当我翻阅Silverlight Document时,发现相关篇章我先前阅读,并做过简要笔记,但竟然仍旧犯错误,而且是搜索到这里才查阅的。想来或许些许郁闷,但也无需太过自责,很多功能、用法只有实践过、错过,才会对其印象深刻并掌握,即便先前看过,很多东西也不是看一遍就清楚理解其内容。 反正跨域这玩意,现在也不是什么太大的问题,一般都有打包解决方案,譬如在做豆友地图时就发现,可以使用 JSONP 方式来支持跨域调用豆瓣API等等。 |
Silverlight2 现在支持的Image格式有jpg和png,部分png编码也不支持,同时有些png在xaml的design预览中不可见,但运行时可见。请看XAML markup中两行代码的异同: <StackPanel Background="White" Orientation="Vertical"> <Image x:Name="blue" Source="/images/blue.png" Stretch="None"/> <Image x:Name="green" Source="images/green.png" Stretch="None" /> </StackPanel> 反斜杠forward-slash?有没有虾米区别呢?如图所示,名称为Resource测试示例解决方案资源管理器以及运行效果:以反斜杠开头的blue图片需要放在ClientBin目录下的相应文件夹里;不以反斜杠开头的green图片应该放在Resource目录下的相应文件夹里。这样才可正确引用,否则会发生ImageError。 ![]() 为什么?两者都是相对路径,到底反斜杠带来了什么区别呢?反斜杠开头的相对路径,代表的相对位置是应用程序运行的根目录,即.xap压缩包内,若在这其中寻找不到要引用的文件,则相对路径的回退机制(fallback mechanism )自动在在.xap所在的目录,本例即为ClientBin目录中寻找加以引用。两个位置都没有,才会发生错误。不以"/"开头,则代表的相对位置是引用该图片的XAML文件所在的目录,本例即page.xaml文件所在的Resource目录。 那么究竟选择哪种方式呢?把xap文件重命名为zip文件,解压之,再用reflector反编译其中的dll文件,发现其包含了green图片,却没有blue图片。其实也很明显,blue所在的images文件夹与.xap同级,自然不会包含在内。由此可知,不以反斜杠开头的green图片嵌入到Silverlight程序中的xap文件直接下载到客户端,而blue图片则按需索取(on-demand),当显示时再去下载。当数据量较大时,不以反斜杠开头的方式加载程序的时间就过长,用户体验不好,以"/"开头自然就无此无虑,不过以反斜杠开头的话,在xaml中设计预览看不到,只有程序运行才可以看到。(若为了预览,可先从ClienBin那里复制一份放在page.xaml同级目录下供设计使用,程序发布时予以删除。) 除了在XAML中直接确定Image的Source URI,当然也可以在code-behind中确定,此时反斜杠的用法和XAML中相通。 C#:Image img = new Image(); img.Source = new BitmapImage(new Uri("test.jpg", UriKind.Relative)); //page.xaml所在目录下 //img.Source = new BitmapImage(new Uri("/test.jpg", UriKind.Relative)); //.xap所在目录下 话说要是使用诸如http://www.liongg.net/test.jpg之类绝对URI就没反斜杠什么事了。在代码中还可以利用Application.Current.Host.Source.AbsolutePath等方法,我试验下了,发觉太麻烦还没意思,不再多管。 再深入一点,甚至图片放在与page.xaml同级的文件夹下,一样可以使用反斜杠进行引用。只需要在相应图片的属性里,把Build Action选择为"Content"即可,不过该图片还是被放到.xap压缩包里了,这是后话,参见《Sivlerlight Resource 概览》。 |