Лабораторный практикум 3
Лабораторная работа №11. RSS-новости
Задание
Создать приложение для Windows Phone 7, загружающее из Интернета RSS-новости по разным тематикам (воспользоваться функциональностью класса WebClient). Предусмотреть возможность сохранения загруженных лент в изолированном хранилище.
Освоение
- работа с HTTP посредством WebClient
- взаимодействие Linq и XML
- связывание (Binding)
- задача запуска (WebBrowserTask)
- изолированное хранилище
Описание
Создадим новый проект Silverlight for Windows Phone – Windows Phone Application.
Откроем файл разметки главной страницы MainPage.xaml. Будем создавать приложение на основе элемента Panorama. Для этого подключим библиотеку Microsoft.Phone.Controls и добавим пространство имен:
xmlns:ctrl="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
Наша Panorama будет состоять из 5 разделов (PanoramaItem): политика, экономика, спорт, музыка и новости MSDN. Каждый PanoramaItem будет включать список ListBox. Списки будем заполнять динамически с использованием шаблонов (Template) и связывания (Binding). Каждая новость будет иметь дату (pubDate) и заголовок (title). В коде для каждого списка будем задавать источник привязки. Содержимое текстовых блоков списков привяжем к источнику привязки с помощью ключевого слова Binding. Следующая структура:
<TextBlock Text="{Binding pubDate}" />
означает, что свойству Text текстового блока автоматически будет присваиваться значение поля pubDate источника привязки.
Также в файле разметки определим стили для текстовых блоков с помощью тега:
<phone:PhoneApplicationPage.Resources>
Добавим две кнопки меню в разметку: "Обновить" и "Перейти". При нажатии на кнопку "Обновить" будем загружать ленту из Интернета и разбивать ее на отдельные новости. При этом соответствующий список будет заполняться автоматически. При нажатии на кнопку "Перейти" будем открывать браузер Windows Phone 7 со ссылкой на выделенную новость.
Итак, файл разметки примет следующий вид:
<phone:PhoneApplicationPage
x:Class="Wp7IUSLab14.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:ctrl="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="696"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<phone:PhoneApplicationPage.Resources>
<Style x:Key="lblDate" TargetType="TextBlock">
<Setter Property="Foreground" Value="LightBlue" />
<Setter Property="FontSize" Value="20" />
<Setter Property="FontStyle" Value="Italic" />
</Style>
<Style x:Key="lblTitle" TargetType="TextBlock">
<Setter Property="FontSize" Value="22" />
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</phone:PhoneApplicationPage.Resources>
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<!--StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="MY APPLICATION"
Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="page name" Margin="9,-7,0,0"
Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel-->
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ctrl:Panorama Name="pnrmRSS" Title="новости">
<ctrl:PanoramaItem Header="политика">
<ListBox Name="lstRssPolit">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding pubDate}" Style="{StaticResource lblDate}" />
<TextBlock Text="{Binding title}" Style="{StaticResource lblTitle}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ctrl:PanoramaItem>
<ctrl:PanoramaItem Header="экономика">
<ListBox Name="lstRssEcon">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding pubDate}" Style="{StaticResource lblDate}" />
<TextBlock Text="{Binding title}" Style="{StaticResource lblTitle}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ctrl:PanoramaItem>
<ctrl:PanoramaItem Header="спорт">
<ListBox Name="lstRssSport">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding pubDate}" Style="{StaticResource lblDate}" />
<TextBlock Text="{Binding title}" Style="{StaticResource lblTitle}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ctrl:PanoramaItem>
<ctrl:PanoramaItem Header="музыка">
<ListBox Name="lstRssMusic">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding pubDate}" Style="{StaticResource lblDate}" />
<TextBlock Text="{Binding title}" Style="{StaticResource lblTitle}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ctrl:PanoramaItem>
<ctrl:PanoramaItem Header="MSDN">
<ListBox Name="lstRssMSDN">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding pubDate}" Style="{StaticResource lblDate}" />
<TextBlock Text="{Binding title}" Style="{StaticResource lblTitle}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ctrl:PanoramaItem>
</ctrl:Panorama>
</Grid>
</Grid>
<!--Sample code showing usage of ApplicationBar-->
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
<shell:ApplicationBarIconButton IconUri="/Images/appbar.refresh.rest.png"
Text="Обновить" Click="MenuRefresh_Click" />
<shell:ApplicationBarIconButton IconUri="/Images/appbar.next.rest.png"
Text="Перейти" Click="MenuGo_Click" />
<!--shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text="MenuItem 1"/>
<shell:ApplicationBarMenuItem Text="MenuItem 2"/>
</shell:ApplicationBar.MenuItems-->
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
</phone:PhoneApplicationPage>
Перейдем к написанию кода.
Создадим класс для новостей (к полям данного класса и будем привязывать содержимое текстовых полей разметки):
public class PostMessage
{
public string pubDate { get; set; }
public string title { get; set; }
public string link { get; set; }
}
Для того чтобы загружать RSS-ленту добавим в класс 2 переменные и 5 констант со ссылками на источники:
private const string RSS_POLIT = "http://news.yandex.ru/politics.rss";
private const string RSS_ECNMK = "http://news.yandex.ru/business.rss";
private const string RSS_SPORT = "http://news.yandex.ru/sport.rss";
private const string RSS_MUSIC = "http://news.yandex.ru/music.rss";
private const string RSS_MSDN = "http://blogs.msdn.com/b/rudevnews/rss.aspx";
private WebClient client;
private int m_nRssLoadIndex = -1;
Добавим в конструктор инициализацию веб-клиента:
client = new WebClient(); client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(WebClient_DownloadStringCompleted);
При нажатии на кнопку "Обновить" будем загружать веб-содержимое по ссылкам:
private void MenuRefresh_Click(object sender, EventArgs e)
{
m_nRssLoadIndex = pnrmRSS.SelectedIndex;
switch (m_nRssLoadIndex)
{
case 0:
LoadRSS(new Uri(RSS_POLIT));
break;
case 1:
LoadRSS(new Uri(RSS_ECNMK));
break;
case 2:
LoadRSS(new Uri(RSS_SPORT));
break;
case 3:
LoadRSS(new Uri(RSS_MUSIC));
break;
case 4:
LoadRSS(new Uri(RSS_MSDN));
break;
default:
MessageBox.Show("Ошибка при загрузке новостей.");
break;
}
}
private void LoadRSS(Uri url)
{
client.DownloadStringAsync(url);
}
Напишем обработчик окончания загрузки (будем отправлять загруженную строку e.Result на обработку):
// Окончание загрузки строки
private void WebClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null)
{
ParseAndDislayRSS(e.Result);
}
}
При обработке будем использовать встроенные средства XML-парсера и Linq. Для этого добавим в код директиву:
using System.Xml.Linq;
Затем с помощью LInq запроса получим коллекцию новостей (типа PostMessage). Данную коллекцию будем задавать как источник для списков (свойство ItemsSource).
private void ParseAndDislayRSS(string strRSS, bool isSaveToStorage = true)
{
//получаем новости, парсим их и присваиваем списку
XElement twitterElements = XElement.Parse(strRSS);
var postList =
from tweet in twitterElements.Descendants("item")
select new PostMessage
{
title = tweet.Element("title").Value,
pubDate = tweet.Element("pubDate").Value,
link = tweet.Element("link").Value
};
switch (m_nRssLoadIndex)
{
case 0:
lstRssPolit.ItemsSource = postList;
if (isSaveToStorage) SaveToIsolatedStorage(strIStoragePolit, strRSS);
break;
case 1:
lstRssEcon.ItemsSource = postList;
if (isSaveToStorage) SaveToIsolatedStorage(strIStorageEconom, strRSS);
break;
case 2:
lstRssSport.ItemsSource = postList;
if (isSaveToStorage) SaveToIsolatedStorage(strIStorageSport, strRSS);
break;
case 3:
lstRssMusic.ItemsSource = postList;
if (isSaveToStorage) SaveToIsolatedStorage(strIStorageMusic, strRSS);
break;
case 4:
lstRssMSDN.ItemsSource = postList;
if (isSaveToStorage) SaveToIsolatedStorage(strIStorageMsdn, strRSS);
break;
default:
MessageBox.Show("Ошибка при обработке новостей.");
break;
}
}
Загруженную ленту сразу же будем сохранять в изолированном файловом хранилище. Для этого в классе определим глобальные константы с именами файлов:
private const string strIStoragePolit = "Lab14Polit.xml";
private const string strIStorageEconom = "Lab14Econom.xml";
private const string strIStorageSport = "Lab14Sport.xml";
private const string strIStorageMusic = "Lab14Music.xml";
private const string strIStorageMsdn = "Lab14Msdn.xml";
Для того чтобы воспользоваться изолированным хранилищем определим директивы:
using System.IO.IsolatedStorage; using System.IO;
В конструкторе класса будем загружать новость из изолированного хранилища (аналогичные строки для всех тем новостей):
if (IsIsolatedStorageExist(strIStoragePolit))
{
m_nRssLoadIndex = 0;
ParseAndDislayRSS(LoadFromIsolatedStorage(strIStoragePolit), false);
}
Работу с изолированным хранилищем обеспечивают следующие четыре функции:
private void SaveToIsolatedStorage(string histFileName, string histText)
{
IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
IsolatedStorageFileStream fileStream = fileStorage.CreateFile(histFileName);
StreamWriter sw = new StreamWriter(fileStream);
sw.Write(histText);
sw.Close();
fileStream.Close();
}
private string LoadFromIsolatedStorage(string histFileName)
{
IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
IsolatedStorageFileStream fileStream = fileStorage.OpenFile(histFileName, System.IO.FileMode.Open);
StreamReader sr = new StreamReader(fileStream);
string strRes = sr.ReadToEnd();
sr.Close();
fileStream.Close();
return strRes;
}
private bool IsIsolatedStorageExist(string histFileName)
{
IsolatedStorageFile sileStorage = IsolatedStorageFile.GetUserStoreForApplication();
return sileStorage.FileExists(histFileName);
}
private void RemoveIsolatedStorage(string histFileName)
{
if (IsIsolatedStorageExist(histFileName))
{
IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
fileStorage.DeleteFile(histFileName);
}
}
При нажатии на кнопку "Перейти" будем создавать экземпляр класса WebBrowserTask (задача запуска) с указанием прямой ссылки на новость (в начале файла необходимо определить директиву "using Microsoft.Phone.Tasks"):
WebBrowserTask webTask = new WebBrowserTask();
webTask.Uri = new Uri(((PostMessage)(lstRssPolit.SelectedItem)).link);
webTask.Show();
Теперь можно скомпилировать приложение, запустить на эмуляторе или телефоне и проверить его функциональность.