码迷,mamicode.com
首页 > Windows程序 > 详细

Prism 4 文档 ---第10章 Silverlight和WPF之间共享代码

时间:2014-11-21 18:10:14      阅读:339      评论:0      收藏:0      [点我收藏+]

标签:des   style   blog   http   io   ar   os   使用   sp   

    本主题帮助你理解来自Prism的多目标和它的优缺点。多目标的代码针对两个不同的平台有大致相同的代码库。这允许同时保持代码尽可能多一样能够产生针对两种不同技术的二进制文件。在这种情况下,本节介绍的技术是WPF和Silverlight。本主题包含了一些你在使用这些技术开发多目标应用程序的时候的注意事项。
目标和有点
    当在编写具有相似功能和能力的WPF和Silverlight应用程序的时候,努力使用一个代码库很有意义。尽管WPF和Silverlight平台非常相似,但他们只有有限的二进制兼容性。仅Silverlight4中引用的一组特定的内核,可移植的框架程序集可以被加载到.NET Framework4.0的运行时中。
    因为Prism为WPF和Silverlight提供了大量的相似的功能。许多的代码可以针对这两种技术进行生成。支持多目标应用主要是有关实现在两个环境之间共享代码和组件的最大化的可能性的模式和结构,以及用于允许一个应用程序集成环境特定的功能,使之能够充分利用台式机或浏览器的的特定的功能。通过创建使用Prism的多目标组合应用程序,您可以重用跨越WPF和Silverlight应用程序的源代码。

超出范围

    如果你可以组织你的组件,以充分利用Silverlight和WPF之间的二进制兼容性的支持,你应该这样做。关于这一点的更多信息,情况CLR团队发布的博客"Sharing Silverlight Assemblies with .NET Apps."
    本章不打算来形容这种情况下,相反,它描述了通过共享源代码构建多目标应用所面临的挑战和解决方案。

多目标场景

    主要情况是对于同时提供了功能丰富的桌面体验和宽范围的互联网应用体验的应用程序。在这种情况下,您可能希望开发对WPF和Silverlight或一个提供不同的功能和工作流程相同的功能和工作流程的应用程序。以下一些多目标的应用程序:
  • 你可以为用户提供一个在办公室的具有全功能的以及在旅行时可缩减功能的基于浏览器版本的的应用程序。
  • 你可以为内部用户提供一个桌面班的应用程序并为外部人员或参与者一个浏览器应用程序。
    例如,一个企业可能想同时拥有一个那些用户想要通过电话来下订单电话呼叫中心的应用程序和一个那些想要在网上下订单的客户能够在网上下订单的应用程序。然而,形式是完全不一样的。电话呼叫中心桌面订单的形式相比在网上下订单的形式提供了更多信息和扩展功能。然而,因为他们完成类似的东西将有订单和业务逻辑可以跨两个场景中重复使用的某些部分。
    面向服务的应用程序更容易实现多目标,因为Silverlight先天就是面向服务的。Silverlight由于它的目标功能设置和安全机制使得它不支持本地存储或者访问数据库。另外,连接的应用程序也更容易多目标,因为Silverlight的关联性质。

多目标的思考

    通过解决方案的多目标性,你也应该考虑以下几点:
  • Silverlight提供的独立存储在本地客户端计算机上存储空间有限。
  • 你可能会在你的多目标的解决方案中的代码中失简洁性和易读性。因为一些WPF的功能在Silverlight中并没有实现。你将需要解决这些问题,并且这将会导致你的代码不再优雅或者易读。
  • 默认情况下,Silverlight应用程序在一个安全的沙箱中运行,所以有几件事情,你不能在沙箱环境外进行的。这些应用具有受限访问本地计算机和被限制,以帮助防止恶意行为。这些限制防止对设备的访问,并与其他正在运行的程序进行交互。
注意:
如果你需要访问这些项目的其中一些,你可以创建一个具有提升信任权限的浏览器以外的Silverlight应用程序。更多信息,请看MSDN上的"Trusted Applications"
  • Silverlight只支持异步通信,所以你不能在多目标应用程序中使用同步通信。

