• 【windows8开发】C++开发Metro风格App


    在win8系列前面的文章里跟大家分享过,win8下基于WinRT有3种开发Metro UI App的方式,一种是XAML+C++,一种是html+css+javascript,一种是XAML+C#。开发平台和框架相关的详细内容可以参考这篇文章:
    先说明下自己的开发环境吧,我是用VMWare装了Win8的消费者预览版,然后又装了Visual Studio 11 Express Beta for Windows 8,大家也可以尝试下,毕竟实际动手敲下代码跑一下,感觉会更直观一点。

    在本文中会跟大家一起来讨论下用C++结合XAML怎么去开发Metro App。还是通过一个实际例子来进行说明吧,这里让我们来开发一个Rss订阅的客户端。假设就订阅我自己CSDN博客里的《windows8 app开发专栏》,最后实现效果如下图所示:


    首先,打开Vs2011,新建一个Visual C++,Metro Style,Blank Applicaiton的工程。新工程大致如下:


    简单介绍下工程里的文件吧,
    1. App.xaml:本应用程序对象,虽然也是xaml格式,但是不包含任何UI。
    2. BlankPage.xaml: 默认空白页面,可以直接修改XAML或通过designer设计页面UI。
    3. BlankPage.xaml.h, BlankPage.xaml.cpp:包含默认页面UI的event和基本逻辑,但不包含BlankPage.xaml里UI生成的代码。
    4. App.xaml.h, App.xaml.cpp:Application相关事件和处理,其中会调用BlankPage页面。
    5. Package.appxmanifest:定义App相关的基本信息。包括App名字,描述,logo等。
    6. pch.h, pch.cpp: 预编译文件
    7. BlankPage.g.h, BlankPage.g.cpp: 展开External Dependencies会看到这个文件,它包含了BlankPage.xaml里UI生成的代码,其中的class通过partial关键字定义,其实跟BlankPage.xaml.h里声明的是同一个class。需要注意的是,一般情况下都不要自己去修改这个文件,它会由开发环境自动跟相关Page同步更新。
    关于partial关键字以及后续代码中会出现的很多C++/CX的新特性,如果不明白可以参考下面这篇文章:

    接着我们就开始实际功能的开发吧。
    由于需要通过Rss获取信息,所以肯定需要一个存放数据的容器。新建一个Module.h文件,实际代码如下所示:
    #pragma once
    #include "pch.h"
    #include <collection.h>
    
    namespace RssReader
    {
       // To be bindable, a class must be defined within a namespace
       // and a bindable attribute needs to be applied.
       [Windows::UI::Xaml::Data::Bindable]
       public ref class DataItem sealed
       {
       public:
          DataItem(void){}
          ~DataItem(void){}
    
          property Platform::String^ Title;
          property Platform::String^ Author;
          property Platform::String^ Content;
          // Temporary workaround (use Object^ not DateTime):
          // property Windows::Foundation::DateTime PubDate;
          property Platform::Object^ PubDate;
       };
    
    
       [Windows::UI::Xaml::Data::Bindable]
       public ref class ModuleData sealed
       {
       public:
          ModuleData(void)
          {
             m_items = ref new Platform::Collections::Vector<Platform::Object^>();
          }
          ~ModuleData(void){}
         
          property Platform::String^ Title;           
          property Windows::Foundation::Collections::IVector<Platform::Object^>^ Items
          {
             Windows::Foundation::Collections::IVector<Platform::Object^>^ get() {return m_items; }
          }
       private:
           Platform::Collections::Vector<Platform::Object^>^ m_items;
       };  
    }

    这里包含了两个class,ModuleData类是Rss订阅信息的容器,DataItem类则定义了容器中每条单独的信息的格式。
    我们再把上面定义的数据容器集成到BlankPage中,在BlankPage.xaml.h中,追加以下代码:
    private:
         void GetModuleData(Platform::String^ feedUriString);
         ModuleData^ moduleData;

    并且include相关文件:
    #include "Module.h"

    而在BlankPage.xaml.cpp中,加上以下代码:
    首先,是加上关联文件和命名空间:
    #include <ppltasks.h>
    using namespace Windows::Web::Syndication;
    using namespace Concurrency;

    其次,在构造函数中创建moduleData:
    moduleData = ref new ModuleData();

    然后,追加GetModuleData方法的实现:
    void BlankPage::GetModuleData(Platform::String^ feedUriString)
    {
       // Create the SyndicationClient and the target uri
       SyndicationClient^ client = ref new SyndicationClient();
       Uri^ feedUri = ref new Uri(feedUriString);
    
       // Create the async operation. feedOp is an
       // IAsyncOperationWithProgress<SyndicationFeed^, RetrievalProgress>^
       auto feedOp = client->RetrieveFeedAsync(feedUri);
       feedOp = client->RetrieveFeedAsync(feedUri);
      
       // Create the task object and pass it the async operation.
       // SyndicationFeed^ is the type of the return value
       // that the feedOp operation will eventually produce.
       task<SyndicationFeed^> createTask(feedOp);
    
       // Create a "continuation" that will run when the first task completes.
       // The continuation takes the return value of the first operation,
       // and then defines its own asynchronous operation by using a lambda.
       createTask.then([this] (SyndicationFeed^ module) -> SyndicationFeed^
          {
             // Get the title of the feed (not the individual posts).
             moduleData->Title = module ->Title->ToString();
            
             // Retrieve the individual posts from the feed.
             auto dataItems = module->Items;
    
             // Iterate over the posts. You could also use
             // std::for_each( begin(feedItems), end(feedItems),
             // [this, feed] (SyndicationItem^ item)
             for(int i = 0; i < (int)dataItems->Size; i++)
             {        
                auto item = dataItems->GetAt(i);
                DataItem^ dataItem = ref new DataItem();
                dataItem->Title = item->Title->Text;
    
                         const wchar_t* title = dataItem->Title->Data();
                         wchar_t key = '8';
                         const wchar_t* result = wcschr((wchar_t*)title, key);
                         if (result != 0) {
                               dataItem->PubDate = ref new Platform::Box<Windows::Foundation::DateTime>(item->PublishedDate);
    
                               dataItem->Author = item->Authors->GetAt(0)->Name;
    
                               if (module->SourceFormat == SyndicationFormat::Atom10)
                               {
                                  dataItem->Content = item->Content->Text;
                               }
                               else if (module->SourceFormat == SyndicationFormat::Rss20)
                               {
                                  dataItem->Content = item->Summary->Text;
                               }
                               moduleData->Items->Append((Object^)dataItem);
                         }
             }
            
             this->DataContext = moduleData;
             return module;        
       }).then ([] (task<SyndicationFeed^> t)
       {
          try
          {
             auto f = t.get();
          }
          catch (std::exception e)
          {
             //Handle exception
          }
       });
    }

    这里的GetModuleData方法是通过Rss订阅URL来获得订阅信息,然后把信息存储到moduleData以及BlankPage的DataContext属性中(关于DataContext后续会有说明),这是是通过异步处理来获取信息的,不理解WinRT异步task的还是先参考下上面提到的C++/CX的文章吧。
    备注:上述代码中的如下代码只是为了过滤出window8专栏的文章,如果要显示所有文章,可以不需要。
    wchar_t key = '8';
    const wchar_t* result = wcschr((wchar_t*)title, key);

    然后我们可以开始开发UI了,如下是最终BlankPage.xaml的代码:
    <Page
        x:Class="RssReader.BlankPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:RssReader"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Loaded="PageLoadedHandler">
    
        <Grid Background="{StaticResource ApplicationPageBackgroundBrush}" Name="Grid1">
            <Grid.RowDefinitions>
                <RowDefinition Height="140" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <TextBlock x:Name="TitleText" Text="{Binding Path=Title}" VerticalAlignment="Center" FontSize="48" Margin="56,0,0,0"/>
    
    
            <Grid Name="Grid2" Grid.Row="1">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="2*" />
                    <ColumnDefinition Width="3*" />
                </Grid.ColumnDefinitions>
                <ListView x:Name="ItemListView" ItemsSource="{Binding Path=Items}" SelectionChanged="ItemListView_SelectionChanged" Margin="10,0,0,10">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <StackPanel>
                                <TextBlock Text="{Binding Path=Title}"  FontSize="24" Margin="5,0,0,0" TextWrapping="Wrap" />
                                <TextBlock Text="{Binding Path=Author}" FontSize="16" Margin="15,0,0,0"/>
                            </StackPanel>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
                <Grid Name ="Grid3" Grid.Column="1" DataContext="{Binding ElementName=ItemListView, Path=SelectedItem}">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>
                    <TextBlock Text="{Binding Path=Title}" FontSize="24" Margin="5,0,0,0" TextWrapping="Wrap" />
                    <WebView x:Name="ContentView" Grid.Row="1" Margin="0,5,20,20"/>
                </Grid>
            </Grid>
        </Grid>
    </Page>

    让我们简单分析下上面的XAML。

    1. 显示Page对应的XAML的最顶层永远都是<page>节点,我们关注下<page>的以下三个属性:
    x:Class="RssReader.BlankPage" :指本Page对应的类名
    xmlns:local="using:RssReader" : 指本Page用到的命名空间名
    Loaded="PageLoadedHandler" :Page加载时的处理,也就是页面Load事件的Handler方法
    (事件处理方法到后面再追加)

    2.<TextBlock>中,都有这样的属性:
    Text="{Binding Path=Title}"
    GetModuleData
    这里的Binding其实是绑定了BlankPage类的成员变量,还记得前面GetModuleData的处理中有如下代码:
    this->DataContext = moduleData;

    这里其实就是绑定到了BlankPage类DataContext成员变量,而DataContext被设置成为了moduleData,所以其实就是指本文本框显示的内容为this->DataContext->Title(即moduleData的成员变量Title)。也许有人会感到疑惑,因为在BlankPage类的定义中并没有定义DataContext这个成员变量。其实前面提到过了,BlankPage.g.h中有UI生成的相关代码是通过partial关键字也被定义为了BlankPage类的内容。而DataContext在UI的Framework层应该有被定义,DataContext就是用作UI对象的数据绑定。

    3. 在<ListView>,<WebView>等标签中,还看到x:Name的属性,它指代的是本UI空间对应的实例对象名字,比如<ListView>中为x:Name="ItemListView",那么<ListView>的对象名就是ItemListView,BlankPage类中可以直接通过ItemListView对象来处理<ListView>的行为。

    最后,在以上XAML中,<Page>和<ListView>中还分别定义了一个事件,
    <Page>:Loaded="PageLoadedHandler"
    <ListView>:SelectionChanged="ItemListView_SelectionChanged"

    所以还需要在代码里添加事件处理:
    BlankPage.xaml.h中为BlankPage类添加如下方法,方法名跟上面属性值对应:
    void ItemListView_SelectionChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::SelectionChangedEventArgs^ e);
    void PageLoadedHandler(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e);

    BlankPage.xaml.cpp中添加以上事件处理方法的具体实现:
    void BlankPage::PageLoadedHandler(Platform::Object^ sender,
              Windows::UI::Xaml::RoutedEventArgs^ e)
    {
           GetModuleData("http://blog.csdn.net/my_business/rss/list");
    }
    
    void BlankPage::ItemListView_SelectionChanged (Platform::Object^ sender,
              Windows::UI::Xaml::Controls::SelectionChangedEventArgs^ e)
    {
       DataItem^ dataItem = safe_cast<DataItem^>(ItemListView->SelectedItem);
       if (dataItem != nullptr)
       {
          // Navigate the WebView to the blog post content HTML string.
          ContentView->NavigateToString(dataItem->Content);
       }
    }

    http://blog.csdn.net/my_business/rss/list是博客Rss订阅的URL
    OK,至此大功告成,编译运行一下就可以看到开始的那个画面的。

    总结一下,
    1. C++结合XAML的形式加上Framework提供的很多接口确实可以非常方便的开发Metro UI风格的App。
    2. 对于没了解过C++/CX以及XAML的C++普通开发者,要马上上手有点难度,前期还是需要一点投入。投入多少因人而异。
    3. 个人还是更倾向于C++开发底层和逻辑层组件,Javascript开发UI的开发模式。

  • 相关阅读:
    图片局部放大插件jquery.jQZoom.js
    FastCGI for iis6不能限制程序池的CPU
    技术普及帖:你刚才在淘宝上买了一件东西
    转载 ListView动态加载数据模板
    转载 Drawable、Bitmap、byte[]之间的转换
    转载 一个ImageView 点击时是一张图片,放开时换另一张图片
    转载 java抽象类与接口的区别
    转载 Android AsyncTask
    转载 Android实现ListView异步加载图片
    转载 Android权限大全
  • 原文地址:https://www.cnblogs.com/secbook/p/2655107.html
Copyright © 2020-2023  润新知