silverlight:使用 Silverlight 2 创建可尽情涂鸦的 Web 应用程序

  本文将介绍以下内容:

  Silverlight InkPresenter Control控件

  Web 应用手写内容

  手写识别

  创建透明背景和图像背景

  本文使用以下技术:

  Silverlight 2、Expression Blend 和 Visual Studio 2008   这篇文章基于 Visual Studio 2008 SP1、Silverlight 2 和 Expression Blend 预发布版而撰写文中所有信息均有可能发生变更

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />目录

  InkPresenter 介绍

  不需要 Tablet PC

  InkPresenter 101

  添加事件和背景

  更时尚 InkPresenter

  获得 Silverlight 式外观

  向 InkPresenter 添加背景图像或视频

  笔划设计基础知识

  手写识别

  InkAnalyzer 类

  服务器端分析

  将服务挂接到 Silverlight 客户端

  使用服务保留注释

  存储和检索注释

  整理总结

  Silverlight 是 Microsoft 在过去几年中开发出最令人兴奋新 Web 技术的这项技术非常重要现在几乎每年 MIX 会议都是以 Silverlight™ 及其诸多功能为中心Silverlight 1.0 最初发布于 2007 年Silverlight 2 最终版本的前每个新版本都增加了令人印象深刻新功能Silverlight 超炫功能的是 InkPresenter Control控件只是此功能尚未得到自己应得关注使用 InkPresenter Control控件Internet 用户能够直接从其浏览器在 Silverlight 应用中进行绘制

  由于 Silverlight 适用于各种操作系统和浏览器所以 InkPresenter 也是如此这就去除了浏览器、操作系统和硬件这另组限制我将在此处介绍举例应用结合使用了 Silverlight 2 Microsoft® .NET Framework 编程功能和 InkPresenter允许用户为预定义图像集添加注释、执行手写识别、将注释和已识别文本保存到服务器端数据库中、检索所选图像注释以及根据和图像关联文本筛选图像数据库和手写识别功能由 Windows® Communication Foundation (WCF) 服务提供图 1 显示了已完成应用

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />   图 1 已完成应用

  需要介绍说明本文介绍所有功能也可以在 Silverlight 1.0 中完成实际上在 Silverlight 2 问世的前我在 Silverlight 1.0 中编写过个类似应用其中包含了完全相同功能但是如果客户端上没有可供您使用以 .NET 为目标代码您就需要利用 .NET Web 服务来获得更多功能

  InkPresenter 介绍

  使此应用得以实现 InkPresenter Control控件是个笔划集合容器每个笔划都由组 StylusPos 组成请注意Silverlight 中还存在另个 Stroke 属性它是 Shape 类成员注意不要将两者混淆

  以笔和纸为例每次用笔接触纸时就开始了个笔划然后继续来回移动笔可以绘制个圆或写个字将笔从纸上提起则表示该笔划结束再次放下笔就会开始个新笔划

  笔划具有些 DrawingAttributes可定义笔划颜色、宽度和其他几个属性等特征笔划中各个点也具有属性:代表位置 X 坐标和 Y 坐标以及 PressureFactor在计算机中使用数字化器 PressureFactor它允许您以编程方式根据用户将笔针按入数字化器力度影响笔划图 2 介绍说明了该类层次结构

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />   图 2 InkPresenter 类

  和 Silverlight 中其他可视化元素InkPresenter 对象及其子项可以表示为 XAML图 3 显示了 InkPresenter 中 3个小笔划图 4 显示了 StrokeCollection 中某个笔划 XAML 表示形式尽管笔划很小数字化器同样能够收集到大量数据如果使用鼠标进行相同测试则收集到数据量就会明显减少笔针点只是成对而且点收集时间间隔加大

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />   图 3 些笔划

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />图 4 InkPresenter Control控件中个笔划

<StrokeCollection>
<StrokeCollection xmlns="http://schemas.microsoft.com/client/2007">
 <Stroke>
  <Stroke.DrawingAttributes>
   <DrawingAttributes Color="#FF000000"     OutlineColor="#00000000" Width="3" Height="3" />
  </Stroke.DrawingAttributes>
  <Stroke.StylusPos>
   <StylusPo X="81.4583358764648" Y="96.5833282470703" />
   <StylusPo X="81.4583358764648" Y="96.5833282470703" />
   <StylusPo X="81.0833358764648" Y="96.4166717529297" />
   <StylusPo X="81.0833358764648" Y="96.4166717529297" />
   <StylusPo X="81.0833358764648" Y="96.4166717529297" />
   <StylusPo X="81.0833358764648" Y="96.4166717529297" />
   <StylusPo X="81.0833358764648" Y="96.4166717529297" />
   <StylusPo X="80.4583358764648" Y="96.8333282470703" />
   <StylusPo X="80.4583358764648" Y="96.8333282470703" />
   <StylusPo X="80" Y="97.2916717529297" />
   <StylusPo X="80" Y="97.2916717529297" />
   <StylusPo X="79.625" Y="97.75" />
   <StylusPo X="79.625" Y="97.75" />
   <StylusPo X="79.625" Y="97.75" />
   <StylusPo X="79.625" Y="97.75" />
   <StylusPo X="79.625" Y="96.5416717529297" />
   <StylusPo X="79.8333358764648" Y="95.7083358764648" />
   <StylusPo X="80.25" Y="94.7916641235352" />
   <StylusPo X="80.7916641235352" Y="93.5416641235352" />
   <StylusPo X="81.5" Y="92.125" />
   <StylusPo X="82.4166641235352" Y="90.4583358764648" />
   <StylusPo X="83.4583358764648" Y="88.5833358764648" />
   <StylusPo X="84.75" Y="86.5416641235352" />
   <StylusPo X="86.1666641235352" Y="84.3333358764648" />
   <StylusPo X="87.7083358764648" Y="82.1666641235352" />
   <StylusPo X="89.25" Y="79.9166641235352" />
   <StylusPo X="90.75" Y="77.9583358764648" />
   <StylusPo X="92" Y="76.0833358764648" />
   <StylusPo X="93.1666641235352" Y="74.8333358764648" />
   <StylusPo X="94" Y="73.625" />
   <StylusPo X="94.7083358764648" Y="73.1666641235352" />
   <StylusPo X="95.125" Y="73.1666641235352" />
   <StylusPo X="95.125" Y="73.1666641235352" />
   <StylusPo X="95.125" Y="73.1666641235352" />
   <StylusPo X="94.7083358764648" Y="73.5" />
  </Stroke.StylusPos>
 </Stroke>
