在 WPF 和 Silverlight 2 中反复使用代码

Dino Esposito

代码下载位置: CuttingEdge2008_10.exe (604 KB)
在线浏览代码

本专栏基于 Silverlight 2 的预发布版本。文中的所有信息均有可能发生变更。

clear.gif 目录

WPF 与 Silverlight 2 的兼容性
视觉状态管理器简介
共享代码
有关 WPF 应用程序的推论
编写跨平台的 WPF 代码
分析托管代码
获取用于关键代码的策略
示例分析
最终注意事项

在 Silverlight 2 中,您可以使用可扩展应用程序标记语言 (XAML) 来设计和渲染用户界面。与此同时,您可以利用内置的核心 CLR 处理浏览器内的托管代码。这样基于 Web 的 Silverlight 2 应用程序与桌面 Windows Presentation Foundation (WPF) 应用程序就变得极为相似。编程模型相似的好处之一是可以在二者之间轻松重用代码。在本专栏中,我将介绍几种模式,它们能在 Silverlight 2 和 WPF 之间最为轻松地共享代码和 XAML 标记。

Silverlight 中的 CoreCLR

Silverlight 2 确实包含一个 CLR,但并不是其他 .NET 应用程序和程序集使用的 CLR。Silverlight CLR 也称为 CoreCLR,在设计时充分考虑了不同用途。CoreCLR 专为跨平台互操作性而设计,可与 CLR 同时运行并支持不同的安全模型以及不同版本的基础类库。2008 年 8 月的出色专栏《CLR 全面透彻解析》中对此进行了详细说明(请参阅 msdn.microsoft.com/magazine/cc721609)。

Silverlight 和 .NET 应用程序使用不同 CLR 意味着您不能在两个针对 .NET 应用程序和 Silverlight 应用程序的项目中引用同一个程序集。主要问题出在 mscorlib 程序集上。Silverlight 正常使用所需的功能集非常小-仅仅是内核。但任何 .NET 程序集都要链接标准版本的 mscorlib,这就是问题所在。

在本专栏所讨论的示例应用程序中,我利用一个接口来共享 Windows Presentation Foundation 应用程序与 Silverlight 应用程序。唯一的解决方法是在两项目之间复制 C# 及接口定义,因为您没有共同引用的程序集,因此在 .NET Framework 版本中,有必要将标准 mscorlib 程序集内的功能分成两部分:内核事务和桌面事务,以便为 Silverlight 和 .NET 程序集之间的二进制兼容性奠定基础。

WPF 与 Silverlight 2 的兼容性

继引入 Silverlight 2 后,XAML 不动声色地成为新一代的 UI 模块的 API。Silverlight 2 支持完整 WPF 框架的子集,其中包括丰富布局管理、数据绑定、样式、媒体、动画、图形和模板功能。

但在 Silverlight 2 内完全支持 XAML 受可下载插件的大小限制。在只有几兆的空间内(Beta 2 中不到 5MB),Silverlight 2 插件必须提供核心 CLR,即包括 WPF 和 Windows Communication Foundation (WCF) 客户端平台子集、XAML 分析器以及大量 Silverlight 特定控件的 Microsoft .NET Framework 3.5。

在 Silverlight 2 中不支持 WPF 3D 图形功能,其中的某些属性和元素也已被丢弃或裁减。综上所述,您已拥有可兼容的子集,因此您可以制作 Silverlight 版本的较为复杂的 WPF 用户界面。稍后,我将回头继续讨论兼容性的关键部分。

请注意,尽管 Microsoft 也在 Silverlight 中添加了某些新 API,但它们目前在桌面版本的 WPF 中并没有相对应的功能。最相关的功能包括控件(如 DataGrid)和类(如用于简单的 GET 样式网络调用的 WebClient)。这些功能也将被添加到 WPF 中。

视觉状态管理器简介

