Статьи

Создание программы чтения RSS для Windows Phone 7 — Разработка структуры

Эта статья состоит из двух отдельных частей. В первой части я описываю пользовательский интерфейс и структурные аспекты приложения, а во второй части я описываю логику приложения.

Начиная

Сейчас, когда для Windows Phone 7 разрабатывается множество приложений, я не видел ни одного устройства для чтения RSS, поэтому я решил написать свое собственное — просто для удовольствия. Думая об очень простом рабочем процессе приложения, я пришел к этой обобщенной идее:

Существует представление, которое отображает содержимое канала — заголовок и краткий обзор элемента (подробности). Это то, что интересует пользователя. Может быть неограниченное количество каналов, которые агрегируются, но в конце содержимое каждого канала отображается вместе с другими в главном представлении. Может быть немного грязно, но на данный момент я экспериментирую.

Конечный пользователь может управлять каналами, определяя набор URL-адресов каналов, которые впоследствии сохраняются в качестве параметров приложения в изолированном хранилище. Эти URL считаются источниками агрегации.

The data retrieved from feeds is stored in a non-persistent manner in a dictionary. When the application is closed or deactivated, the data is lost and when the application is launched again it should be re-aggregated.

Working on the solution

I created a simple Windows Phone 7 Application (Silverlight). I started with the main page, and I modified its XAML markup to include a list. This was added to the existing Grid.

<Grid x:Name="ContentGrid" Grid.Row="1">
<ListBox ItemsSource="{Binding FeedItems}" Margin="13,0,13,0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,10,0,10">
<TextBlock x:Name="ItemName" Text="{Binding ItemTitle}" Style="{StaticResource PhoneTextNormalStyle}"></TextBlock>
<TextBlock x:Name="ItemDetails" Text="{Binding ItemDetails}" Style="{StaticResource PhoneTextSubtleStyle}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>

I am defining a custom data template for each item, since there will be the title and feed entry details displayed in one item. Don’t look at the ItemsSource and Text properties at this time – I will talk about the binding endpoints later on.

Also, on the main page there will be the application bar that will allow quick access to some feed tools – refreshing the existing items and managing feed URLs. So the following XAML snippet is added to the main page:

<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem x:Name="reloadFeeds" Text="reload feeds" Click="reloadFeeds_Click" ></shell:ApplicationBarMenuItem>
<shell:ApplicationBarMenuItem x:Name="manageFeeds" Text="manage feeds" Click="manageFeeds_Click"></shell:ApplicationBarMenuItem>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

If you are wondering what an application bar is, here is what it looks like:

That is pretty much it for the main page. Obviously, you can (and you should) change the application and page title.

The next step is creating the page that will be displaying the list of feed URLs and will allow the end user to manage those. I added a portrait page to the solution and named it ManageFeeds.xaml.

Its default structure is left intact, but I added an additional grid in the second row of the existing layout grid:

<Grid x:Name="ContentGrid" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="400"></RowDefinition>
<RowDefinition Height="100"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<ListBox x:Name="MainList" Margin="13,0,13,0" Grid.Row="0" ItemsSource="{Binding FeedList}"></ListBox>
<TextBlock Text="FEED URL:" Grid.Row="1" FontSize="25" Margin="10,35,0,0"></TextBlock>
<TextBox x:Name="urlHolder" Grid.Row="1" Margin="120,10,70,10"></TextBox>
<Button Grid.Row="1" Margin="400,10,0,10" Content="+" FontSize="30" Click="Button_Click"></Button>
<Button Grid.Row="2" Content="REMOVE SELECTED" Click="Remove_Click"></Button>
</Grid>

Here I have three separate rows. The first one is used to hold the list of feed URLs, the second one is used to hold the TextBox control that will contain a new URL and an Add button, while the third row only holds a Remove Selected button that will let the user remove the selected item out of those that are currently registered. Once again, don’t look at the binding endpoints for now.

Overall, when you run the application, the page looks like this:

To make working with RSS feeds easier, I created a helper class, called FeedData, that is placed inside the FeedHelper folder that you should create:

The structue of the above mentioned class is relatively simple for now:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace WP7_FeedReader.FeedHelper
{
public class FeedData
{
public ObservableCollection<string> FeedList { get; set; }
}
}

I will be using the FeedList collection to store the list of feed URLs (temporary — later on those will be transfered to isolated storage).

In the App.xaml.cs I am referencing a FeedData class instance and populating the FeedList property with existing saved URLs. These URLs are stored in the isolated storage as application settings. Since at this point I am not using any other settings, I am simply considering an entry in  IsolatedStorageSettings.ApplicationSettings to be a user-defined feed URL.

public static FeedHelper.FeedData Data;

// Constructor
public App()
{
Data = new FeedHelper.FeedData();
Data.FeedList = new ObservableCollection<string>();
if (System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings.Count != 0)
{
foreach (string key in System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings.Keys)
{
Data.FeedList.Add(System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings[key].ToString());
}
}

UnhandledException += Application_UnhandledException;
InitializeComponent();
InitializePhoneApplication();
}

Я разработал приложение таким образом, поэтому есть основная модель, используемая для отображения элемента, и модель элемента, которая будет представлять каждый элемент фида отдельно. Этот принцип связан с двумя отдельными классами, которые я собираюсь создать:
MainModel и
ItemModel .

В вашем решении создайте папку с именем ViewModel и создайте в ней два новых файла классов — MainModel.cs и ItemModel.cs . Теперь для ItemModel я определил следующую структуру класса:

using System;
using System.ComponentModel;

namespace WP7_FeedReader.ViewModel
{
public class ItemModel : INotifyPropertyChanged
{
private string itemTitle;
public string ItemTitle
{
get { return itemTitle; }
set
{
if (value != itemTitle)
{
itemTitle = value;
NotifyPropertyChanged("ItemTitle");
}
}
}

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

public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

The properties will represent the feed item title and details accordingly. Instances of this class will be passed to the MainModel, that has an ObservableCollection used specifically to store feed items.

I defined the structure for MainModel the following way:

using System;
using System.Collections.ObjectModel;
using System.IO.IsolatedStorage;

namespace WP7_FeedReader.ViewModel
{
public class MainModel
{
public ObservableCollection<ItemModel> FeedItems {get; set;}
}
}

The FeedItems property is modifed publicly, specific to the current instance — a refreshed item list will automatically re-bind the ListBox on the main page.

Now that you know the structure of MainModel and ItemModel, you should also know that App.xaml.cs is also the place where the MainModel instance is initialized for the main page to use:

public static ViewModel.MainModel Model;

Inside the App constructor:

Model = new ViewModel.MainModel();

You can see where the binding is pointing to, but you need to explicitly specify the DataContext for each page. First of all, in the main page, you need to set the DataContext to the MainModel instance defined in App:

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
this.DataContext = App.Model;

base.OnNavigatedTo(e);
}

The ManageFeeds page should have its DataContext set to the known FeedData instance:

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
this.DataContext = App.Data;
base.OnNavigatedTo(e);
}

You can see now that all binding links are satisfied, since proper class instances are referenced.

The ‘skeleton’ for the RSS reader is ready, so it is now time to work on the logic of the application, that will be covered in the next article.