...
</StrokeCollection>


  使用 InkPresenter 实际上也就是创建其笔划并和笔划进行交互但默认情况下InkPresenter 并不执行这些操作它会提供些事件和思路方法;允许您添加和删除 StrokeCollection还允许您访问 Stroke 以便和其进行交互但是是否以编程方式跟踪 InkPresenter Control控件边界内鼠标或笔针活动并生成笔划决定权在您

  不需要 Tablet PC

  在 Windows Presentation Foundation (WPF) 和 Silverlight 问世的前开发人员是依靠 Tablet PC SDK 创建自定义来利用 Tablet PC 绘制功能SDK 是组带有 .NET 包装 COM API能使用 .NET、Visual Basic® 6.0 和 C 进行开发必需使用 Windows XP Tablet PC Edition 操作系统

  从 Tablet PC SDK 1.7 版本开始重要Control控件 InkOverlay 运行便不再需要对系统有“完全信任”权限您可以创建启用了手写功能 Windows 窗体Control控件并将其嵌入到网页中就像使用了任 ActiveX® Control控件尽管 ActiveX Control控件仅限于在 Internet Explorer® 中使用但这也在开发人员将绘制和其他手写功能引入到 Web 方向上迈出了第

  在 Windows Vista® 中Tablet PC 功能是作为流成员内置到操作系统中并且用于 Tablet 功能开发 API 已并入 WPF InkCanvas 对象这就意味着任何安装了 .NET Framework 3.0 计算机都支持 Tablet 功能即使该计算机不是 Tablet PC 也无妨定要认识到在 Tablet 数字化器上使用笔针得到分辨率比使用鼠标所得到分辨率要高得多(相差多个数量级)

  Silverlight 中手写功能是 WPF 中提供功能子集但是两者的间有个重要区别即 WPF InkCanvas 有个 InkPresenter 属性可在 InkCanvas 上显示手写内容但在 Silverlight 中没有 InkCanvas您必须直接使用 InkPresenter

  最重要Silverlight 不限于 Windows 或 Internet Explorer因此许多环境都能够使用启用手写功能网站WebSite

  InkPresenter 101

  适用于 Visual Studio® 2008 Silverlight 工具将 Silverlight XAML 设计器嵌入到了 Visual Studio 2008 中但是设计图面本身是只读因此您可能会发现在 Expression Blend™ 2.5 中执行某些 InkPresenter 设计工作将更加方便这两个应用可以很好地集成因此这实现起来相当简单熟悉 Expression Blend 的后它也会给您带来很多乐趣

  我喜欢在 Visual Studio 中开始我项目默认项目模板会为我提供很大帮助然后当我想在 XAML 中开展某些设计工作时我会在 Expression Blend 中打开该项目通过在 Visual Studio IDE 解决方案资源管理器中右键单击 XAML 文件然后选择相应选项在 Expression Blend 中打开该文件您就可以轻松实现此操作如果没有 Expression Blend 2.5您可以直接在 Visual Studio 2008 中手动编辑 XAML这样就可以立即看到更改结果

  创建 Silverlight 项目时您可以选择使用 Visual Basic 或 C#尽管我 Visual Basic 技能不错但对于此项目我选择使用 C#我在 Silverlight 1.0 中工作时存储过段 JavaScript 代码随时可以移植到 C#

  在 Visual Studio 中创建项目后个任务是在 Expression Blend 中打开该项目以便开始设计在 Expression Blend 设计器中打开 XAML 文件后您可以将 InkPresenter Control控件添加到设计图面中若要查找 InkPresenter Control控件您将需要通过单击工具箱底部 >> 图标打开 Expression Blend 资源库然后必须单击“显示所有内容”复选框才能查看 InkPresenter 以及其他些不是很常用Control控件或者您还可以使用搜索框搜索Control控件

  将 InkPresenter 拖动到 XAML 设计图面上;然后使用其属性窗口为此Control控件命名我选择了“inkP”本文稍后部分代码将频繁用到此名称

  在设计图面上选中 InkPresenter 后您可以看到它边界但在未选中时它看起来似乎消失了InkPresenter Control控件是个能够呈现手写内容容器尽管它确实具有背景属性但没有 Fill、Stroke(对于边框)或其他Control控件提供许多其他属性因此您需要另个Control控件来提供可视化边界

  举个简单举例向画布添加个和 InkPresenter 具有相同位置和维度矩形默认情况下矩形具有黑色边框(Stroke 属性)其中 StrokeThickness 为 1该矩形(我称的为“inkBorder”)和 InkPresenter 在画布中应该属于同级InkPresenter Z 次序必须高于矩形(即位于其上方)否则矩形将覆盖 InkPresenter

  由于在设计图面上查找 InkPresenter Control控件比较困难因此当需要选择它时最简单思路方法就是在“对象和时间线”面板中进行选择此操作会在设计图面上突出显示该Control控件到目前为止如果您要测试解决方案(通过按 F5)就会看到矩形边框但是如果您尝试使用鼠标或笔针在边框内部绘图则不会有任何效果

  添加事件和背景

  如上文所述InkPresenter 仅仅是个笔划集容器本身并不能创建笔划您必须通过响应 InkPresenter 上事件以编程方式创建笔划捕获笔划关键事件是 MouseLeftButtonDown、MouseMove 和 MouseLeftButtonUp当 InkPresenter 收到 MouseLeftButtonDown 事件时您需要在内存中创建个新笔划并将其添加到 InkPresenter StrokeCollection 中在 InkPresenter 内部来回移动鼠标以创建 MouseMove 事件时您需要向该笔划添加 StylusPos只要用户触发了 MouseLeftButtonUp 事件那么无论是通过将笔针从数字化器上提起还是通过释放鼠标您都需要完成该笔划

  借助 Expression Blend 2.5就可以轻松使用“属性”窗口来关联事件和Control控件了选择 inkP Control控件然后单击“属性”窗口顶部事件图标来查看Control控件事件接下来分别双击上述 3个事件处理文本框对于每个事件处理会将相应思路方法添加到 Visual Studio 中 XAML Control控件代码隐藏中Visual Studio 和 Expression Blend 的间可以双向集成每次做出更改后Visual Studio 都会要求您确认更改因此请注意任务栏上闪烁 Visual Studio 图标

  创建了这 3个处理切换到 Visual Studio 以添加图 5 中代码这些代码允许 InkPresenter 在用户和Control控件进行交互时收集和显示笔划数据此代码执行先前列出任务:创建新笔划并添加笔针点笔针点可通过 MouseLeftButtonDown 事件和 MouseMove 事件 MouseEventArgs 进行访问

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />图 5 收集和显示笔划数据