WPF 中还将添加 Silverlight 2 附带的另一个极为出色的功能 – 用于控件的视觉状态管理器 (VSM)。VSM 通过引入视觉状态和状态转换简化了交互控制模板的开发。由于 WPF 的引入,开发人员可通过模板和样式自定义 WPF 控件的外观和行为。例如,你不能只更改某个控件的形状和外观,还应该定义该控件在点击、得到或失去焦点等事件时的新行为或动画。

使用 VSM,视觉状态和状态转换将替您完成其中的部分工作。典型的 VSM 视觉状态有普通、鼠标悬停、禁用和获得焦点。您可以为这些状态每种定义一个样式或形状。状态转换用于定义控件如何从一种状态转换到另一种可视状态。通常可通过动画定义转换。在运行时,Silverlight 将播放相应的动画并应用指定的样式流畅地将控件从一个状态转换到另一个状态。

要定义视觉状态,在控件模板中插入标记片段,如下所示:

clear.gif 复制代码

<vsm:VisualStateManager.VisualStateGroups>
  <vsm:VisualStateGroup x:Name="CommonStates">
    <vsm:VisualState x:Name="MouseOver">
      <Storyboard>
        ...
      </Storyboard>
    </vsm:VisualState>
  </vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>

每个控件都定义一组或多组状态,如 CommonStates 和 FocusStates。而每组都定义具体的视觉状态,如“MouseOver”、“Pressed”和“Checked”。对于每种视觉状态和状态之间的转换,您可以定义一个 Silverlight 可在适当时候自动播放的故事板。

简言之,WPF 中有很多 Silverlight 2 不支持的功能,而 Silverlight 2 中的很多功能 WPF 也不具备。很大程度上是这些差异影响了 XAML 级别的兼容性。您可以使用公共子集实现完全兼容,并且令人欣慰的是,它足以保证您能执行几乎所有的操作。

共享代码

当桌面版本的 WPF 支持 VSM 时,您可以对 Silverlight 和 Windows 桌面应用程序使用相同的方法,并共享 WPF 和 Silverlight 项目之间的控制模板。在此之前,让我们先看看您现在是如何在 WPF 桌面和 Web 项目之间重用代码的。

WPF 应用程序由 XAML 和托管代码组成。托管代码的目标是所支持版本的 .NET 的多个类。您应当使用一个桌面 WPF 和 Silverlight 2 都能理解的 XAML 公共子集。同样地,您应当组织好您的代码隐藏类,这样就能很好地处理后端框架之间的差异。

您主要希望在两个场合中重用 WPF 代码。一种情况是您现在已有一个 WPF 桌面应用程序,希望通过 Web 提供该程序以简化维护和部署。另一种情况是您希望为现有的系统开发一个前端,并将其应用于 Windows 和 Web 客户端。

我将从 WPF 到 Silverlight 的角度着手处理重用代码的问题。在重构代码和标记的模式方面,两者之间有非常小的差异。

有关 WPF 应用程序的推论

典型的 WPF 应用程序是通过对象树(Window 为树的根)构建的。Window 元素又包含大量按照各种方式布置和堆放的子元素。这些元素涉及基本形状、布局管理器、故事板和控件(包括自定义的第三方和用户控件)。下面是一个基本示例:

clear.gif 复制代码

<Window x:Class="Samples.MyTestWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Test App" Height="300" Width="350">
    <StackPanel Margin="10">
        ...
    </StackPanel>
</Window>

该代码无法照原样合并到 Silverlight 2 应用程序中。首先,Silverlight 中不支持 Window 元素。在 Silverlight 程序集中,根本没有诸如 System.Windows 命名空间中的类。所有 Silverlight 应用程序的根标记都是 UserControl。该元素被映射到在 System.Windows.Controls 命名空间中定义的 UserControl 类上。

下面是一个标题被修改的 Silverlight 应用程序:

clear.gif 复制代码

<UserControl x:Class="Samples.MyTestWindow"
    xmlns="http://schemas.microsoft.com/client/2007"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Test App" Height="300" Width="350">
    <StackPanel Margin="10">
        ...
    </StackPanel>
</UserControl>

