`
oju887jn
  • 浏览: 12509 次
最近访客 更多访客>>
社区版块
存档分类
最新评论

Windows Phone开发(三)-- 导航原理分析

 
阅读更多

Windows Phone开发(三)-- 导航原理分析
2011年06月23日
  前两篇文章中,我们的Demo代码都是基于页面切换的,而我们在Silverlight框架下开发的就是以XAML文件为基础的事件驱动程序。也就是说我们的程序会由一个或多个页面作成,这一点和Web程序很相似,所以页面间的切换就很重要。 这一篇文章就来将介绍Windows Phone平台上导航功能。 从Silverlight3开始,提供了内置的导航框架,可以比较轻松的在 Silverlight Page之间进行切换,并且可以和浏览器的前进、后退按钮集成。在Silverlight 3之前的版本,Silverlight没有特定的导航框架,项目中页面之间的切换是通过修改RootVisual布局容器的内容而实现的
  。利用SDK中提供了Frame和Page可以完成导航操纵。其中Frame是导航的框架,Page通过Frame加载显示并实现页面间的导航。
  Windows Phone程序也是基于Sliverlight的Page Model进行导航,同时你也可以使用后退按钮进行后退操纵。WP上核心的导航容器控件为PhoneApplicationFrame,他可以容纳一个PhoneApplicationPage。我们可以创建多个页面,通过Frame来进行导航。  
  
  对于Windows Phone来说,只允许有一个Frame,一个Frame有一下特性: 操作寄宿的Page页面的属性,比如orientation
  指定页面呈现的客户端区域
  为状态栏和程序栏保留空间
  监听obscured和unobscured事件
  而对于Page来说,他会填充满Frame所在的空间。除此之外,程序中还有Status bar和Application Bar,他们都能设置visible 属性;Window Phone也支持屏幕旋转,但是只有在转动设备时才能使之旋转,而不能通过编程的方式实现,因为orientation是只读属性;我们只能通过设置SupportedOrientations来完成;机器的后退按钮可以完成导航中的后退功能,也能关闭键盘,菜单,弹出窗体,同时还能关闭程序。
  关于使用这两个空间导航,参见前两篇文章的例子。定义一个Frame,设置到VisualRoot。在配置文件中设置开始导航的页面。然后通过Frame或者NavigationService来进行导航。参考:Frame and Page Navigation for Windows Phone 我们还是接着上一篇文章程序启动来展开,看看Frame是如何去导航的。 程序启动的的第一步是在App类中,我们创建了一个PhoneApplicationFrame实例。因为PhoneApplicationFrame是继承于Frame的,所以我们先看看Frame构造函数做了什么。 internal Frame() { base.DefaultStyleKey = typeof(Frame); base.Loaded += new RoutedEventHandler(this.Frame_Loaded); this._hostInfo = new HostInfo(); } 构造函数中绑定一个Loaded事件和设置Host信息(如果看过SilverLight的源码,会发现和这的构造函数是有区别的)。接着就看看PhoneApplicationFrame的构造函数 //有删减 public PhoneApplicationFrame() { Action a = null; ShellFrame.Initialize(); base.DefaultStyleKey = typeof(PhoneApplicationFrame); this.Orientation = PageOrientation.PortraitUp; this._visibleRegion = rect; base._navigationService = new NavigationService(this); if (!Frame.IsInDesignMode() && !base._hostInfo.Rehydrated) { if (a == null) { a = delegate { base.Load(); }; } Deployment.Current.Dispatcher.BeginInvoke(a); } if (Current == null) { Current = this; } } 这里面前面一部分是设置界面方向相关的一些内容,然后设置了可见区域,这里被省略了,这些都是上面说的Frame有的特性;接下来的工作很重要定义了一个NavigationService对象,保存在Frame的字段中。我们接着看看这个NavigationService对象构造函数 internal NavigationService(PhoneApplicationFrame nav) { this._navigationPendingLock = new object(); this._cacheRequiredPages = new Dictionary(); PerfUtil.BeginLogMarker(MarkerEvents.TH_INIT_NAVIG ATIONSERVICE, "NavigationService started"); Guard.ArgumentNotNull(nav, "nav"); this._host = nav; HostFrame = nav; HostInfo info = new HostInfo(); this._shellPageManagerCallback = new ShellPageManagerCallback(); this._shellPageManagerCallback.OnCancelRequestEven tHandler = (EventHandler) Delegate.Combine(this._shellPageManagerCallback.On CancelRequestEventHandler, new EventHandler(this.ShellPageManager_OnCancelRequest )); this._shellPageManagerCallback.OnPageStackReactiva tedEventHandler = (EventHandler) Delegate.Combine(this._shellPageManagerCallback.On PageStackReactivatedEventHandler, new EventHandler(this.She llPageManager_OnPageStackReactivated)); this._shellPageManagerCallback.OnResumePageRequest EventHandler = (EventHandler) Delegate.Combine(this._shellPageManagerCallback.On ResumePageRequestEventHandler, new EventHandler(this.ShellP ageManager_OnResumePageRequest)); this._shellPageManagerCallback.OnInvokeReturningEv entHandler = (EventHandler) Delegate.Combine(this._shellPageManagerCallback.On InvokeReturningEventHandler, new EventHandler(this.OnInvoke Returning)); this._shellPageManager = new ShellPageManager(info.LastInstanceId, info.HostWnd, this._shellPageManagerCallback); this._shellPageManager.OnObscurityChangeEventHandl er += new EventHandler(this._host.Sh ellPageManager_OnObscurityChange); this._shellPageManager.OnLockStateChangeEventHandl er += new EventHandler(this._host.Sh ellPageManager_OnLockStateChange); this._shellPageManager.PauseSupported = true; this._quirkShouldNotAllowBackgroundNavigation = QuirksMode.ShouldNotAllowBackgroundNavigation(); this._quirkShouldCallOnNavigatingFromPageForExtern alNav = QuirksMode.ShouldCallOnNavigatingFromPageForExtern alNavigations(); this._quirkShouldForceTextBindings = QuirksMode.ShouldForceTextBindings(); ChooserListener.Initialize(); }  这里主要是定义了一个_cacheRequiredPages和设置了host和Frame的值为传入的PhoneApplicationFrame对象。这时,PhoneApplicationFrame和NavigationService相互引用对方,和面绑定了一些事件。而PhoneApplicationFrame构造函数继续执行,Dispatcher.BeginInvoke(a)来执行Frame.Load方法。 internal void Load() { if (!this._loaded) { this._navigationService.InitializeJournal(); this._navigationService.InitializeContentLoader(); this._navigationService.InitializeNavigationCache( ); this._loaded = true; if (!IsInDesignMode()) { UriMapperBase uriMapper = this.UriMapper; if (!this._navigationService.Resume()) { string uriString = this.ApplyDeepLinks(); if (uriString != null) { this.Navigate(new Uri(uriString, UriKind.Relative)); } else if (this._deferredNavigation != null) { this.Navigate(this._deferredNavigation); this._deferredNavigation = null; } else if (this.Source != null) { this.Navigate(this.Source); } else if (uriMapper != null) { Uri uri = new Uri(string.Empty, UriKind.Relative); Uri uri2 = uriMapper.MapUri(uri); if ((uri2 != null) && !string.IsNullOrEmpty(uri2.OriginalString)) { this.Navigate(uri); } } } } else if (this.Source != null) { base.Content = string.Format(CultureInfo.InvariantCulture, Resource.Frame_DefaultContent, new object[] { this.Source.ToString() }); } else { base.Content = typeof(Frame).Name; } } }
  我们仔细看看这个方法。 A. 执行了3个初始化的方法,InitializeJournal初始化历史记录相关的对象,InitializeNavigationCache初始化缓存相关的对象;
  InitializeContentLoader初始化内容加载相关的对象
  B. InitializeContentLoader中生成了一个PageResourceContentLoader对象,它是 Silverlight 框架中当前唯一的 INavigationContentLoader实现。他的作用就是用来加载要显示的xaml页面,这里是为Frame加载Page做准备,后面会继续介绍。
  C. 然后我们看到一些列的Navigate方法。记得上一篇中我们谈到过为什么没有调用Navigate或者设置Source属性,还能显示MainPage.xaml的问题。是的,就在这里,在这里调用了Navigate方法。到底是那一个方法呢?我们在这里做个小实验。 //NavigationPage="MainPage.xaml" 删除这个属性    
  修改WMAppMainfest.xml文件。这样就没有默认的页面了,我们在App中加入对Source的设置和Navigate调用。 RootFrame = new PhoneApplicationFrame(); RootFrame.Source = new Uri("/Page1.xaml", UriKind.Relative); RootFrame.Navigate(new Uri("/Page2.xaml", UriKind.Relative)); RootFrame.Navigated += CompleteInitializePhoneApplication; 运行程序发现程序正常显示了Page2.xaml页面,那么说明第一个条件判断的ApplyDeepLinks方法是从配置文件中获取NavigationPage的方法。具体如何获得的这里就不深究了,有兴趣自己看源码吧,通过调试发现是在Host对象的TaskPage属性中。这时Frame就导航到了指定的页面。此时也完成了导航框架的初始化。那么系统如何获得Page2的Uri的呢?这个后面解释。  private void InitializePhoneApplication() { if (phoneApplicationInitialized) return; RootFrame = new PhoneApplicationFrame(); RootFrame.Navigated += CompleteInitializePhoneApplication; RootFrame.NavigationFailed += RootFrame_NavigationFailed; phoneApplicationInitialized = true; } private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e) { // Set the root visual to allow the application to render if (RootVisual != RootFrame) RootVisual = RootFrame; // Remove this handler since it is no longer needed RootFrame.Navigated -= CompleteInitializePhoneApplication; } 我们在回头看看上一篇留下的疑问,为什么这里要绑定Navigated事件。现在应该明白了,在PhoneApplicationPhone的构造函数中,调用了Navigate方法导航到配置文件中的MainPage.xaml,当导航完成时,就吧Frame设置到VisualRoot,这时就能把Frame中的Page显示出来了。这里可能会有个疑问,在构造函数中调用Navigation,但在对象构造完才绑定Navigated事件,这能行吗? 在构造函数中调用的是下面的方法: Deployment.Current.Dispatcher.BeginInvoke(a);  这方法实际是把操作发送UI线程执行,但是目前我们还在执行App的构造函数,所有只有构造好App对象UI才能处理这个Load操作,这是我个人看法不知道对不对,有待在研究。 框架初始化完成后,执行Load方法中的Navigate,让Frame导航到指定页面,那么导航到这个页面是怎么加载这个页面的呢? 我们先看看Frame的Navigate方法到底是怎么执行的。 public bool Navigate(Uri source) { if (this._loaded) { return this._navigationService.Navigate(source); } this._deferredNavigation = source; return true; } 我们发现塔实际是调用NavigationService的方法,实际上Frame和NavigationService中有很多同名的方法,实际都是在NavigationService类中实现的,他才是实际负责导航和加载页面的。这也和我们开始标注的Frame的4点特性吻合。
  这里还有个_deferredNavigation字段,表示延迟导航。实际这个我们在上面的例子中留下的疑问。上面的例子我们删除掉配置文件中的MainPage.xaml,而增加了调用RootFrame.Navigate方法显示了Page2,这时并没有真的导航,应为Frame还没有初始化好,只是包Uri保存到了这个字段中。所以返回True不代表完成导航。等App构造完成,执行Load方法时没有获取配置文件,而_deferredNavigation字段又有值,就导航到了Page2。 public bool Navigate(Uri source) { Action a = null; Uri uri = source; NavigationMode mode = NavigationMode.New; PerfUtil.BeginLogMarker(MarkerEvents.TH_PAGE_NAVIG ATION, "Page navigation: " + ((uri == null) ? "" : uri.ToString())); try { JournalEntry journalEntry = new JournalEntry(uri.OriginalString, uri); this.Journal.AddHistoryPoint(journalEntry); return true; } catch (Exception exception) { if (this.RaiseNavigationFailed(uri, exception)) { throw; } return true; } }  以上代码是NavigationService.Navigate方法的实现,只保留了关键代码。其中将传入的Uri保存到了一个JournalEntry的实体类中,此类是表示后退或前进导航历史记录中的一个条目。我们把加载的Page都用AddHistoryPoint保存起来。internal void AddHistoryPoint(JournalEntry journalEntry) { Guard.ArgumentNotNull(journalEntry, "journalEntry"); this._shellPagePending = this._shellPageManager.CreatePage(journalEntry.Sou rce.ToString()); if (this._shellPagePending == null) { throw new InvalidOperationException("Unable to create ShellPage"); } this._shellPagePending.ShellPageCallback.OnNavigat eAwayEventHandler += new EventHandler(this.ShellPage_N avigatedAway); this._shellPagePending.ShellPageCallback.OnNavigat eToEventHandler += new EventHandler(this.ShellPage_Nav igatedTo); this._shellPagePending.ShellPageCallback.OnRemoveE ventHandler += new EventHandler(this.ShellPage_Removed Page); this._shellPagePending.ShellPageCallback.OnBackKey PressInternalEventHandler += new EventHandler(this.ShellPage_B ackKeyPressed); this._shellPageManager.NavigateTo(this._shellPageP ending); this.IsBusy = true; }  通过CreatePage创建了一个ShellPage对象,这是一个未公开的类型,所以没有资料,但是应该是和界面有关,这里不深入。然后注册了相关的事件,最后调用了NavigateTo方法。但是此方法看不到具体实现。根据这里情况推测应该是跳转到指定的ShellPage页面。这个时候会触发Journal对象的OnNavigateAwayEvent事件,执行绑定的方法: private void ShellPage_NavigatedAway(object sender, NavigateAwayEventArgs args) { if (!args.IsExternal) { if ((args.Direction == Direction.Back) && (this._backStack.Count > 0)) { this.IsBusy = false; this._lastRemovedEntry = this._currentEntry; this._currentEntry = this._backStack.Pop(); NavigationMode back = NavigationMode.Back; this.UpdateObservables(this._currentEntry, back); } } else { this.OnNavigatedExternally("", new Uri("app://external/", UriKind.Absolute), (args.Direction == Direction.Back) ? NavigationMode.Back : NavigationMode.New); } } 此方法中包含一个名为UpdateObservables方法,此方法是 private void UpdateObservables(JournalEntry currentEntry, NavigationMode mode) { bool isPaused = null == currentEntry.PageInstance; this.OnNavigated(currentEntry.Name, currentEntry.Source, mode, isPaused); }  OnNavigate触发了在NavigateService中InitializeJournal()注册的Journal的Navigated事件 this._journal.Navigated += new EventHandler(this.Journal_Navigate d) 最后执行绑定的NativagetService中的Journal_Navigated方法: private void Journal_Navigated(object sender, JournalEventArgs args) { this.NavigateCore_ContinueNavigation(args.Uri, args.NavigationMode, args.IsPaused); } private bool NavigateCore_ContinueNavigation(Uri uri, NavigationMode mode, bool isPagePaused) { try { Uri mappedUri = this.GetMappedUri(uri); Uri mergedUriAfterMapping = this.GetMergedUriAfterMapping(mappedUri); Uri mergedUri = this.GetMergedUri(uri); this._currentNavigation = new NavigationOperation(mergedUriAfterMapping, mergedUri, uri, mode); this.IsNavigationInProgress = true; if (!isPagePaused && (mode == NavigationMode.Back)) { PhoneApplicationPage reusedPage = this._journal.CurrentEntry.PageInstance; this.Host.Dispatcher.BeginInvoke(delegate { this.CompleteNavigation(reusedPage, mode); }); } else { if (!isPagePaused && (mode != NavigationMode.New)) { throw new InvalidOperationException("Invalid NavigationMode, only New and Back are supported"); } this._contentLoader.BeginLoad(this._currentNavigat ion.Uri, new AsyncCallback(this.ContentLoader_BeginLoad_Callbac k), this._currentNavigation); } } catch (Exception exception) { if (this.RaiseNavigationFailed(uri, exception)) { throw; } } return true; }  实际上执行的就是NavigateCore_ContinueNavigation 方法,我们注意到 ._contentLoader.BeginLoad()方法,这里用到了我们前面Frame初始化说到的PageResourceContentLoader对象来加载指定XAML文件。
  以上就是Frame加载XAML文件的全部过程。从Navigate到实际的NavigateCore_ContinueNavigation之间很负责,涉及了一些没有公开的类型。但实际意图很简单,就是对要导航的页面进行历史记录操作。 上面介绍了XAML被PageResourceContentLoader对象加载到Frame。那么XAML是如何显示到界面上的呢?
  Silverlight 导航系统使用此类作为其默认的内容加载程序。此类是 INavigationContentLoader 的默认实现,且此类的实例是 Frame.ContentLoader 属性的默认值。虽然您通常会加载 Page 实例,Silverlight 导航系统要求导航目标应为 UserControl 实例。Page 类派生自 UserControl 类,并提供附加导航支持
  以上是MSDN对PageResourceContentLoader的解释,实际总用就是加载应用程序包(.xap 文件)中对应于给定 URI 的页。我们根据源码也看到Navigate方法最终是通过此对象来加载URI页面。 this._contentLoader.BeginLoad(this._currentNavigat ion.Uri, new AsyncCallback(this.ContentLoader_BeginLoad_Callbac k), this._currentNavigation);  我们看看BeginLoad方法的实现 public override IAsyncResult BeginLoad(Uri uri, AsyncCallback userCallback, object asyncState) { SendOrPostCallback d = null; Action a = null; PageResourceContentLoaderAsyncResult result = new PageResourceContentLoaderAsyncResult(uri, asyncState); if (uri == null) { result.Exception = new ArgumentNullException("uri"); } if (SynchronizationContext.Current != null) { if (d == null) { d = delegate (object args) { BeginLoad_OnUIThread(userCallback, result); }; } SynchronizationContext.Current.Post(d, null); } else { if (a == null) { a = delegate { BeginLoad_OnUIThread(userCallback, result); }; } Deployment.Current.Dispatcher.BeginInvoke(a); } return result; }  实际是调用了BeginLoad_OnUIThread方法,而里面又调用了GetLocalXaml方法来加载本地的XAML文件,而且获得了xclass属性指定的类型。最后使用Activator.CreateInstance创建了此XAML文件类对象的实例,保存到result中。 private static void BeginLoad_OnUIThread(AsyncCallback userCallback, PageResourceContentLoaderAsyncResult result) { if (result.Exception != null) { result.IsCompleted = true; userCallback(result); } else { try { string pagePathAndName = UriParsingHelper.InternalUriGetBaseValue(result.Ur i); string localXaml = GetLocalXaml(pagePathAndName); if (string.IsNullOrEmpty(localXaml)) { result.Exception = new InvalidOperationException(string.Format(CultureInf o.CurrentCulture, Resource.PageResourceContentLoader_NoXAMLWasFound, new object[] { pagePathAndName })); } else { string xClass = GetXClass(localXaml); if (string.IsNullOrEmpty(xClass)) { try { result.Content = XamlReader.Load(localXaml); } catch (Exception exception) { result.Exception = new InvalidOperationException(string.Format(CultureInf o.CurrentCulture, Resource.PageResourceContentLoader_XAMLWasUnloadab le, new object[] { pagePathAndName }), exception); } } else { Type typeFromAnyLoadedAssembly = GetTypeFromAnyLoadedAssembly(xClass); if (typeFromAnyLoadedAssembly == null) { result.Exception = new InvalidOperationException(string.Format(CultureInf o.CurrentCulture, Resource.PageResourceContentLoader_TheTypeSpecifie dInTheXClassCouldNotBeFound, new object[] { xClass, pagePathAndName })); } else { result.Content = Activator.CreateInstance(typeFromAnyLoadedAssembly ); } } } } catch (Exception exception2) { result.Exception = exception2; } finally { result.IsCompleted = true; if (userCallback != null) { userCallback(result); } } } } 这里BeginLoad是用异步的方式来加载,避免UI线程等待。而加载完成后执行回调方法 ContentLoader_BeginLoad_Callback private void ContentLoader_BeginLoad_Callback(IAsyncResult result) { DependencyObject obj2 = null; Uri uriBeforeMapping = null; try { NavigationOperation asyncState = result.AsyncState as NavigationOperation; NavigationOperation operation2 = this._currentNavigation; if ((operation2 != null) && (operation2.Uri == asyncState.Uri)) { uriBeforeMapping = operation2.UriBeforeMapping; obj2 = this._contentLoader.EndLoad(result) as DependencyObject; if (!(obj2 is UserControl)) { throw new InvalidOperationException(string.Format(CultureInf o.InvariantCulture, Resource.NavigationService_ContentIsNotAUserContro l, new object[] { (obj2 == null) ? "null" : obj2.GetType().ToString(), "System.Windows.Controls.UserControl" })); } PhoneApplicationPage p = obj2 as PhoneApplicationPage; if (p != null) { this._journal.CompletePendingShellPage(p); Frame host = this.Host; p.InternalVisibleRegionChanged += new EventHandler(host.OnVi sibleRegionChanged); Frame frame2 = this.Host; p.BeginOrientationChanged += new EventHandler(frame2.OnB eginOrientationChanged); Frame frame3 = this.Host; p.BeginLayoutChanged += new EventHandler(frame3.OnB eginLayoutChanged); Frame frame4 = this.Host; p.InternalBackKeyPress += new EventHandler(frame4.OnBackKeyPress) ; } JournalEntry.SetNavigationContext(obj2, new NavigationContext(UriParsingHelper.InternalUriPars eQueryStringToDictionary(asyncState.Uri, true))); obj2.SetValue(NavigationServiceProperty, this); this.CompleteNavigation(obj2, asyncState.Mode); } } catch (Exception exception) { if (this.RaiseNavigationFailed(uriBeforeMapping, exception)) { throw; } } } 加载完成后,从result中获得PhoneApplicationPage对象,也就是之前加载的XAML文件。然后设置了导航的Context内容,这个用来页面间传值的。接下来 obj2.SetValue(NavigationServiceProperty, this)方法设置了NavigationServiceProperty依赖属性。这个属性和导航时的空引用异常有关。而 CompleteNavigation方法用来完成导航。 this.RaiseNavigated(content, uriBeforeMapping, mode, null != existingContentPage, existingContentPage, newContentPage);  实际上她执行的也是Load方法,这个方法已经在PhoneApplicationFrame的构造函数中执行过了,有_loaded字段标记,也就不会在执行了。 RootVisual的实现看不到,这里贴的是Silverlight的实现。这里用到了XcpImports,这个对象上一篇文章介绍过了,最终是使用Core presentation framework的方法设置了RootVisual,通过Frame得到当前的Page,这时Presentation Core会根据当前的对象树生成显卡可以识别的三角形,最终显示到屏幕上。这方面内容可以参考WPF Presentation  在程序启动的时候执行,整个过程和前面基本一样。相比Windows Phone,他们的构造函数简单了很多。public Frame() { base.DefaultStyleKey = typeof(Frame); base.Loaded += new RoutedEventHandler(this.Frame_Loaded); this._navigationService = new NavigationService(this); } internal NavigationService(Frame nav) { this._cacheRequiredPages = new Dictionary(); Guard.ArgumentNotNull(nav, "nav"); this._host = nav; } 当页面开始呈现,加载了Frame到对象树后触发了Loaded时间, 最终调用了NavigateService.Navigate方法。public bool Navigate(Uri source) { return this.NavigateCore(source, NavigationMode.New, false, false); } 这里也和Windows Phone不同,NavigateService中直接调用了NavigateCore方法。此方法实现和Windows Phone一样,此方法中有用的是NavigateCore_StartNavigation,最终是通过的this._contentLoader.BeginLoad来完成的。然后显示核心把加载的页面显示到屏幕上。
  以上是Silverlight的过程,相比Windows Phone简单一些。因为Loaded事件触发的时间不同,所以WP会自动根据配置文件设置在App对象建立后就导航到指定页面;而SL是在加载对象准备呈现时触发,而且没有配置文件,所以必须手动设置一下。这也就解释了我们前一篇的疑问。也是为什么SL模仿WP来创建框架,却不能在Navigated时间中才设置RootVisual。因为此事件需要设置运行了Loaded方法才能触发。 本文主要对导航框架源代码进行了简单的解析,了解了程序启动后导航到第一个界面的过程。首先创建了PhoneApplication对象,在此对象构建完成后,调用了Frame的Loaded方法来导航到配置文件指定的URI;然后加载此XAML文件,然后触发Frame的Navigated方法设置RootVisual,此属性会调用显示框架的Native方法,将对象树呈现到屏幕上。
分享到:
评论

相关推荐

    windowsPhone开发(三)--导航原理分析

    这一篇文章就来将介绍WindowsPhone平台上导航功能。从Silverlight3开始,提供了内置的导航框架,可以比较轻松的在SilverlightPage之间进行切换,并且可以和浏览器的前进、后退按钮集成。在Silverlight3之前的版本,...

    史上最好传智播客就业班.net培训教程60G 不下会后悔

    微软推出的Windows Phone平台是微软在移动互联网时代的一个重量级产品,微软对于WindowsPhone7的推广力度非常大,因此很多公司也开始进行Windows Phone7产品的研发,2011年下半年Windows Phone7开发人员的需求将会...

    白帽子讲浏览器安全.钱文祥(带详细书签).pdf

    本书兼顾攻击者、研究者和使用者三个场景,对大部分攻击都提供了分析思路和防御方案。本书从攻击者常用技巧的“表象”深入介绍浏览器的具体实现方式,让你在知其然的情况下也知其所以然。 第1篇 初探浏览器安全 1 1...

    JAVA上百实例源码以及开源项目

    此时此景,笔者只专注Android、Iphone等移动平台开发,看着这些源码心中有万分感慨,写此文章纪念那时那景! Java 源码包 Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这...

    JAVA上百实例源码以及开源项目源代码

    此时此景,笔者只专注Android、Iphone等移动平台开发,看着这些源码心中有万分感慨,写此文章纪念那时那景! Java 源码包 Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这...

Global site tag (gtag.js) - Google Analytics