简介

在当前的开发中,NuGet的使用已经有了不小的地位,特别是应用.NET Core的UWP开发里,模块化的平台本身更是直接依赖于NuGet这一包管理器。

有时自己开发了一个不错的控组件,想通过NuGet与广大开发中分享,以享受包管理器快捷、模块化的优势,该如何做呢?本文将就基本的UWP控件的开发与打包,来为大家介绍UWP与NuGet配合的过程。

UWP控件的开发

对于控件的开发,想必大家都是身经百战,已无须多言。不过本文仍有一些小小的建议,可以帮助大家创造出更普适、更易用的控件。

选择库的类型

对于UWP应用来说,portable类库、普通的UWP class类库以及Windows Runtime组件库,都是可以接受的,但是这里推荐大家使用Windows Runtime组件来组织自己的控件。得益于WinRT的新架构,只要遵从了WinRT的接口要求(如果违反了,编译器会报告的),组件就能在C# app或者C++/CX app里交互使用了,好处不小。

考虑XAML的布局系统

另外,就不得不提到一些为了能让控件更易使用所需的准备。之前我们开发了PullToRefresh.UWP这一控件,并且通过NuGet包的形式放出。后来我们慢慢地注意到这一控件会使得Visual Studio中的XAML设计器无法正常工作,为此我们进行了一些探索,得出的经验就是:对于新编写的控件,需要考虑到XAML框架的布局问题。

XAML控件框架,由Measure和Arrange两个步骤贯穿始终,诸如SizeChanged等事件都是这两个步骤的产物。而VS的XAML设计器在分析开发者编写的XAML文件时,也是着重考虑这两个标准的布局流程的。至于Loaded,SizeChanged等等事件,如果控件编写者把布局操作置于其中(虽然方便,但实际上还会引发新一轮布局,可能影响性能),而且设计器有时会考虑不到,导致设计器表现异常(控件外观改变,甚至设计器崩溃)。

XAML的标准控件自不必说,设计器可以很好的解析它们的XAML布局(在generic.xaml中)以及它们提供的Measure和Arrange过程。这也是大家能在XAML设计器中看到Grid按我们的定义分割行列,StackPanel自动以栈式排布子项的原因。如果大家的新编写的控件着重于业务,比如完成一定的计算后更新文本显示,也无需担心这些。但是,如果控件更倾向于提供某种布局方案,就不能不考虑这一点了。所以考虑到有时部分实现会导致引用控件后,设计器工作不正常,编写控件时应尽量使用Measure/Arrange来编写复杂布局过程,而不是依赖布局事件。(当然简单的实现完全可以在事件中实现)

此外还有一个小技巧,就是Windows.ApplicationModel.DesignMode类[1]的DesignModeEnabled属性。顾名思义,它就是用来判断此时控件是工作在实际的应用里,还是VS的设计器里的。有时控件的布局实在是非常复杂,哎呀设计器总不正常搞不定,不妨对这一属性进行一下判断,为设计器提供简化的布局流程。

用NuGet打包控件

控件相关文件

一般情况下,一个metro控件会带上零零散散一堆文件,XAML布局啊code-behind啊,控件还分自定义和模板的……

为了让NuGet包能在引用者的项目里正常工作,需要按正确的结构组织编译出的种种控件文件。为了方便这一过程,Visual Studio提供了一个选项,在控件项目的属性——生成(Build)页中,勾选“Generate Library Layout”,如图:

这之后再build,就会在输出目录里组织出正确的包结构了。(当然需要在release也进行这一配置)

另外,需要特别注意的一点,就是*.rd.xml文件[2]。有时项目中会存在这一文件(在根目录\Properties文件夹下),并且在引用者项目处于release配置下,需要调用本地.NET native工具链时,可能会需要这一文件。如果不把它也放进包中,有时会导致引用者项目编译失败。这一文件需要特别处理,我们用NuGet打包章节再说。

用NuGet打包

打包的第一步就是获取NuGet程序:https://www.nuget.org/

同时,需要一个配置文件来说明当前的包信息,即nuspec配置文件。接下来本文用PullToRefresh.UWP这一控件包来举例,此例中配置文件命名为PullToRefresh.UWP.nuspec。其最基本的格式如下:

<?xml version="1.0"?>
<package >
<metadata>
<id>PullToRefresh.UWP</id>
<version>0.3.4</version>
<title>PullToRefresh.UWP</title>
<authors>MS-UAP</authors>
<owners>MS-UAP</owners>
<projectUrl>http://www.cnblogs.com/ms-uap/p/4814507.html</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Generic Pull Down to Refresh implementation for UWP.</description>
<copyright>Copyright 2015</copyright>
<tags>UWP XAML</tags>
</metadata>
</package>

