- 摘要
主要是介绍协议HTTP、ISAPI、WSGI,以及Web Application Server(集成了Web Server和Application Server,比如:CherryPy库)。最后,介绍了基于WebDav库的应用在CherryPy和IIS环境下的运行示例。本文可以看作是一个CherryPy的背景知识,方便大家理解一些概念。
- HTTP
The Hypertext Transfer Protocol (HTTP) is an application protocol for distributed, collaborative, hypermedia information system.[1] HTTP is the foundation of data communication for the World Wide Web.
HTTP是一个网络协议,目标是:客户端给一个URL的请求,HTTP服务器返回一点东西给客户端。
- ISAPI
The Internet Server Application Programming Interface (ISAPI) is an N-tier API of Internet Information Services (IIS), Microsoft's collection of Windows-based web server services. The most prominent application of IIS and ISAPI is Microsoft's web server.
ISAPI是HTTP服务器的应用编程接口,用来支持HTTP服务器加载和调用的DLL。即客户端的URL请求最终会被调用到HTTP服务器的DLL中。
- WSGI
The Web Server Gateway Interface (WSGI /ˈwɪzˌɡiː/) defines a simple and universal interface between web servers and web applications or frameworks for the Python programming language.
WSGI用在Python中,可以认为是服务器的应用框架接口协议。有了它用户可以将网页应用独立出来,运行的时候接到任何支持WSGI接口协议的框架中即可。
WSGI的框架如上图所示,wsgiapp运行在wsgi container中,它通过middleware和webserver对接。wsgiapp需要用户自己定义,实现自己的处理逻辑,其它部分则有相应的库来实现,比如:CherryPy。
- WebDav
Web Distributed Authoring and Versioning (WebDAV) is an extension of the Hypertext Transfer Protocol (HTTP) that facilitates collaboration between users in editing and managing documents and files stored on World Wide Web servers. A working group of the Internet Engineering Task Force (IETF) defines WebDAV in RFC 4918.
WebDav扩展了HTTP网络协议,目的是用来支持客户端管理服务器上的文件,同时实现对文件的版本管理。
- Web Application Server
Web Application Server可以认为是一个服务器的应用模型。
- Web Server用来处理网络请求、维护连接等操作。
- Application Server则一方面用来管理Application,另一方面则针对Web Server传递过来的请求转发给相应的Application。
- Web Application则包含了处理请求的逻辑,并将返回请求对应的信息。
- CherryPy
CherryPy is an object-oriented web application framework using the Python programming language. It is designed for rapid development of web applications by wrapping the HTTP protocol but stays at a low level and does not offer much more than what is defined in RFC 2616.
CherryPy是基于Python的Web Application Server,它将Web Server和Application Server合并为同一的server。CherryPy是基于HTTP协议的,同时它内置的CherryPyWSGIServer模块可以支持WSGI接口协议。对CherryPy而言,Web Application只是一个函数、类、或者一个module。
- WsgiDAV
WsgiDAV is a generic WebDAV server written in Python and based on WSGI.
WsgiDAV是一个基于WSGI的服务器上的Python应用,实现了WebDAV协议。
- pywin32
pywin32基于Python,可以让Python代码调用windows的api。
- isapi-wsgi
isapi-wsgi基于Python,可以让WSGI接口协议嫁接到ISAPI上,从而支持WSGI应用。
- CherryPy + WsgiDAV
CherryPy本身就是一个支持WSGI的Server,所以可以将WsgiDAV运行在其中作为服务器的Server。下面是一个实例:
from tempfile import gettempdir from wsgidav.fs_dav_provider import FilesystemProvider from wsgidav.version import __version__ from wsgidav.wsgidav_app import DEFAULT_CONFIG, WsgiDAVApp __docformat__ = "reStructuredText" rootpath = r"D:\downloads\ocsync_test" provider = FilesystemProvider(rootpath) config = DEFAULT_CONFIG.copy() config.update({ "provider_mapping": {"/": provider}, "user_mapping": {}, "verbose": 1, "enable_loggers": [], "propsmanager": True, # True: use property_manager.PropertyManager "locksmanager": True, # True: use lock_manager.LockManager "domaincontroller": None, # None: domain_controller.WsgiDAVDomainController(user_mapping) }) app = WsgiDAVApp(config) # Following is from CherryPy from cherrypy import wsgiserver # Create WSGI server and start it. server = wsgiserver.CherryPyWSGIServer( ('192.168.4.54', 8080), app, server_name='server_name') server.start()程序的上半部分是基于WsgiDAV的用来定义一个Web Application, 下半部分是基于CherryPy,用来定义一个Web Application Server。直接运行该脚本即可。
- IIS + isapi-wsgi + WsgiDAV
首先,需要配置IIS,一个是根据isapi-wsgi的安装文档,设置兼容IIS6;另一个是要支持ISAPI。实例如下:
from tempfile import gettempdir from wsgidav.fs_dav_provider import FilesystemProvider from wsgidav.version import __version__ from wsgidav.wsgidav_app import DEFAULT_CONFIG, WsgiDAVApp __docformat__ = "reStructuredText" rootpath = r"D:\downloads\ocsync_test" provider = FilesystemProvider(rootpath) config = DEFAULT_CONFIG.copy() config.update({ "provider_mapping": {"/": provider}, "user_mapping": {}, "verbose": 1, "enable_loggers": [], "propsmanager": True, # True: use property_manager.PropertyManager "locksmanager": True, # True: use lock_manager.LockManager "domaincontroller": None, # None: domain_controller.WsgiDAVDomainController(user_mapping) }) app = WsgiDAVApp(config) import isapi_wsgi # The entry points for the ISAPI extension. def __ExtensionFactory__(): return isapi_wsgi.ISAPISimpleHandler(app) if __name__=='__main__': # If run from the command-line, install ourselves. from isapi.install import * params = ISAPIParameters() # Setup the virtual directories - this is a list of directories our # extension uses - in this case only 1. # Each extension has a "script map" - this is the mapping of ISAPI # extensions. sm = [ ScriptMapParams(Extension="*", Flags=0) ] # To serve from root, just set Name="/" vd = VirtualDirParameters(Server="My CherryPy", Name="/", Description = "ISAPI-WSGI ISAPISimpleHandler root Demo", ScriptMaps = sm, ScriptMapUpdate = "replace" ) params.VirtualDirs = [vd] HandleCommandLine(params)上半部分来自于WsgiDAV,同上一个实例。下半部分来自于isapi-wsgi,主要用来生成一个dll,供IIS调用,dll再调用到WsgiDAV应用中。要运行这个实例,首先要在IIS中创建名称为My CherryPy的站点,然后执行python demo.py install将WsgiDAV关联到/下,取消关联则使用python demo.py remove。(假设代码保存在demo.py中,同时命令要运行在管理员权限下)
- 附注
- 参考
[OpenGL ES 03]3D变换:模型,视图,投影与Viewport
罗朝辉 (http://blog.csdn.net/kesalin)
本文遵循“署名-非商业用途-保持一致”创作公用协议
本来打算直接写教程 04 的,但是想到3D 变换涉及的数学知识较多,往往是很多初学者的拦路虎(比如我自己)。再加上OpenGL ES 2.0 不再提供OpenGL ES 1.0中 3D 变换相关的一些重量级函数,如 glMatrixMode(GL_PROJECTION); glMatrixMode(GL_MODELVIEW); glLoadMatrixf; glMultMatrix 等,这些函数在 OpenGL ES 2.0 中均需要我们自己去实现。 如果不对线性代数与几何知识作一些简单介绍,恐怕不少人难以理解文中的一些步骤为什么要那么做。因此今天这一篇文章将放弃原定计划,先来介绍一些 3D 数学以及 3D 变换相关的知识。BTW,原定计划的代码示例已经写好了,有兴趣的同学可以先行浏览,代码放在这里,运行效果如下:
一,3D数学历史
我们都学过几何学,应该都知道欧几里得(公元前3世纪希腊数学家)这位几何学鼻祖,正是这位大牛创建了欧几里得几何学,他提出了基于 X,Y,Z 三轴的三维空间概念。到了17世纪,又出了位大牛笛卡尔,我们通常所说的笛卡尔坐标就是他的创造,笛卡尔坐标非常完美地将欧几里得几何学理论与代数学联系到一块。正是因为有了笛卡尔坐标,我们才能够用简单的矩阵(Matrix)来表示三维变换。但用矩阵来表示三维变换操作有一个无法解决的问题-万向节锁 。什么是万向节锁呢?简单地说就是两个轴旋转到同一个方向上去了,这两个轴平行了,因此就比原来少了一维(详情可参考这里)。过了一百多年,汉密尔顿(Sir William Rowan Hamilton)创建了四元数(quaternion)解决了因为旋转而导致万向节锁的问题,四元数还有其他用处,但在3D数学里主要是用来处理旋转问题。
好吧,或许你看得一头雾水,不要紧,你只要知道:用矩阵来表示3D变换,但矩阵在表示旋转时可能会导致万向节锁的问题,而使用四元数可以避免万向节锁就可以了。
二,矩阵变换
在前面提到可使用 Matrix 来表示三维变换操作,那么变换又是如何通过 Matrix 实现的呢?下面就来讲这个。在这里我推荐一本3D数学入门书籍:《3D数学基础:图形与游戏开发》
通常我们使用 4 维向量 (x, y, z, w) 表示在3D空间中的一个点,最后一维 w 表示齐次坐标。齐次坐标的含义是两条平行线在投影平面的无穷远处相交于一点,但在 Matrix 中没有表示无穷大,所以增加了齐次坐标这一维。你可以想象下,火车轨道的两条边在无限远处看起来就相交于一点,齐次坐标详细的介绍可以参考这篇文章。
矩阵运算规则:
1) 若矩阵 A 和 B 不是互逆矩阵,则不满足乘法交换律,即 A × B 不等于 B × A;
2) M × N 阶的矩阵只能和 N × O 阶的矩阵相乘,即 N 的阶数相等,结果为 M × O 阶的矩阵;
3) 矩阵 A × B 的运算过程是 A 的每一行依次乘以 B 的每一列作为结果矩阵中的一行;
4) 矩阵 A 的逆矩阵 B 满足 A × B = B × A = 单位矩阵。
5) 单位矩阵是对角线上的值为1,其余均为 0 的矩阵。单位矩阵不影响坐标变换(你可以将下面的3D变换矩阵换成单位矩阵来思考下)。
3D空间的物体投影到2D平面上时,就需要使用到齐次坐标,因此我们需要使用 4 × 4 的 Matrix 来表示变换。在编程语言中,这样的 Matrix 可用大小为 16 的一维数组或4 × 4 的二维数组来表示。由于矩阵乘法不满足乘法交换律,用数组表示 Matrix 又分为两种形式:行主序和列主序,它们在本质上是等价的,只不过是一个是右乘(行主序,矩阵放右边)和一个是左乘(列主序,矩阵放左边)。OpenGL 使用列主序矩阵,即列矩阵,因此我们总是倒过来算的(左乘矩阵,变换效果是按从右向左的顺序进行): 投影矩阵 × 视图矩阵 × 模型矩阵 × 3D位置。
4× 4列矩阵的数组表示:数字表示数组下标对应的行列位置:
那么
平移矩阵可表示为:
平移矩阵 × 列矩阵(a, b, c, 1) = 列矩阵(a + x, b + y, c + z, 1)。
缩放矩阵可表示为:
缩放矩阵 × 列矩阵(a, b, c, 1) = 列矩阵(a × sx, b × sy, c × sz, 1)。
绕 X 轴旋转的旋转矩阵可表示为:
绕 X 轴旋转的旋转矩阵 × 列矩阵(a, b, c, 1) = 列矩阵(a, b × cos(θ) - c × sin(θ), b × -sin(θ) + c × cos(θ), 1)。
绕 Y 轴旋转的旋转矩阵可表示为:
绕 Y 轴旋转的旋转矩阵 × 列矩阵(a, b, c, 1) = 列矩阵(a × cos(θ) - c × sin(θ), b , a × -sin(θ) + c × cos(θ), 1)。
绕 Z 轴旋转的旋转矩阵可表示为:
绕 Z 轴旋转的旋转矩阵 × 列矩阵(a, b, c, 1) = 列矩阵(a × cos(θ) - b × sin(θ), a × -sin(θ) + b × cos(θ), c, 1)。
三,OpenGL 中的实现
OpenGL 使用右手规则进行旋转,因此逆时针方向的选择是正角度的,而顺时针方向的旋转是负角度的。还记得中学学物理时候的右手规则么?忘记了的话,看下图:
注意:
前面说到矩阵乘法不满足乘法交换律,因此你对一个3D坐标先进行旋转,然后进行平移(平移矩阵 × 旋转矩阵 × 3D坐标);与先进行平移,然后进行旋转(旋转矩阵 × 平移矩阵 × 3D坐标)得到的效果是大为迥异的。如下图所示:
在第一种情况下,我们通常称旋转是在 local space 中进行,因为它是绕着物体自己的中心点进行的,而在后一种情况下的旋转通常称为是在 world space 中进行的。我们知道点是可以在坐标空间之间相互转换的,这是一个很重要的概念。OpenGL 中物体最初是在本地坐标空间中,然后转换到世界坐标空间,再到 camera 视图空间,再到投影空间,这一系列转换都是靠 matrix 计算来实现。
上面的这个过程在 OpenGL 及 OpenGL ES 1.0 中,对应的代码类似于:
glViewport (0, 0, (GLsizei) w, (GLsizei) h); a) glMatrixMode (GL_PROJECTION); b) glLoadIdentity (); glFrustum (-1.0, 1.0, -1.0, 1.0, 1.5, 20.0); c) glMatrixMode (GL_MODELVIEW); d) glClear (GL_COLOR_BUFFER_BIT); glColor3f (1.0, 1.0, 1.0); glLoadIdentity (); /* clear the matrix */ /* viewing transformation */ gluLookAt (0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); e) glScalef (1.0, 2.0, 1.0); /* modeling transformation */ f) glutWireCube (1.0); g) glFlush ();
说明:
a) 是用于viewport(视口)变换,viewport 变换发生在投影到2D 投影平面之后,该变换是将投影之后归一化的点映射到屏幕上一块区域内的坐标。视口变换的目的是指定投影之后图像在屏幕上显示的区域。如下示意图所示:
视口变换 glViewport(x, y, width, height); x,y 是投影平面描绘在屏幕或窗口上的起始位置(注意屏幕坐标以左上方为原点),width和height是以像素为单位,指投影平面在屏幕上描绘的区域大小。如果投影平面的宽高比,与width/height比不相同(如上面的右图),那么描绘的场景就会扭曲。
从裁剪到屏幕的整个过程如下图所示,w 就是前面提到的齐次坐标那一维,从 Clip Space 到 Normalized Device Space 就是投影规范化的过程,从 Normalized Device Space 到 Window Space 就是 viewport 变换过程。
该转换内部计算公式为:
(xw, yw)是屏幕坐标,(x, y, width, height)是传入的参数,(xnd, ynd)是投影之后经归一化之后的点(上图中 Normalized Device Space 空间的点)。因此 viewport 变换就是将投影之后归一化的点转换为真正可用于在屏幕上进行渲染的屏幕坐标;
b) 是说明下面的 matrix 是用于投影变换的,在本例中,是通过语句 c) glFrustum 来设置透视投影变换的。投影变换有两种:正交投影和透视投影,后面会有详细介绍;
d) 是说明下面的 matrix 是用于模型视图变换,注意,OpenGL 和 OpenGL ES 都将模型变换与视图变换结合在一起,而不是分开为两个,这是因为模型变换等价于视图变换的逆变换。视图变换是将物体转换到观察者(一般称之为 camera)的视线空间中。你可以想象一下,照相时,你可以:A)照相机不懂,旋转自己的头找个侧面像,也可以B)自己不动,照相机旋转一定的角度来达到同样的效果。下面的两幅图分别描述了情形A)和情形B):
情形A):旋转物体,相机不动
情形B):旋转相机,物体不动
在 OpenGL 中,我们在设置场景(scene)的时候通常是采取情形B)的做法,因此在语句 e) 处,我们设置相机的位置和朝向,来设定视图变换,之后的语句 f) glScale 是设定在模型变换的,最后语句 g) 在本地空间描绘物体。
注意
写 OpenGL 代码时从前到后的顺序依次是:设定 viewport(视口变换),设定投影变换,设定视图变换,设定模型变换,在本地坐标空间描绘物体。而在前面为了便于理解做介绍时,说的顺序是OpenGL 中物体最初是在本地坐标空间中,然后转换到世界坐标空间,再到 camera 视图空间,再到投影空间。由于模型变换包括了本地空间变换到世界坐标空间,所以我们理解3D 变换是一个顺序,而真正写代码时则是以相反的顺序进行的,如果从左乘矩阵这点上去理解就很容易明白为什么会是反序的。
有了上面 3D 变换的整体概念,下面来详细说说投影变换与视图变换。
四,投影变换
投影变换的目的是确定 3D 空间的物体如何投影到 2D 平面上,从而形成2D图像,这些 2D 图像再经视口变换就被渲染到屏幕上。前面提到投影变换有两种:正交投影和透视投影。透视投影用的比较广泛,它与真实世界更相近:近处的物体看起来要比远处的物体大;而正交投影没有这个效果,正交投影通常用于CAD或建筑设计。下面是正交投影与透视投影效果示意图:
正交投影 透视投影
透视投影可以通过两种方式来表述,OpenGL 及 OpenGL ES 1.0 提供其中一种: glFrustum,而 glut 辅助库提供了另外一种:gluPerspective。它们本质上是相同的,只不过是不同的表述而已:
视锥体/视景体:
glFrustum(left, right, bottom, top, zNear, zFar);
left,right, bootom,top 定义了 near 裁剪面大小,而 zNear 和 zFar 定义了从 Camera/Viewer 到远近两个裁剪面的距离(注意这两个距离都是正值)。由这六个参数可以定义出六个裁剪面构成的锥体,这个锥体通常被称之为视锥体或视景体。只有在这个锥体内的物体才是可以见的,不在这个锥体内的物体就相当于不再视线范围内,因而会被裁减掉,OpenGL 不会这些物体进行渲染。
由于 OpenGL ES 2.0 不提供此函数,因此我们需要自己实现该函数。其计算公式如下:
假设:l = left, r = right, b = bottom, t = top, n = zNear, f = zFar,有
透视图:
gluPerspective(fovy, aspect, zNear, zFar);
fovy 定义了 camera 在 y 方向上的视线角度(介于 0 ~ 180 之间),aspect 定义了近裁剪面的宽高比 aspect = w/h,而 zNear 和 zFar 定义了从 Camera/Viewer 到远近两个裁剪面的距离(注意这两个距离都是正值)。这四个参数同样也定义了一个视锥体。
在 OpenGL ES 2.0 中,我们也需要自己实现该函数。我们可以通过三角公式 tan(fovy/2) = (h / 2)/zNear 计算出 h ,然后再根据 w = h * aspect 计算出 w,这样就可以得到 left, right, top, bottom, zNear, zFar 六个参数,代入在介绍视锥体时提到的公式即可。
正交投影在 OpenGL 及 OpenGL ES 1.0 中是由 glOrtho 来提供的,我们可以把正交投影看成是透视投影的特殊形式:即近裁剪面与远裁剪面除了Z 位置外完全相同,因此物体始终保持一致的大小,即便是在远处看上去也不会变小。
glOrtho(left, right, bottom, top, zNear, zFar);
left,right, bootom,top 定义了 near 裁剪面大小,而 zNear 和 zFar 定义了从 Camera/Viewer 到远近两个裁剪面的距离(注意这两个距离都是正值)。
假设:xmax = right, xmin = left, ymax = top, ymin = bottom, zmax = far, zmin = near,正交投影的计算可分为两步:首先平移到视锥体的中心,然后缩放。
平移矩阵:(图中的2min 应为 zmin)
缩放矩阵:
正交投影矩阵 R = S × T:
五,视图变换
视图变换的目的是为了让我们能观察到某个角度的场景(从观察者的角度来说)或者说是为了将物体从世界坐标转换到相机视线所在视图空间中来(从3D物体角度来说)。这可以通过设定观察者的位置和朝向来实现的或对物体进行3D变换来实现,通常前面一种方式来实现(即设定观察者的位置与朝向)。如下图所示,xyz坐标轴表示的是世界坐标,蓝白色区域为视图空间,视图变换就是要将长方体从世界空间中转换到视图空间的坐标体系中去,然后再投影规范化,然后再经 viewport 转换映射到屏幕上渲染出来。
在 OpenGL 中,我们可以通过工具库提供的 gluLookAt 这个函数来实现此功能。该函数的原型为:
gluLookAt(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz);
eye 表示 camera/viewer 的位置, center 表示相机或眼睛的焦点(它与 eye 共同来决定 eye 的朝向),而 up 表示 eye 的正上方向,注意 up 只表示方向,与大小无关。通过调用此函数,就能够设定观察的场景,在这个场景中的物体就会被 OpenGL 处理。在 OpenGL 中,eye 的默认位置是在原点,指向 Z 轴的负方向(屏幕往里),up 方向为 Y 轴的正方向。在接下来的教程 04 中,使用的就是这个默认设置。
OpenGL ES 2.0 也没有提供该函数,glulookat 的内部实现其实就是先旋转到与观察者视线相同的方向,然后再平移到观察者所在的位置。其实现伪码如下:
Matrix4 GetLookAtMatrix(Vector3 eye, Vector3 at, Vector3 up){ Vector3 forward, side; forward = at - eye; normalize(forward); side = cross(forward, up); normalize(side); up = cross(side, forward); Matrix4 res = Matrix4( side.x, up.x, -forward.x, 0, side.y, up.y, -forward.y, 0, side.z, up.z, -forward.z, 0, 0, 0, 0, 1); translate(res, Vector3(0 - eye)); return res; }
上面代码中的 cross 是叉积,normalize 是规范化,Matrix4 是列主序,translate 是平移。
六,后记
3D 变换是对初学者来说是比较困难的,我尽量写得明白点,但效果如何就不得而知了。写这一篇花了我不少时间,但对四元数和万向节锁也只是提及而已,未详细介绍,以后再单独介绍吧。Nate Robin 写了一个3D 变换的可视化教程工具,对于理解投影,视图,模型变换非常有帮助,强烈建议下载运行该程序,并调整相关参数看看效果。下面传张截图以诱惑你去下载:点此进入下载页面(Windows 和 Mac 版本都有)
七,引用
1,《OpenGL 编程指南》
2,《3D数学基础:图形与游戏开发》
3,http://cse.csusb.edu/tong/courses/cs420/notes/viewing2.php
4,http://www.mesa3d.org/
5,http://db-in.com/blog/2011/04/cameras-on-opengl-es-2-x/
前面我们讲了如何用文件选择器选取文件,其实,还有一个目录选择器,不过,我先放着
不说,因为它用起来和选取文件是一个道理,大家不妨尝试一下举一反六。
今天,我们说一说如何从“人脉”中选取联系人信息,说白了,也和文件选取的方法基本
一样,后面做实例演练的时候,大家会看到的。
在开始实践之前,我们需要准备一些数据,即联系人,如果没有,你可以随便加几个来测试。
从可爱的“开始”屏幕中找到“人脉”应用,并启动。
如果是第一次使用,会要求你输入MS帐号获取联系人,我们就按提示操作,如果你没有
Live ID,就随便申请一个呗。
验证并登陆成功后,你就会看到你的好友们了。
如果你还没有联系人,调出工具条,点击“新建”按钮。
至于联系人信息,我们目前只是测试,所以,随例填就行了,如下图所示。
填完后,点击“保存”,这样就创建了联系人记录。为了方便测试,你可以多建N条记录。
好了,有了数据,我们就可以动手练习了。
1、启动美丽的VS2012,新建一个app应用。
2、打开MainPage.xaml,界面这按下面的XAML布局即可,简便易读嘛。
<Page
x:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:myAppExample"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="15,27,0,22">
<Button Content="选取联系人" Click="onPick"/>
</StackPanel>
<ListView Grid.Row="1" Margin="25" x:Name="lvContacts" ScrollViewer.
HorizontalScrollMode="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto">
<ListView.ItemsPanel><ItemsPanelTemplate>
<WrapGrid Orientation="Vertical" MaximumRowsOrColumns="5" ItemWidth="300" VerticalChildrenAlignment="Center"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Grid>
</Page>
3、在XAML文档中,找到Button的Click事件处理程序,从弹出的菜单中选择
“定位到事件处理程序”。
后台的处理代码,如下。
a、记得引入命名空间Windows.ApplicationModel.Contacts,我们用到的ContactPicker类就在
该命名空间下;
b、通过调用PickMultipleContactsAsync方法,可以一次性选择多位联系人,它是一个异步方法
,记得加上等待关键字;
c、每一位联系人的信息,由一个ContactInformation实例表示,因为通过foreach循环可以取出
所选择的联系人信息,其中Name属性表示不联系人的名字,本例我们需要取得联系人的手机
号码,因而我们访问PhoneNumbers属性,由于一位联系人可能拥用N个手机号码(比如工作
用的,家庭用的,泡妞专用的),所以,无论是PhoneNumbers属性也好,Emails属性也罢,
都是一个ContactField列表,而每个ContactField就代表一个字段信息,重要的属性我们一般
会读取Name和Value,比如:
phoneno : 13521103823email : abcd@126.com
Name就是phoneno,Value就是13521103823。
现在,我们可以运行应用程序了。