.Windows.Ink.Stroke stroke;
void inkP_MouseLeftButtonDown(object sender, MouseEventArgs e)
{
 inkP.CaptureMouse;
 Stroke = .Windows.Ink.Stroke;
 Stroke.StylusPos.Add(e.StylusDevice.GetStylusPos(inkP));
 inkP.Strokes.Add(Stroke);
}
void inkP_MouseMove(object sender, MouseEventArgs e)
{
  (Stroke != null)
 {
  Stroke.StylusPos.Add(e.StylusDevice.GetStylusPos(inkP));
 }
}
void inkP_MouseLeftButtonUp (object sender, MouseEventArgs e)
{
 Stroke = null;
 inkP.ReleaseMoustCapture;
}


  再有块这个拼图就要完成了InkPresenter 必须包含 Background 属性才能接收鼠标事件Background 属性和其他Control控件 Fill 属性类似但不具备您可以对 Fill 执行其他自定义项根据您设计方案您可以对背景进行些创造性设计但是现在只需将 Background 属性设置为“透明”即可

  您可以在 Expression Blend 中使用 InkPresenter Control控件“属性”窗口设置此背景思路方法是为该背景选择纯色画笔然后将其 Alpha 值设置为 0种思路方法是直接在 XAML 中键入 Background="Transparent"

  以下是在关联事件并指定 Background 属性后这两个Control控件 XAML 代码: <Rectangle Margin="20,30,35,24"
 x:Name="inkBorder" Stroke="#FF000000"/>