当然还有别的办法创建这个文件,比如从程序集创建、直接和项目关联等等。这里我们采用最简单的方法,其他方法可以参见NuGet的帮助文档[3]。

然后我们需要在nuspec所在的文件夹里建立一些目录,用来表示NuGet包支持的平台:

.\lib\uap10.0 就表示UWP平台了。

然后把最初VS生成的文件拷贝到.\lib\uap10.0里。

对*.rd.xml的特别处理

首先需要在build出的项目名文件夹(.\lib\uap10.0\PullToRefresh.UWP)里建立一个Properties文件夹,并把rd.xml文件放到那里(如果有这个文件的话)。

全部完成后,待打包的文件 总的结构如下:

│  PullToRefresh.UWP.nuspec

└─lib
└─uap10.
│ PullToRefresh.UWP.pri
│ PullToRefresh.UWP.winmd

└─PullToRefresh.UWP
│ PullToRefresh.UWP.xr.xml

├─Properties
│ PullToRefresh.UWP.rd.xml (需要额外处理)

└─Themes
Generic.xbf

最后在nuspec所在的文件夹里,用命令行执行“nuget pack PullToRefresh.UWP.nuspec“,就会在当前目录生成一个NuGet包了!然后就可以在NuGet的网站上将其上传了。

了解NuGet目录

在此例中,我们只建立了一个lib文件夹。实际上,NuGet还在标准中支持build、ref、runtimes等目录,在这里为大家做一些简单介绍,为不同的应用场景起到一些启发:

  • Lib目录:最基本的目录,包含了引用者项目需要的程序集等等组件。
  • Build目录:用于存放MSBuild相关的配置文件,正常情况下应该是很少用到的。但有时可以利用这一特性实现一些特别的功能,比如编译时在输出窗口打印一下Hello World:)。
  • Ref目录:很特别的一个目录,用于为编译器提供公共接口信息,比较直观的说法就是能改变VS的智能提示,也能隐藏一些接口。如果没有这个目录的话,lib目录其实就充当了提供接口信息的角色,当然也提供了实现。一般来说完全用不到这个目录。
  • Runtimes目录:和CPU架构有关的就得放在这里了,如果是C++\CX开发的控件,我们就需要用到这一目录。Runtimes目录的下一级目录名必须是运行时标识,如win-x86或者win8-x86。具体的使用形式可以参考http://docs.nuget.org/create/uwp-create#runtimes

更多信息还是需要参见NuGet目录结构说明[4]。

P.S. 因为native的dll必须被拷贝到运行文件目录里去,除了通过runtimes目录自动完成复制,还能通过在build目录里添加MSBuild配置文件,比如通过这样的方式告诉编译器 平台相关的文件在哪:

<Target Name="xxx" BeforeTargets="ResolveAssemblyReferences">
<ItemGroup Condition=" '$(Platform)' == 'x86' or '$(Platform)' == 'x64' or '$(Platform)' == 'ARM'">
<Reference>
<HintPath>包含$(Platform)的路径</HintPath>
</Reference>
</ItemGroup>
</Target>

对于通常用C#编写的库,因为是托管代码,我们可以直接将其配置为生成AnyCPU类型的程序集或WinRT组件,并将编译出的文件直接置于lib\uap10.0\下。我们的PullToRefresh.UWP例子就是如此。

参考

[1] DesignMode类:https://msdn.microsoft.com/library/windows/apps/br224664

[2] Runtime Directives (rd.xml) 配置文件:https://msdn.microsoft.com/en-us/library/dn600639(v=vs.110).aspx

[3] NuGet创建包:http://docs.nuget.org/Create/Creating-and-Publishing-a-Package

[4] NuGet目录结构说明:http://docs.nuget.org/create/uwp-create#directory-structure