<Window> 和 <UserControl> 元素边界内的所有标记代码应继续匹配。您负责修改原始的桌面 XAML 使其符合 Silverlight XAML 分析器的要求。该任务会从两种语法的高兼容性受益良多。在多数情况下,调整 XAML 只是修复少数元素和属性。举两个示例进行说明。

桌面 WPF 中包含一个 Silverlight 无法识别的 <label> 元素。将此标记迁移到 Silverlight 中时,您需要为该标签找到一个工作区。一个内部包含文本块的矩形是个较为可行的解决方案。

在 WPF 中,您可以使用 ToolTip 属性将工具提示与控件关联起来,如下所示:

clear.gif 复制代码

<Button Tooltip="Click me" ... />

在 Silverlight 2 中,不支持 ToolTip 属性。您必须使用 ToolTipService,如以下代码段所示:

clear.gif 复制代码

<Button ToolTipService.ToolTip="Click me" ... />

应注意这两种解决方案都是在桌面 WPF 中运行的。另外,桌面 WPF 中的 ToolTipService 提供了大量的附加工具提示属性,如位置、偏移、初始延迟和持续时间。但 Silverlight 不支持其中任一项额外属性。

这些都是 WPF 和 Silverlight 之间的兼容性问题吗?这取决于您使用 WPF 的方式。通常,将重要的 WPF 应用程序迁移到 Silverlight 中是很困难的,实际上这甚至不是我们的主要目的。首先,在 Silverlight 2 中没有任何触发器,而您可能在任何地方需要使用它们。例如,UI 元素中有一个触发器集合 - FrameworkElement 的子代,但不是在样式、数据和控制模板中。

同样地,Silverlight 中支持数据绑定,但与在 WPF 中的支持方式不同。例如,您具有了 Binding 元素,即具有了数据环境、数据模板和可见集合。如前所述,您没有任何触发器,而简化的 XAML 标记却比在 WPF 中更需要您频繁地编写代码。此外,内部实现也完全不同。与 WPF 相比,Silverlight 中的 Binding 对象的属性要少很多。

全球化是可能导致您头疼的另一方面。出于性能原因,核心 CLR 不包括其自己的所有支持区域的全球化数据。而 Silverlight 中的 CultureInfo 类依赖于由基础操作系统提供的全球化功能。这意味着您无法跨越不同的操作系统给予应用程序同样的全球化设置。

最后,WPF 包括一个更为丰富的控件集,而 Silverlight 中则没有。一个不错的示例是 RichTextBox 控件。

总之,将 Silverlight 应用程序迁移 WPF 中没有什么价值,尽管作为开发人员您应当参与处理那些可能由丰富对象模型所导致的潜在性能问题。要牢记的一点事实是 Silverlight 支持桌面 WPF 提供的可能情况的子集,您负责决定是否可通过选择适当的功能来设计出跨平台的解决方案。

编写跨平台的 WPF 代码

将代码从 WPF 项目迁移到 Silverlight 项目中最简单的办法是使用用户控件。除 XML 命名空间截然不同和标记差异之外,使用用户控件是在桌面与基于 Web 的 WPF之间共享标记和代码的唯一方法。

如果您的 WPF 应用程序是以本机形式组织的或可进行重构,充分地利用用户控件,将代码迁移到 Silverlight 中要比其他方式(剪切和粘贴)简单得多。

这样 WPF 和 Silverlight 项目之间就可以实现程序集共享了吗?在 Silverlight 2 中,您肯定能创建打包到程序集中的自定义类库。但您应当意识到这些库针对的是 .NET Framework 的 Silverlight 版本,并使用了不同的安全模型(有关详细信息,请参阅本月期刊中的 CLR 全面透彻解析)。

您在桌面 WPF 应用程序中使用的所有程序集都必须重编译为 Silverlight 类库,以确保它们能引用正确的程序集并合法地调用类。显然,该重编译过程中会修复对不受支持类的调用,从而产生额外工作。

