Understanding development of Windows 8 applications on XAML/С#. Implementing a simple RSS Reader. Part 1.

Stanislav Pavlov

This is the translated copy of the original article written by Stanislav Pavlov for habrahabr.ru

 

In this article we will try to understand how to create a Windows 8 application and create a simple application – RSS feed reader on XAML/C#.

Creating an application from a template

We start development from a template of a Grid application and will develop on XAML/C#.

In the File menu, select New Project -> Visual C#, then — Windows Store. Then select the Grid App (XAML) template in the central part of the New Project window. Specify an application name in the Name field, for example MyReader and if needed, specify the folder where the application will be located in the Location folder. Press OK.

 

1

 

As a result, we get virtually fully functional application in terms of navigation and data display which displays sample data. Let’s see how it works by pressing F5, green arrow or selecting Debug ->Start Debugging

2

As we can see, this is virtually a fully functional application:

  • press any gray tile or group header;
  • use the “Back” button on the inner pages of the application;
  • switch the application into Snapped mode.

Come back to Visual Studio and stop debugging the application (Shift+F5, red square or select Debug -> Stop Debugging).
Let’s now review the main files of our solution:

  • *.xaml — application page mark-up on XAML language.
  • *.xaml.cs — a code-behind file of a page on C# language.
  • App.xaml — initialization of a Windows Store application object on XAML language. It does not have visual presentation and is not an application page.
  • App.xaml.cs — a code-behind file on C# language for App.xaml. In this file, events of application level are handled. They include start, activation by search query, and deactivation of an application. Besides, in App.xaml.cs you can trap unhandled exceptions and capture errors of navigation between pages.
  • The Common folder, StandardStyles.xaml file — a file of style resources for graphic controls. It’s got involved in the App.xaml file and its styles are available on all application pages.
  • Package.appxmanifest — an application manifest, a XML file that contains various application settings. There is a visual editor which is opened by double-clicking Package.appxmanifest file name in the Solution Explorer window.
  • AssemblyInfo.cs — a configuration file where some metadata of the main application assembly are defined.
  • MyReader_TemporaryKey.pfx — a cryptographic certificate with the private key with which an application is signed.
  • The Asset folder, the Logo.png, SmallLogo.png, StoreLogo.png files — logos for big and small tiles of an application, and an icon for a list of all applications.
  • SplashScreen.png — an image for the splash screen which is displayed when application loading.

Besides, the Common folder includes files with common code that is used in an application. And the DataModel folder includes a file with application data model —SampleDataSource.cs.

Changing data for display

So, the application already works and sample data source is implemented there. Thus, the task #1 is to replace a sample data source with a real data source. Let’s start from deleting default constructor of the SampleDataSource class where initialization by static data is performed.

public SampleDataSource()

Then, we take advantage of the inbuilt Visual Studio refactoring features and rename SampleDataSource to RSSDataSource, SampleDataGroup to RSSDataGroup, SampleDataItem to RSSDataItem and SampleDataCommon to RSSDataCommon.

3

 

To create holistic world view, we can rename the SampleDataSource.cs file to RSSDataSource.cs.

To simplify work with class of data in this sample, let’s facilitate it. To do that, let’s replace the following part of the RSSDataSource class:

 

private static RSSDataSource _sampleDataSource = new RSSDataSource();

private ObservableCollection _allGroups = new ObservableCollection();

public ObservableCollection AllGroups

{

get { return this._allGroups; }

}

With the analogous code by its meaning that simplifies perception and further development of the sample.

public static readonly ObservableCollection<RSSDataGroup> AllGroups = new ObservableCollection<RSSDataGroup>();

So, we are ready to add real data.

 

Let’s write a simple method which will take thread of RSS or ATOM data and present it for further display in application interface.

 

