Статьи

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

ОБНОВЛЕНИЕ: Если у вас возникли проблемы с подачей, вызванные неверной датой, обязательно прочитайте этот пост и загрузите обновленный проект.

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

Добавление и удаление URL фидов

Как известно, приложение поддерживает несколько каналов. Поэтому контент агрегируется из более чем одного канала одновременно. Итак, на странице ManageFeeds вы должны добавить обработчик событий для кнопки « Добавить» , который позволит пользователю добавлять несколько URL-адресов. Если вы посмотрите на существующую разметку XAML, обработчик событий уже объявлен в разметке элемента, поэтому все, что вам нужно сделать, это реализовать его:

private void Button_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(urlHolder.Text.Trim()))
{
if (!App.Data.FeedList.Contains(urlHolder.Text))
{
if (Uri.IsWellFormedUriString(urlHolder.Text, UriKind.Absolute))
{
App.Data.FeedList.Add(urlHolder.Text);
urlHolder.Text = string.Empty;
}
}
}
}

Я проверяю, правильно ли передан URL-адрес (это позволит избежать проблем с недействительными URL-адресами) через Uri.IsWellFormattedUriString . Я думал об использовании веб-запроса, чтобы проверить, работает ли URL, но, поскольку это телефон, это может быть слишком дорого с точки зрения трафика.

Поскольку FeedList является ObservableCollection и привязан к ListBox , содержащему URL-адреса, ListBox автоматически перепривязывается, и вы увидите, что элемент добавлен в список без дополнительного кода.

По этой причине удаление элемента связано только с удалением фактического элемента из списка.

private void Remove_Click(object sender, RoutedEventArgs e)
{
if (MainList.SelectedItem != null)
{
App.Data.FeedList.Remove(MainList.SelectedItem.ToString());
}
}

Сохранение URL фидов для последующего повторного использования

Это делается путем реализации метода Save внутри класса App :

void SaveList()
{
IsolatedStorageSettings.ApplicationSettings.Clear();

if (App.Data.FeedList.Count != 0)
{
foreach (string item in App.Data.FeedList)
{
IsolatedStorageSettings.ApplicationSettings.Add(item, item);
}
}

IsolatedStorageSettings.ApplicationSettings.Save();
}

Это обновит настройки приложения с элементами, которые в настоящее время присутствуют в коллекции. Позднее этот метод должен вызываться из Application_Closing (срабатывает при закрытии приложения) и Application_Deactivation (срабатывает при отправке приложения в фоновом режиме).

// Code to execute when the application is deactivated (sent to background)
// This code will not execute when the application is closing
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
SaveList();
}

// Code to execute when the application is closing (eg, user hit Back)
// This code will not execute when the application is deactivated
private void Application_Closing(object sender, ClosingEventArgs e)
{
SaveList();
}

Получение содержимого канала

Я был удивлен этим, но по умолчанию я не мог получить доступ к System.ServiceModel.Syndication . Даже при попытке добавить ссылку, библиотеки не было в списке. Решение этой проблемы простое — все, что вам нужно сделать, это использовать тот же диалог Добавить ссылку, перейти в <SystemDrive>: \ Program Files \ Microsoft SDK \ Silverlight \ v4.0 \ Libraries \ Client и выбрать System.ServiceModel.Syndication. dll :

После этого в своем классе FeedHelper я  создаю метод GetItems () :

public static void GetItems()
{
App.Model.FeedItems.Clear();

foreach (string item in App.Data.FeedList)
{
WebClient client = new WebClient();
client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
client.DownloadStringAsync(new Uri(item));
}
}

Прежде всего, я очищаю существующий кэш элементов канала, а затем инициализирую WebClient для каждого зарегистрированного канала. Для каждого канала создается отдельный экземпляр WebClient, поскольку один экземпляр не может обрабатывать несколько загрузок одновременно. Как видите, этот метод привязан к DownloadStringCompletedEventHandler для экземпляра WebClient, который используется внутри него. Обработчик события выглядит так:

static void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (!string.IsNullOrEmpty(e.Result))
{
XmlReader reader = XmlReader.Create(new StringReader(e.Result));
SyndicationFeed feed = SyndicationFeed.Load(reader);

foreach (SyndicationItem sItem in feed.Items)
{
if ((sItem != null) && (sItem.Summary != null) && (sItem.Title != null))
{
App.Model.FeedItems.Add(
new ViewModel.ItemModel()
{
ItemDetails = sItem.Summary.Text,
ItemTitle = sItem.Title.Text
}
);
}
}
}
}

