C#+XAML的Metro应用开发入门(三)

知识点

1.了解和使用Windows 8应用中的导航;

2.利用导航和一些常用控件实现一个文本阅读器;

一、Windows 8中的导航

         Windows 8中的导航主要通过Frame中的Navigate方法进行,Navigate的原型如下:

  •     public bool Navigate(Type sourcePageType);
  •     public bool Navigate(Type sourcePageType, object parameter);

         可以看出,在页面导航的过程中可以传递object类型的参数,这使得页面之间的数据传递非常灵活,但是Windows 8的挂起恢复机制要求传递的参数能够可序列化,因此在进行页面间的数据传递时,对参数的类型和组织方式也要有所考虑。

二、在Windows 8应用中使用导航

         为了简单起见,我们设计了如下功能的TXT文本阅读器:

         1.在首页中打开一个文件选择器,选择一个TXT文件;

         2.在正文页显示TXT文章的标题和内容;

  创建工程

  打开Visual Studio,选择文件〉新建项目,在新建对话框中选择Visual C#模版,建立一个空的应用Blank App(XAML),名称可以自己选择,点击确定即可完成项目的创建。

     视图设计

     主页仅仅放置了一个按钮,布局比较简单,代码如下:

<Page
    x:Class="TextReader.MainPage"
    IsTabStop="false"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:TextReader"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Button Content="打开" Width="120" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnOpen"/>
    </Grid>
</Page>

     在正文页我们选择创建一个基本页,Visual Studio会为我们生成一个带后退按钮和标题的页面,我在这个基础上进行改造,加入一个装在ScrollViewer容器中的TextBlock来显示文章内容,同时采用数据绑定将标题和内容绑定至不同的数据,代码如下:

<common:LayoutAwarePage
    x:Name="pageRoot"
    x:Class="TextReader.ArticlePage"
    DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
    IsTabStop="false"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:TextReader"
    xmlns:common="using:TextReader.Common"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Page.Resources>

        <!-- TODO: Delete this line if the key AppName is declared in App.xaml -->
        <x:String x:Key="AppName">My Application</x:String>
    </Page.Resources>

    <!--
        This grid acts as a root panel for the page that defines two rows:
        * Row 0 contains the back button and page title
        * Row 1 contains the rest of the page layout
    -->
    <Grid Style="{StaticResource LayoutRootStyle}">
        <Grid.RowDefinitions>
            <RowDefinition Height="140"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- Back button and page title -->
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Button x:Name="backButton" Click="GoBack" IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}" Style="{StaticResource BackButtonStyle}"/>
            <TextBlock x:Name="pageTitle" Grid.Column="1" Text="{Binding ArticleTitle}" Style="{StaticResource PageHeaderTextStyle}"/>
        </Grid>
        <ScrollViewer Grid.Row="1" >
            <TextBlock Text="{Binding ArticleContent}" FontSize="24" Margin="20" TextWrapping="Wrap">
            </TextBlock>
        </ScrollViewer>
        <VisualStateManager.VisualStateGroups>

            <!-- Visual states reflect the application's view state -->
            <VisualStateGroup x:Name="ApplicationViewStates">
                <VisualState x:Name="FullScreenLandscape"/>
                <VisualState x:Name="Filled"/>

                <!-- The entire page respects the narrower 100-pixel margin convention for portrait -->
                <VisualState x:Name="FullScreenPortrait">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PortraitBackButtonStyle}"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>

                <!-- The back button and title have different styles when snapped -->
                <VisualState x:Name="Snapped">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedBackButtonStyle}"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="pageTitle" Storyboard.TargetProperty="Style">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedPageHeaderTextStyle}"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
    </Grid>
</common:LayoutAwarePage>

     选择一个TXT文件并导航至文章正文页

     为首页中的按钮添加一个Click事件,在事件处理函数中调用FileOpenPicker来选择文件,其作用与文件选择对话框相当。选择完文件之后,我们将页面导航至文章正文页,并向其传递文章标题和内容等数据。代码如下,其中高亮显示的部分为导航操作。

private async void OnOpen(object sender, RoutedEventArgs e)
        {
            var picker = new FileOpenPicker();
            picker.FileTypeFilter.Add(".txt");
            picker.CommitButtonText = "打开";
            picker.SettingsIdentifier = "选择一个文件";
            picker.ViewMode = PickerViewMode.List;
            picker.SuggestedStartLocation = PickerLocationId.Desktop;
            var file = await picker.PickSingleFileAsync();
            if (file != null)
            {
                Stream stream = await file.OpenStreamForReadAsync();
                if (stream.CanSeek)
                    stream.Seek(0, SeekOrigin.Begin);
                if (stream.CanRead)
                {
                    long length = stream.Length;
                    byte[] buffer = new byte[length];
                    stream.Read(buffer,0,(int)length);

                    List<string> bundle = new List<string>();
                    bundle.Add(file.DisplayName);
                    bundle.Add(Encoding.GetEncoding("gb2312").GetString(buffer,0,(int)length));

                    this.Frame.Navigate(typeof(ArticlePage), bundle);
                }                              
            }
        }

     数据绑定

     为了有效复习数据绑定的知识,我们在文章正文页的数据呈现上也采用了数据绑定,设计了一个ArticleViewModel用呈现文章的标题和内容,这个类的设计如下:

 class ArticleViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private string articleTitle;
        public string ArticleTitle
        {
            get
            {
                return articleTitle;
            }
            set
            {
                if (articleTitle != value)
                {
                    articleTitle = value;
                    NotifyPropertyChanged("ArticleTile");
                }
            }
        }

        private string articleContent;
        public string ArticleContent
        {
            get
            {
                return articleContent;
            }
            set
            {
                if (articleContent != value)
                {
                    articleContent = value;
                    NotifyPropertyChanged("ArticleContent");
                }
            }
        }

        public ArticleViewModel(string tile, string content)
        {
            this.articleTitle = tile;
            this.articleContent = content;
        }

        private void NotifyPropertyChanged(string propertyName)
        {
            if (null != PropertyChanged)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

     为了实现数据绑定,我们需要设置UI元素的DataContext属性来关联ViewModel,这里我们在文章正文页的OnNavigateTo方法中接收首页传递的文章标题和文章内容数据,构造一个ArticleViewModel实例,并将DataContext设置为该实例,代码如下:

protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            var parameter = (List<string>)e.Parameter;
            articleViewModel = new ArticleViewModel(parameter[0], parameter[1]);
            this.DataContext = articleViewModel;
        }

         另外需要注意的是,由于文章正文页采用了基本页(LayoutAwarePage)作为了模板,系统为我们自动生成了应用挂起时的一些状态保存工作,而这个例子我们并不涉及挂起时的状态保存操作,为了避免调用这些方法,我们将OnNavigateFrom方法重载为空的方法,如下:

 protected override void OnNavigatedFrom(NavigationEventArgs e)
        {         
        }

         最终的运行效果如下图所示:

应用运行效果图

 完整代码下载

(原创文章,转载请注明作者schbook:seekerxu@163.com)

 

原文地址:https://www.cnblogs.com/schbook/p/2621726.html