Лабораторный практикум 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();
Теперь можно скомпилировать приложение, запустить на эмуляторе или телефоне и проверить его функциональность.