<InkPresenter Margin="20,30,35,24"
 x:Name="inkP"
 MouseLeftButtonDown=
  "inkP_MouseLeftButtonDown"
 MouseLeftButtonUp=
  "inkP_ MouseLeftButtonUp"
 MouseMove="inkP_MouseMove"
 Background="Transparent"
 Opacity="1"/>


  现在在 Expression Blend 或 Visual Studio 中运行该项目时您就可以在 InkPresenter 上即时看到所绘笔划(请参见图 6)

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />   图 6 InkPresenter 上笔划

  更时尚 InkPresenter

  正如您所看到虽然 InkPresenter 是个容器但和其他可视化元素(如矩形)相比它更像是个画布您需要将 InkPresenter 和其他元素相结合才能使其看起来更加有趣;否则对 Silverlight 不公平因此请在 Expression Blend 中打开现有 XAML以便使用先前为 InkPresenter 创建边框时添加矩形来调整外观使其更加整洁

  首先我们来圆化边框矩形选择该矩形将其 RadiusX 和 RadiusY 属性设置为 25现在矩形已经拥有了美观圆化边;但是InkPresenter 各角延伸到了可视边框以外并将接受手写内容解决方案是更改或剪切 InkPresenter 边界使其和可视边框相吻合这可以通过使用 Silverlight 剪切功能重塑 InkPresenter 形状来完成

  使用 Expression Blend 后剪切某个元素使其和另个元素形状相吻合就比较容易了但在此的前请先为 inkBorder 矩形创建个副本以供稍后使用在“对象和时间线”窗口中右键单击这个新矩形从其上下文菜单中选择“路径”然后选择“生成剪切路径”然后Expression Blend 将弹出个窗口要求您选择要按路径剪切哪个对象也就是说哪个对象将采用矩形形状选择 InkPresenter这会产生两个结果:InkPresenter 现在采用矩形形状;新矩形消失当矩形成为 InkPresenter 剪切路径时它就不再是个对象了至此定明白复制矩形原因了

  生成 XAML 如图 7 所示运行该项目以测试 InkPresenter 新边缘它将如图 8 所示在 Silverlight 中可以使用任何形状作为剪切路径因此您可以根据自己喜好为 InkPresenter 创建任何形状图 9 显示了个用于剪切 InkPresenter 随机形状举例

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />图 7 已剪切对象 XAML

<Rectangle x:Name="inkBorder" Width="346" Height="234"
 Stroke="#FF000000" Canvas.Top="25" Canvas.Left="25"
 RadiusX="25" RadiusY="25"/>
<InkPresenter x:Name="inkP"
 Width="607" Height="408" Canvas.Left="25" Canvas.Top="34"
 MouseLeftButtonDown="inkP_MouseLeftButtonDown"
 MouseLeftButtonUp="inkP_MouseLeftButtonUp"
 MouseMove="inkP_MouseMove"
 Background="Transparent"
 Clip="M0.5,25.5 C0.5,11.692881 11.692881,0.5 25.5,0.5 L581.5, 0.5 C595.30712,0.5 606.5,11.692881 606.5,25.5 L606.5, 382.5 C606.5, 396.30712 595.30712,407.5 581.5,407.5 L25.5,407.5 C11.692881, 407.5 0.5,396.30712 0.5,382.5 z" >
</InkPresenter>


使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />   图 8 剪切 InkPresenter 边缘

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />   图 9 将这个随机绘制形状用于剪切 InkPresenter

  获得该 Silverlight 外观

  通过 Silverlight您可以使用透明度创建有趣可视层 InkPresenter 也可以采用半透明图面外观若要实现此效果首先应将背景图像添加到画布中我使用是 Silverlight.net 网站WebSite中提供背景(请访问 silverlight.net)通过将图像拖动到画布中并将其 Stretch 属性设置为 Fill即可轻松设置背景请务必确保图像 Z 次序是设计图面中列出个Control控件否则它会位于矩形和 InkPresenter 顶部从而覆盖它们

  接下来修改矩形为其赋予黑色背景种思路方法是选择“属性”窗口中“填充画笔”然后将其 R、G 和 B 值都设置为 0将矩形 Opacity 更改为 10%使其呈现半透明效果

  虽然可以在 InkPresenter 上设置背景色并为其设置透明度但此透明度还会影响手写内容我喜欢将 InkPresenter 背景设置为完全透明并使用些其他Control控件提供这种效果您可能会发现亲自更改Control控件背景色 Alpha 值并将其效果和更改Control控件 Opacity 效果进行比较过程很有趣您还可以使用 DrawingAttribute Color 和 OutlineColor 属性 Alpha 属性直接更改手写内容透明度此效果和 WPF InkCanvas 和 Tablet PC SDK 中 DrawingAttribute.Transparency 属性效果完全相同图 10 显示了和半透明矩形相结合 InkPresenter为您绘图背景提供了美观视觉效果

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />   图 10 创建半透明背景

  向 InkPresenter 添加背景图像或视频

  在某些情况下半透明背景是个非常吸引人解决方案但您还可以使用图像甚至是视频作为背景若要创建此类背景InkPresenter 实际 Background 属性不会更改图像或视频将作为 InkPresenter 对象子元素添加如果子元素没有 Height、Width、Left 或 Top 属性它将继承父级 InkPresenter 属性

  尝试下 — 使用 Expression Blend 向 XAML 设计图面添加图像元素然后将该图像拖动到 InkPresenter或者您可以直接添加 XAML: <InkPresenter x:Name="inkP"
 Width="607" Height="426" Canvas.Left="25" Canvas.Top="34"
 MouseLeftButtonDown="inkP_MouseLeftButtonDown"
 MouseLeftButtonUp="inkP_MouseLeftButtonUp"
 MouseMove="inkP_MouseMove"
 Background="Transparent">
 <Image Source="Ass/Leaves.jpg" Stretch="Fill" />