总之,只需少量的工作即可取得 WPF 应用程序,并使用 Silverlight 通过 Web 将其公开。如采取这一方式,您实际上是将代码公开在很多非 Windows 平台上,客户端不必安装完整的 .NET Framework。图 1 显示了一个简单的 WPF 独立桌面应用程序。图 2 显示了通过 Silverlight 在 Internet Explorer 中承载的同一应用程序。

图 1 示例 WPF 应用程序

图 2 适用于 Silverlight 的 WPF 应用程序(单击图像可查看大图)

分析托管代码

出于某些原因,WPF 与 Silverlight 间的代码重用被看作一个 XAML 问题。如前所述,在调整代码时会遇到一些 XAML 问题,但最大的难题是代码隐藏类。

在 Windows 和 Silverlight 中,XAML 文件都是与用 C# 或其他托管语言编写的代码隐藏类配对的。遗憾的是,这些代码隐藏类针对的是不同版本的 .NET Framework。桌面版本的 WPF 依赖于 .NET Framework 3.5 的完全基类库 (BCL),而 Silverlight 2 使用的是轻量级版本的 BCL。

Silverlight 版本的 BCL 明显比较小,但仍然支持一些基本功能,如集合、反射、正则表达式、字符串处理、线程和计时器。您还有一些用于调用各种服务(如 XML Web 服务、WCF服务和 ADO.NET 数据服务)的工具。此外,丰富的网络支持让您能通过 HTTP 进行通信-使用 Plain Old XML (POX) 和具像状态传输 (REST) 服务,通常可到达任何公共 HTTP 端点。网络支持还包括(跨域)套接字和双工通信。

最后,Silverlight BCL 能与 XML 数据良好协同,包括特殊版本的 XmlReader 和 XmlWriter 类。这些类与桌面版本的 .NET Framework 内的相似类极为相像。

有了这些核心功能,Silverlight 2 可完全支持 LINQ to Objects、LINQ to XML 以及表达式树。从 Beta 2 开始,Microsoft 还将全新的 LINQ 添加到了 JavaScript Object Notation (JSON) 提供程序中以直接对 JSON 数据运行 LINQ 查询。

另一点要注意的是这种联网只能在 Silverlight 中异步发生。要进行同步调用,您需要尽可能借助调用浏览器互操作性层并从浏览器实现 XMLHttpRequest(有关详细信息,请参阅 go.microsoft.com/fwlink/?LinkId=124048)。

主要问题是 WPF 和 Silverlight 代码隐藏类利用截然不同的类库。这一事实比任何 XAML 差异都要妨碍可重用性。我们接下来解决该问题。

获取用于关键代码的策略

在编写将由 Silverlight 和 Windows 运行时共享的代码时,您应当非常了解各个平台的支持对象。在理想状态下,您会得到一个在各个平台上需要特殊处理的请求任务列表。接下来,将对象中的这些代码片段隔离并从其中提取一个接口。

这样一来,您将得到完全可重用的代码块,它可调用关键功能的独立组件。之后,将此类应用程序迁移到 Silverlight(或 WPF)中就变得像用那些平台特定的其他组件替换关键组件一样容易。因为所有这些组件仍公开了一个公共接口,它们的实现对于主代码块是透明的。

基于此方法的模式即“策略”模式。有关此方法的正式定义,请参阅 go.microsoft.com/fwlink/?LinkId=124047。简言之,“策略”模式在您需要动态地更改那些用于在应用程序中执行某个特定任务的运算时非常有用。

当您已标识出那些可能根据运行时状况发生变化的代码区域时,就可以定义用于指定代码抽象行为的接口了。接下来,创建一个或多个用于实现该接口的策略类,以表示可完成这些抽象行为的各种方法。之后再更改策略类时,您只需更改对该接口定义的运算求解的方法即可。这样做可以将行为的实际实现(即策略)与使用它的代码分离开来。

例如,在 ASP.NET 中,“策略”模式常用于成员、角色、用户配置文件等提供程序模型的实现中。ASP.NET 运行时知道需要处理某个特定的接口,即成员和用户。同时还了解如何找到并实例化这些接口类型的具体类。但运行时组件仅依靠接口,这使得该具体类的详细信息与 ASP.NET 不相关。

