|
Будьте добры сообщите какой срок проверки заданий и каким способом я буду оповещен! |
Работа в офф-лайне и файл настроек
XAML+C#. Практическое занятие №3
Задание: продолжая проект, полученный в результате выполнения второго практического задания, вынесите список источников новостей в отдельный файл и добавьте кеширование данных и вывод информации о наличии интернет-соединения в приложении.
- В качестве подтверждения выполнения лабораторной работы от вас потребуется предоставить скриншот главной страницы приложения, отображающей данные при отсутствии интернет-соединения.
ЗАМЕЧАНИЕ: напоминаем, что ваше приложение должно быть уникальным:
- Использовать в качестве источников данных источники, отличные от тех, которые приводятся в инструкциям к практическим занятиям
- Внешний вид приложения должен соответствовать выбранной вами тематике приложения
Добавление файла настроек
Начнем с достаточно простой задачи – добавление файла настроек. Для этого, щелкнув правой кнопкой мыши по проекту, выберете Add, New Folder и назовите новую папку Data.
Теперь, щелкнув правой кнопкой мыши по папке Data, выберите Add, New Item …, Visual C#, Data, XML File, назовите его config.xml и нажмите кнопку Add.
Давайте сделаем файл конфигурации с заделом на будущее, чтобы можно было из него конфигурировать как и что будет отображаться у нас в приложении:
<?xml version="1.0" encoding="utf-8" ?>
<config>
<application>
<settings>
<title>Мега-ридер</title>
</settings>
</application>
<feeds>
<feed>
<id>1</id>
<title>bash.im</title>
<url>http://bash.im/rss/</url>
<description>Это bash!</description>
<type>rss</type>
<view>variablesize</view>
<policy>http://wintheweb.ru/openprivacy.html</policy>
</feed>
<feed>
<id>2</id>
<title>Комиксы bash.im</title>
<url>http://bash.im/rss/comics.xml</url>
<description>Комиксы</description>
<type>images</type>
<view>variablesize</view>
<policy>http://wintheweb.ru/openprivacy.html</policy>
</feed>
</feeds>
</config>Итак, у нас корневой элемент config, содержит 2 элемента: application и feeds. В элементе application будем хранить настройки приложения, а в feeds – наши источники RSS с идентификатором (id), названием (title), ссылкой на RSS (url), описанием (description), типом контента (type), вариантом отображения (view) и соответствующей политикой конфиденциальности (policy).
Теперь осталось прочитать этот файл и в соответствии с настройками провести инициализацию приложения.
Желая развивать приложение дальше, мы задумываемся о том, что надо бы иметь возможность изменять эти настройки. Поэтому, первая задача, которую мы должны решить – при первом запуске приложения скопировать файл настроек из места куда он развернется, туда, где мы можем в него писать.
Для того, чтобы файл настроек включился в сборку, пометьте его в свойствах, как Content, который будет копироваться, если он более новый.
В файл App.xaml.cs добавим новую функцию:
public static async Task<bool> CopyConfigToLocalFolder()
{
//получаем папку с именем Data в локальной папке приложения
var localFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("Data",CreationCollisionOption.OpenIfExists);
//получаем список файлов в папке Data
var files = await localFolder.GetFilesAsync();
//получаем список всех файлов, имя которых config.xml
var config = from file in files
where file.Name.Equals("config.xml")
select file;
//нам возращается IEnumrable - а он гарантирует тольок один проход
//копируем в массив - если не беспокоитесь об этом - просто уберите эту строчку
//а в условии проверяйте config.Count()
// if (config.Count() == 0) { }
var configEntries = config as StorageFile[] ?? config.ToArray();
//то же самое, что config.Count() == 0, но гарантиует от странных ошибок
//т.е. в целом мы проверили, что файла config.xml нет в подпапке Data
//папки локальных данных приложения
if (!configEntries.Any())
{
//получаем папку Data из установленого приложения
var dataFolder = await Package.Current.InstalledLocation.GetFolderAsync("Data");
//получаем файл сonfig.xml
var configFile = await dataFolder.GetFileAsync("config.xml");
//копируем его в локальную папку данных
await configFile.CopyAsync(localFolder);
return true;
}
return false;
}Она из места куда установлена программа, в папке Data находит файл config.xml и копирует его в подпапку Data локальной папки приложения, причем, если папка отсутствует – она создается, а если папка уже присутствует и в ней есть файл, копирования не происходит. Мы это делаем, чтобы в дальнейшем сохранять в config.xml настройки и чтобы они не перезаписывались при исполнении этой функции.
Что конкретно выполнят каждая из строчек кода – можно узнать из комментария над ней.
И, наконец, добавим необходимые using, если вы не сделали этого раньше, используя команды контекстного меню:
Теперь нужно добавить чтение файла и разбор его содержимого.
В рамках данной лабораторной мы будем разбирать только feeds часть XML, в качестве самостоятельной работы добавьте разбор всего XML, дополните его своими секциями и также их считайте в коде.
Итак, приступим.
Сначала добавим в наш проект класс для представления данных RSS потока Feed, аналогично тому, как мы добавляли config.xml:
В получившимся файле, изменим определение класса Feed, чтобы оно стало выглядеть так:
public class Feed
{
public string id { get; set; }
public string title{ get; set; }
public string url { get; set; }
public string description { get; set; }
public string type { get; set; }
public string view { get; set; }
public string policy { get; set; }
}Итак, у нас теперь есть класс с полями, куда мы можем считывать нашу конфигурацию.
Осталось прочитать XML файл и разобрать его.
Добавим для этого следующую функцию в файл App.xaml.cs:
public static async Task<IEnumerable<Feed>> ReadSettings()
{
//получаем папку в которой находится наш файл конфигурации
var localFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync
("Data", CreationCollisionOption.OpenIfExists);
//получаем список файлов в папке Data
var files = await localFolder.GetFilesAsync();
//получаем список всех файлов, имя которых config.xml
var config = from file in files
where file.Name.Equals("config.xml")
select file;
//нам возращается IEnumrable - а он гарантирует тольок один проход
//копируем в массив - если не беспокоитесь об этом - просто уберите эту строчку
//а в условии проверяйте config.Count()
// if (config.Count() == 0) { }
var configEntries = config as StorageFile[] ?? config.ToArray();
//то же самое, что config.Count() == 0, но гарантиует от странных ошибок
//т.е. в целом мы проверили, что файла config.xml нет в подпапке Data
//папки локальных данных приложения
if (!configEntries.Any())
await CopyConfigToLocalFolder();
//получаем конфигурационный файл
var configFile = await localFolder.GetFileAsync("config.xml");
//считываем его как текст
var configText = await FileIO.ReadTextAsync(configFile);
//загружаем его как XML
XElement configXML = XElement.Parse(configText);
//разбираем XML инициализируя данным массив
var feeds =
from feed in configXML.Descendants("feed")
select new Feed
{
id = feed.Element("id").Value,
title= feed.Element("title").Value,
url = feed.Element("url").Value,
description = feed.Element("description").Value,
type = feed.Element("type").Value,
view = feed.Element("view").Value,
policy = feed.Element("policy").Value
};
//отдаем наружу массив с конфигурацией RSS потоков
return feeds;
}И, наконец, добавим необходимые using, если вы не сделали этого раньше, используя команды контекстного меню:
using System.Threading.Tasks; using System.Xml.Linq;
Все что делается в этой функции – описано построчно в комментариях. Итак, у нас уже все готово, чтобы теперь у нас все инициализировалось из XML файла.
Для этого в функции LoadState в файле GroupedItemsPage.xaml.cs заменим код
RSSDataSource.AddGroupForFeedAsync("http://blogs.msdn.com/b/stasus/rss.aspx");
RSSDataSource.AddGroupForFeedAsync("http://www.spugachev.com/feed");на код:
var feeds = await App.ReadSettings();
foreach (var feed in feeds)
{
await RSSDataSource.AddGroupForFeedAsync(feed.url);
}Т.е. мы получаем список RSS потоков и инициализируем нашу читалку пробегая по ним всем.
Сделайте самостоятельно проверку, что файл в из поставки приложения отличается от того, который уже скопирован и, в этом случае, копируйте файл из поставки приложения поверху. Таким образом у вас появится возможность обновлениями config.xml обновлять то, что показывает приложение.
Добавление офф-лайнового режима работы
Для работы при отсутствии интернета можно закэшировать данные, которые скачиваются вашим приложением.
Для этого сначала поменяем функцию AddGroupForFeedAsync чтобы она выглядела следующим образом:
public static async Task<bool> AddGroupForFeedAsync(string feedUrl, string ID)
{
string clearedContent = String.Empty;
if (RSSDataSource.GetGroup(feedUrl) != null) return false;
var feed = await new SyndicationClient().RetrieveFeedAsync(new Uri(feedUrl));
//получаем папку с именем Data в локальной папке приложения
var localFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync
("Data",CreationCollisionOption.OpenIfExists);
//получаем/перезаписываем файл с именем "ID".rss
var fileToSave = await localFolder.CreateFileAsync(ID+".rss",CreationCollisionOption.ReplaceExisting);
//сохраняем фид в этот файл
await feed.GetXmlDocument(SyndicationFormat.Rss20).SaveToFileAsync(fileToSave);
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 = GetImageFromPostContents(i);
if (i.Summary != null)
clearedContent = i.Summary.Text;
else
if (i.Content != null)
clearedContent = i.Content.Text;
if (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;
}
Добавленное выделено цветом.
- Мы добавили параметр в вызов функции – ID RSS в нашем конфигурационном файле
- Мы добавили вызовы нескольких функций для сохранения результата в локальную папку Data
И, наконец, добавим необходимые using, если вы не сделали этого раньше, используя команды контекстного меню:
using Windows.Storage;
Также в функции LoadState в файле GroupedItemsPage.xaml.cs изменим код вызова функции
var feeds = await App.ReadSettings();
foreach (var feed in feeds)
{
await RSSDataSource.AddGroupForFeedAsync(feed.url,feed.id);
}Запустите и проверьте, что все работает.
Теперь при отсутствии интернета – мы можем выводить данные сохраненных файлов.
Для начала перегрузим функцию AddGroupForFeedAsync чтобы она загружали RSS поток из сохраненного файла. Код очень простой – мы создаем новый Feed и загружаем его из XML документа:
public static async Task<bool> AddGroupForFeedAsync(StorageFile sf)
{
string clearedContent = String.Empty;
if (RSSDataSource.GetGroup(sf.DisplayName) != null) return false;
var feed = new SyndicationFeed();
feed.LoadFromXml(await XmlDocument.LoadFromFileAsync(sf));
var feedGroup = new RSSDataGroup(
uniqueId: sf.DisplayName,
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 = GetImageFromPostContents(i);
if (i.Summary != null)
clearedContent = i.Summary.Text;
else
if (i.Content != null)
clearedContent = i.Content.Text;
if (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;
}Во всем остальном функция осталась такой же самой.
Теперь в в функции LoadState в файле GroupedItemsPage.xaml.cs дополним код обработки отсутствия сети. Но для начала добавим в XAML этой же страницы индикацию режима оффлайн работы. В XAML заголовка страницы необходимо добавить еще одно определение колонки, а в контент еще один TextBox:
<!-- Back button and pagetitle-->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="backButton" Click="GoBack" IsEnabled="{Binding Frame.CanGoBack,
ElementName=pageRoot}" Style="{StaticResource BackButtonStyle}"/>
<TextBlock x:Name="pageTitle" Text="{StaticResource AppName}" Grid.Column="1"
IsHitTestVisible="false" Style="{StaticResource PageHeaderTextStyle}"/>
<TextBlock x:Name="OfflineMode" Grid.Column="2" IsHitTestVisible="false"
Style="{StaticResource PageHeaderTextStyle}" Text="оффлайнн режим" FontSize="18"
Foreground="#DEF40B0B" HorizontalAlignment="Right"
VerticalAlignment="Top" Height="37" Margin="0,-16,18,0" Visibility="Collapsed"/>
</Grid>
И допишем функцию:
protected async override void LoadState(Object navigationParameter,
Dictionary<String, Object> pageState)
{
if (NetworkInformation.GetInternetConnectionProfile().GetNetworkConnectivityLevel() !=
NetworkConnectivityLevel.InternetAccess)
{
//получаем папку с именем Data в локальной папке приложения
var localFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync
("Data", CreationCollisionOption.OpenIfExists);
//получаем список файлов в папке Data
var cachedFeeds = await localFolder.GetFilesAsync();
//получаем список всех файлов, имя которых config.xml
var feedsToLoad = from feeds in cachedFeeds
where feeds.Name.EndsWith(".rss")
select feeds;
//нам возращается IEnumrable - а он гарантирует тольок один проход
//копируем в массив
var feedsEntries = feedsToLoad as StorageFile[] ?? feedsToLoad.ToArray();
if (feedsEntries.Any())
{
this.DefaultViewModel["Groups"] = RSSDataSource.AllGroups;
foreach (var feed in feedsEntries)
{
await RSSDataSource.AddGroupForFeedAsync(feed);
}
OfflineMode.Visibility = Visibility.Visible;
}
else
{
var msg = new MessageDialog("The program need an internet connection to work.
Please check it and restart the porgram.");
await msg.ShowAsync();
}
}
else
{
this.DefaultViewModel["Groups"] = RSSDataSource.AllGroups;
OfflineMode.Visibility = Visibility.Collapsed;
var feeds = await App.ReadSettings();
foreach (var feed in feeds)
{
await RSSDataSource.AddGroupForFeedAsync(feed.url, feed.id);
}
}
}
Каждый раз, когда вы приходите на страницу LoadState – проверяется, если ли у вас подключение к сети, и если есть – загружаются новые данные. Если нет, но у нас есть закешированные данные – отображаются они и метка – оффлайн режим. Таким образом, при навигации по приложению, в случае появления интернет соединения – при переходе на главную страницу произойдет автоматическое переключение режима.
Самостоятельно выполните рефакторинг кода функции LoadState, чтобы он не был насколько "лапшеобразным".