</InkPresenter>


  现在可以在图像上直接绘图了或者可以减小图像 Opacity 值也使其成为半透明如图 11 所示

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />   图 11 使用 Opacity 属性创建半透明图像

  添加视频也很容易您要使用 MediaElement而不是 ImageExpression Blend 处理视频方式和处理图像方式区别虽然可以将视频从 Silverlight 项目拖放到 XAML 设计图面上但在运行项目时找不到该文件因此您需要将视频放置到主机 Web 项目内部然后MediaElement 源属性需要引用该文件 URLMedia Element 不会像 Image 样自动填充 InkPresenter您必须手动调整大小或直接在 XAML 中添加 Stretch="Fill"以下举例将在 InkPresenter 背景中播放视频: <InkPresenter x:Name="inkP" . . .  >
 <MediaElement Height="246" x:Name="Butterfly_wmv" Width="345"
  Source="http://localhost:52476/MSDNMagAnnotationClient_Web/Ass/Butterfly.wmv"
  Stretch="Fill"/>
</InkPresenter>


  有关如何使用 MediaElements 以及和其交互详细信息请参阅 Silverlight 文档

  笔划设计基础知识

  默认情况下笔划默认绘制属性将创建高度和宽度都为 3 黑色笔划此值表示和设备无关像素 (DIP)不能设置为小于 2

  在代码中您可以创建些思路方法和事件处理来影响各种笔划属性如 penWidth 和 penColor例如currentColor 变量可以通过Control控件单击事件更改其值接着在创建新笔划后该变量便可用于 inkP_MouseLeftButtonDown 事件中

  若要进行尝试请在类中添加变量声明将默认值设置为 black如下所示: .Windows.Media.Color currentColor =    Colors.Black;

  在以下举例中我创建了个思路方法可用作任意数量彩色矩形 MouseLeftButtonDown 事件此思路方法可确定矩形颜色然后使用该颜色作为 currentColor 变量值: private void ChangeColor(object sender, MouseButtonEventArgs e)
{
 Rectangle rec = (Rectangle)sender;
 SolidColorBrush scb = (SolidColorBrush)rec.Fill;
 currColor = scb.Color;
}


  在 inkP_MouseLeftButtonDown 思路方法中添加以下代码可将 DrawingAttributes 属性设置为 currentColor 变量: Stroke.DrawingAttributes.Color = currentColor;

  最后您还需要种思路方法来触发更改添加两个矩形元素个元素 Fill 设置为 black将另个元素 Fill 设置为 red每个矩形都需要个 MouseLeftButtonDown 事件来 ChangeColor 思路方法以下 XAML 会创建两个显示为圆形矩形对象: <Rectangle MouseLeftButtonDown="ChangeColor"
 Width="24" Height="22" Fill="#FF000000"
 Stroke="#FF000000" RadiusX="25" RadiusY="25"
 Canvas.Left="-10" Canvas.Top="282"/>
<Rectangle MouseLeftButtonDown="RedInk"
 Width="24" Height="22"
 Fill="#FFCE0C0C" Stroke="#FF000000"
 RadiusX="25" RadiusY="25"
 Canvas.Left="25" Canvas.Top="282"/>


  或者您还可以编写代码深入到 StrokeCollection 内部以更改现有笔划颜色

  OutlineColor 是 Stroke 对象 DefaultDrawingAttributes 的如果您计划在多色背景(如图像)中进行绘制则使用轮廓颜色绘制手写内容将会很有帮助您可以将以下代码添加到 inkP_MouseLeftButtonDown 事件中以设置 Stroke OutlineColor: Stroke.DrawingAttributes.OutlineColor =
 Colors.White;


  图 12 介绍说明了这概念

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />   图 12 有框墨迹

  手写识别

  应用中存在众多超炫手写功能其中的就是手写识别虽然此功能最初就是 Tablet PC SDK 部分也是 WPF 项功能但并未包含在 Silverlight 中不过通过将笔划数据发送到将执行识别并返回结果 ASP .NET Web 服务或 WCF 服务中您也可以在 Silverlight 应用中使用手写识别功能

  Microsoft Research 收集了百多万用户手写举例用于创建手写识别算法当您发现手写体字母比印刷体字母更易于识别时您可能会感到很吃惊另外还针对多种语言提供了手写识别引擎其中包括多种亚洲语言

  该引擎可以分析笔划集合还可以确定某组笔划是代表字、句子、段落还是绘图识别引擎可以从包含多个字组笔划中识别出各个字然后将这个字视为个整体接着识别引擎将使用其举例和算法提供出组可能字供您选择如果您发送组字(如句子)它就会返回组字以及系列替代选项过去只有 Tablet PC 上可实现识别功能而现在任何支持 Silverlight 计算机/浏览器组合都可以实现识别功能!

  InkAnalyzer 类

  .Windows.Ink.InkAnalyzer 可以执行手写识别此类使用起来非常简单只需将笔划集传递到 InkAnalyzer 对象 Analyze 思路方法然后使用名为 Successful 布尔属性确定 Analyze 是否成功完成如果 Successful 为 true则 GetRecognizedString 思路方法将返回最佳推断值而 GetAlternates 思路方法将返回个备选

  虽然 Silverlight API 并不包含 InkAnalyzer 类但是您仍可以使用 Web 或 WCF 服务为 Silverlight 应用提供手写识别Web 服务器可以托管 WPF API 并提供执行识别功能但是这需要进行些转换

  首先您需要在要执行手写识别功能所有组件中引用图 13 中 API前两个 API 可通过 Visual Studio 中“添加引用”界面随时使用最后两个 API 和 Tablet PC SDK 同安装位于 Program FilesReference AssembliesMicrosoftTablet PCv1.7 目录下当进行编码时您将需要了解 IACore 和 IAWinFX 命名空间的间模糊引用最后还需要引用随 Tablet PC SDK 同安装 IALoader.dll如果您使用是 Windows Vista则可以在 C:Program FilesMicrosoft SDKsWindowsv6.0Bin 下找到此文件

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />图 13 WPF API