示例分析

我们分析一下图 12 中所示的示例应用程序。在 Silverlight 和 Windows 中,该应用程序都为用户提供了一个可键入股票代码以获得当前报价的表单。图 3 显示了该应用程序在用户单击按钮时的图示。用户接口使用 Model-View-Presenter (MVP) 模式在单个表示器类中传递 UI 后台的所有逻辑。而该表示器随之调用一个内部 QuoteService 类,它通过向 StockInfo 对象提供即将合并到用户接口中的所有信息给出最终响应(请参阅图 4)。

图 3 应用程序的行为图示(单击图像可查看大图)

clear.gif 图 4 表示器类

clear.gif 复制代码

namespace Samples
{
    class SymbolFinderPresenter
    {
        // Internal reference to the view to update
        private ISymbolFinderView _view;

        public SymbolFinderPresenter(ISymbolFinderView view) {
            this._view = view;
        }

        public void Initialize() { }

        // Triggered by the user's clicking on button Find
        public void FindSymbol() {
            // Clear the view before operations start
            ClearView();

            // Get the symbol to retrieve 
            string symbol = this._view.SymbolName;
            if (String.IsNullOrEmpty(symbol)) {
                _view.QuickInfoErrorMessage = "Symbol not found.";
                return;
            }

            QuoteService service = new QuoteService();
            StockInfo stock = service.GetQuote(symbol);

            // Update the view
            UpdateView(stock);
        }

         private void ClearView() {
            _view.SymbolDisplayName = String.Empty;
            _view.SymbolValue = String.Empty;
            _view.SymbolChange = String.Empty;
            _view.ServiceProviderName = String.Empty;
        }

        private void UpdateView(StockInfo stock) {
             // Update the view
            _view.QuickInfoErrorMessage = String.Empty;
            _view.SymbolDisplayName = stock.Symbol;
            _view.SymbolValue = stock.Quote;
            _view.SymbolChange = stock.Change;
            _view.ServiceProviderName = stock.ProviderName;
        }
     }
}

QuoteService 类不会试图自己检索报价。它首先创建一个提供程序,然后依赖它工作。QuoteService 类执行了一个非常简单的运算。如果存在 Internet 连接,它就使用采用了公共 Web 服务的提供程序类来获取财务数据,否则将转变为一个仅返回随机数字的伪提供程序。因此有时候 QuoteService 类需要检测 Internet 连接,如图 5

clear.gif 图 5 QuoteService 检测 Internet 连接

clear.gif 复制代码

public StockInfo GetQuote(string symbol)
{
    // Get the provider of the service
    IQuoteServiceProvider provider = ResolveProvider();
    return provider.GetQuote(symbol);
}
private IQuoteServiceProvider ResolveProvider()
{
    bool isOnline = IsConnectedToInternet();
    if (isOnline)
        return new FinanceInfoProvider();

    return new RandomProvider();
}

到目前为止,Silverlight 和 WPF 之间没有任何区别,所有的代码均可完全重用。要检测 .NET 中的 Internet 连接,您可以对 NetworkInterface 对象使用某些静态方法。该对象是在 System.Net.NetworkInformation 命名空间中定义的。特别的,GetIsNetworkAvailable 方法返回了一个用于表示是否存在网络连接的布尔值。令人遗憾的是,该值并未对 Internet 连接做过多说明。要确保可访问 Internet,唯一的方法就是尝试 ping 主机(请参阅图 6)。

clear.gif 图 6 用 ping 检测 Internet 连接

clear.gif 复制代码

private bool IsConnectedToInternet()
{
    string host = "...";
    bool result = false;
    Ping p = new Ping();
    try
    {
        PingReply reply = p.Send(host, 3000);
        if (reply.Status == IPStatus.Success)
            return true;
    }
    catch { }

    return result;
}