多目标元素

    通常情况下有许多的代码与实际展示结束是毫无关系的。由于Silverlight和.NET Framework运行时本质上非常相近。大部分的代码可以两种技术之间共享。这也鼓励大量使用展示隔离的模式,以从实际可视化展示和展现逻辑之间分开。为了最大化的分离UI和非UI代码。你可以使得以下多目标源代码元素:
  • 展现模式。模式例如MVVM,MVP和MVC,如果在平台之间逻辑大部分相同时都可以使用这些模式。
  • 服务。帮助展现的服务可以经常用于多目标。
  • 单元测试。许多单元测试在工具中可以是多目标的,例如Silverlight 单元测试,框架可以使用例如MSTest的相同的属性结构。
  • 简单的视图。如果XAML被用于支持Silverlight和WPF。如果一个视图仅由基本控件和简单的数据绑定组成,它有可能也可以在WPF和Silverlight之间共享。
    Silverlight 的API大部分是.NET Framework的API的子集,所以它使用这种更小的API来开发应用程序来减少使用在多个平台之间不能同时使用的功能的机会经常很有意义。由于Siliverlight和WPF的XAML之间的不同,以下元素比较难以复用:
  • Complex Views(XAML)
  • Controls
  • Styling
  • Animation
  • Expression Blend behaviors 和triggers

一个多目标的解决方案:多链接的项目

    Silverlight和WPF只有有限的二进制兼容,所以一些代码还组合必须在每个目标环境中被重新编译。Prism采用的机制用于在应用程序的结构和将模块代码引用到多个项目的提供指导。每个工程都管理者所有的引用,资源,和WPF和Silverlight目标环境指定的代码。在两个项目之间连接的代码的共享使得它们自动的编译到各自的目标中,单元测试可以类似的被两个环境间共享,以使得它们可以被一次编写在任何一个目标环境中运行目标代码。Prism 团队创建了一个名为项目连接器的工具来帮助创建和维护这些连接。项目连接器工具用于在Prism类库,快速启动,和实现引用中连接项目。
    注意:    
    你可以从Visual Studio Gallery 中下载项目连接器或者打开Visual Studio,在工具菜单中点击扩展管理,在扩展工具管理窗口的联机库中查找项目连接器。
    非UI代码和组件可能更容易被共享,所以坚持分离UI模式是确保UI和非UI部分的应用程序或模块被清晰地划分开至关重要的。    

核心应用程序

    这个模式的整体是基于应用程序内核代码共享的并且然后使用实现了WPF和Silverlight特定功能的扩展项来扩充的。应用程序的内核定义了应用程序的整体结构并且包含了在两个环境中通用的代码和组件。Silverlight是WPF的主要的一个子集,所以在Silverlight中开发应用程序内核减少了依赖一个在WPF 中支持但在Silverlihgt中不支持的API的风险。
    注意:
    在Prism源码树中许多的项目实际上在WPF项目中拥有内核源文件,将Silverlight项目连接到WPF项目的文件。这主要是由于Prism的第一个版本实在Silverlight发布之前构建的历史原因造成的。
    下面的插图展示了Multi-Targeted QuickStart的资源管理器。QuickStart的大多数的WPF版本是连接的文件因为应用程序的内核实在Silverlight中开发的。在QuickStart中找到的共享的文件包括图片,模块,服务,接口和资源。共享(连接)的文件被突出显示。
bubuko.com,布布扣
bubuko.com,布布扣

创建多目标应用程序

    本节描述三方面来考虑开发多目标的应用程序:
  • 设计和编码方针。这一节描述了在Silverlight和WPF之间共享代码的考虑内容。
  • 程序的指导方针。这一节描述了Silverlight和WPF之间共享代码的实现方式。
  • 团队建设指导方针。这一节描述了当构建多目标程序是特定的软件团结构建的问题。
  • Silverlight和WPF之间的对比。这一节描述了Silverlight和WPF之间的区别。

设计和编码方针

    设计和编码方针包含以下内容:
  • 使用表现分离模式来最大化共享代码的数量。
  • 当可能时,编辑可以在两个平台下编译的代码。如果不行,按以下操作:
    • 使用#if标签如果你有简单或者单行构造方法。
    • 在类的大部分相似但是一些方法具有特定平台的实现时使用部分类
    • 只有当你需要在一个平台调用外部方法但是在另一个平台不需要调用外部方法时使用部分方法
    • 使用单一职责构建指定平台的类
  • 为Silverlight和WPF创建各自的解决方案文件夹
  • 在重构代码时间长Silverlight和WPF的引用。
    下面的章节将更详细的单独描述这些方针。