API 功能
PresentationCore.dll 包含 .Windows.Ink API
WindowsBase.dll 包含供 Collection 使用功能
IAWinFX.dll 将 InkAnalyzer 类和功能添加到 .Windows.Ink 中
IACore.dll 通过 .Windows.Ink.AnalysisCore 提供 InkAnalyzerBase 类和功能



  为了通过线路将 Stroke 从 InkPresenter 传输到服务中需要对笔划进行序列化但这些对象是无法序列化因此您需要为 StrokeCollection 创建个基于 XAML 表示形式这样就可以对串进行序列化并将其发送到服务了

  在 Silverlight 1.0 中需要使用 JavaScript 才能创建此串表示形式而在 Silverlight 2 中您能够使用 LINQ to XML这是个优势(如果您使用是 Visual Basic还可以获得使用 XML 文本这优势)

  图 14 中显示代码可深入到 StrokeCollection 对象内部(能够读取 DrawingAttributes、StylusPos 及其详细信息)并使用 LINQ to XML 创建其 XAML 表示形式此代码有多种用途包括手写识别但识别功能会忽略 DrawingAttributes如果您创建此思路方法只是为了进行识别则可以省略收集 DrawingAttributes 代码

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />图 14 基于笔划创建 XAML 表示形式

public XElement StrokestoXAML(StrokeCollection mystrokes)
{
 //this method uses LINQ to XML
 //be sure to add the to each element in order to load back
 //o a StrokeCollection later with the XAMLReader
  xmlnsString = "http://schemas.microsoft.com/client/2007";
 XNamespace xmlns = xmlnsString;
 XElement XMLStrokes = XElement(xmlns + "StrokeCollection",
    XAttribute("xmlns", xmlnsString));
 //create stroke, then add to collection element   
 XElement mystroke;
 foreach (Stroke s in mystrokes)
 {
  mystroke = XElement(xmlns + "Stroke",
    XElement(xmlns + "Stroke.DrawingAttributes",
     XElement(xmlns + "DrawingAttributes",
      XAttribute("Color", s.DrawingAttributes.Color),
      XAttribute("OutlineColor",
             s.DrawingAttributes.OutlineColor),
      XAttribute("Width", s.DrawingAttributes.Width),
      XAttribute("Height", s.DrawingAttributes.Height))));
  //create pos separately then add to mystroke XElement
  XElement myPos = XElement(xmlns + "Stroke.StylusPos");
  foreach (StylusPo sp in s.StylusPos)
  {
   XElement mypo = XElement(xmlns + "StylusPo",
     XAttribute("X", sp.X.),
     XAttribute("Y", sp.Y.));
   //add the po to the pos collection of the stroke
   myPos.Add(mypo);
  }
  //add the pos collection to the stroke
  mystroke.Add(myPos);
  //add the stroke to the collection
  XMLStrokes.Add(mystroke);
 }
  XMLStrokes;
}


  注意如何使用命名空间构建 XAMLSilverlight 2 XMLReader 要求在 XAML 元素根目录下使用此命名空间以便稍后将 XAML 加载到对象中

  服务器端分析

  现在可以将串作为参数传递给服务操作从而返回串形式结果此操作所使用思路方法必须基于 XAML 串重新创建个 StrokeCollection然后可以将该 StrokeCollection 发送到 InkAnalyzer 中在服务器上您不再能够访问 Silverlight API而将使用 WPF API

  虽然 WPF 也包含 XAMLReader.Load 思路方法但 WPF StrokeCollection 和 Silverlight StrokeCollection 的间存在些细微差别因此将无法识别架构并且 XAMLReader.Load 也将失败但是您可以使用 LINQ to XML 轻松实现对 XAML 深入研究并读取各个 Stroke 元素中数据然后构建新 WPF Stroke 对象

  WPF 和 Silverlight StrokeCollection 的间差别极小例如WPF Stroke.DrawingAtrributes 没有 Outline Color并且 WPF 笔划中 StylusPos 基于和设备无关坐标系而非 Silverlight 中使用像素值虽然存在这些差别但在 Silverlight 和 WPF 中Stroke、StrokeCollection 和 StylusPo 类位于相同命名空间中

  图 15 中 CreateWPFStrokeCollectionfromXAML 思路方法使用 LINQ to XML 来创建 Stroke 元素集合然后遍历这些元素为每个元素创建个新 Stroke 对象该思路方法将再次使用 LINQ 创建 StylusPos 集合然后为每个笔划创建 StylusPosCollection请注意由于执行识别时不需要使用 Color 等 DrawingAttributes因此此处并非完全重新创建 StrokeCollection这两个思路方法依赖于的前讨论 PresentationCore 和 WindowsBase API创建 StrokeCollection 后便可将其传递到将执行分析思路方法中

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />图 15 CreateWPFStrokeCollectionfromXAML 思路方法

