.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號:N120512402
出刊日期: 2012/5/23
一個手機應用程式可以擁有多個操作頁面,因此你必需了解如何在專案中新增頁面,以及切換、瀏覽頁面,甚至在必要時,在不同頁面之間傳遞資料。本文將介紹如何在Windows Phone 7.5應用程式之中新增頁面,瀏覽到不同頁面,以及在不同頁面之間該如何傳遞資料。
新增頁面
在Windows Phone應用程式之中,若要新增一個頁面,可以選取Visual Studio 2010的「Project」選單,然後選取的「Add New Item」選項,叫出「Add New Item」對話盒。接著在「Add New Item」對話盒中選取「Windows Phone Portrait Page 」或「Windows Phone Landscape Page」,參考圖1所示:
![clip_image002[4] clip_image002[4]](http://blogs.uuu.com.tw/Articles/image.axd?picture=clip_image002%5B4%5D_thumb_1.jpg)
圖 1:新增一個頁面。
WMAppManifest.xml檔案
每一個Windows Phone應用程式都會有一個XML格式,名為WMAppManifest.xml的檔案,存在於Visual Studio 2010 -「Solution Explorer」視窗-「你的專案」-「Properties」目錄下。它包含應用程式的manifest資料,描述應用程式的App ID等相關資訊,未來當你執行應用程式時,或將應用程式發佈到Windows Phone Marketplace時,會讀取此檔案中的資訊進行驗證,例如Author、Publisher與ProductID等。
設定起始頁面
Windows Phone應用程式執行時,預設會啟動MainPage.xaml頁來執行,若要修改這個設定,可以開啟WMAppManifest.xml檔案,修改DefaultTask 項目的NavigationPage,以變更起始頁面,例如底下的範例設定起始頁為Page1.xaml檔案。
<Tasks>
<DefaultTask Name ="_default" NavigationPage="Page1.xaml"/>
</Tasks>
在不同頁面之間瀏覽
在Windows Phone應用程式之中,每一個頁面(Page)都會有一個唯一的URI (Uniform Resource Indicator)位址,你可以利用定義在System.Windows.Navigation命名空間之下的NavigationService物件所提供的Navigate方法 ,以非同步的方式來瀏覽到不同的頁面。例如以下程式碼利用NavigationService物件所提供的Navigate方法從MainPage1.xaml瀏覽到Page1.xaml頁面:
NavigationService.Navigate(new Uri("/Page1.xaml" ,
UriKind.RelativeOrAbsolute));
UriKind.RelativeOrAbsolute代表Uri的類型不固定,頁面可以存在於目前頁面的絕對或相對位址,甚至可以存在於另一個組件之中。
回到上一個頁面
當應用程式有多個不同的頁面時,您可以利用Windows Phone上的「Back」按鍵回到上一頁,參考圖2所示。
![clip_image004[4] clip_image004[4]](http://blogs.uuu.com.tw/Articles/image.axd?picture=clip_image004%5B4%5D_thumb.jpg)
圖 2:回到上一頁或結束程式。
若目前顯示在畫面上的頁面是Windows Phone應用程式的第一頁(假設為MainPage),而使用者按下了Windows Phone的「Back」鍵,這時候應用程式便會結束執行。若不想讓使用者按下「Back」鍵結束程式,或回到上一頁,你可以攔截PhoneApplicationPage 的BackKeyPress事件,此事件觸發時,可以利用CancelEventArgs參數的Cancel屬性來決定是否取消「Back」的行為。
例如,當使用者從Windows Phone應用程式的MainPage.xaml瀏覽到Page1.xaml後,你可以在Page1類別(Page1.xaml.cs檔案)撰寫以下事件處理程式碼,將CancelEventArgs參數的Cancel屬性設為true,這樣按下Windows Phone的「Back」鍵時就不能夠回到MainPage.xaml頁:
private void PhoneApplicationPage_BackKeyPress ( object sender , System.ComponentModel.CancelEventArgs e )
{
e.Cancel = true;
}
您也可以在應用程式的第一頁(MainPage)攔截PhoneApplicationPage物件的BackKeyPress事件,讓使用者有機會取消結束應用程式的動作,參考以下程式碼,在BackKeyPress事件處理常式中,利用MessageBox類別顯示一個訊息方塊,詢問使用者是否真的要離開應用程式:
private void PhoneApplicationPage_BackKeyPress ( object sender , System.ComponentModel.CancelEventArgs e )
{
if ( MessageBox.Show("確定離開程式?" ,
"結束應用程式" , MessageBoxButton.OKCancel) != MessageBoxResult.OK )
{
e.Cancel = true;
}
}
如此當應用程式執行時,按下Windows Phone的「Back」鍵後,就可以看到畫面上出現一個訊息方塊,詢問是否結束應用程式。若使用者按下OK按鈕,則程式將結束執行;若使用者按下「Cancel」按鈕,程式會停留在主畫面,參考圖3所示:
![clip_image006[4] clip_image006[4]](http://blogs.uuu.com.tw/Articles/image.axd?picture=clip_image006%5B4%5D_thumb.jpg)
圖 3:攔截PhoneApplicationPage物件的BackKeyPress事件提供取消結束應用程式的機會。
在不同頁面之間傳遞資料
Windows Phon應用程式中每一頁面(Page)都是一個獨立的頁面,無法共享資料,若要在不同頁面之間傳遞資料,可以使用類似網頁之間利用查詢字串的做法一樣來進行資料傳遞動作。
舉例來說,若目前Windows Phone應用程式MainPage中包含一個ListBox顯示Employee清單資料,參考圖4所示:
![clip_image008[4] clip_image008[4]](http://blogs.uuu.com.tw/Articles/image.axd?picture=clip_image008%5B4%5D_thumb.jpg)
圖 4:MainPage中包含一個ListBox顯示Employee清單資料。
ListBox的XAML設定如下:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox Height="358" Name="listBox1" Width="460" SelectionChanged="listBox1_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ID}" Style="{StaticResource PhoneTextLargeStyle}" />
<TextBlock Text="{Binding Name}" Style="{StaticResource PhoneTextSubtleStyle}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Employees與Employee類別的定義如下:
public class Employees
{
List<Employee> data = null;
public List<Employee> Items { get { return data; } set { data = value; } }
public Employees ( )
{
data = new List<Employee>();
data.Add(new Employee() { ID = 1 , Name = "Mary" });
data.Add(new Employee() { ID = 2 , Name = "Candy" });
data.Add(new Employee() { ID = 3 , Name = "Betty" });
}
}
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
}
MainPage 類別中(MainPage.xaml.cs檔案)攔截了PhoneApplicationPage_Loaded事件,初始化Employees物件並設定ListBox的ItemsSource:
Employees empList = null;
private void PhoneApplicationPage_Loaded ( object sender , RoutedEventArgs e )
{
if ( empList == null )
{
empList = new Employees();
listBox1.ItemsSource = empList.Items;
}
}
我們希望當使用者點選畫面上MainPage頁面ListBox中的Employee資料時,便自動瀏覽到Page1.xaml,並將Employee的ID與Name組成查詢字串:
private void listBox1_SelectionChanged ( object sender , SelectionChangedEventArgs e )
{
string id = ( (Employee) listBox1.SelectedItem ).ID.ToString();
string name = ( (Employee) listBox1.SelectedItem ).Name;
string addr =
string.Format("/Page1.xaml?ID={0}&Name={1}" , id , name);
NavigationService.Navigate(new
Uri(addr , UriKind.RelativeOrAbsolute));
}
改寫OnNavigatedFrom和OnNavigatedTo方法
Page物件包含OnNavigatedFrom和OnNavigatedTo兩個方法,可以讓你在不同頁面之間傳遞資料。OnNavigatedFrom方法在頁面不在作用中(Active)時執行;而OnNavigatedTo方法則是在頁面作用中時執行。
當使用者從MainPage瀏覽到Page1時,我們要將傳遞過來的資料讀取出來,顯示在畫面上,目前Page1.xaml包含Silverlight Element如下:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock Height="30" HorizontalAlignment="Left" Margin="46,44,0,0" Name="textBlock1" Text="ID:" VerticalAlignment="Top" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="32,135,0,0" Name="textBlock2" Text="name:" VerticalAlignment="Top" />
<TextBox Height="72" HorizontalAlignment="Left" Margin="110,32,0,0" Name="textBox1" Text="" VerticalAlignment="Top" Width="460" />
<TextBox Height="72" HorizontalAlignment="Left" Margin="110,110,0,0" Name="textBox2" Text="" VerticalAlignment="Top" Width="460" />
</Grid>
在Page1類別(Page1.xaml.cs檔案)中,我們便可以改寫OnNavigatedTo方法,利用NavigationContext物件來取得上一頁傳入的查詢字串ID與Name的值,並將取得的值顯示在頁面中TextBlock上:
protected override void OnNavigatedTo ( System.Windows.Navigation.NavigationEventArgs e )
{
string id , name;
if ( NavigationContext.QueryString.TryGetValue("ID" ,
out id) )
textBox1.Text = id;
if ( NavigationContext.QueryString.TryGetValue("Name" ,
out name) )
textBox2.Text = name;
}
參考圖5所示,範例程式執行的結果:
![clip_image010[4] clip_image010[4]](http://blogs.uuu.com.tw/Articles/image.axd?picture=clip_image010%5B4%5D_thumb.jpg)
圖 5:資料瀏覽程式執行的結果。
在不同頁面之間傳遞物件
使用查詢字串來傳資料很簡單,但是若資料量太大,要將資料串接成查詢字串便不太方便。若要傳遞的資料有特殊的結構,也很難使用查詢字串來表達,這種時候比較適合使用物件在不同頁面之間傳遞資料。
若要宣告讓兩個頁面都可以存取到的物件變數,你可以將之宣告在App類別之中(App.xaml.cs檔案),例如以下範例程式碼宣告一個Employee型別的aEmployee變數:
public partial class App : Application
{
public Employee aEmployee;
//程式碼略
}
若要讓MainPage和Page1共享資料,你可以在第一個頁面(MainPage)將Employee物件放到App類別的aEmployee變數:
private void listBox1_SelectionChanged ( object sender , SelectionChangedEventArgs e )
{
Employee emp = listBox1.SelectedItem as Employee;
App thisApp = Application.Current as App;
thisApp.aEmployee = emp;
NavigationService.Navigate(new Uri("/Page1.xaml" ,
UriKind.RelativeOrAbsolute));
}
在Page1類別(Page1.xaml.cs檔案)中改寫OnNavigatedTo方法,利用Application.Current屬性取得目前的App物件,就可以存取到aEmployee物件的值:
protected override void OnNavigatedTo ( System.Windows.Navigation.NavigationEventArgs e )
{
base.OnNavigatedTo(e);
App thisApp = Application.Current as App;
Employee emp = thisApp.aEmployee;
textBlock1.Text = emp.ID.ToString();
textBlock2.Text = emp.Name;
}
程式的執行結果和上例相同。
瀏覽到不同組件的頁面
有時Windows Phone應用程式專案的頁面很多,你可能會選擇將部分的頁面獨立出來放到其它專案,例如放在Windows Phone Class Library類型的專案之中,編譯在外部組件之內。
舉例說明,例如在你的Windows Phone應用程式方案中,加入一個Windows Phone Class Library專案,參考圖6所示。
![clip_image012[4] clip_image012[4]](http://blogs.uuu.com.tw/Articles/image.axd?picture=clip_image012%5B4%5D_thumb.jpg)
圖 6:加入一個Windows Phone Class Library專案。
在Windows Phone應用程式中,加入參考,參考圖7所示:
![clip_image014[4] clip_image014[4]](http://blogs.uuu.com.tw/Articles/image.axd?picture=clip_image014%5B4%5D_thumb.jpg)
圖 7:加入參考。
選取要參考的是Windows Phone Class Library專案,參考圖8所示:
![clip_image016[4] clip_image016[4]](http://blogs.uuu.com.tw/Articles/image.axd?picture=clip_image016%5B4%5D_thumb.jpg)
圖 8:專案參考。
要瀏覽到不同組件的語法為:
/組件名稱;component/頁面檔名
若要跨不同組件進行換頁的動作,可以透過兩種方法來達成。第一種方式是透過HyperlinkButton控制項來達成。例如在Windows Phone應用程式畫面中加一個HyperlinkButton,然後設定NavigateUri屬性瀏覽到PhoneClassLibrary1專案中的Page2.xaml檔案:
<HyperlinkButton NavigateUri="/PhoneClassLibrary1;component/Page2.xaml" Content="巡覽到外部組件"/>
另一種做法是利用NavigationService的Navigate方法,只要Uri能符合上述的語法設定就行了:
NavigationService.Navigate(new Uri("/PhoneClassLibrary1;component/Page2.xaml" , UriKind.Relative));
同樣地,若要傳遞資料到不同組件的頁面中,可以利用前文探討的語法,例如在Windows Phone應用程式中使用查詢字串的語法傳遞ID與Name:
string addr =
string.Format("/PhoneClassLibrary1;component/Page2.xaml?ID={0}&Name={1}" , 1 , "Mary");
NavigationService.Navigate(new
Uri(addr , UriKind.RelativeOrAbsolute));
在PhoneClassLibrary1專案中的Page2.xaml,改寫OnNavigatedTo方法,取出查詢字串中的值,顯示在畫面上:
protected override void OnNavigatedTo ( System.Windows.Navigation.NavigationEventArgs e )
{
string id , name;
if ( NavigationContext.QueryString.TryGetValue("ID" ,
out id) )
textBox1.Text = id;
if ( NavigationContext.QueryString.TryGetValue("Name" ,
out name) )
textBox2.Text = name;
}
套用ViewModel
應用程式最好可以參照一些軟體設計模式來開發,這樣可以避免許多已知的問題。例如前文定義的Employee類別應該只存放員工資料,不要進行其他像編輯資料的動作。你可以將這些程式碼獨立出來,放在ViewModel類別。
ViewModel類別就是一個.NET的類別,它的主要功能是用來封裝我們想要展示的資料,並處理資料展示和繫結的邏輯,以及資料的驗證檢查動作。資料驗證主要是為避免使用者輸入錯誤的資料,而導致程式出錯,您應該適當地提供資料驗證的機制,來防止這個問題,像是員工的年齡應該為數值資料,不可以接受文字資料…等等。加入ViewModel類別的另一個好處是,可以讓你的測試程式碼更容易撰寫。
撰寫ViewModel類別
您可以將ViewModel類別視為資料物件與使用者介面的中介者。以下EmployeeView類別便是一個ViewModel範例,它實作INotifyPropertyChanged介面,以偵測類別中的屬性值是否有異動,並在屬性資料異動後,觸發PropertyChanged事件。另外,類別中包含兩個屬性:ID與Name屬性,這兩個屬性將會繫結到使用者介面上供做Employee物件資料編輯之用:
public class EmployeeView : INotifyPropertyChanged
{
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
if ( PropertyChanged != null )
{
PropertyChanged(this ,
new PropertyChangedEventArgs("name"));
}
}
}
private int id;
public int ID
{
get
{
return id;
}
set
{
id = value;
if ( PropertyChanged != null )
{
PropertyChanged(this ,
new PropertyChangedEventArgs("id"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void Load ( Employee emp )
{
Name = emp.Name;
ID = emp.ID;
}
public void Save ( Employee emp )
{
emp.ID = ID;
emp.Name = Name;
}
}
另外EmployeeView類別中包含的Load與Save方法則是用來編輯Employee物件用的。Load方法將Employee物件載入EmployeeView物件 ;而Save方法則是將EmployeeView物件的資料複製到Employee物件 。
延續前文範例,MainPage的ListBox控制項中包含Employees清單資料,當點選其中一筆資料時,我們希望能夠跨頁面跳到Page1來編輯此Employee資料。因此,在App類別中(App.xaml.cs檔案)定義了Employee型別的aEmployee與Employees型別的aList變數,便於跨頁面存取,並在App類别建構函式中初始化aList變數:
public partial class App : Application
{
public Employee aEmployee;
public Employees aList;
public App ( )
{
UnhandledException += Application_UnhandledException;
InitializeComponent();
InitializePhoneApplication();
if ( System.Diagnostics.Debugger.IsAttached )
{
Application.Current.Host.Settings.EnableFrameRateCounter = true;
PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled;
}
aList = new Employees();
}
//其它程式略
在MainPage類別(MainPage.xaml.cs檔案)中使用ObservableCollection集合來存放Employee物件所成的集合。ObservableCollection集合物件是一個動態的資料集合,可以自動偵測集合中的物件異動的情況,例如是否有新增、刪除或修改的情形出現。
以下程式範例,在MainPage頁面載入時,建立ObservableCollection集合物件存放員工清單資料,然後利用儲存在App層級的List<Employee>物件進行初始化:
ObservableCollection<Employee> empList=null;
private void PhoneApplicationPage_Loaded ( object sender , RoutedEventArgs e )
{
App thisApp = Application.Current as App;
empList = new ObservableCollection<Employee>(
thisApp.aList.Items);
listBox1.ItemsSource = empList;
}
MainPage.xaml.cs完整程式碼:
public partial class MainPage : PhoneApplicationPage
{
ObservableCollection<Employee> empList=null;
// Constructor
public MainPage ( )
{
InitializeComponent();
}
private void PhoneApplicationPage_Loaded ( object sender , RoutedEventArgs e )
{
App thisApp = Application.Current as App;
empList = new ObservableCollection<Employee>(
thisApp.aList.Items);
listBox1.ItemsSource = empList;
}
private void listBox1_SelectionChanged ( object sender , SelectionChangedEventArgs e )
{
if ( listBox1.SelectedItem == null ) return;
Employee emp = listBox1.SelectedItem as Employee;
App thisApp = Application.Current as App;
thisApp.aEmployee = emp;
NavigationService.Navigate(new Uri("/Page1.xaml" ,
UriKind.RelativeOrAbsolute));
}
private void PhoneApplicationPage_BackKeyPress ( object sender , System.ComponentModel.CancelEventArgs e )
{
if ( MessageBox.Show("確定離開程式?" ,
"結束應用程式" , MessageBoxButton.OKCancel) != MessageBoxResult.OK )
{
e.Cancel = true;
}
}
}
而Page1.xaml檔案定義畫面如下,包含一個按鈕用來存檔;另一個按鈕則用來回到上一頁(MainPage) ,參考圖9所示:
![clip_image018[4] clip_image018[4]](http://blogs.uuu.com.tw/Articles/image.axd?picture=clip_image018%5B4%5D_thumb.jpg)
圖 9:使用者介面。
Page1.xaml的XAML參考如下:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock Height="30" HorizontalAlignment="Left" Margin="46,44,0,0" Name="textBlock1" Text="ID:" VerticalAlignment="Top" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="32,135,0,0" Name="textBlock2" Text="name:" VerticalAlignment="Top" />
<TextBox Text="{Binding ID,Mode=TwoWay}" Height="72" HorizontalAlignment="Left" Margin="99,29,0,0" Name="textBox1" VerticalAlignment="Top" Width="334" />
<TextBox Text="{Binding Name,Mode=TwoWay}" Height="72" HorizontalAlignment="Left" Margin="99,142,0,0" Name="textBox2" VerticalAlignment="Top" Width="334" />
<Button Content="Save" Height="72" HorizontalAlignment="Left" Margin="46,316,0,0" Name="button1" VerticalAlignment="Top" Width="160" Click="button1_Click" />
<Button Content="Back" Height="72" HorizontalAlignment="Left" Margin="242,316,0,0" Name="button2" VerticalAlignment="Top" Width="160" Click="button2_Click" />
</Grid>
Page1類別中,建立EmployeeView物件的實體,在Page1.xaml頁面若要編輯Employee資料,可在OnNavigatedTo方法從App物件取得要編輯的員工物件,再叫用EmployeeView類別中的Load方法,將員工物件載入EmployeeView編輯:
EmployeeView view = new EmployeeView();
protected override void OnNavigatedTo ( System.Windows.Navigation.NavigationEventArgs e )
{
base.OnNavigatedTo(e);
App thisApp = Application.Current as App;
view.Load(thisApp.aEmployee);
ContentPanel.DataContext = view;
}
若使用者想要更新修改過的EmployeeView物件,便可以從App物件取得目前正在編輯的員工物件,再叫用EmployeeView類別中的Save方法,將員工資料複製到EmployeeView物件:
App thisApp = Application.Current as App;
view.Save(thisApp.aEmployee);
NavigationService 物件有一個GoBack方法,可以讓你透過此方法回到上一頁。這個方法會比使用NavigationService 物件的Navigate方法來回到上一頁要有效率。程式參考如下:
NavigationService.GoBack();
Page1.xaml.cs完整程式:
public partial class Page1 : PhoneApplicationPage
{
public Page1 ( )
{
InitializeComponent();
}
EmployeeView view = new EmployeeView();
protected override void OnNavigatedTo ( System.Windows.Navigation.NavigationEventArgs e )
{
base.OnNavigatedTo(e);
App thisApp = Application.Current as App;
view.Load(thisApp.aEmployee);
ContentPanel.DataContext = view;
}
private void button1_Click ( object sender , RoutedEventArgs e )
{
App thisApp = Application.Current as App;
view.Save(thisApp.aEmployee);
}
private void button2_Click ( object sender , RoutedEventArgs e )
{
NavigationService.GoBack();
}
}
當你執行程式時,MainPage會先顯示員工清單,參考圖10所示:
![clip_image020[4] clip_image020[4]](http://blogs.uuu.com.tw/Articles/image.axd?picture=clip_image020%5B4%5D_thumb.jpg)
圖 10:範例執行結果。
點選任一員工資料進入到Page1進行編輯,參考圖11所示:
![clip_image022[4] clip_image022[4]](http://blogs.uuu.com.tw/Articles/image.axd?picture=clip_image022%5B4%5D_thumb.jpg)
圖 11:範例執行結果。
存檔完後,回到MainPage,修改過的資料馬上呈現在畫面上,參考圖12所示:
![clip_image024[4] clip_image024[4]](http://blogs.uuu.com.tw/Articles/image.axd?picture=clip_image024%5B4%5D_thumb.jpg)
圖 12:範例執行結果。
總結
當Windows Phone應用程式中包含多個頁面時,您可以使用NavigationService物件的Navigate方法瀏覽到別的頁面。 NavigationService物件的GoBack方法可以回到上一頁。使用ObservableCollection集合物件存放清單資料可以偵測集合中資料的異動,你可以使用ObservableCollection集合物件來存放這些資料。
範例下載:
4_PhoneApp4.rar (365.25 kb)