共享你的控件 -- 用NuGet包装自己的控件的更多相关文章

  1. Atitit. BigConfirmTips 控件 大数据量提示确认控件的原理and总结O9

    Atitit. BigConfirmTips 控件 大数据量提示确认控件的原理and总结O9 1. 主要的涉及的技术 1 2. 主要的流程 1 3. 调用法new confirmO9t(); 1 4. ...

  2. [转载]ExtJs4 笔记(8) Ext.slider 滚轴控件、 Ext.ProgressBar 进度条控件、 Ext.Editor 编辑控件

    作者:李盼(Lipan)出处:[Lipan] (http://www.cnblogs.com/lipan/)版权声明:本文的版权归作者与博客园共有.转载时须注明本文的详细链接,否则作者将保留追究其法律 ...

  3. Android控件之CheckBox(复选框控件)

    一.有两种状态: 选中状态(true).未选中状态(false) 二.属性 android:id = "@+id/checkbox" android:layout_width=&q ...

  4. Jquery 操作Html 控件 CheckBox、Radio、Select 控件 【转】http://www.cnblogs.com/lxblog/archive/2013/01/09/2853056.html

    Jquery 操作Html 控件 CheckBox.Radio.Select 控件   在使用 Javascript 编写前台脚本的时候,经常会操作 Html 控件,比如 checkbox.radio ...

  5. web页面动态加载UserControl,并调用用户控件中的方法来初始化控件

    1,HTML页 头部注册: <%@ Register Src="~/WorkLog/WorkLogNewV1/UserControl/CeShiBu.ascx" TagPre ...

  6. WPF 中动态创建、删除控件,注册控件名字,根据名字查找控件

    动态创建控件 1.容器控件.RegisterName("Name",要注册的控件)   //注册控件 2.容器控件.FindName("Name") as  控 ...

  7. 初步探讨WPF的ListView控件(涉及模板、查找子控件)

    本文结合模板的应用初步介绍ListView的应用 一.Xaml中如何建立数据资源 大部分数据都会来自于后台代码,如何Xaml同样的建立数据源呢?比如建立一个学生List: 首先引入命名空间: xmln ...

  8. ParentWindow属性及其一系列函数的作用——适合于那些不需要父控件管理内存释放的子控件

    TWinControl = class(TControl) property ParentWindow: HWnd read FParentWindow write SetParentWindow; ...

  9. 双缓冲绘图和窗口控件的绘制——ATL ActiveX 窗口控件生成向导绘制代码OnDraw的一个错误 .

    双缓冲绘图和窗口控件的绘制 ---ATL ActiveX 窗口控件生成向导绘制代码OnDraw的一个错误 cheungmine 我们通常使用ATL COM组件,生成一个带窗口的ActiveX控件,然后 ...

随机推荐

  1. 188. Best Time to Buy and Sell Stock IV leetcode解题笔记

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  2. mysql 主从不同步

    今天发现Mysql的主从数据库没有同步 先上Master库: mysql>show processlist; 查看下进程是否Sleep太多.发现很正常. show master status; ...

  3. GIL与线程互斥锁

    GIL 是解释器级别的锁,是限制只有一个原生线程运行,防止多个原生线程之间修改底层的共享数据.而线程互斥锁是防止多个线程同时修改python内存空间的共享数据.

  4. java 获取服务器 linux 服务器IP 信息

    public String getUnixLocalIp() { String ip = ""; try { Enumeration<?> e1 = (Enumerat ...

  5. Fiddler 工作原理

    Fiddler工作原理: 就在在客户端与服务器端创建一个代理服务器: 在开启Fiddler后,Fiddler会自动窜改浏览器的代理,例如我们打开Fiddler,打开IE浏览器--设置--Interne ...

  6. JSF中使用jquery拦截ajax请求

    jsf提供一个内置的jsf.ajax.request方法给我们使用,如果在jquery中使用,则需要做一些更改.  此处因为使用jquery,所以可以不必在控件中添加onclick方法了,可以给控件配 ...

  7. this、super关键字

    this关键字 this 关键字用来表示当前对象本身,或当前类的一个实例,通过 this 可以调用本对象的所有方法和属性. public class Demo{ public int x = 10; ...

  8. 「2014-2-23」Note on Preliminary Introduction to Distributed System

    今天读了几篇分布式相关的内容,记录一下.非经典论文,非系统化阅读,非严谨思考和总结.主要的着眼点在于分布式存储:好处是,跨越单台物理机器的计算和存储能力的限制,防止单点故障(single point ...

  9. C#匿名函数的坑

    在for循环中catch索引 for (int i = 0; i < n; i++) { foo(() =>{ if (i == x) //这里的i始终都是最后一个... { //bala ...

  10. 【dubbo】dubbo项目基本结构及provider构建

    dubbo项目基本结构如下,分别部署于不同服务器: 1.provider(接口API 实现) 2.consumer(web) 3.zookeeper 4.DB provider构建 1.api构建 i ...