private StrokeCollection CreateWPFStrokeCollectionfromXAML
 ( XAMLStrokes)
{
 //because was used to create this
 // (for Silverlight to reuse the XAML),
 //you need to insert the namesace o the Descendent's parameter
 var xmlElem = XElement.Parse(XAMLStrokes);
 XNamespace xmlns = xmlElem.GetDefaultNamespace;
 StrokeCollection objStrokes = StrokeCollection;
 //Query the XAML to extract the Strokes
 var strokes = from s in xmlElem.Descendants(xmlns+ "Stroke") select s;
 foreach (XElement strokeNodeElement in strokes)
 {
  //query the stroke to extract its StylusPos
  var pos = from p
   in strokeNodeElement.Descendants(xmlns + "StylusPo") select p;
  //create Stylus pos collection from po element values
  StylusPoCollection poData =
    .Windows.Input.StylusPoCollection;
  foreach (XElement poElement in pos)
  {
   double Xpo = Convert.ToDouble(poElement.Attribute("X").Value);
   double Ypo = Convert.ToDouble(poElement.Attribute("Y").Value);
   poData.Add( StylusPo(Xpo, Ypo));
  }
  //create a Stroke from the StylusPoCollection
  .Windows.Ink.Stroke stroke =
    .Windows.Ink.Stroke(poData);
  //add the stroke to the StrokeCollection
  objStrokes.Add(stroke);
 }
  objStrokes;
}


  可以在 WCF 或 ASMX Web 服务中使用图 16 中思路方法接受来自 InkPresenter StrokeCollection并返回代表最佳推断结果此功能依赖于 IAWinFX 和 IACore

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />图 16 将串传递给识别器

public RecognizeStrokes( XAMLStrokes)
{
 try
 {
  //custom method to create WPF StrokeCollection from the -based XAML
  var strokeColl = CreateWPFStrokeCollectionfromXAML(XAMLStrokes);
  var IA = .Windows.Ink.InkAnalyzer;
  IA.AddStrokes(strokeColl);
  var status = IA.Analyze;
   (status.Successful)
    IA.GetRecognizedString;
  
    "Not Recognized";
 }
 catch (Exception ex)
 {
  //trap and display errors at design time, not in production code 
   "error:" + ex.Message;
 }
}


  将服务挂接到 Silverlight 客户端

  RecognizeStrokes 思路方法封装在 ASMX 或 WCF 服务内部因此在 Silverlight 应用中可轻松 Web 服务思路方法我在解决方案中使用了 WCF 服务来提供手写识别功能如需有关 WCF 任何帮助请参阅 Silverlight.net 网站WebSite上简单快速入门指南该指南介绍了如何创建可使用 Silverlight 访问 WCF 服务

  虽然在基于 Windows 应用中通常是随着手写内容绘制来动态进行识别但是在分布式应用还是让用户显式请求识别比较合理因此您需要在设计图面上提供个Control控件来启动此过程创建个Control控件如 XAML 中按钮您需要针对该按钮 Click 事件提供个事件处理用于触发 GetXAMLfromStrokes 思路方法;然后将生成 XAML 发送到 Web 服务进行识别在我 Web 服务中此操作称为“Recognize”您还需要个 TextBlock Control控件来显示返回我将其称为“RecoText”

  将 WCF 服务引用添加到 Silverlight 应用代理将只实现对服务操作异步因此您需要个用于处理 RecognizeCompleted 思路方法(请参见图 17)如果注释以多行形式写出则可能会通过硬回车符加以识别因此我没有使用 RecoText TextBox Control控件而是改用了 ScrollViewer Control控件如图 18 所示 使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />图 17 识别操作

private void RecognizeButtonHandler(object sender, RoutedEventArgs e)
{
 StrokeCollection sc = inkP.Strokes;
 XElement sXML = StrokestoXAML(sc,true);
  ss = sXML.;
 GetRecoString(ss);
 //the next two lines are to test the validity of XAML created
 //(this would make a great unit test for this application)
 //StrokeCollection sc2 = StrokeCollection;
 //sc2 = (StrokeCollection).Windows.Markup.XamlReader.Load(ss);
}
private void GetRecoString( inkStrokes)
{
 Binding binding = BasicHttpBinding;
 EndpoAddress endpo =
  EndpoAddress("http://myserver/SilverlightInkService.svc");
 var svc = ServiceReference1.SilverlightInkServiceClient(binding,
  endpo);
 svc.RecognizeCompleted
  EventHandler<ServiceReference1.RecognizeCompletedEventArgs>
  (svc_RecognizeCompleted);
 svc.RecognizeAsync(inkStrokes);
}
private void svc_RecognizeCompleted(object sender,
 ServiceReference1.RecognizeCompletedEventArgs e)
{
 RecoText.Text = e.Result.;
}


