首页 / 资讯中心 / 文章详情

Silverlight培训笔记:XAML布局、控件与数据绑定实战

Silverlight培训笔记:XAML布局、控件与数据绑定实战 ★ FEATURED ARTICLE
Silverlight的培训笔记我一直想整理拖延了快半年。这次借着“Day 1: Overview UI Elements”这个切入点把第一天实际讲课的内容、现场演示和学员的坑一起写出来。这几天囤着的人可以放心看这是一份实打实的课堂记录不是那种点到为止的目录式教程。Silverlight虽然已经是过去式但它的UI设计思想、XAML的声明式写法、数据绑定的MVVM雏形在今天的前端开发里还能看到大量影子。这套内容适合三类人需要接手遗留系统的开发者、想搞懂XAML系技术脉络的人、对老富客户端技术好奇的技术玩家。1. 培训第一课Silverlight从哪来到底解决什么问题1.1 Silverlight骨子里是什么Silverlight是浏览器端的一个插件运行时用来承载富交互的Web应用。你可以在HTML页面里嵌入一个Silverlight插件对象然后通过XAP包把编译好的.NET程序下载到客户端运行。说白了它是在浏览器里面跑一个精简版的.NET环境UI用XAML描述业务逻辑用C#写界面和代码完全分离。这个思路在当时很前卫比起那一时期常见的服务端控件回发模式Silverlight让网页应用第一次有了桌面程序那种流畅交互。Silverlight不算是一个单纯的UI库它覆盖了输入输出、媒体播放、网络通信、矢量图形、动画、数据绑定这一整套客户端应用需要的能力。XAML天生适合描述树状界面一层接一层的元素关系非常直观C#做后台逻辑又比JavaScript成熟得多IDE的调试体验更是当时网页开发圈里羡慕的对象。这也是为什么后来很多团队用Silverlight做内部管理系统、数据看板、在线设计工具——界面复杂度再高XAML和代码分离的架构都能扛得住。现在回头看Silverlight最值得学的不是某个控件怎么写而是它的整体分层思路View只管呈现Code-behind只做少量事件处理复杂状态交给数据绑定去驱动。这个View和ViewModel分离的套路后来成了现代前端框架里的标配。所以今天学Silverlight不是让你倒退回去写legacy代码而是帮你理解很多现代框架的底层逻辑到底从哪来的。1.2 一个XAML页面从零到显示要经历什么先别急着打开电脑写代码。第一天培训刚开始我让学员先在脑子里过一遍Silverlight应用的加载链路。一个完整请求流程大概是这样的浏览器加载HTML页面里面有一段object标签指向一个.xap文件。浏览器检测到Silverlight插件把.xap下载到本地。.xap包里有编译好的程序集和AppManifest.xaml插件从这个清单找到入口。运行时启动入口Application类加载根页面比如MainPage.xaml解析XAML树触发初始化逻辑。最后界面渲染出来用户开始交互。这个链路看着简单但调试排错时你得时刻记着它。比如界面空白先别怀疑XAML写错了可能是.xap根本没下载成功或者是AppManifest.xaml里入口类的命名空间写错了也可能是插件初始化抛了异常被吞掉。很多时候问题不在那几行看的见的代码而在加载链路的上游。为了让学员有个直观感受我第一天第一件事是带着大家建了一个最简单Silverlight项目。默认模板会生成App.xaml、MainPage.xaml和对应的.cs文件。直接按F5一个空白页面就算跑起来了。你再往MainPage.xaml的Grid里丢一个Button这个极简项目的UI就从空壳变成有内容了。这个过程不超过五分钟但它是建立整个心法的基础。1.3 开发环境搭建里容易踩的坑Silverlight后台的完整工具链现在不太好凑齐了因为官方早就停止迭代新版的开发工具也不再默认支持。培训第一天我专门留了半小时让大家配置环境。这里有一条个人经验IDE版本和SDK版本一定要匹配别用最新的IDE硬跑老热门框架。Silverlight 5是最后的版本对应的SDK、运行时、浏览器插件缺一不可。安装顺序上先装运行时再装SDK和IDE插件最后创建项目这一套下来基本能跑通。遇到过学员的问题页面能打开但插件区域是一片灰底通常原因就是Silverlight运行时缺失或者版本不匹配。另一个常见状况是创建项目时找不到Silverlight模板那多半是IDE版本太新模板类型里根本就没了这个选项要么换指定版本要么通过扩展市场找旧模板。经验之谈不是每个搜索出来的模板都靠谱我刚入行时从网上下了一个第三方模板结果命名空间全是错的后面几天一直在跟它搏斗。配置好环境后我建议每人都亲自动手建一次项目而不是直接拿示例工程跑。因为建项目的过程能暴露一堆环境问题趁课堂上解决总比回家自己折腾半天强。2. UI布局界面排版的基本盘2.1 先搞懂Canvas所有坐标的起点Silverlight网格系统不复杂但初学者容易一上来就玩高级布局结果搞出一堆嵌套混乱。我的教学方法很老派从Canvas开始。Canvas是最基础的布局容器所有子元素通过Canvas.Left和Canvas.Top依附属性指定绝对坐标位置。它不讲排版不搞自动换行把元素丢到指定坐标就完事。用Canvas理解控件的定位机制特别直白因为XAML里每个控件最终都是一颗视觉树的节点Canvas上的两个附加属性就是在告诉布局引擎这个节点应该放在父容器的哪个位置。很多新手不理解为什么是Canvas.Left而不是Left这里有个关键概念叫附加属性——这种属性不属于控件自身而是由父容器来决定如何解释和使用。同一个控件放进CanvasCanvas.Left有效放进GridCanvas.Left就完全不生效两套坐标系互不调度。课堂上有学员问我是不是所有界面都用Canvas定位最精确我给的答案是不建议。Canvas写起来直观但做自适应界面、响应变化、多分辨率适配时它就是个噩梦。每个元素都得手工计算坐标页面一改坐标全乱。所以Canvas适合做画布类工具、固定尺寸的简单页面它在上面教学的价值远比实际业务场景高。2.2 StackPanel和WrapPanel的不同性格StackPanel是Silverlight里第二类常用容器它按垂直或水平方向把子元素依次堆叠。这个控件的特性就是“只管顺序不管重叠”默认情况下如果子元素超出可视区域它不会换行也不会压缩超出部分直接被裁剪掉或者撑破边界。理解这个特性很重要因为很多布局错乱问题都出在StackPanel无限堆叠导致的尺寸计算异常上。我让学生记住一句话StackPanel适合做固定顺序的工具条、纵向表单、导航菜单不要拿它当页面骨架来布局复杂界面。当你的界面里面出现三层以上的StackPanel嵌套时就该停下来想想是不是该换成网格布局了。同类控件里还有一个WrapPanel它能自动把放不下的元素换行排列特别适合做标签集合、按钮组这类内容个数不固定的场景。课堂上一个实操练习是用StackPanel做一个纵向排列的登录表单里面一个账号文本框、一个密码框、一个登录按钮。这种场景它表现得很自然代码量也不大。但如果你把三个StackPanel嵌在一起想让某个区域自动填满剩余高度你会发现尺寸布局总是不如预期——因为StackPanel在主轴方向不会分配额外空间给子元素它的特性就是“子元素有多高我就有多高”。2.3 Grid是所有复杂界面的定海神针Silverlight培训第一天的重头戏我一定会安排在Grid上。Grid按行和列切分区域子元素通过Grid.Row和Grid.Column决定自己落在哪个单元格。它的行列尺寸有三种模式固定像素值、Auto自动适应内容、Star按比例分配。上手体验好的学员会觉得Grid无非就是个表格容器但实际用处在于Star比例分配。你可以把页面分成三份左侧导航一份主内容区两份当浏览器窗口拉伸时所有区域同步改变大小完全不需要写任何代码去响应尺寸变化。这个比例伸缩能力就是Silverlight做自适应界面最核心的武器。第一天我给学员演示了一个稍微复杂点的布局顶部标题栏用Auto高度底部状态栏用Auto高度中间主体区域用Star比例撑满剩余空间。这样不管窗口多大中间内容区永远自动填满。这个布局模式后来每天都在项目里用到可以说理解了这一课就等于掌握了Silverlight界面骨架的大半。另外要说一个很容易忽略的特征Grid里行列默认是置空的第0行第0列没写会被自动使用。如果同一个单元格放了多个元素后面的元素会盖在前面元素上面这个叠加行为可以专门用来做水印、遮罩层这类效果但如果你没意识到这一点也容易让视觉上多余的控件挡住内容调试半天找不到原因。实操时有个习惯必须养成列定义和行定义必须写在Grid.ColumnDefinitions和Grid.RowDefinitions里别想着投机在子元素上直接写坐标。3. UI元素逐个拆解从文本到列表的选择清单3.1 文本输入与展示TextBlock、TextBox、PasswordBoxSilverlight的文本展示控件是TextBlock它不是用来输入的而是用来展示纯文本的。很多初学者会把它和TextBox搞混两者的区别一句话就能讲清楚TextBlock是显示文本TextBox是编辑文本。TextBlock支持换行、文字溢出处理、富文本片段组合通过TextWrappingWrap可以让它在空间不足时自动换行通过TextTrimming可以在不换行的情况下以省略号截断文字。TextBox在Silverlight中的默认边框极细在浅色背景上几乎看不见这是我第一天提醒学员注意的第一个视觉坑。如果不主动设置BorderBrush和BorderThickness文本框放在白底页面上完全像悬浮元素用户不知道这里可以输入。让学员给TextBox加个浅灰色边框就是把这种“看不见的控件”变成“可见的交互区”的典型操作。密码输入要用PasswordBox它的核心属性是PasswordChar保密字符和Password实际内容。Silverlight没有原生验证控件所以密码强度校验、格式校验都得自己在事件里写。第一天的练习是模拟一个注册页面让学员在密码框失焦时检查密码长度不足的话在旁边的TextBlock里显示红字提示。这个练习的价值在于让学员明白Silverlight的控件本身功能很克制组合成产品级交互需要自己拼接逻辑。3.2 按钮家族与基本交互Silverlight的按钮家族比很多人想象中丰富。最常用的是Button其次是CheckBox、RadioButton、ToggleButton、RepeatButton和HyperlinkButton。它们各有各的语义但都离不开最核心的Click事件。Button的界面模板其实是可以完全替换的。Silverlight控件的视觉呈现由ControlTemplate决定想做一个圆角渐变按钮不需要去外面找什么第三方UI框架直接在Button的模板里定义Border和Background就能实现。第一天培训我不建议大家深入模板美化但一定会灌输一个观念控件的外观和逻辑是解耦的改样式不该碰后台事件代码。RadioButton有个Silverlight特有的坑如果你直接在页面里放几个RadioButton而不做任何分组设置它们默认属于同一组同一时间只能选中一个但如果你把它们放进不同的ItemsControl比如两个ListView每个容器内部会被自动视为独立分组跨容器选择就不再互斥。这个行为经常让做问卷类界面的同学懵掉。想明确分组只有一个可靠手段显式设置GroupName属性。HyperlinkButton是Silverlight里的“链接”控件它看起来像文本链接点击后通过NavigateUri属性跳转。它在Silverlight里跳转的目标页面其实是同进程内的新建页面而不是重新加载整个应用——这个机制和网页里的a标签完全不同说白了你是在Silverlight自己的导航框架里移动代价比传统网页跳转小得多。3.3 列表和选择ListBox、ComboBox、DataGridListBox是老牌列表控件核心在ItemsSource和ItemTemplate两个属性上。ItemsSource绑定数据源ItemTemplate定义每个条目长什么样。很多初学者会问为什么不直接把数据对象ToString显示出来就完事这不完全对。一个简单的场景列表里要显示用户的名字和邮箱两行文字左对齐。如果直接用对象绑到ListBox它只会调用对象的ToString()方法把类名或者一串字符显示出来界面非常不可读。正确做法是定义ItemTemplate里面放一个StackPanel第一行TextBlock绑定Name第二行绑定Email再设置好文字颜色和大小。ListBox变成可视化数据模板后整个界面马上就专业起来了。ComboBox是下拉选择控件它的SelectedIndex、SelectedItem、SelectedValue三个属性经常让新手晕头转向。我的解释是SelectedIndex只是告诉你选中的是第几个从0开始SelectedItem是那个数据对象本身SelectedValue是从数据对象里取出来的某个属性值。三者各有用途但大多数人关心的是“选中的是哪条数据”所以日常用SelectedItem居多。课堂上一个学员问ComboBox能不能做成只能选不能输入的。当然能Silverlight默认就是只选不输它的编辑模式默认为不可输入。但如果需要做一个可搜索的下拉框就必须自己扩展模板——Silverlight本身没有带下拉搜索功能的现成控件这也是当时很多项目要自己封装自定义控件的原因。3.4 DataGrid数据密集型界面的第一选择DataGrid是Silverlight里最占篇幅、最容易踩坑的控件。第一天我为了它留了一个小时。它默认就带表头、排序、列宽拖拽、选中高亮这些功能把数据源绑到ItemsSource后它会根据对象属性自动生成列。听起来很方便但实际用起来没几个人真的让AutoGenerateColumns保持默认开启因为自动生成的列有几大缺点列名直接暴露字段名、所有列宽度固定、日期时间显示格式不可控、不想要的字段也会显示出来。所以第一件事就是关闭自动生成列AutoGenerateColumnsFalse然后手动写DataGridTextColumn、DataGridCheckBoxColumn、DataGridTemplateColumn。手动定义列之后Binding路径、Header显示文本、列宽度就全在你控制范围内了。我让学生在练习里把用户列表的性别字段做成CheckBox把状态字段做成不同颜色这些看似小事能极大提升阅读体验。DataGrid绑定更新有一个心智模型上的坎当你修改了数据源里某条记录DataGrid界面不会自动更新。Silverlight的通知机制走的是属性级事件你的数据模型要实现INotifyPropertyChanged并触发变更事件DataGrid才会刷新单元格。如果不做这一步你改了数据模型界面上纹丝不动这是Silverlight新手最常见的“数据没生效”问题的根因。4. 让界面活起来事件处理与数据绑定4.1 最简单的两事件接法Silverlight的事件处理和WinForms很像在XAML里给控件挂一个事件名在Code-behind里实现同名方法即可。Button的Click事件大概长这样Button x:NameSaveButton Content保存 ClickSaveButton_Click /对应的Code-behind文件里private void SaveButton_Click(object sender, RoutedEventArgs e) { // 处理保存逻辑 }这个写法谁都能看懂。但有个细节初学者容易忽略事件函数的签名必须完全匹配object sender和RoutedEventArgs e否则编译不通过。好在IDE一般情况下都会自动生成方法签名手写时老省掉第二个参数结果编译器直接报错。另一个绑定事件的方式是纯代码动态订阅SaveButton.Click SaveButton_Click;。这种方式在Silverlight里也经常用特别是控件在代码里动态创建、或者一个处理器要挂到多个控件上时很常见。我第一天会让学员用两种方法各写一次主要是让他们理解XAML里其实只是在做事件订阅的声明和时间相关的逻辑都在托管代码里。4.2 事件里的灵魂参数sender和e很多新手拿到sender不知道拿它干嘛完全当成摆设。实际上sender就是触发事件的那个控件本身它在多个控件共用同一个事件处理器时特别有用。比如页面里有十个Button内容分别是1到10你可以让它们都指向同一个Button_Click事件然后在里面通过((Button)sender).Content.ToString()判断用户点了哪个按钮。这个模式减少了一堆重复的事件处理方法代码干净很多。RoutedEventArgs在Silverlight里提供路由事件的信息比如KeyDown事件里的Key属性就能判断用户按的是哪个键盘按键。做一个快捷键功能时你只需在页面根元素上订阅一次KeyDown就能捕获页面上所有子控件触发上来的按键事件这个就是路由事件的好处你不用给每一个控件挂事件在父容器上统一处理就行。实操经验里还有一点Silverlight的鼠标事件在触屏设备上表现并不理想MouseLeftButtonDown在触屏上可能不触发要适配触屏项目必须额外处理触摸事件。第一批做平板端Silverlight应用的人几乎都被这个坑过我现在提出来是想让你将来碰触屏场景时第一时间想到替代方案。4.3 数据绑定UI和逻辑的桥梁数据绑定是Silverlight UI的最关键机制。简单理解就是你把界面上某个控件的属性比如TextBlock的Text通过{Binding Name}这样的语法绑到数据对象的Name属性上绑定管理器自动维护两者之间的联系。你不用写textBlock.Text obj.Name这种代码只要把数据源赋给DataContext界面就能自动填充内容。一个最小化、可理解的绑定例子页面上有一个TextBox和一个TextBlockTextBox输入的内容实时显示在TextBlock上。直接在页面的Loaded事件里把整个页面this设为DataContext然后TextBlock的Text绑定到TextBox的Text属性——你要建一个公开属性供绑定读取。练习中大多数人卡在“页面属性需要公开”这一点上因为绑定引擎访问的是属性不是字段也不是私有成员。绑定有个方向问题ModeTwoWay表示界面和源码双向同步OneWay表示源码值推给界面默认情况下TextBox这类输入控件的绑定是TwoWay而TextBlock是OneWay。新手容易犯迷糊的是TextBox文字变了但绑定的属性没更新——Silverlight里TextBox的默认绑定更新时机是失焦时不是每敲一个字符就更新一次。如果希望一打字就更新要设置绑定的更新时机属性为PropertyChanged。这里有个取舍属性每敲一个字符就变后端逻辑很容易被频繁触发需要根据场景选择更新时机。4.4 INotifyPropertyChanged绕不开的必修课INotifyPropertyChanged是Silverlight数据绑定体系里最重要的接口一个数据模型只要实现了它就能主动通知界面属性发生变化。入门时的数据模型往往就是普通类属性自动变化了界面不刷新原因就在模型没有实现通知机制。这个插件一理解了你就能立刻明白为什么很多MVVM框架里每个ViewModel类都要写那么一大段重复代码。一个标准实现大概是这样的public class User : INotifyPropertyChanged { private string _name; public string Name { get { return _name; } set { if (_name ! value) { _name value; OnPropertyChanged(Name); } } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }这套代码初看又土又长但它是后面所有双向绑定、列表刷新、表单联动的基础。我第一天会让每个学员自己敲一遍这个模式不让他们直接拷贝。因为只有亲自动手敲一遍才能记住属性setter里比较旧值、赋值、触发事件这三步缺一不可。任何一步丢了都会出现一种最难排查的bug程序不报错界面就是不刷新。5. 第一天最容易踩的坑排查实录5.1 页面一片空白连报错都没有这是Silverlight初学者最高频的问题。排除思路很关键先判断.xap有没有下载成功再看运行时有没有抛异常。在浏览器的开发者工具里切到“网络”标签找到.xap请求如果404查HTML里object标签的source属性路径如果能下载但页面空白大概率是运行时异常打开IDE的输出窗口Silverlight的运行异常会在那里打出来而浏览器页面只会显示一个灰色的插件区域不会弹窗。还有一个URL方案上的坑Silverlight的XAP是通过当前页面相对路径解析的页面在子目录、应用在根目录source没写好就会404。另一个坑是跨域引用如果你的Silverlight应用部署在另一台服务器XAP跨域加载需要目标服务器提供跨域策略文件支持。总之空白页排查顺序网络面板检查XAP → 输出窗口看异常 → 检查AppManifest入口 → 再逐行看XAML。5.2 图片怎么都显示不出来Silverlight里的Image控件绑图片要么给Source属性一个相对路径要么用代码创建一个BitmapImage并指定URI。常见的问题是相对路径写错了层级、文件名大小写不匹配、图片格式不受支持。Silverlight对图片格式的支持比浏览器少得多比如某些WebP、SVG它根本不识别。碰到不死不活的显示失败先检查图片格式。还有一个场景相当隐蔽如果Silverlight应用需要加载另一个域名下的图片对方服务器必须返回跨域策略文件否则就算图片地址能直接在浏览器打开Silverlight里的Image一样显示不出来。这个坑排查起来很痛苦因为看起来是图片路径问题实际上对方域名的策略文件没有放行。我在第一天总结的排查次序是路径 → 文件名 → 格式 → 跨域。前三个能用肉眼检查跨域只能看浏览器网络请求或者服务端日志去确认。5.3 布局错乱该撑满的没撑满布局问题十有八九是没理解三种尺寸分配模式。Auto高度由内容决定内容多高它就多高固定像素值你写多少就多少窗口拉伸也不变Star比例分配才具备伸缩能力。想要一行或一列自动撑满剩余空间就是用Star。我遇到过一个学员给主内容区域设了固定高度窗口一大底下留出一大块空白看起来特别不专业。另一个高发问题是嵌套Grid时漏了Grid.Row和Grid.Column的设计。子元素默认都在第0行第0列你往里加了第二行新控件没设Grid.Row1它跟第一行的控件重叠界面直接乱成一团。这种情况跟布局引擎没任何关系就是个典型的控件属性遗漏问题。养成每次写布局时先把Grid.RowDefinitions和Grid.ColumnDefinitions写明再给子元素配上行列号的习惯能消灭大量低级错误。5.4 调试工具Silverlight程序员的老朋友Silverlight的调试体验当年吊打同期的Web开发但你得知道怎么用它。Visual Studio里F9下断点F5启动调试断点命中后能看变量、警察局堆栈、即时窗口执行表达式。Silverlight调试的启动方式有点特殊要在项目属性里把调试器类型设为Silverlight否则你按F5浏览器是起来了断点一个都不命中。如果断点没被命中最先怀疑的就是调试器类型设置。第二个常见问题是调试时浏览器插件和IDE调试器通讯不上换个浏览器或者清理浏览器缓存有时能解决。另外要学会看“输出”窗口里面的异常信息比页面上的灰底直观得多。我还保留着一个经验Silverlight里死锁特别难排查。UI线程一旦被阻塞界面就假死你要学会用调用堆栈和线程窗口确认是不是在相同线程里做了耗时操作。第一天不深究这个但我会提醒学员界面卡顿优先怀疑后台逻辑在UI线程里干了重活异步处理是Silverlight项目里绕不开的能力。5.5 一些零散的编码习惯第一天培训的收尾我通常不说技术说习惯。Silverlight编程里几个细节能省去后面大量改代码的时间XAML里能写属性就写属性别堆事件代码命名尽量标准化x:Name前缀别丢把公共样式写进App.xaml的资源字典别在每个页面重复贴样式每次改动UI都按F5跑一遍别攒了一堆改动才运行定位问题会难到怀疑人生。顺带说一个我很想强调的点版本兼容性。Silverlight 5之后就不再更新了这个技术本身停在历史里。做新项目不要选它写代码时也要时刻把握“这段逻辑迁移到现代框架怎么设计”的思路。比如XAML里数据模板和绑定移到今天的前端框架里就是组件和状态管理事件处理器里的sender和e换到现在就是回调函数和上下文对象。学的是Silverlight沉淀的是UI架构设计能力这笔账怎么算都不亏。第一天的内容就绪后第二天的课程我就直接进入控件模板定制和动画系统了。那时候学员已经能把页面排得像模像样绑定逻辑也能自己写出来接下来把它们做得好看、顺畅才是真正好玩的部分。
阅读完成 · 觉得有帮助?
咨询建站