Windows App一般情況下,同一時刻只能有一個應用程序實例在運行,為了在特殊需求下可以同時呈現不同的UI,SDK提供了多視圖操作支持。
應用程序可以創建新的應用視圖,以新的視圖為基礎可以呈現與主視圖不同的內容,但又不影響主視圖的UI。這些視圖既可以在同一個窗口中切換,也可以用新的窗口來呈現新的視圖。這些窗口,用戶可以拖放到不同的虛擬桌面中。
其實,視圖的創建、切換、顯示都不難,主要的難點在於完成這些操作所需要的類型被分布在不同的命名空間中,故不熟悉SDK的朋友可能找不到。
視圖管理相關的API主要分布在以下兩個命名空間下:
Windows.ApplicationModel.Core
Windows.UI.ViewManagement
Core下面主要用到兩個類。CoreApplication類負責創建視圖,調用CreateNewView方法可以創建一個新的視圖,創建后以CoreApplicationView對象返回。已創建的視圖在CoreApplication.Views列表中,在應用程序運行期間,所有被創建的視圖都在這個列表中,所以,還是節約一下資源,不要亂創建視圖。
另外,在Windows.UI.ViewManagement命名空間下,也有幾個類,也是用來操作視圖,比較重要,上面的幾個Core是用於創建視圖,而ViewManagement下的類都是用來操作具體的某一個視圖的。
ApplicationView類用於獲取視圖ID,設置視圖標題等,其中,依靠ApplicationViewTitleBar類還可以自定窗口標題欄、標題欄按鈕的背景顏色和前景顏色。
要在新窗口上顯示某個視圖,或者切換視圖,都由ApplicationViewSwitcher類來完成,它是靜態的,直接可以拿來耍,不用實例化。
下面我做了個例子,這個例子在主視圖上放了幾個網站鏈接,點擊某個鏈接后,可以在新窗口中打開瀏覽目標網頁。
核心代碼如下:
// 創建新的視圖 CoreApplicationView newView = null; if (CoreApplication.Views.Count > 1) { newView = CoreApplication.Views[1]; } // 如果沒有這個視圖,就創一個 if (newView == null) { newView = CoreApplication.CreateNewView(); } int newViewID = default(int); // 初始化視圖 // 注意,必須在對應的線程上執行 await newView.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { // 獲取視圖ID,有兩種方法 // 方法一:GetApplicationViewIdForWindow法,注意線程要對應 // Window必須是與當前視圖關聯的窗口 // int viewID = ApplicationView.GetApplicationViewIdForWindow(newView.CoreWindow); // 方法二:最簡單 // 因為當前執行的代碼就在新視圖的UI線程上的 // 所以GetForCurrentView所返回的就是剛創建的新視圖 ApplicationView theView = ApplicationView.GetForCurrentView(); // 設置一下新窗口的標題(可選) theView.Title = content; // 必須記下視圖ID newViewID = theView.Id; // 初始化視圖的UI ucDisplayPage uc = Window.Current.Content as ucDisplayPage; if (uc == null) { uc = new ucDisplayPage(); uc.HorizontalAlignment = HorizontalAlignment.Stretch; uc.VerticalAlignment = VerticalAlignment.Stretch; uc.MinWidth = 450d; uc.MinHeight = 300d; Window.Current.Content = uc; } uc.TargetWebpageUri = uri; Window.Current.Activate(); // 必須調用Activate方法,否則視圖不能顯示 /* 注意: 在App類中,Window.Current獲取的是主視圖(程序剛啟動時,至少要有一個視圖,不然用戶連毛都看不見了)所在的窗口。 而因為此處的代碼是在新創建的視圖的UI線程上執行的,故Window.Current自然獲取的是新視圖所在的窗口。 */ }); // 開始顯示新視圖 bool b = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewID); if (b) { // 成功顯示新視圖 } else { // 視圖顯示失敗 }
這里面其實沒什么難點,關鍵點是你要理解。 CoreApplication.CreateNewView創建視圖這個應該不難理解,但是在初始化新視圖的UI時,一定一定一定要700%注意,每個視圖都會由一個獨立的UI線程來管理。所以,你的代碼是在主視圖里面寫的,你不能直接在主視圖中訪問新視圖,必須要從與新視圖(CoreApplicationView對象)關聯的Dispatcher來執行。
在插入到Dispatcher隊列的代碼中,Window.Current所指的已經不是主視圖的窗口了,在App類中訪問Window.Current當然返回的是主窗口,但是由於視圖分布在不同的UI線程上,在新視圖的Dispatcher中執行的代碼,Window.Current得到的是新窗口的引用。
這時候可以和平時一樣,給窗口的Content屬性設置UI對象,安排新窗口要顯示的內容。我的例子中用的是一個用戶控件(User Control),這個不用我多介紹,凡是搞過Win開發的,不管你是WPF也好,WinForms也罷,肯定知道用戶控件,就是用現有的控件進行二次組裝,比重新開發一個控件方便。
由於使用ApplicationViewSwitcher來顯示或切換視圖時用的是視圖ID(整數,很多時候是個負值,如-3122),因此我們必須獲取新視圖的ID號,才能用ApplicationViewSwitcher類來顯示它。
一種方法是在Dispatcher插入的代碼中訪問ApplicationView.GetApplicationViewIdForWindow方法,它可以從新視圖所屬的窗口中獲取到視圖ID。
另一種最簡單的方法是直接用ApplicationView.GetForCurrentView方法得到當前視圖的引用,因為這行代碼是寫在新視圖的UI線程上的,所以它獲取到的自然是新視圖的引用。GetForCurrentView方法在哪個線程上調用,它就獲取那個線程關聯的視圖。
最后一個關鍵點是,在新視圖的線程上安排好窗口要顯示的內容后,一定要調用窗口的Activate方法,保證窗口被激活,否則窗口會永遠停留在初始屏幕。
在Win 8.1的時候,你不調用Activate方法也無所謂,因為8x的應用是全屏的,而10x的應用是既可以全屏,也可以窗口化的,所以,你一定要調用Activate方法。原理和做法與初始化App的OnLaunch方法中一樣。
好了,關鍵點給大家分析了一下,重點是大家自己能不能理解,編程這玩意兒就是這樣,理解了就輕松,不理解就腦痛。
下面來運行一下,點擊主窗口上的鏈接,可以在新窗口中打開網頁。而且你可以把新窗口拖到其他虛擬桌面上。
示例源代碼下載:http://files.cnblogs.com/files/tcjiaan/MultiViewApp.zip