使用表现分离最大化共享代码数量

    在平台间共享视图-代码会比较困难。如果你讲逻辑代码从UI中分离共享展现和业务逻辑将会更容易一些。另外,这使得代码更容被理解和维护。

编写在两个平台中编译的代码

    当可能时,编辑可以在两个平台下编译的代码来进行复用。当这种方式变得太复杂时,你必须权衡使用两份代码比一份基类代码跟优雅。例如,在Silverlight中,你可以在ItemsControl的Items属性上执行以下LINQ表达式。
ItemsControl someItemsControl = new ItemsControl();
someItemsControl.Items.Add(new TextBox());
bool hasDependencyObjects = someItemsControl.Items.Any(item => item is DependencyObject);
    然而,由于在WPF中 Items属性不是IEnumerable<T>的,这种实现方式将不能正常工作。而不是在WPF和Silverlight中创建一个不同版本,选择一个较低的首选,但是单一来源的解决方案,如下面的代码。
ItemsControl someItemsControl = new ItemsControl();
someItemsControl.Items.Add(new TextBox());
bool hasDependencyObjects = false;
foreach (var item in someItemsControl.Items)
{
    if (item is DependencyObject)
    {
        hasDependencyObjects = true;
        break;
    }
}
    这种简单的方式在Silverlight中编写,因为在.NET Framework中它是被迫使用的。

如果你有简单或者单行构造 使用#if 声明

    有时候你不能有创建一个单一的代码基础因为WPF和Silverlight之间的不兼容性。在这种情况下,你可以使用#if SILVERLIGHT标签来创建又掉进的编译部分。下面的代码示例展示了使用#if SILVERLIGHT声明。
#if SILVERLIGHT
            System.Windows.Application.Current.RootVisual = shell;
#else
            Application.Current.MainWindow = shell;
            shell.Show();
#endif
    然而,#if有一些不好:
  • 代码缺少易读性和易维护性。如果代码中散列这#if声明,它会变得很难阅读和维护。
  • 调试变得困难。如果在构造方法中有一个编译错误。当你尝试打开文件时,Visual Studio 选择了包含物理文件的解决方案而不是包含错误的解决方案,这意味这你必须手动的关闭这个文件然后打开正确的解决方案或者在没有只能提示的情况下编辑代码。   

在类的大部分相似但是一些方法具有特定平台的实现时使用部分类

    当Silverlight和WPF之间的变化变得更加复杂时,你可以创建部分类。标记共享的类作为部分类并且然后为特定的平台创建一个部分类。这样同样应用到单元测试中。以下是一些额外的建议:
  • 尽量将特定平台的方法保持为私有的。在这种方式中,单元测试将不需要为特定的平台而包含特定的逻辑。
  • 确保类有单独的,清晰的职责,任何为特定平台编写的部分类应该指改变详细的实现内容。
    注意:
    如果在两个平台之间的区别变得非常广泛,并且两种平台的类变得非常不同,考虑创建指定平台的类来替换掉部分类。
    以下示例代码展示了一个部分类,RealEstateService,它在Multi Targeting QuickStart项目中在Silverlight和WPF项目中共享。
