在Windows Phone 7和Windows 8 Metro上编译相同的代码(译文)
By S.F.
本文链接 https://www.kyfws.com/news/compiling-same-code-on-windows-phone-7-and-windows/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 12 分钟阅读 - 5542 个词 阅读量 0在Windows Phone 7和Windows 8 Metro上编译相同的代码(译文)
原文地址:https://www.codeproject.com/Articles/368982/Compiling-Same-Code-on-Windows-Phone-7-and-Windows
原文作者:Joel Ivory Johnson
译文由本站翻译
前言
下载代码(1.14 MB)我正在考虑如何编写适用于Windows Phone 7和Windows 8(Metro)的代码.从理论上讲,可以使用的某些技术是众所周知的.但是我想尝试一下.因此,我决定在一段废弃的代码中尝试一些事情.
我在考虑如何编写可在Windows Phone 7和Windows 8(Metro)上运行的代码.从理论上讲,可以使用的某些技术是众所周知的.但是我想尝试一下.因此,我决定在一段废弃的代码中尝试一些事情.这使我可以自由地进行实验,而不必担心未选择"最佳"方式的长期后果.对于我的废弃应用程序,我决定制作一个可以获取某人的Facebook朋友列表并将其显示在屏幕上的应用程序.因此,此代码将必然需要针对Facebook验证用户身份.如果您以前没有使用过Facebook API,请不要担心太多.您仍然应该能够跟进,因为这并未深入研究可用的内容. 我设法使用了一些技巧来制作Windows Metro版本的RestSharp.我将在另一篇文章中讨论.
注册您的申请
在开始进行任何编码之前,您需要在Facebook开发人员门户中注册新的Facebook应用程序.这也意味着您需要拥有一个Facebook帐户.因为我不想打扰我的同事在Feed中测试项目,所以我还创建了[secondatyy facebook帐户](http://www.facebook.com/profile.php?id=100003755834501http://www.facebook. com/profile.php?id =100003755834501)进行测试(这是给我的猫的,如果您热衷于猫的图片,请随时与朋友联系).该门户网站位于developer.facebook.com.单击屏幕顶部的菜单项"应用程序"将带您到一个页面,该页面显示您所有已注册的应用程序(如果有),并使您可以注册更多.如果这是您第一次使用门户,则将没有任何应用程序.我将引导您完成注册应用程序.我只会在必要时吸引您注意其他选项.我没有提到的选项可以保留其默认状态(很可能为空白). 点击"创建新应用"按钮.您将看到一个对话框提示,您需要在其中输入正在创建的应用程序的名称.一旦停止键入,该对话框将自动验证您输入的名称,并通知您是否需要进行拼音.一旦获得对话框认为有效的名称,请单击"继续"按钮.
下一个对话框是验证码.我真的不喜欢验证码.虽然我知道存在这些对话框是为了确保机器人没有注册新的应用程序,但我遇到的问题是,我会多次错误地输入错误信息(例如:字符是小写字母" L",大写字母" L"还是大写字母" I"?).
通过验证码后,您将有机会为应用程序指定其他详细信息,并有机会更改应用程序的图标和类别.为"选择应用程序如何与Facebook集成"选择哪个选项可能并不明显.可用选项均未包含您将与Windows Phone或Metro应用程序自动关联的描述.选择"网站".在开发过程中,指定的网站是否存在并不重要.但是您将需要在理论上处理登录信息的页面上输入地址.输入此信息后,选择"保存更改".此页面上有三部分信息,您稍后将在其中将它们复制到应用程序中:App ID,App Secret和站点URL.使此页面保持打开状态,以便您以后参考时可以复制该信息.
现在制作您的移动应用程序.在撰写本文时,您需要将Visual 2010用于Windows Phone应用程序,将Visual Studio 11用于Windows Metro应用程序.大部分操作将非常相似,因此我可以并行讨论要遵循的程序,并注意偶尔出现的差异.
创建新的应用程序项目
我创建了Windows Phone的Silverlight应用程序和Windows 8 Metro项目. T here将是重要的代码块,这两者之间是相同的项目.发生这种情况时,我将只创建该文件的一个版本,但将在两个项目中都将其引用.链接文件的图标左下角显示的箭头很容易识别.如果您从未链接过文件,则可以在此处中找到更多信息.
有条件编译的代码
在这些文件中,可能仍然存在特定于Windows Phone或Windows 8的部分.这些部分将根据在其上编译代码的平台而有条件地被注释掉.对于特定于Windows 8 Metro应用程序的代码,代码被包装在#if NETFX_CORE/#endif块中.对于Windows 7代码,我已将代码包装在#if SILVERLIGHT
/#endif
块中.我还可以使用#if WINDIWS_PHONE/#endif块
如果有一大块特定于一个平台的代码块,而不是其他特定于某个平台的代码,则可以选择只将包含代码的文件包含在一个文件中,而不包含在另一个文件中,而不使用条件编译指令.您将看到大多数UI代码都完成了此操作(尽管XAML相似,但两个项目之间并未共享).
创建身份验证页面
在我可以在Facebook中调用任何方法之前,我需要验证用户身份. Facebook使用一种开放式身份验证.因此,我们需要在应用程序中使用Web浏览器.向您的项目添加一个新页面,其名称为" AuthenticationPage.xaml".此页面上唯一需要的元素是Web浏览器. Metro和Windows Phone 7之间将遇到的许多差异都在UI相关代码中.需要用于表示浏览器元素的元素略有不同.
应用常数
我们将需要一些数据元素.它们全部在您向Facebook注册的应用程序条目中.我需要在多个地方使用它们.身份验证所需的逻辑很简单,我将其保留在UI代码中.我将在此应用程序的Metro和WP7版本中共享的逻辑分为部分类定义.那
static class ApplicationConstants
{
const string REDIRECT_URL = "https://mysite.com/SomePostAuthenticationPage.html";
const string APP_ID = "_YOUR_APP_ID_";
const string APP_SECRET = "_YOUR_APP_SECRET_";
const string PERMISSIONS = "read_friendlists";
}
您需要用这些常量替换您自己的应用程序的值(" PERMISSIONS"常量除外).
创建身份验证页面
对于身份验证页面的代码背后发生的事情,一个非常高级的概述是它将加载Facebook Open Authentication页面,然后监视用户导航到的路径.如果用户已通过身份验证,则浏览器将定向到之前已为该应用程序注册的站点.发生这种情况时,URL上将附加一个验证码.该代码将被解析并交换为令牌. UI并没有做很多事情.因此,后面的代码看起来很简单.
public partial class AuthenticationPage : PhoneApplicationPage
{
public AuthenticationPage()
{
InitializeComponent();
ViewModel = (App.Current as J2i.Net.FacebookAuthenticationTest.App).ViewModel;
ViewModel.AuthenticationAttempted = true;
}
private void AuthenticationWindow_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
}
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
PrepareForAuthentication();
}
private void AuthenticationWindow_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
}
private void AuthenticationWindow_Navigating(object sender, NavigatingEventArgs e)
{
ProcessWebPageLoad(e.Uri.ToString());
}
}
使用部分类处理代码差异
如果两页后面的代码略有不同,则其余逻辑相同.我没有复制它,而是将公共逻辑放在了部分类定义中.局部类的名称与页面的类相同.因此,在编译时,来自后台代码和部分类的代码将被编译为单个类.通用代码在" AuthenticationPage.xaml.shared.cs"中.隐藏代码从共享文件中引用的第一个方法是" PrepareForAuthentication()".此方法将构建身份验证URL,该URL包含允许应用程序标识自身,需要哪些权限的参数以及出于安全原因应传递的随机字符串.如果您想查看有关这些参数的更多信息,可以在Facebook身份验证文档.
void PrepareForAuthentication()
{
_unique = Guid.NewGuid().ToString();
string targetUrl = String.Format("https://www.facebook.com/dialog/oauth?" +
"client_id={0}" +
"&redirect_uri={1}" +
"&scope={2}" +
"&state={3}",
// for touch/phone friendly version of the authentication page
//"&display=touch",
Uri.EscapeUriString(ApplicationConstants.APP_ID),
Uri.EscapeUriString(ApplicationConstants.REDIRECT_URL),
Uri.EscapeUriString(ApplicationConstants.PERMISSIONS),
Uri.EscapeUriString(_unique));
AuthenticationWindow.Navigate(new Uri(targetUrl));
}
ProcessWebPage()方法查看浏览器导航到的URL,以查看它是否是我们的重定向URL.如果不是,那么什么也不会发生.如果是,则提取查询参数.我们最感兴趣的参数是"代码"和"状态",因为它们将指示成功的身份验证尝试.请求代码以请求进行Facebook API调用的访问令牌.
void ProcessWebPageLoad(string targetUriString)
{
if (targetUriString.IndexOf(ApplicationConstants.REDIRECT_URL) == 0)
{
string _code = GetQueryParam(targetUriString, "code");
string _state = GetQueryParam(targetUriString, "state");
string _error = GetQueryParam(targetUriString, "error");
string _errorReason = GetQueryParam(targetUriString, "error_description");
string _errorDescription = GetQueryParam(targetUriString, "error_reason");
if (
(!String.IsNullOrEmpty(_code)) &&
(!String.IsNullOrEmpty(_state)) &&
(_unique.Equals(_state))
)
{
ExchangeCodeForAccessToken(_code);
}
}
}
要求访问令牌是另一个Web调用,但这不是我们需要浏览器的调用.它不会返回任何用户友好的内容,因此我们无需使用浏览器即可拨打电话.获取访问令牌只是构建URL并从结果中获取令牌的问题.
void ExchangeCodeForAccessToken(string code)
{
//Building the Request URL
string targetUrl = String.Format("https://graph.facebook.com/oauth/access_token?" +
"client_id={0}" +
"&redirect_uri={1}" +
"&client_secret={2}" +
"&code={3}",
Uri.EscapeUriString(ApplicationConstants.APP_ID),
Uri.EscapeUriString(ApplicationConstants.REDIRECT_URL),
Uri.EscapeUriString(ApplicationConstants.APP_SECRET),
Uri.EscapeUriString(code));
var webRequest = HttpWebRequest.CreateHttp(targetUrl);
//Request the URL
webRequest.BeginGetResponse((o) =>
{
//Stuff the response into a string
var response = webRequest.EndGetResponse(o);
var rs = response.GetResponseStream();
StreamReader sr = new StreamReader(rs);
var responseString = sr.ReadToEnd();
//Get the access token and its expiration date
string accessToken = GetQueryParam(responseString,"access_token");
DateTime expirationDate = DateTime.MinValue;
string expiresString = GetQueryParam(responseString, "expires");
if (!String.IsNullOrEmpty(expiresString))
{
int expireTime;
if (int.TryParse(expiresString, out expireTime))
{
expirationDate = DateTime.Now.AddSeconds(expireTime);
}
}
//If we successfully got a token and expiration save them, start grabbing the
//friend list, and then navigate to the previous page
if (!String.IsNullOrEmpty(accessToken) && (expirationDate != DateTime.MinValue))
{
ViewModel.AccessInfo = new AccessInfo() { ExpirationDate = expirationDate, Token = accessToken };
ViewModel.SaveAccessToken();
ViewModel.UpdateFriendList();
ReturnToPreviousPage();
}
}, null);
}
在Windows Phone 7和Windows 8上,导航略有不同.因此,我在Metro和Windows Phone 7版本的应用程序的代码背后都添加了" ReturnToPreviousPage()“方法.每个都包含相应版本的导航代码.
检索朋友列表
在两个平台上,用于检索好友列表的代码都相同. Facebook可能不会在一个呼叫中返回整个朋友列表.如果从调用的返回结构中检索到更多朋友,则返回结构中的一个成员中将有一个名为” Next"的地址(位于" Paging"中),该地址包含指向结果下一页的地址.我不是自己解析结果,而是使用[James Newton-King的JSON.Net.](http://james.newtonking.com/pages/json-net.aspxhttp://james.newtonking. com/pages/json-net.aspx)解析结果并将其累积到一个临时列表中.知道所有结果后,它们就会移到可绑定列表中,该列表将驱动UI上的列表框.
public void UpdateFriendList(string targetUrl = null, List previousUserList = null)
{
if (AccessInfo == null)
return;
if (previousUserList == null)
previousUserList = new List();
var request = System.Net.HttpWebRequest.CreateHttp(targetUrl ?? String.Format(FRIEND_LIST_REQUEST, AccessInfo.Token));
request.BeginGetResponse((o) =>
{
var response = request.EndGetResponse(o);
var responseStreamReader = new StreamReader(response.GetResponseStream());
string friendListText = responseStreamReader.ReadToEnd();
Newtonsoft.Json.JsonSerializer s = new JsonSerializer();
Data.FriendListResponse v = Newtonsoft.Json.JsonConvert.DeserializeObject(friendListText);
if ((v != null) && (v.Data != null))
{
foreach (var user in v.Data)
{
previousUserList.Add(user);
}
}
if ((v!=null) && (v.Paging != null) && (v.Paging.Next != null))
UpdateFriendList(v.Paging.Next, previousUserList);
else
{
previousUserList.Sort((a1, a2) => { return a1.Name.CompareTo(a2.Name); });
DispatchInvoke(() =>
{
FriendList.Clear();
foreach (var u in previousUserList)
FriendList.Add(u);
}
);
}
}, null);
}
派送差异
Windows平台中有一个规则,即您不能从第二线程修改UI.在基于XAML的平台上,您可以确保使用名为Dispatcher的对象将修改UI的调用编组到UI线程中.在Windows Phone 7和Windows 8上,使用此对象的方式有所不同.我通过制作名为" DispatchInvoke()“的方法来抽象化这些差异.它在每个平台上的编译方式不同,并将在每个平台上调用适当的代码.
public void DispatchInvoke(Action a)
{
#if SILVERLIGHT
if (MainViewModel.Dispatcher == null)
a();
else
Dispatcher.BeginInvoke(a);
#else
if ((Dispatcher != null) && (!Dispatcher.HasThreadAccess))
{
Dispatcher.InvokeAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, (obj, invokedArgs) => { a(); }, this, null);
}
else
a();
#endif
}
保存数据
为了保存简单的数据类型,我倾向于使用为Windows Phone 7制作的简单组件(可以在[here](http://j2i.net/blogEngine/post/2011/02/10/Simple-Data- WP7.aspx上的序列化http://j2i.net/blogEngine/post/2011/02/10/Simple-Data-Serialization-on-WP7.aspx)).在Windows 8上,所有文件IO是异步的.因此,必须修改该组件才能在Metro上运行.我已经在[另一页]上显示了更改的详细信息(http://j2i.net/blogEngine/post/2012/04/15/Data-Serializer-updated-for-Windows-Metro.aspxhttp://j2i .net/blogEngine/post/2012/04/15/Data-Serializer-updated-for-Windows-Metro.aspx).由于Windows Phone 7版本使用阻止呼叫,而Metro版本是异步的,因此无法使呼叫看起来相同.但是,这种差异只适用于一种方法.
public void SaveAccessToken()
{
if (this.AccessInfo != null)
{
#if NETFX_CORE
DataSaver<AccessInfo>.SaveMyDataAsync(this.AccessInfo, "_accessToken.xml");
#endif
#if SILVERLIGHT
DataSaver<AccessInfo>.SaveMyData(AccessInfo, "_accessToken.xml");
#endif
}
}
该项目的大多数代码在MainViewModel类中.完整的代码如下.它不应包含您从上方无法识别的任何内容.顶部的using指令包含条件编译指令,因为某些类存在的名称空间在Windows Phone 7和Windows 8 Metro上有所不同.
using System;
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using Newtonsoft.Json;
using J2i.Net.FacebookAuthenticationTest.Data;
using J2i.Net.FacebookAuthenticationTest.Utility;
#if SILVERLIGHT
using System.Windows.Threading;
#endif
#if NETFX_CORE
using System.Threading.Tasks;
using J2i.Net.FacebookAuthenticationTest.Data;
using Windows.UI.Xaml;
#endif
namespace J2i.Net.FacebookAuthenticationTest.ViewModel
{
public class MainViewModel: INotifyPropertyChanged
{
const string FRIEND_LIST_REQUEST = "https://graph.facebook.com/me/friends?access_token={0}";
public MainViewModel()
{
#if SILVERLIGHT
var a = DataSaver<AccessInfo>.LoadMyData("_accessToken.xml");
if(a !=null)
{
this.AccessInfo = a;
this.UpdateFriendList();
}
#endif
#if NETFX_CORE
DataSaver<AccessInfo>.LoadDataAsync("_accessToken.xml", (info, exc)=>
{
if (info != null)
{
this.AccessInfo = info;
this.UpdateFriendList();
}
});
#endif
}
ObservableCollection<FacebookUser> _friendList;
public ObservableCollection<FacebookUser> FriendList
{
get { return (_friendList) ?? (_friendList = new ObservableCollection<FacebookUser>()); }
set { _friendList = value; }
}
string _friendListText = String.Empty;
public string FriendListText
{
get { return _friendListText; }
set
{
if (value != _friendListText)
{
_friendListText = value;
RaisePropertyChanged("FriendListText");
}
}
}
public void SaveAccessToken()
{
if (this.AccessInfo != null)
{
#if NETFX_CORE
DataSaver<AccessInfo>.SaveMyDataAsync(this.AccessInfo, "_accessToken.xml");
#endif
#if SILVERLIGHT
DataSaver<AccessInfo>.SaveMyData(AccessInfo, "_accessToken.xml");
#endif
}
}
AccessInfo _accessInfo;
public AccessInfo AccessInfo
{
get { return (_accessInfo) ?? (_accessInfo = new AccessInfo()); }
set
{
if (_accessInfo != value)
{
_accessInfo = value;
}
}
}
bool _authenticationAttempted = false;
public bool AuthenticationAttempted
{
get { return _authenticationAttempted || (!String.IsNullOrEmpty(AccessInfo.Token)); }
set { _authenticationAttempted = value; }
}
#if NETFX_CORE
static Windows.UI.Core.CoreDispatcher _dispatcher;
public static Windows.UI.Core.CoreDispatcher Dispatcher
{
get
{
if (_dispatcher != null)
return _dispatcher;
if ((Window.Current==null)||(Window.Current.Content == null))
return null;
return Window.Current.Content.Dispatcher;
}
set { _dispatcher = value; }
}
#endif
#if SILVERLIGHT
static Dispatcher _dispatcher;
public static Dispatcher Dispatcher
{
get
{
if (_dispatcher!=null)
return _dispatcher;
var app = (App.Current as J2i.Net.FacebookAuthenticationTest.App);
if (app.RootFrame == null)
return null;
return (app.RootFrame.Dispatcher);
}
set
{
_dispatcher = value;
}
}
#endif
public void UpdateFriendList(string targetUrl = null, List<FacebookUser> previousUserList = null)
{
if (previousUserList == null)
previousUserList = new List<FacebookUser>();
var request = System.Net.HttpWebRequest.CreateHttp(targetUrl ?? String.Format(FRIEND_LIST_REQUEST, AccessInfo.Token));
request.BeginGetResponse((o) =>
{
var response = request.EndGetResponse(o);
var responseStreamReader = new StreamReader(response.GetResponseStream());
string friendListText = responseStreamReader.ReadToEnd();
Newtonsoft.Json.JsonSerializer s = new JsonSerializer();
Data.FriendListResponse v = Newtonsoft.Json.JsonConvert.DeserializeObject<Data.FriendListResponse>(friendListText);
if ((v != null) && (v.Data != null))
{
foreach (var user in v.Data)
{
previousUserList.Add(user);
}
}
if ((v!=null) && (v.Paging != null) && (v.Paging.Next != null))
UpdateFriendList(v.Paging.Next, previousUserList);
else
{
previousUserList.Sort((a1, a2) => { return a1.Name.CompareTo(a2.Name); });
DispatchInvoke(() =>
{
FriendList.Clear();
foreach (var u in previousUserList)
FriendList.Add(u);
}
);
}
}, null);
}
void DispatchInvoke(Action a)
{
#if SILVERLIGHT
if (MainViewModel.Dispatcher == null)
a();
else
Dispatcher.BeginInvoke(a);
#else
if ((Dispatcher != null) && (!Dispatcher.HasThreadAccess))
{
Dispatcher.InvokeAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, (obj, invokedArgs) => { a(); }, this, null);
}
else
a();
#endif
}
protected void RaisePropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
DispatchInvoke(()=>
{
RaisePropertyChanged(propertyName);
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
Windows Mobile Intermediate Win7 Windows-Phone-7 Metro-design WinRT 新闻 翻译