Здесь я проверяю, есть ли результат для URL, используемого для поиска содержимого канала. Если есть, то я создаю экземпляр XmlReader , который получит содержимое в формате XML, а затем передаю его экземпляру SyndicationFeed, который значительно упрощает обработку RSS-каналов. Если элемент не равен нулю (как и его основные используемые свойства), то для каждого элемента создается новый экземпляр ItemModel, который добавляется в коллекцию элементов.

ПРИМЕЧАНИЕ. Чтобы избежать исключений NullReference, в конструкторе приложения добавьте следующую строку:

Model.FeedItems = new ObservableCollection<ViewModel.ItemModel>();

На той же странице ManageFeeds я переопределяю метод OnNavigatedFrom :

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
FeedHelper.FeedData.GetItems();
base.OnNavigatedFrom(e);
}

Таким образом, содержимое канала обновляется, когда пользователь отходит от списка каналов.

На главной странице для пункта меню « Перезагрузить каналы » на панели приложения необходимо реализовать те же функции. Поскольку в XAML объявлен обработчик событий, все, что вам нужно, это предоставить вызов метода в коде:

private void reloadFeeds_Click(object sender, EventArgs e)
{
FeedHelper.FeedData.GetItems();
}

Если вы запустите приложение и добавите несколько действительных каналов, то при перезагрузке списка вы увидите агрегированный контент.

Открытие постов

Теперь приложение кажется полезным, но не в такой степени, как могло бы быть. Каждый элемент синдикации указывает на фактическое опубликованное сообщение. Так почему бы не открыть этот пост на новой странице?

Для этого добавьте новую книжную страницу и назовите ее DetailsView.xaml .

Теперь вам нужно изменить существующий класс ItemModel, включив в него еще одно свойство — ItemLink , в котором будет храниться назначенная ссылка для публикации. Свойство вместе со связанным приватным полем должно выглядеть так:

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

Добавьте его в ItemModel и после этого перейдите к обработчику событий client_DownloadStringCompleted (n FeedHelper ) и измените добавленный экземпляр ItemModel, добавив еще одно свойство — ItemLink :

App.Model.FeedItems.Add(
new ViewModel.ItemModel()
{
ItemDetails = sItem.Summary.Text,
ItemTitle = sItem.Title.Text,
ItemLink=sItem.Links[0].Uri.ToString()
}
);

Теперь на странице, которую вы недавно создали ( DetailsView ), во внутренней сетке, добавьте элемент управления WebBrowser. Моя разметка сетки выглядит так:

<Grid x:Name="LayoutRoot" Background="Transparent">   
<phone:WebBrowser Name="webBrowser1"/>
</Grid>

Все хорошо на этом этапе, но для ListBox на главной странице необходим обработчик событий , поэтому при нажатии на один элемент он переходит на страницу с помощью веб-браузера и открывает назначенную страницу. Для этого убедитесь, что ListBox на главной странице имеет связанный обработчик события SelectionChanged :

<ListBox x:Name="MainList" ItemsSource="{Binding FeedItems}" Margin="13,0,13,0" SelectionChanged="ListBox_SelectionChanged">

Фактический обработчик события:

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (MainList.SelectedItems.Count != 0)
{
NavigationService.Navigate(new Uri("/DetailsView.xaml?item=" + MainList.SelectedIndex, UriKind.Relative));
}
}

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

void DetailsView_Loaded(object sender, RoutedEventArgs e)
{
string index = "";
if (NavigationContext.QueryString.TryGetValue("item", out index))
{
int _index = int.Parse(index);
webBrowser1.Navigate(new Uri(App.Model.FeedItems[_index].ItemLink));
}
}

И это все. После запуска приложения перезагрузите каналы и нажмите на любой элемент. Вы увидите открытую страницу сведений и соответствующую ссылку, на которую вы переходите в элементе управления WebBrowser :

Если вы хотите загрузить проект, вы можете сделать это, нажав здесь [ZIP-файл — решение Visual Studio 2010]. Конечно, средства разработки Windows Phone 7 требуются.