图 6 中的唯一问题在于 Silverlight 2 中并不支持该代码。(同样,该代码也不受 NetworkInterface 对象的支持。)您需要在可替换类中隔离此代码(以及您发现可能出现兼容性问题的其他任何代码)。(有关此限制的详细信息,请参阅本期随附的 Silverlight 中 CoreCLR 侧栏)。在配套的源代码中,我为这些种可能出现问题的方法创建了实用工具接口,然后创建了实现各个平台的接口的策略类,如图 7 所示。

clear.gif 图 7 用于检测 Internet 连接的组件

clear.gif 复制代码

public partial class SilverCompatLayer : ICompatLib
{
    public bool IsConnectedToInternet()
    {
        string host = "...";
        bool result = false;
        Ping p = new Ping();
        try
        {
            PingReply reply = p.Send(host, 3000);
            if (reply.Status == IPStatus.Success)
                return true;
        }
        catch { }

        return result;
    }

    public string GetRawQuoteInfo(string symbol)
    {
        string output = String.Empty;
        StreamReader reader = null;

        // Set the URL to invoke
        string url = String.Format(UrlBase, symbol);

        // Connect and get response
        WebRequest request = WebRequest.Create(url);
        WebResponse response = request.GetResponse();

        // Read the response
        using (reader = new StreamReader(response.GetResponseStream()))
        {
           output = reader.ReadToEnd();
           reader.Close();
        }

        return output;
    }

    // A few other methods that require a different implementation 
    // (See source code)
    ...
}

这里的接口将平台特定的策略类与承载应用程序分离开来。那时就可以隐藏在 Factory 方法后实例化具体策略类的代码,下列代码可安全地在 WPF 和 Silverlight 中使用:

clear.gif 复制代码

private bool IsConnectedToInternet()
{
    ICompatLib layer = ServiceResolver.ResolveCompatLayer();
    return layer.IsConnectedToInternet();
}

SilverCompatLayer 类存在于一个独立的程序集中。将 WPF 代码迁移到 Silverlight 中时,您需要更改的就是此程序集,反之亦然。

创建完必要的平台特定的策略类后,剩余的所有任务就是创建该应用程序的 Silverlight 版本。派生自原始 WPF 应用程序的 Silverlight 项目中包含除兼容性程序集以外的所有文件的精确副本。

在本文关联的下载代码中,您将注意到我通过在 Factory 方法中显式实例化具体类来确定使用哪种策略实现。您可以像这样地直接实例化类,或从配置文件中读取它们的名称,然后使用反射获取实例。这些实现细节主要取决于您当前构建的系统类型。就涉及到的 WPF-to-Silverlight 兼容性而言,策略和分层是需要理解的关键概念。

最终注意事项

在示例应用程序中,我进行了一次网络调用。在原始的 WPF 应用程序中,该调用为同步调用。但在 Silverlight 2 中,根本不支持同步网络调用。进行同步调用的唯一方法就是调用 XMLHttpRequest 的浏览器实现(有关详细信息,请参阅源代码)。

在示例代码中,我成功地将原始 WPF 应用程序迁移到了 Web 上。迁移代码时,您可能希望考虑到 Silverlight 环境的本机功能并在调整时修改应用程序的结构。请注意,在我的简单示例中,使用 Silverlight 编程模型重新编写该应用程序比根据 WPF 应用程序进行改编所花费的代码和工作都要少许多。

因此,当 WPF 和 Silverlight 在纯粹的可视编程语言(如 XAML)领域有很多共同之处。但基础编程模型就稍有不同,在 WPF 中有效的解决方案不一定就适合于 Silverlight,反之亦然。这就是说,在 WPF 与 Silverlight 应用程序之间的 XAML 和代码共享一定可以实现。

请将您想向 Dino 询问的问题和提出的意见发送至 cutting@microsoft.com

Dino Esposito 目前是 IDesign 的架构师,也是《Programming ASP.NET 3.5 Core Reference》的作者。Dino 定居于意大利,经常在世界各地的业内活动中发表演讲。您可加入他的博客,网址为 weblogs.asp.net/despos

转载于:https://www.cnblogs.com/slteam/archive/2008/11/25/1340677.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值