namespace RealEstateListingViewer.Services
{
    public partial class RealEstateService: IRealEstateService
    {
        public RealEstate GetRealEstate ()
        {
            ...
            return property;
        }

    }
    以下代码实例展示了在Multi-Targeting QuickStart.中的RealEstateService类中指定Silverlight的部分类中的检索图片,
 
namespace RealEstateListingViewer.Services
{
    public partial class RealEstateService
    {
        /// <summary>
        /// Return the images. In Silverlight, you want to download the image
        /// from a web server.
        /// You can either store the images on the server or build an
        /// HTTP handler to retrieve the images. 
        /// </summary>
        private static BitmapImage GetImage()
        {
            Uri imageUri;
            Uri source = App.Current.Host.Source;

            if (source.ToString().StartsWith("file://", StringComparison.OrdinalIgnoreCase))
            {
                imageUri = new Uri("../Images/House.jpg", UriKind.Relative);
            }
            else
            {
                source = new Uri(string.Format(CultureInfo.InvariantCulture, "{0}://{1}:{2}/", source.Scheme, source.Host, source.Port));
                imageUri = new Uri(source, "Images/house.jpg");
            }
            return new BitmapImage(imageUri);
        }
    }
}
以下代码展示了在在Multi-Targeting QuickStart.中的RealEstateService类中指定WPF的部分类中的检索图片,
namespace RealEstateListingViewer.Services
{
    public partial class RealEstateService
    {
        /// <summary>
        /// Return the images. In a windows application, normally you
        /// retrieve the image from a database.
        /// But for simplicity, it is just being retrieved from the file system.
        /// </summary>
        private static BitmapImage GetImage()
        {
            return new BitmapImage(new Uri("../Images/house.jpg", UriKind.Relative));
        }
    }
}
仅当你需要在一个平台而不是其他平台上调用外部方法时使用部分方法
    如果一些工作需要仅在Silverlight或者仅在WPF中执行。你也可以使用部分方法。这意味着你可以将一个方法的接口放到父类中并且将这个接口的实现仅放到指定平台的类中。对于另一个平台,编译器将会移除方法调用。这里有一些部分方法的限制:
  • 部分方法的声明必须以partial关键字开始并且返回类型必须为void。
  • 部分方法可以拥有ref参数但是不能有out参数
  • 部分方法是隐性的private的,因此,不能为virtual。
  • 部分方法不能是extern的,引入呈现的主题决定了它们是否被定义或者被实现。
  • 部分方法可以拥有static和unsafe修饰符
  • 分部方法可以传统的。在部分方法的声明中定义了约束,并可以选择性地在实现中重复定义这些规则。参数和类型参数名称在实现中不必与定义的名称一模一样。
  • 你不可以将一个delegate赋值给一个部分方法。

使用单一职责构建指定平台的类

    通常情况下,将影响所有指定平台的代码放到一个单独的类中(例如,服务或者服务代理)更有意义。如果大部分的逻辑在两个平台中狗不同的话那将会发生。在这种方式下,你可以为服务创建一个指定平台的实现,例如一个获取数据缓存,或者授权。这种方式在仅展现某一个平台可以提供的功能的情况也能起作用。以下是一些额外的建议:
  • 在不同的平台中使用通用的接口来共享代码。例如,在Prism类库中,有几个用于加载模块类型的特定平台的类,比如用于Silverlight的XapModuleTypeLoader和用于桌面程序的FileModuleTypeLoader。它们头实现了IModuleTypeLoader接口。
  • 当在不同的平台中共享一些功能时,有利于组成整体继承(例如,使用策略模式)。换句话讲,确定在一个共享类中分解出具有特定功能共享的代码是否有益于。在某些情况下,使用继承是有意义的。

为Silverlight和WPF创建各自的解决方案文件夹

    使用解决方案文件夹保持你的解决方案的有组织性。通常,你可以通过使用两个文件夹来做这些,启用一个用于Silverlight代码,另一个用于WPF代码。例如如何组织你的应用程序的结构,查看Multi-Targeting QuickStart.

重构代码时检查Silverlight和WPF的引用

    有时候,一个Silverlight引用可能陷入一个WPF项目,反之亦然。这是通过使用重构工具所引起的。如果你得到关于未在WPF项目中引用的Silverlight组件意想不到的编译器错误,请检查引用。

过程指导

    过程指导包含以下内容:
  • 在Silverlight中开发内核。
  • 在源项目与目标项目之间连接共享代码。
  • 为Silverlight和WPF项目使用相同的命名空间。
    接下来的几节将会详细的描述这些指导。

在Silverlight中开发应用程序内核

    由于Silverlight是WPF的一个主要的子集,相同的代码在WPF中不需要大的改动就可以运行,所以你应该在Silverlight中开发应用程序内核。

在源项目与目标项目之间连接共享代码

    对于那些Silverlight和WPF通用的文件但是需要不同的引用的情况,你应该使用连接文件。一种方法就是使用项目连接器工具在源项目与目标项目之间连接共享代码。

在Silverlight和WPF项目中使用相同的命名空间

    由于共享代码要求相同的命名空间,所以保持项目之间的命名空间相同。

团队构建指导

    这一章节将会描述团队构建指导。

在构建地配置团队构建