使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />   图 18 对已识别文本使用 ScrollViewer Control控件

  为了获得更好测试体验您可能希望在 XAML 页上再添加个按钮用以清除手写内容和已识别文本以方便您体验各种注释顾名思义InkPresenter.Strokes.Clear 可以删除 InkPresenter 中笔划

  使用服务保留注释

  在此解决方案中Web 服务重要用途是保留注释无论 StrokeCollection 是手写文本、绘图还是其他标记在多数情况下您都希望将其保留在数据库中或其他某种类型数据存储中您还可以使用 Silverlight 中提供 IsolatedFileStorage 将注释数据保存在用户计算机上但在本文中我将着重介绍服务器端持久性

  就持久性而言通常您会保存整个 StrokeCollection并可以随时将其添加到另 InkPresenter 中我已经提到必须对 StrokeCollection 对象进行序列化所以最简单思路方法是使用 GetXAMLfromStrokes 思路方法创建 XAML 串表示形式

  创建了 XAML 串的后其他工作就和在数据库中存储其他任何文本没什么两样了请记住如果是针对绘图XAML 就可能会变得非常庞大因此当定义用于存储 XAML 数据库字段以及客户端和服务设置时您必须考虑这可能性以适应庞大数据量传输

  对于 WCF您可能还会考虑使用 JavaScript Object Notation (JSON) 序列化此格式比 XML 序列化数据压缩程度更高虽然可以使用 SQL Server® 2005 及其更高版本中 XML 数据类型但除非您打算利用 XML 数据类型优势(可以对其进行查询和编制索引具有架构支持并允许修改数据)否则般不使用执行此类任务使用 SQL Server 类型(如 nvarchar甚至是针对预期大型绘图 nvarchar(MAX))即可

  在举例应用用户可以从中选择各种图像每个图像注释都将使用图像文件名存储数据库中选中某个图像后便可从数据库中检索到其注释并显示该注释

  存储和检索注释

  就识别而言只能在服务中传递单个但是在存储和检索数据时您将传入 XAML也可能传入和注释相关其他元数据如创建日期、用户或者对该注释所属图像或视频引用WCF 使用 DataContracts 管理消息上述各种组件借助属于图像文件注释举例我将创建个操作使其能够同时存储图像路径和 XAML然后检索特定文件路径 XAML

  此 WCF 服务结合使用接口和属性来定义操作和数据约定您可在图 19 中看出定义了 3个独立服务操作:StoreImageXAML、RetrieveImageXAML 和 RecognizeStoreImageXAML 采用 ImageXAMLComposite 类型参数该参数由 ImageXAMLComposite 类定义为 DataContract另外两个操作只接受和返回单个因此要简单得多服务类实现了这 3个思路方法用于可执行识别和数据库交互辅助思路方法这些和数据库协作思路方法使用 LINQ to SQL(请参见图 20)

使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />图 19 定义 OperationContracts

SilverlightInkWCFService
{
 [ServiceContract]
 public erface ISLInk
 {
  [OperationContract]
  void StoreImageXAML(ImageXAMLComposite value);
  [OperationContract]
   RetrieveImageXAML( imageName);
  [OperationContract]
   Recognize( XAMLString);
 }
 [DataContract]
 public ImageXAMLComposite
 {
   imagePath;
   xamlString;
  [DataMember]
  public XAMLString
  {
   get { xamlString; }
    { xamlString = value; }
  }
  [DataMember]
  public ImagePath
  {
   get { ImagePath; }
    { ImagePath = value; }
  }
 }
}


使用 Silverlight 2 创建可尽情涂鸦<img src='/icons/89271de.gif' /> Web 应用<img src='/icons/89271chengxu.gif' />图 20 识别辅助思路方法

public void StoreImageXAML(ImageXAMLComposite value)
{
 //ExistingRows, InsertRow and updateRow use LINQ to the SQL
 //SubmitChanges
  (ExistingRows(value.ImagePath) true)
  insertRow(value.ImagePath, value.XAMLString);
 
  updateRow(value.ImagePath, value.XAMLString);
}
public ImageXAMLComposite RetrieveImageXAML( imagePath)
{
 //getXAML method performs a LINQ to SQL query
  getXAML(imagePath);
}
public Recognize( XAMLString)
{
  RecognizeStrokes(XAMLString);
}


  整理总结

  至此我已介绍了有关创建 InkPresenter 和和其进行交互、执行手写识别功能以及通过服务存储和检索注释 XAML 表示形式所有重要内容即使没有 Tablet PC 或者并未使用 Windows Vista您仍然可以使用本应用(甚至可通过鼠标使用)但解决方案效果不会很好

  另个有趣可用思路方法是使用视频虽然可以使用 Silverlight 动画和触发器来播放笔划但协调计时和视频却是项挑战不过也充满了乐趣

  可以从 go.microsoft.com/fwlink/?LinkId=122132 中下载 Silverlight、SDK 以及适用于 Visual Studio 2008 和 Expression Blend 2.5 2008 年 6 月预览版 Silverlight Tools Beta2

  特别要感谢来自 Microsoft Stefan Wick他在笔记本、Tablet PC 和 UMPC 开发 MSDN® 论坛中提供了很大帮助并为本文提供了重要指导

  Julia Lerman 是位 .NET 顾问她在软件Software构建方面有 20 余年经验她还是 .NET 社区中有名会议发言人、作者、Microsoft .NET MVP 和 Vermont .NET 用户组负责人她即将出版新书名为Programming Entity FrameworkJulia 博客是 thedatafarm.com/blog

Tags:  silverlight2 silverlight.2.0 silverlight是什么 silverlight

延伸阅读

最新评论

发表评论