Trang (pages)

Trong các bài trước chúng ta đã làm quen với layout và các điều khiển (controls hay views) trong Xamarin.Forms, tuy nhiên, chúng ta vẫn chỉ làm việc trên một giao diện duy nhất. Trong thực tế, các ứng dụng di động có rất nhiều giao diện khác nhau.

Giao diện người dùng trong Xamarin.Forms gắn liền với các đối tượng trang (page objects) và các đối tượng này thừa kế từ đối tượng Page. Xamarin.Forms cung cấp một số đối tượng trang phổ biến:

Kiểu trang Mô tả
ContentPage Hiển thị một đối tượng điều khiển
TabbedPage Cho phép điều hướng giữa các trang con dùng tabs
CarouselPage Cho phép điều hướng giữa các trang bằng thao tác vuốt màn hình (swipe)
MasterDetailPage Chia nội dung thành hai phần: phần thông tin tổng quát (Master) và phần thông tin chi tiết (Detail). Có thể điều hướng giữa các phần bằng thao tác vuốt màn hình.
NavigationPage Là chiếc cầu nối hay cách thức cho phép điều hướng giữa các trang.

Các đối tượng trang có thể được tạo theo 3 cách:

  • Dùng XAML
  • Dùng mã C#
  • Dùng các mẫu được cung cấp sẵn trong Visual Studio

Trong bài viết này chỉ giới thiệu hai cách là XAML và các mẫu sẵn có, cách dùng mã C# có thể tham khảo từ tài liệu Microsoft.

Để tiện theo dõi, chúng ta sẽ tạo một dự án Xamarin.Forms mới tên là NavigApp.

 Đối tượng ContentPage

Đối tượng ContentPage là đối tượng trang đơn giản nhất và chỉ cho phép hiển thị một đối tượng điều khiển. Mặc định, khi chúng ta tạo thành công dự án NavigApp, tập tin MainPage.xaml sẽ có nội dung như sau:


<?xml version="1.0" encoding="utf-8" ?>

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

xmlns:local="clr-namespace:NavigApp"

x:Class="NavigApp.MainPage">

<Label Text="Welcome to Xamarin.Forms!"

VerticalOptions="Center"

HorizontalOptions="Center" />

</ContentPage>

Trong tập tin XAML trên, đối tượng ContentPage được tạo bằng phần tử <ContentPage> và các thuộc tính của nó. Bên trong đối tượng trang này chứa duy nhất một đối tượng Label (nếu muốn thêm các điều khiển khác, ví dụ Button, chúng ta cần đặt tất cả các điều khiển này trong một layout). Tập tin MainPage.xaml.cs tương ứng như sau:


namespace NavigApp

{

     public partial class MainPage : ContentPage

     {

          public  MainPage()

           {

             InitializeComponent();

           }

     }

}

Để ý rằng, lớp MainPage thừa kế trực tiếp từ lớp ContentPage.

Chúng ta cũng có thể tạo một đối tượng ContentPage bằng cách dùng mẫu sẵn có. Để thực hiện điều này, trong cửa sổ Solution Explorer nhấp chuột phải dự án NavigApp và chọn Add > New Item…

Chọn các tùy chọn và điền một số thông tin như hình sau:

Như hình trên, chúng ta có thể chọn mẫu ContentPage nếu muốn sử dụng XAML hay có thể chọn mẫu ContentPage (C#) nếu muốn tạo trang bằng mã C#.

Đối tượng ContentPage có thể được dùng độc lập hoặc được dùng làm nội dung cho các đối tượng trang khác như chúng ta sẽ thấy trong các đối tượng trang tiếp theo.

Đối tượng MasterDetailPage

Cho phép chúng ta chia nội dung thành hai phần liên quan nhau: phần Master thể hiện những thông tin tổng quát và phần Detail mô tả chi tiết cho các thông tin từ phần Master. Đoạn mã XAML sau minh họa cách tạo một đối tượng MasterDetailPage và các thành phần MasterDetail:


<?xml version="1.0" encoding="utf-8" ?>

<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

xmlns:local="clr-namespace:NavigApp"

x:Class="NavigApp.MainPage" >

<MasterDetailPage.Master>

<ContentPage Title="Content Page">

<Label Text="This is the Master" HorizontalOptions="Center"

VerticalOptions="Center"/>

</ContentPage>

</MasterDetailPage.Master>

<MasterDetailPage.Detail>

<ContentPage>

<Label Text="This is the Details" HorizontalOptions="Center"

VerticalOptions="Center"/>

</ContentPage>

</MasterDetailPage.Detail>

</MasterDetailPage>

Để tạo một đối tượng MasterDetailPage chúng ta dùng phần tử <MasterDetailPage>, phần Master được tạo từ collection <MasterDetailPage.Master>, phần Detail được tạo từ collection <MasterDetailPage.Detail>. Chú ý rằng chúng ta cần cung cấp thuộc tính Title cho đối tượng ContentPage trong collection <MasterDetailPage.Master>.

Một lưu ý khác từ tài liệu Microsoft: để tiện lợi, trong phần Master chúng ta sẽ thể hiện nội dung qua đối tượng ContentPage, nội dung phần Detail có thể thể hiện qua các đối tượng trang như TabbedPage hay ContentPage.

Nội dung của tập tin code-behind tương ứng từ tập tin XAML trên:


namespace NavigApp

{

     public partial class MainPage : MasterDetailPage

     {

        public  MainPage()

       {

          InitializeComponent();

       }

    }

}

Lớp MainPage thừa kế trực tiếp từ lớp MasterDetailPage. Kết quả khi thực thi ứng dụng:

Vuốt màn hình từ bên trái để chuyển sang phần Master:

Mặc định phần Detail sẽ xuất hiện đầu tiên khi ứng dụng thực thi, chúng ta có thể thay đổi sự xuất hiện của phần Detail và phần Master thông qua thuộc tính IsPresented. Nếu IsPresented = fasle thì phần Detail sẽ xuất hiện trước, ngược lại phần Master sẽ xuất hiện trước.

Chúng ta cũng có thể thêm MasterDetailPage từ mẫu có sẵn trong Visual Studio (tương tự ContentPage):

Đối tượng TabbedPage

Cho phép tổ chức các trang theo các chủ đề khác nhau. Mỗi chủ đề là một tab. Đoạn mã XAML sau sẽ tổ chức các nội dung theo 3 chủ đề tương ứng 3 tabs dùng phần tử <TabbedPage>:


<?xml version="1.0" encoding="utf-8" ?>

<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

xmlns:local="clr-namespace:NavigApp"

x:Class="NavigApp.MainPage" >

<TabbedPage.Children>

<ContentPage Title="First">

<Label Text="This is the first page" HorizontalOptions="Center" VerticalOptions="Center"/>

</ContentPage>

<ContentPage Title="Second">

<Label Text="This is the second page" HorizontalOptions="Center" VerticalOptions="Center"/>

</ContentPage>

<ContentPage Title="Third">

<Label Text="This is the third page" HorizontalOptions="Center" VerticalOptions="Center"/>

</ContentPage>

</TabbedPage.Children>

</TabbedPage>

Tất cả các nội dung được đặt trong collection <TabbedPage.Children>; nội dung mỗi tab được tổ chức trong một <ContentPage>. Tập tin code-behind tương ứng cho đoạn mã XAML trên:


namespace NavigApp

{

    public partial class MainPage : TabbedPage

    {

         public  MainPage()

        {

           InitializeComponent();

        }

     }

}

MainPage thừa kế trực tiếp từ TabbedPage. Kết quả khi thực thi chương trình:

TabbedPage cũng có thể được thêm từ mẫu sẵn có:

Đối tượng CarouselPage

Tương tự TabbedPage, nhưng thay vì điều hướng giữa các nội dung thông qua các tabs, chúng ta có thể điều hướng thông qua vuốt màn hình. CarouselPage thích hợp cho việc tạo một gallery ảnh như đoạn mã XAML sau:


<?xml version="1.0" encoding="utf-8" ?>

<CarouselPage xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

xmlns:local="clr-namespace:NavigApp"

x:Class="NavigApp.MainPage" >

<CarouselPage.Children>

<ContentPage Title="First">

<Image Source="cat.jpg" HorizontalOptions="Center" VerticalOptions="Center"/>

</ContentPage>

<ContentPage Title="Second">

<Image Source="dog.jpg" HorizontalOptions="Center" VerticalOptions="Center"/>

</ContentPage>

<ContentPage Title="Third">

<Image Source="mouse.jpg" HorizontalOptions="Center" VerticalOptions="Center"/>

</ContentPage>

</CarouselPage.Children>

</CarouselPage>

Tương tự TabbedPage, các nội dung được tổ chức trong collection <CarouselPage.Children> của phần tử <CarouselPage>. Đoạn mã code-behind tương ứng:


namespace NavigApp

{

    public partial class MainPage : CarouselPage

    {

       public  MainPage()

      {

        InitializeComponent();

       }

    }

}

Lớp MainPage thừa kế trực tiếp từ lớp CarouselPage. Kết quả khi thực thi chương trình:

Vuốt màn hình sang trang thứ hai:

Sang trang thứ ba:

Lưu ý chúng ta phải thêm các ảnh đến thư mục Resources > Drawable của dự án Android:

Điều hướng giữa các trang (Navigation)

Quá trình điều hướng trong Xamarin.Forms được tổ chức thông qua một ngăn xếp còn gọi là ngăn xếp điều hướng (navigation stack). Việc di chuyển từ trang A sang trang B tương đương với việc đưa (push) trang B vào đỉnh ngăn xếp thay cho trang A:

Việc quay ngược đến trang kề trước hay di chuyển từ từ trang B ngược về trang A tương đương với việc lấy (pop) trang B ra khỏi ngăn xếp và đỉnh ngăn xếp được thay bằng trang A:

Để tiện theo dõi, chúng ta sẽ thêm vào dự án NavigApp trang thứ hai kiểu ContentPage:

Tập tin SecondPage.xaml:


<?xml version="1.0" encoding="utf-8" ?>

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

x:Class="NavigApp.SecondPage"

Title="Second Page">

<ContentPage.Content>

<StackLayout>

<Label Text="Welcome to second page!"

VerticalOptions="CenterAndExpand"

HorizontalOptions="CenterAndExpand" />

<Button Text="Previous Page"

VerticalOptions="CenterAndExpand"

HorizontalOptions="CenterAndExpand"

Clicked="prevPage"/>

</StackLayout>

</ContentPage.Content>

</ContentPage>

Tập tin SecondPage.xaml.cs:


namespace NavigApp

{

  [XamlCompilation(XamlCompilationOptions.Compile)]

  public partial class SecondPage : ContentPage

  {

     public SecondPage ()

     {

        InitializeComponent ();

     }

     private void prevPage(object sender, EventArgs e)

     {

     }

  } 

}

Chú ý rằng, trang SecondPage chứa một Button PREVIOUS PAGE và phương thức prevPage() dùng để xử lý sự kiện Click cho Button này.

Để điều hướng, đầu tiên chúng ta phải tạo một trang gốc (root page) hay là trang đầu tiên được đưa vào ngăn xếp hàng đợi bằng cách dùng đối tượng NavigationPage trong phương thức khởi tạo của lớp App trong tập tin App.xaml.cs:


public App ()

{

     InitializeComponent();

     //MainPage = new NavigApp.MainPage();

     FirstPage = new NavigationPage(new FirstPage());

}

Bây giờ, chúng ta thêm một Button đến giao diện MainPage và thêm một phương thức tên nextPage xử lý sự kiện Click cho Button này:

MainPage.xaml


<?xml version="1.0" encoding="utf-8" ?>

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

xmlns:local="clr-namespace:NavigApp"

x:Class="NavigApp.MainPage"

Title="Main Page">

<StackLayout>

<Label Text="I am Main Page" HorizontalOptions="Center"

VerticalOptions="Center"/>

<Button Text="Next Page" HorizontalOptions="Center" VerticalOptions="Center"

Clicked="nextPage"/>

</StackLayout>

</ContentPage>

MainPage.xaml.cs


namespace NavigApp

{

     public partial class MainPage : ContentPage

     {

          public  MainPage()

          {

              InitializeComponent();

          }

          private void nextPage(object sender, EventArgs e)

          {

          }

    }

}

Giao diện MainPage lúc này:

Nếu chúng ta muốn trang MainPage trong ứng dụng NavigApp trở thành trang gốc, thay đổi đoạn mã C# trong phương thức khởi tạo App như sau:


public App ()

{

    InitializeComponent();

    //MainPage = new NavigApp.MainPage();

    MainPage = new NavigationPage(new MainPage());

}

Như vậy, lúc này trang MainPage đang ở đỉnh của ngăn xếp điều hướng. Thực thi lại ứng dụng:

Lúc này sẽ xuất hiện một thanh điều hướng và tiêu đề trang. Để khi nhấn nút NEXT PAGE chúng ta sẽ chuyển sang trang SecondPage hay trang SecondPage được đẩy vào ngăn xếp chúng ta dùng phương thức PushAsync của thuộc tính Navigation. Phương thức nextPage() được điều chỉnh như sau:


async private void nextPage(object sender, EventArgs e)

{

    await Navigation.PushAsync(new SecondPage());

}

Chú ý rằng chúng ta phải thêm từ khóa async vào khai báo của phương thức nextPage. Khi nhấn nút NEXT PAGE từ trang MainPage, trang SeconPage sẽ xuất hiện như sau:

Một thanh điều hướng cùng với nút Back mặc định (<–) sẽ xuất hiện. Chúng ta trở lại trang MainPage tức là chúng ta lấy trang SecondPage ra khỏi ngăn xếp theo hai cách:

  • Dùng nút Back mặc định
  • Sử dụng phương thức PopAsync của thuộc tính Navigation

Nếu chúng ta sử dụng phương thức PopAsync, phương thức prevPage xử lý sự kiện Click của button PREVIOUS PAGE được điều chỉnh như sau:


async private void prevPage(object sender, EventArgs e)

{

   await Navigation.PopAsync();

}

Lúc này, để trở lại trang MainPage từ trang SecondPage chúng ta có thể dùng nút Back mặc định trên thanh điều hướng hay dùng nút PREVIOUS PAGE.

Chuyển dữ liệu khi điều hướng (passing data when navigating)

Chúng ta có thể chuyển dữ liệu khi điều hướng từ trang này sang trang khác theo hai cách:

  • Chuyển qua tham số của một hàm khởi tạo trang
  • Dùng BindingContext

Bài viết này sẽ minh họa cách chuyển dữ liệu đến trang MainPage khi điều hướng dùng tham số của hàm khởi tạo trang. Dùng BindingContext có thể tham khảo tài liệu Microsoft hay bài viết sau. Các bước chuyển dữ liệu đến MainPage:

Trong tập tin MainPage.xaml: thêm thuộc tính x:Name đến Label và thay đổi giá trị thuộc tính Text của Label này:


<ContentPage ...>

<StackLayout>

<Label x:Name="name" Text="I am " HorizontalOptions="Center"

VerticalOptions="Center"/>

<Button .../>

</StackLayout>

</ContentPage>

Trong tập tin MainPage.xaml.cs: thêm một tham số đến hàm khởi tạo MainPage và hiển thị thông tin từ tham số này thông qua Label:


public  MainPage(string name)

{

     InitializeComponent();

     var lbName = this.FindByName<Label>("name");

     lbName.Text += name;

}

Trong tập tin App.xaml.cs: điều chỉnh lại NavigationPage trong phương thức khởi tạo App như sau:


public App ()

{

     InitializeComponent();

     MainPage = new NavigationPage(new MainPage("Minh"));

}

Kết quả khi thực thi, MainPage sẽ như sau:

Tổng kết

Trong bài viết này chúng ta đã làm quen với khái niệm trang và cách điều hướng giữa các trang thông qua ngăn xếp điều hướng. Nếu chúng ta đã quen thuộc với lập trình Android trước đó thì khái niệm trang trong Xamarin.Forms tương ứng với khái niệm Activity trong Android và  đối tượng NavigationPage tương ứng với đối tượng Intent. Chúng ta có thể chuyển dữ liệu từ trang này sang trang khác khi điều hướng trong Xamarin.Forms bằng cách dùng tham số của một hàm khởi tạo trang hay dùng BindingContext. Phần Data Binding sẽ là chủ đề trong bài viết tiếp theo.