    如果你使用团队构建来构建一个解决方案来承载WPF和Silverlight项目,你可能会遇到文件冲突的问题。默认情况下,团队构建将会把输出从工程拷贝到一个单独的输出文件夹。由于WPF和Silverlight工程应该拥有相同的输出名称,将会有文件冲突问题组织你编译工程或者运行单元测试。
    通过设置CustomizableOutDir属性为true,你可以高数团队构建构建到一个位置而不是将输出拷贝到一个单独的位置,这将会阻止名称冲突的为难题。
    另外,如果你设置了这个属性并且在你的构建中想要运行单元测试,你也需要指定TestContainers的位置。
<PropertyGroup>
<!--Build in place-->
    <CustomizableOutDir>true</CustomizableOutDir>
</PropertyGroup>

  <!—Override the BeforeTestConfiguration target to specify where the testcontainers live. -->
  <Target Name="BeforeTestConfiguration">

    <!--
      Change the outdir, because the testtoolstask needs this to execute all the tests
    -->
    <PropertyGroup>
      <OutDir>$(SolutionRoot)\Source\</OutDir>
    </PropertyGroup>

    <!--Create a list of all tests dll‘s to run (test only the desktop versions) -->
    <CreateItem Include="$(SolutionRoot)\**\Desktop\**\Bin\Debug\%2a.Tests.dll">
      <Output ItemName="TestContainer" TaskParameter="Include" />
    </CreateItem>

  </Target>
    注意:
    这个例子假定你的桌面项目放置在一个名为Desktop的文件夹中。MSTest.exe只有在目标桌面版本的是.NET Framework的时候才能运行单元测试。所以Silverlight测试程序集被执行。

Silverlight和WPF 对比

    Silverlight和WPF都允许你基于XAML和.NET Framework开发丰富的用户体验的应用程序。然而,两种平台之间有一些不同,以及这些不同需要在Silverlight和WPF程序之间过度时或者当构建一个目标包括WPF和Silverlight应用程序时需要仔细的考虑。
    注意:
    这个主题描述了Silverlight4和.NET Framework 4.0一部分的WPF之间的不同。这些不同期待在将来的版本中北减少。

Silverlight和WPF 架构概述

    WPF为开发人员提供了一个构建包括UI,媒体和文档等的富窗体应用程序统一的编程模型。WPF使得软件开发人员提供了一个声明为基础的语言(XAML),用于指定基于矢量的图形,可以扩展并充分利用硬件加速的优势,提供了“用户体验”(UX)一个新的水平。
    Silverlight是一个跨浏览器,跨平台实施的.NET框架为提供下一代丰富的交互式媒体和内容通过网络,台式机业务应用程序和浏览器承载的富互联网应用程序(RIA),可以集成数据和服务从许多来源。 Silverlight允许开发人员构建应用程序,显著增强典型的最终用户体验,与传统的Web应用程序相比。像WPF,Silverlight提供一个基于XAML语言来指定用户界面。
    Silverlight和WPF共享了许多相同的功能和特性,但是它们建立在不同的运行时的堆栈的顶部,就是下面的插图。WPF利用完整的.NET Framework和执行上的公共语言运行时(CLR)。 Silverlight是基于XAML的一个子集,完整的.NET框架,并在不同版本的CLR执行。
bubuko.com,布布扣
bubuko.com,布布扣

Silverlight与WPF之间的区别

    为了保持Silverlight的小而轻,一些WPF和.NET Framework功能在Silverlight中是不可用的。正因为如此,有可能是微妙和不那么微妙的差别是要构建一个针对WPF和Silverlight的应用程序时,或者Silverlight和WPF之间过度应用程序时要慎重考虑。本节将描述在Prism团队开发Prism是遇到的一些主要的不同。这些区别针对Silverlight4和WPF 4.0,当前编写时的版本。
资源
    资源是一些可以存储几乎任何元素(字符串,画刷,样式,数据源,以及许多其他的)的键-值的简单的集合。资源可以关联到几乎任何暴露了ResourceDictionary类型的Resoures属性的元素类。以下是Silverlight和WPF关于资源的一些不同:
    资源可以包含静态或者动态内容。动态内容可以在运行时背改变并且资源的消费者将会自动的被更新。Silverlight中不支持动态资源引用;因此,只有静态资源引用可用。

触发器