public static async Task<bool> AddGroupForFeedAsync(string feedUrl)
{
    string clearedContent = String.Empty;

    if (RSSDataSource.GetGroup(feedUrl) != null) return false;

    var feed = await new SyndicationClient().RetrieveFeedAsync(new Uri(feedUrl));

    var feedGroup = new RSSDataGroup(
        uniqueId: feedUrl,
        title: feed.Title != null ? feed.Title.Text : null,
        subtitle: feed.Subtitle != null ? feed.Subtitle.Text : null,
        imagePath: feed.ImageUri != null ? feed.ImageUri.ToString() : null,
        description: null);

    foreach (var i in feed.Items)
    {
        string imagePath = null;

        if (i.Summary != null)
            clearedContent = Windows.Data.Html.HtmlUtilities.ConvertToText(i.Summary.Text);
        else
            if (i.Content != null)
                clearedContent = Windows.Data.Html.HtmlUtilities.ConvertToText(i.Content.Text);

        f (imagePath != null && feedGroup.Image == null)
            feedGroup.SetImage(imagePath);

        if (imagePath == null) imagePath = "ms-appx:///Assets/DarkGray.png";

        feedGroup.Items.Add(new RSSDataItem(
            uniqueId: i.Id, title: i.Title.Text, subtitle: null, imagePath: imagePath,
            description: null, content: clearedContent, @group: feedGroup));
    }

    AllGroups.Add(feedGroup);
    return true;
}

The code is quite simple. Using SyndicationClient we get and parse data from RSS/ATOM, then create a group and add records to it. At the same time, we clear the content of HTML tags to make it look better and allow adding an image (but will do this later).

 

Don’t forget to add the following directions to the using block:

 

using Windows.Web.Syndication;
using System.Threading.Tasks;

 

To check right along how it works, double click the GroupedItemsPage.xaml.cs file to open it and go ahead to the LoadState method that initializes data and replace the sampleDataGroups data source with RSSDataSource.AllGroups, and also delete unnecessary initialization of sampleDataGroups. Now LoadState code consists of one line:

protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
    this.DefaultViewModel["Groups"] = RSSDataSource.AllGroups;
}

Now, to make everything work we need to add some RSS. Let’s use the newly-created AddGroupForFeedAsync function and add 2 lines to the LoadState function:

 

RSSDataSource.AddGroupForFeedAsync("http://blogs.msdn.com/b/stasus/rss.aspx");
RSSDataSource.AddGroupForFeedAsync("http://www.spugachev.com/feed");

Build a project and run it.

4

 

Bingo! Works!

 

But, well, it’s not really pretty… So, let’s make it pretty!

Making pretty

The first thing to do is to add pictures to the posts and tiles in the grouped display.

Coming back to RSSDataSource.cs and add a magic method that gets pictures from RSS posts:

 

private static string GetImageFromPostContents(SyndicationItem item)
{
    string text2search = "";

    if (item.Content != null) text2search += item.Content.Text;
    if (item.Summary != null) text2search += item.Summary.Text;

    return Regex.Matches(text2search,
            @"(?<=<img\s+[^>]*?src=(?<q>['""]))(?<url>.+?)(?=\k<q>)",
            RegexOptions.IgnoreCase)
        .Cast<Match>()
        .Where(m =>
        {
            Uri url;
            if (Uri.TryCreate(m.Groups[0].Value, UriKind.Absolute, out url))
            {
                string ext = Path.GetExtension(url.AbsolutePath).ToLower();
                if (ext == ".png" || ext == ".jpg" || ext == ".bmp") return true;
            }
            return false;
        })
        .Select(m => m.Groups[0].Value)
        .FirstOrDefault();
}

 

In this method, using regular expressions we try to find pictures (with png, jpg and bmp extensions) in the post text or summary. Don’t forget to add the following directions to the ‘using’ block:

 

using System.Text.RegularExpressions;
using System.IO;

Now let’s add method calls to the providently left placeholder in the AddGroupForFeedAsync method. We replace

string imagePath = null;

with

string imagePath = GetImageFromPostContents(i);

 

Rebuild and run the application.

5

 

Seems that now it looks better, but why all topics are displayed as tiles of the same size? Perhaps, we need to make them as in Windows Store!

 

This seems to be a more complicated task than the ones we’ve done before. But it won’t stop us!

 

To implement such functionality we need to create a custom class, an inheritor from GridView. Add a new class file called VariableSizeGridView.cs to the project. In a new file, add the following direction to the “using” block:

using Windows.UI.Xaml.Controls;

and indicate that the class is inherited from GridView and override there a class method: PrepareContainerForItemOverride. As a result you should get something like that:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml.Controls;

namespace MyReader
{
    class VariableSizeGridView: GridView 
    {
        protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item)
        {
            base.PrepareContainerForItemOverride(element, item);
        }
    }
}

 

In fact, the PrepareContainerForItemOverride method will  define logic by which the tiles of different sizes will be displayed as group presentation. Now we need to prepare mark-up in the GroupedItemsPage.xaml file.

 

First, we replace GridView with our class— local:VariableSizeGridView. Then, in ItemPanelTemplate we supplement VariableSizedWrapGrid properties by indicating Item size and maximum number of columns and rows, so it would look the following way:

 

<VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,80,0" ItemWidth="200" ItemHeight="125" MaximumRowsOrColumns="9" />

Add a custom Item template into page resources:

 

<DataTemplate x:Key="CustomItemTemplate">
            <Grid HorizontalAlignment="Left">
                <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
                    <Image Source="{Binding Image}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
                </Border>
                <StackPanel Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}" VerticalAlignment="Bottom">
                    <TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}"  Height="90" Margin="15,0,15,0" FontSize="30" />
                </StackPanel>
            </Grid>
        </DataTemplate>

And set it as an Item template instead of standard one in local :VariableSizeGridView:

<local:VariableSizeGridView
            x:Name="itemGridView"
            AutomationProperties.AutomationId="ItemGridView"
            AutomationProperties.Name="Grouped Items"
            Grid.RowSpan="2"
            Padding="116,137,40,46"
            ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
            ItemTemplate="{StaticResource CustomItemTemplate}"
            SelectionMode="None"
            IsSwipeEnabled="false"
            IsItemClickEnabled="True"
            ItemClick="ItemView_ItemClick">

Run the application.

6

 

Still not good enough. But we still did not add our code with logic to PrepareContainerForItemOverride.

So, let’s add it:

class VariableSizeGridView: GridView 
{
    private int rowVal;
    private int colVal;
        
    protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item)
    {
        RSSDataItem dataItem = item as RSSDataItem;
        int index = -1;

        if (dataItem != null)
        {
            index = dataItem.Group.Items.IndexOf(dataItem);

        }

        if (index == 1)
        {
            colVal = 2;
            rowVal = 4;
        }
        else
        {
            colVal = 2;
            rowVal = 2;
        }

        if (index == 2)
        {
            colVal = 2;
            rowVal = 4;
        }

        if (index == 5)
        {
            colVal = 4;
            rowVal = 4;
        }

        VariableSizedWrapGrid.SetRowSpan(element as UIElement, rowVal);
        VariableSizedWrapGrid.SetColumnSpan(element as UIElement, colVal);

        base.PrepareContainerForItemOverride(element, item);
    }

 

As you can see we indicate manually the tile display way and order.

Run our app again.

 

7

 

Looks much better! And by implementing templates for Item and work logic of PrepareContainerForItemOverride we can get virtually any presentation within GridView.

 

I think I will finish the first part here. In the second part we will continue making it pretty: add “live” tiles, search and share contracts, and also bring out the RSS feeds to the settings. And one more thing – create style for Title to so it would display in good manner in  grouped way on tiles as it is on the screenshot in the beginning of the article.

 

You can see the project of application we’ve created so far on SkyDrive: aka.ms/w8RSSp1

 

To be continued…

 

About the Author:

Stanislav Pavlov, PhD, MCSD, is a Microsoft MVP since 2004 and Microsoft Regional Director since 2009. He is a trainer and consultant well-known in Russia for his work with the Windows Embedded developer community. He is the author and co-author of many books, including “Introduction to Windows XP Embedded”, “Introduction to Windows Embedded CE 6.0”, that was translated into English and published as «Windows Embedded CE 6.0 Fundamentals».

 

Specialties: Microsoft Windows Embedded, Windows Infrastructure Solutions, Application Development, Information Worker Solutions, Industrial Automation, General Embedded

 

February 1st, 2013

Leave a Comment