随着 Windows 10 发布的,未来 Windows 平台都是统一开发模型,可以只写一个 Appx 包,就可以同时部署到
Windows/ Windowsw Phone/ Tablet /xbox ..平台上了,我们几个 Windows 组的同事也是摩拳擦掌,希望写一
个用户体验很好的客户端。
看了一下 MSDN 最新发布的文档和视频教程,这里作为笔记,大概总结了一下在 Windows 10上,针对 Windows 8.1
和 WP 上的一些技术更新。
贴一张视频里面聚合的路线图:
文档内容有点长,下面截取了几处要点翻译了一下:
1)通过 Windows Universal Platform(UAP)统一的 API (Windows Runtime)和一些响应式的 UI 控件,
可以让你只写一个 app ,便可以安装到各种设备上。
2)PC 运行的是桌面操作系统,基于传统的桌面家族。手机、平板等设备,运行的是 mobile 操作系统,基于的是移动
设备家族。
3) Windows 10 提供了新的 universal 控件,导航面板 和开发工具来帮助你面向多种设备进行开发。例如,你可以利用
多种不同屏幕的分辨率来适配你的手机或者电脑的 UI。
4)Windows Phone 上的 Pivot 控件,现在提供给了 universal 设备上
5)在 XAML 页面可以使用 StateTriggers 来触发页面的 visual state 变换。它可以通过窗口尺寸的变化,来直接改变布局、元素
的属性,而不需要在 C# 页面写逻辑进行 VisualStateManager 进行管理。 (类似于 Web 上 CSS 中的 @media ,来实现响应式布局)
6) 一个新的 UI 共享代码的方式,XAML 文件可以共享一个单独的 code-behind 文件了。
7)在选择目标设备的SDK 时,可以直接通过 “项目” -> “引用” 的方式直接添加(与引用类库类似)。添加目标设备的 SDK(选择 Project > Add Reference >
Universal App Platform > Extensions 添加相应的 SDK)。
8)使用这个方法 ApiInformation.IsApiContractPresent (Windows.Foundation.Metadata.ApiInformation class)检测 api 是否支持,
不仅仅是使用条件编译了。因为有的 api 只有个别设备可以使用,例如,手机上的相机实体按键 api。
下面一些内容翻译自视频教程里面的 ppt。
1、Win 8.1 工程 -> Win 10 工程的调整
1) App 生命周期、后台任务、Tiles 和 toasts 完全一样
2) UAP APIs 是 Windows 8.1 WinRT APIs 的超集 (UAP : universal app platform)
3) 可以使用适应性代码(adaptive code)判断运行的平台,以替换预编译语句:
例如,之前使用:
#if WINDOWS_PHONE_APP // wp 平台的 api 调用 #elif WINDOWS_APP // win pad 平台的 api 调用 #endif
可以替换为 ApiInformation 类进行判断:
// 判断是否是具有实体按键(Back键、Camera 键..) if(Windows.Foundation.Metadata.Apilnformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons")) { // Windows phone 的相关 api }
// Windows.Foundation.Metadata.ApiInformation 类提供的静态方法:
// IsApiContractPresent // IsEnumNamedValuePresent // IsEventPresent // IsMethodPresent // IsPropertyPresent // IsReadOnlyPropertyPresent // IsTypePresent // IsWriteablePropertyPresent
4) 一些 APIs 被弃用了( 比如 phone 8.1 的文件选择 …AndContinue APIs)
5) 桌面右侧的 Charms 被移出了,所以 app 必须提供 UI 来加载 Settings、Share 和 Search。
相关代码逻辑和之前完全一样,不需要修改
5) 替换一些有的工程没有定义的系统样式:比如: PhoneAccentBrush
6) 创建一个响应式的 UI:
Phone/narrow view:
small landscape view:
large landscape view:
8) 添加了新控件:Relative Panel、SplitView(Splitview.Content 的属性有意设置为 Frame)
就像 Windows10 上面的 “计算器”,就是 SplitView控件。在单击左上角的 “汉堡包” 按钮,弹出切换菜单:
Win pad 和 win phone 商店里的 Xbox One SmartGlass 猜测是一个 UAP 包了,在 phone 上的效果:
2、编译指令
// C# Syntax #if WINDOWS_PHONE_APP Windows.Phone.UI.Input.HardwareButtons.BackPressed += this.HardwareButtons_BackPressed; #endif
// C++ Syntax #if WINAPI_FAMILY==WINAPI_FAMILY_PHONE_APP
_backPressedEventToken = HardwareButtons::BackPressed += ref new EventHandler<BackPressedEventArgs^> (this,&NavigationHelper::HardwareButton_BackPressed); #endif
3、支持的平台的 SDK,可以直接通过项目引用进行添加:
4、Segoe MDL2 字体图标资源,更丰富的图标字体可以使用:
前段时间,写过图标资源的文章: 02、Universal app 中按钮图标使用
5、Visual State 可以使用 setters 和 triggers 直接在 XAML 中进行样式的属性的赋值。类似于
Web 上的 CSS媒体查询语法(比如,@media (min-800px) and (max-1200px) { ... })。
从而不需要再 C# 页面通过 VisualStateManager 进行视图状态的切换。例如下面,当窗口大于 600px 时,
StackPanel 对象为横向布局:
<VisualState x:Name="wideState"> <VisualState.Setters> <Setter Target="myPanel.Orientation" Value="Horizontal" /> </VisualState.Setters> <VisualState.StateTriggers> <AdaptiveTrigger MinWindowWidth="600"/> </VisualState.StateTriggers> </VisualState>
触发器的参数(Trigger types):
MinWindowWidth
MinWindowHeight
6、App 与 App 的通信:
1)在 Windows 8.1 时:URI 和 Protocol Activation/Share Contract
2)在 Windows10 UAP:
a) 启动一个指定的 app :
var options = new LauncherOptions(); options.TargetApplicationPackageFamilyName = "24919.InstapaperIt";
var launchUri = new Uri("instapaper:?AddUrl=http%3A%2F%2Fbing.com"); await Launcher.LaunchUriAsync(launchUri, options);
b) 向另一个 app 发送数据;通过向另一个app 发送一个文件 token 的方式,以
向另一个 app 传递自己的文件:
var options = new LauncherOptions(); options.TargetApplicationPackageFamilyName = "24919.InstapaperIt"; var token = SharedStorageAccessManager.AddFile(gpxFile); ValueSet inputData = new ValueSet(); inputData.Add("Token", token); var launchUri = new Uri("instapaper:?AddUrl=http%3A%2F%2Fbing.com"); await Launcher.LaunchUriAsync(launchUri, options, inputData);
c) Launch for Results:启动另一个 app,并获得其返回结果:
// 一个 app var options = new LauncherOptions(); options.TargetApplicationPackageFamilyName = "24919.Instap"; var launchUri = new Uri("instapaper:?AddUrl=http%3A%2F%2Fbing.com"); await Launcher.LaunchUriForResultsAsync(launchUri, options, data); // 另一个 app,把结果存到 ValueSet 字典中,返回 var resultData = new ValueSet(); resultData.Add("Result", value); operation.ProtocolForResultsOperation.ReportCompleted(resultData);
d) 判断当前设备上是否有指定 app (Query URI Support. Discover if app already installed to handle a Uri):
// 判断当前设备上,是否有 app 声明了instapaper 启动协议名称 var queryUri = new Uri("instapaper:"); await Launcher.QueryUriSupportAsync(queryUri, LaunchUriType.LaunchUri); // 判断当前设备上,是否有 app 声明了instapaper 启动协议名称,并且它的 pfn 为 "24919.InstapaperIt" 的指定app var queryUri = new Uri("instapaper:"); string packageFamilyName = "24919.InstapaperIt"; await Launcher.QueryUriSupportAsync(queryUri, LaunchUriType.LaunchUriForResults, packageFamilyName);
e) 来自同一个发布者(同一个开发者账号)的多个 app,可 Share 同一个存储文件夹 和设置(files and settings)。
需要在 app 的清单件中声明一个子文件夹,当系统安装 app 时,会自动创建的:
<Package> <Extensions> <Extension Category="windows.publisherCacheFolder"> <PublisherCacheFolder> <Folder Name="folder1"> </PublisherCacheFolder> </Extension> </Extensions> </Package>
在 app 中获取这个文件夹:
// 获取这个共享的文件夹 Windows.Storage.ApplicationData.Current.GetPublisherCacheFolder("folder1"); // 清空共享文件夹 Windows.Storage.ApplicationData.Current.ClearPublisherCacheFolderAsync();
e) App Services:通过后台任务的方式,一个app 可以作为一个本地轻量级的 Web Service
供其它 app 调用。
7、Drag 和 drop
在以前的 XAML 上,拖、拽操作比较局限,只有特定控件(GridView、ListView)有拖拽事件:
<GridView AllowDrop="true" DragEnter="contactGridView_DragEnter" DragLeave="contactGridView_DragLeave" DragOver="contactView_DragOver" Drop="contactGridView_Drop"/>
在 UAP 上添加了新的拖、拽 API,可以实现 app 之间的拖放,比如:
1)ListViewBase.DragItemsCompleted
2)UIElement.DragAsync()
3)扩展的 DragEventArgs 参数
8、编译时的数据绑定
XAML 中的 Data Binding 会影响性能,动态类型和反射是部分原因。解决方案是编译时(Compile-time binding)
的绑定。优点是全面快速的实例化,高效的 change 侦测和 UI 更新,编译时错误(Compile-time errors)
<!--示例--> <ListView ItemsSource="{x:Bind Groups}" > <ListView.ItemTemplate> <DataTemplate TargetType="data:BookItem" > <TextBlock Text="{x:Bind Book.Title}" /> </DataTemplate> </ListView.ItemTemplate> </ListView>
9、Transform 3D, 很像 CSS 中的 “preserve-3d”
Transform 3D 不会影响子元素的 transforms
1)现有的变换属性(Composite transform):
Translate Transform (move)
Scale Transform (size)
Rotate Transform (angle)
Skew Transform (shape)
Render Origin
利用 Projection 属性(Rotate X/Y/Z) 模拟 3D 效果
2) UAP 中添加了新属性 UIElement.Transform3D:
容器:
Container.PerspectiveTransform3D
Depth
OffsetX
OffsetY
子元素:
Child.CompositeTransform3D
Rotation
Scale
Translate
<Grid Width="300" Height="200"> <Grid.Transform3D> <PerspectiveTransform3D OffsetX="-150" OffsetY="-100" Depth="1000" /> </Grid.Transform3D> <Grid Background="Red"> <Grid.Transform3D> <CompositeTransform3D RotationY="30" TranslateZ="1"/> </Grid.Transform3D> </Grid> <Grid Background="Blue"> <Grid.Transform3D> <CompositeTransform3D RotationX="-30" TranslateZ="-1"/> </Grid.Transform3D> </Grid> </Grid>
10、 App Services:
1) UAP 提供了很多 API 来使 app 之间互相通信:
Windows.ApplicationModel.Contacts
Windows.ApplicationModel.Email
Windows.System.Launcher.LaunchUriAsync 启动h settings, maps, store 等…
其它…
2)UAP 允许 app 之间互相通信:
· Uri 关联,使用 LaunchUriAsync 启动
· File 关联,使用 LaunchFileAsync 启动
· Launch 并获取结果,使用 LaunchUriForResultsAsync
· App Services:用来作为灵活、轻量级的,类似于 web REST 服务:
a、简单的请求、响应消息 api
b、以 string-keyed 形式(ValueSet 类型) 数据包传递
c、易于使用和多个不同的载荷
3)App Services – Client 示例:
AppServiceConnection connection = new AppServiceConnection(); connection.AppServiceName = "microsoftDX-appservicesdemo"; connection.PackageFamilyName = "24919ArunjeetSingh.InstapaperIt"; AppServiceConnectionStatus connectionStatus = await connection.OpenAsync(); if (connectionStatus == AppServiceConnectionStatus.Success) { //向服务端发送数据 var message = new ValueSet(); message.Add("Command", "CalcSum"); message.Add("Value1", Int32.Parse(Value1.Text)); message.Add("Value2", Int32.Parse(Value2.Text)); // 发送消息并等待响应 AppServiceResponse response = await connection.SendMessageAsync(message); if (response.Status == AppServiceResponseStatus.Success) { int sum = (int)response.Message["Result"]; new MessageDialog("Result=" + sum).ShowAsync(); } } else { //如果没有 app service 端,则指导用户下载 app service 端 }
App Services – Service:
namespace AppServicesDemoTask { // 需要继承 IBackgroundTask 接口 public sealed class AppServiceTask : IBackgroundTask { private static BackgroundTaskDeferral _serviceDeferral; public void Run(IBackgroundTaskInstance taskInstance) { // 注册当前后台任务的 取消事件 taskInstance.Canceled += TaskInstance_Canceled; // 获得任务实例的 deferal 对象 _serviceDeferral = taskInstance.GetDeferral(); var appService = taskInstance.TriggerDetails as AppServiceTriggerDetails; if (appService.Name == "microsoftDX-appservicesdemo") { // 可以添加调用 app 的合法性验证 ValidateCaller(appService.CallerPackageFamilyName) ?? appService.AppServiceConnection.RequestReceived += RequestReceived; } } ... private async void RequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) { var message = args.Request.Message; // 服务端使用 Command 参数,作为 client 调用的操作 string command = message["Command"] as string; switch (command) { case "DoIt": { var messageDeferral = args.GetDeferral(); int value1 = (int)message["Value1"]; // ... 做一些处理 //向调用者返回结果 var returnMessage = new ValueSet(); returnMessage.Add("Result", result); var responseStatus = await args.Request.SendResponseAsync(returnMessage); messageDeferral.Complete(); break; } case "Quit": { // 服务端被要求退出,则调用deferral 对象的 Complete 方法,系统终止 Service _serviceDeferral.Complete(); break; } } }
4) 在清单文件中声明 App Service:
<?xml version="1.0" encoding="utf-8"?> <Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"> <Applications> <Application Id="App“> <Extensions> <uap:Extension Category="windows.appService“ EntryPoint="AppServicesDemoTask.AppServiceTask"> <uap:AppService Name="microsoftDX-appservicesdemo" /></uap:Extension> </Extensions> </Application> </Applications> <Capabilities> <Capability Name="internetClient" /> </Capabilities> </Package>
5)双向通信 :Client 和 server 都可以发送和接收消息
Client 和 server 可以保持双向通信的 channel 打开着。Client 端可以给自己
AppServiceConnection 实例注册一个 RequestReceived 事件
AppServiceConnectionStatus connectionStatus = await connection.OpenAsync(); if (connectionStatus == AppServiceConnectionStatus.Success) { connection.RequestReceived += OnRequestReceived; }
6)在调试的时候可以通过 Package.Current.Id.FamilyName 属性获得包的 PFN:
7)App Service 生命周期:
· Server 端的后台任务通过设置 AppServiceTrigger 条件启动
· Client 端可以通过调用 AppServiceConnection 对象的 dispose方法,或者发送指示命令来关闭 Service 端
· 如果 Client 端被挂起,则相应的 Service app 会被关闭
· 当系统资源紧张会导致 Launch 失败或者 Service 被关闭:服务端获得 AppServiceConnectionStatus.ResourcesNotAvailable消息;
当发送消息时获得 AppServiceResponseStatus.ResourceLimitsExceeded
11、消息中心 Action Center Management APIs:
1)管理 app 的通知,开发者可以:
· 移出一个或多个通知
· 给 notification 通知添加标签和分组
· 替换为一个新的 notification
· 设置一个 notification 的过期时间
· 发送一个 “Ghost Toast” notification(只显示在通知中心,但不弹给用户)
2)每个 app 最多在通知中心有 20条通知。最长保存7天(或者更短)
用户可以:
· 追踪(单击)一个通知(从通知中心移出)
· 移出一组通知
· 移出所有通知
3)后台任务触发器(ToastNotificationHistoryChangedTrigger)
当用户从消息中心解除一个 notification 或者一个 app 添加、移出、替换
一个notification 时触发。
使用这个触发器任务,app 可以保持 Toast 、Tile 消息通知的一致性。比如,
用户移出了消息中心的 通知时,tile 上的相关消息也须移出:
// 使用 ToastNotificationHistoryChangedTrigger 触发器的后台任务 public sealed class ActionCenterChangedTask: IBackgroundTask { public void Run(IBackgroundTaskInstance taskInstance) { var toasts = ToastNotificationManager.History.GetHistory(); if (toasts != null) { var count = toasts.Count(); if (count== 0) { BadgeUpdateManager.CreateBadgeUpdaterForApplication().Clear(); } else { XmlDocument badgeXml = BadgeUpdateManager.GetTemplateContent(BadgeTemplateType.BadgeNumber); XmlElement badgeElement = (XmlElement)badgeXml.SelectSingleNode("/badge"); badgeElement.SetAttribute("value", count.ToString()); BadgeNotification badge = new BadgeNotification(badgeXml); BadgeUpdateManager.CreateBadgeUpdaterForApplication().Update(badge); } } } }
12、Web 平台(Sparta 浏览器)
有关 Web 更新有很多,具体视频上讲的很详细。这里只列出一点。
从 Windows 8 开始,已经可以使用 html5/js 的方式开发 native app 了,并且不是通过 WebView 控件,
而是直接通过 JS 调用 Windows Runtime 的 api,但是不允许从 appx 包外的 JS 调用。到了 Windows 10
的 UAP,则去掉了这个限制,甚至可以通过在 app 的清单文件中,指定服务器端的地址,直接下载到本地执行逻辑,
调用 runtime 的 api。通过这种方式,很容易实现各种功能扩展。
清单文件添加服务器端的地址:
<uap:ApplicationContentUriRules> <uap:Rule Type="include" WindowsRuntimeAccess="all" Match="https://rjs.azurewebsites.net/" /> <uap:Rule Type="include" WindowsRuntimeAccess="allowForWebOnly" Match="https://*.facebook.com/" /> <uap:Rule Type="include" WindowsRuntimeAccess="none" Match="http://bing.com/" /> <uap:Rule Type="include" Match="https://*.microsoft.com/" /> </uap:ApplicationContentUriRules>
W3C 标准 Manifest for Web Apps:http://w3c.github.io/manifest/