    触发器允许设计者定义控件的可视化行为,通过声明式指定它的属性在对于事件或者属性改变时做出反映时如何改变,例如在点击一个按钮时突出显示按钮。通常,触发器在当一个控件的属性发生变化时被触发并且导致控件的一个或者多个属性发生变化。触发器被定义在样式内部并且可以被应用与指定目标类型的任何对象。
    Silverlight并不支持Styles,ControlTemplates,或者DataTemplates(Triggers集合并不存在与这些元素中)的触发器。然而,通过使用Silverlight的可视化状态管理器(VSM)来定义控件模板交互也可以达到类似的行为。使用VSM,控件的可视化行为,包含任何动画和转换,被定义在空间的模板中。这些可以在Blend4中很容易的做到。然而,要知道XAML文件将会变得更复杂并且内置在Silverlight中的模板并没有与WPF兼容。
    Blend SDK 提供了用于动画的Blend 行为并且可视化状态管理器可以用于WPF和Silverlight应用一致的外观及感觉。
数据绑定
    WPF和SIlverlight都提供了对数据绑定的支持。以下是Silverlight和WPF之间数据绑定的主要不同:
  • 在Silverlight中,灭有OneWayToSource数据流模式
  • 在Silverlight中,你不能直接绑定到XML数据。一个可能的解决方法是将XML转换为CLR对象,然后绑定到CLR对象。
  • 在Silverlight中,没有XMLDatProvider类。
  • 在Silverlight中,没有DataTemplateSelector类。在WPF中,这个类提供了基于数据对象和数据绑定元素的选择DataTemplate的方式。然而,为了支持特定的MVVM场景,Prism在它的DataTemplateSelector类中为Silverlight提供了类似的功能。

命令

    以下是WPF和Silverlight中关于命令的一些不同:
  • 在Silverlight中路由命令不可用。然而,在Silverlight中ICommand接口可以用,允许开发人员创建它们自定义的命令。Prism类库提供了DelegateCommand 和CompositeCommand 类为简单的命令的实现。
  • 在WPF中,控件可以通过它们的Command属性连接到命令。通过做这些,开发人员可以声明关联的控件和命令。这也意味者一个控件可以通过命令交互以使得它可以滑行命令并且使得它的状态自动更新。在Silverlight中国,一些控件支持Command命令,但是不想WPF那样多。对于那些没有提供了Command属性的Silverlight控件,考虑使用Blend 行为基于触发器来唤醒命令。
  • 在Silverlight中没有输手势和输入绑定。

杂项

    以下是Silverlight和WPF之间的一些杂项的差异:
  • 在Silverlight中,UIElement类有一个内部构造方法;因此,你不能创建一个控件继承它。
  • 在Silverlight中,没有对X:Type扩展标记的支持或者自定义扩展标记的支持。
  • 在Silverlight中,没有对X:Static扩展标记的支持或者自定义扩展标记的支持。在Silverlight中,添加到TabControl控件的子项不会像WPF中那样自动的被封装懂啊一个TabItem中。
  • 所哟Silverlight网络调用都必须是异步的。
  • Silverlight的网络,除了信任外的浏览器应用程序,对于服务器客户端访问策略原产地以外的网站服务器。

更多信息

     你可以从Visual Studio Gallery 中下载项目连接器或者打开Visual Studio,在工具菜单中点击扩展管理,在扩展工具管理窗口的联机库中查找项目连接器。
    关于组织程序集结构来获得在Silverlight和WPF之间二进制兼容性的优势的更多知识,请看CLR 团队发布的博客"Sharing Silverlight Assemblies with .NET Apps":
    关于WPF 架构的更多内容,请看MSDN上的"WPF Architecture"
    关于Silverlight 架构的更多内容,请看MSDN上的"Silverlight Architecture" 
    关于WPF和Silverlight之间的不同的总结更多内容,请看MSDN上的 "WPF Compatibility" on MSDN:

    关于可视化状态管理器和它如何在创建控件中工作的更多内容,请看MSDN上的以下关于创建自定义控件的文章:

    关于创建一个提升信任的应用程序的更多内容,请看MSDN上的, see "Trusted Applications" on MSDN: 

Prism 4 文档 ---第10章 Silverlight和WPF之间共享代码

标签:des   style   blog   http   io   ar   os   使用   sp   

原文地址:http://www.cnblogs.com/xixia/p/4113323.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!