Задание
Создать приложение для Windows Phone 7, загружающее из Интернета RSS-новости по разным тематикам (воспользоваться функциональностью класса WebClient). Предусмотреть возможность сохранения загруженных лент в изолированном хранилище.
Освоение
Описание
Создадим новый проект 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();
Теперь можно скомпилировать приложение, запустить на эмуляторе или телефоне и проверить его функциональность.
Задание
Изменить приложение "Телефонная книжка", добавив работу с локальной базой (загрузка контактов из базы должна происходить на основе связывания данных).
Освоение
Описание
Откроем существующий проект "Телефонная книжка". Удалим существующий статический класс Contact. Вместо него создадим новый класс, представляющий таблицу в локальной базе. Таблица будет содержать несколько свойств: идентификатор (ключевое поле), имя контакта, телефон, версия (на случай обновления данных). Также переопределим обязательные события PropertyChanged, PropertyChanging и функции NotifyPropertyChanged(), NotifyPropertyChanging().
[Table]
public class Contact : INotifyPropertyChanged, INotifyPropertyChanging
{
// Define ID: private field, public property and database column.
private int _ContactsId;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity",
CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int ContactsId
{
get
{
return _ContactsId;
}
set
{
if (_ContactsId != value)
{
NotifyPropertyChanging("ContactsId");
_ContactsId = value;
NotifyPropertyChanged("ContactsId");
}
}
}
// Define item name: private field, public property and database column.
private string _Name;
[Column]
public string Name
{
get
{
return _Name;
}
set
{
if (_Name != value)
{
NotifyPropertyChanging("Name");
_Name = value;
NotifyPropertyChanged("Name");
}
}
}
// Define completion value: private field, public property and database column.
private string _Phone;
[Column]
public string Phone
{
get
{
return _Phone;
}
set
{
if (_Phone != value)
{
NotifyPropertyChanging("Phone");
_Phone = value;
NotifyPropertyChanged("Phone");
}
}
}
// Version column aids update performance.
[Column(IsVersion = true)]
private Binary _version;
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify the page that a data context property changed
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
// Used to notify the data context that a data context property is about to change
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
#endregion
}
Для работы данного кода добавим следующие директивы:
using System.Data.Linq; using System.Data.Linq.Mapping; using System.ComponentModel;
Для удобной работы с таблицей создадим контекст данных. Основной особенностью данного класса является наличие строки подключения к базе (она должна передаваться в конструктор базового класса) и поля типа Table.
public class ContactsDataContext : DataContext
{
// Specify the connection string as a static, used in main page and app.xaml.
public static string DBConnectionString = "Data Source=isostore:/Contacts.sdf";
// Pass the connection string to the base class.
public ContactsDataContext(string connectionString)
: base(connectionString)
{ }
// Specify a single table for the to-do items.
public Table<Contact> Contacts;
}
Теперь перейдем к коду класса главной страницы.
Добавим следующие директивы:
using System.ComponentModel; using System.Collections.ObjectModel;
Добавим наследование от класса INotifyPropertyChanged:
public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
Теперь объявим событие и функцию обработки изменения данных:
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify Silverlight that a property has changed.
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Добавим в класс переменную типа ContactsDataContext (класс, который мы создали выше) и свойство Contacts типа ObservableCollection (коллекция, поддающаяся наблюдению, реализует интерфейс INotifyCollectionChanged, что позволяет получать уведомления об изменениях коллекции и обновляться).
private ContactsDataContext ContactsDB;
private ObservableCollection<Contact> _Contacts;
public ObservableCollection<Contact> Contacts
{
get
{
return _Contacts;
}
set
{
if (_Contacts != value)
{
_Contacts = value;
NotifyPropertyChanged("Contacts");
}
}
}
В конструкторе класса добавим инициализацию:
ContactsDB = new ContactsDataContext(ContactsDataContext.DBConnectionString);
this.DataContext = this;
Изменим методы управления контактами. При добавлении будем создавать новый объект типа Contact и добавлять его в коллекцию Contacts и контекст данных ContactsDB. По завершении необходимо сохранить изменения в базе: ContactsDB.SubmitChanges(). При редактировании с помощью Linq-запроса будем извлекать выделенную "запись" в таблице, затем менять ее в контексте данных и в коллекции. По завершении необходимо сохранить изменения в базе: ContactsDB.SubmitChanges().
private void AddNewContact(string strName, string strPhone)
{
Contact newContact = new Contact { Name = strName, Phone = strPhone };
Contacts.Add(newContact);
// Add item to the local database.
ContactsDB.Contacts.InsertOnSubmit(newContact);
ContactsDB.SubmitChanges();
}
private void EditContact(int nIndex, string strName, string strPhone)
{
if ((nIndex >= 0) (nIndex < listContacts.Items.Count))
{
int idd = Contacts.ElementAt(nIndex).ContactsId;
// change item in the local database.
var ContactsInDB = from Contact cont in ContactsDB.Contacts
where cont.ContactsId == idd
select cont;
foreach (Contact cont in ContactsInDB)
{
cont.Name = strName;
cont.Phone = strPhone;
}
Contacts.ElementAt(nIndex).Name = strName;
Contacts.ElementAt(nIndex).Phone = strPhone;
ContactsDB.SubmitChanges();
}
else
{
MessageBox.Show("Ошибка. Индекс за границами массива.");
}
}
При нажатии на кнопку меню "Изменить" странице PageEdit.xaml будем передавать извлеченные из коллекции имя и телефон контакта. При нажатии на кнопку меню "Удалить" будем удалять контакт из коллекции и контекста данных. По завершении сохранять изменения в базе ContactsDB.SubmitChanges().
// Меню - Изменить
private void ApplicationBarMenuEdit_Click(object sender, EventArgs e)
{
int nIndex = listContacts.SelectedIndex;
if (-1 != nIndex)
{
string strName = Contacts.ElementAt(nIndex).Name;
string strPhone = Contacts.ElementAt(nIndex).Phone;
NavigationService.Navigate(new Uri("/PageEdit.xaml?name=" + Uri.EscapeDataString(strName) +
"phone=" + Uri.EscapeDataString(strPhone) +
"id=" + Uri.EscapeDataString(nIndex.ToString()),
UriKind.Relative));
}
else
{
MessageBox.Show("Контакт не выбран.");
}
}
// Меню - Удалить
private void ApplicationBarMenuRemove_Click(object sender, EventArgs e)
{
int nIndex = listContacts.SelectedIndex;
if (-1 != nIndex)
{
Contact contactForDelete = listContacts.SelectedItem as Contact;
Contacts.Remove(contactForDelete);
ContactsDB.Contacts.DeleteOnSubmit(contactForDelete);
ContactsDB.SubmitChanges();
this.Focus();
}
else
{
MessageBox.Show("Контакт не выбран.");
}
}
Так как мы избавились от статического класса, при нажатии на кнопку "Принять" на странице добавления/редактирования контактов теперь будем переходить не назад по навигации, а непосредственно на главную страницу и передавать значения имени и телефона в качестве параметров:
private void btnApply_Click(object sender, RoutedEventArgs e)
{
if (txtName.Text.Trim().Length > 0)
{
if (txtPhone.Text.Trim().Length > 0)
{
NavigationService.Navigate(new Uri("/MainPage.xaml?name=" + Uri.EscapeDataString(txtName.Text) +
"phone=" + Uri.EscapeDataString(txtPhone.Text) +
"id=" + Uri.EscapeDataString(nCurrentId.ToString()),
UriKind.Relative));
}
else
{
MessageBox.Show("Поле телефона не может быть пустым.");
txtPhone.Focus();
}
}
else
{
MessageBox.Show("Поле имени не может быть пустым.");
txtName.Focus();
}
}
Соответственно изменится и обработчик перехода на главную страницу:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var ContactsInDB = from Contact cont in ContactsDB.Contacts
select cont;
Contacts = new ObservableCollection<Contact>(ContactsInDB);
base.OnNavigatedTo(e);
//если есть ключи, значит - режим редактирования
if (NavigationContext.QueryString.ContainsKey("id")
NavigationContext.QueryString.ContainsKey("name")
NavigationContext.QueryString.ContainsKey("phone"))
{
int nCurrentId;
if (!int.TryParse(NavigationContext.QueryString["id"].ToString(), out nCurrentId))
{
nCurrentId = -1;
}
if (-1 == nCurrentId)
{
//добавление нового контакта
AddNewContact(NavigationContext.QueryString["name"].ToString(),
NavigationContext.QueryString["phone"].ToString());
}
else
{
//редактирование контакта
EditContact(nCurrentId,
NavigationContext.QueryString["name"].ToString(),
NavigationContext.QueryString["phone"].ToString());
}
}
}
Для того чтобы при изменении данных они автоматически менялись в списке на экране телефона, изменим код разметки главной страницы:
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox Name="listContacts" Height="615" ItemsSource="{Binding Contacts}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10,7,10,7">
<TextBlock Text="{Binding Name}" FontSize="28" />
<TextBlock Text="{Binding Phone}" FontSize="22" Margin="50,0,0,0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
С помощью строчки ItemsSource="{Binding Contacts}" мы связали элементы списка с элементами коллекции Contacts. А задав значение свойства Text="{Binding Name}", мы связали содержимое текстового блока с соответствующим полем таблицы. Таким образом список listContacts будет состоять из элементов типа Contact (класс, который мы создали в самом начале), а текстовые блоки будут автоматически заполняться содержимым полей таблицы.
Теперь можно скомпилировать приложение, запустить на эмуляторе или телефоне и проверить его функциональность.
Наше приложение "Телефонная книжка" сохраняет контакты в локальной базе, в чем можно легко убедиться, закрыв приложение и открыв снова.
Задание
Изменить приложение "Телефонная книжка", добавив возможность позвонить и отправить sms выбранному контакту, а также возможность выбрать фотографию из фотоальбома.
Освоение
Описание
Откроем существующий проект "Телефонная книжка". Добавим в файл разметки главной страницы место для фотографии (Image):
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox Name="listContacts" Height="615">
<ListBoxItem>
<StackPanel Orientation="Horizontal">
<Image Width="70" Height="70" />
<StackPanel Margin="10,7,10,7">
<TextBlock Text="Андреев Андрей" FontSize="28" />
<TextBlock Text="+79215555555" FontSize="22" Margin="50,0,0,0" />
</StackPanel>
</StackPanel>
</ListBoxItem>
</ListBox>
</Grid>
Также добавим элементы меню "Позвонить" и "Сообщение":
<!--Sample code showing usage of ApplicationBar-->
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
<shell:ApplicationBarIconButton IconUri="/Images/appbar.add.rest.png" Text="Добавить"
Click="ApplicationBarMenuAdd_Click" />
<shell:ApplicationBarIconButton IconUri="/Images/appbar.edit.rest.png" Text="Изменить"
Click="ApplicationBarMenuEdit_Click" />
<shell:ApplicationBarIconButton IconUri="/Images/appbar.delete.rest.png" Text="Удалить"
Click="ApplicationBarMenuRemove_Click" />
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text="Позвонить" Click="MenuCall_Click" />
<shell:ApplicationBarMenuItem Text="Сообщение" Click="MenuSMS_Click" />
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
При нажатии на данные кнопки будем вызывать соответствующую задачу запуска (позвонить или отправить сообщение). Для этого в начало файла добавим директиву:
using Microsoft.Phone.Tasks;
Задачи запуска предоставляют доступ к программным возможностям платформы. Так, например, задача запуска PhoneCallTask позволяет позвонить на указанный номер телефона, а SmsComposeTask – отправить сообщение:
private void MenuCall_Click(object sender, EventArgs e)
{
int nIndex = listContacts.SelectedIndex;
if (-1 != nIndex)
{
PhoneCallTask call = new PhoneCallTask();
call.DisplayName = ((TextBlock)((StackPanel)((StackPanel)((ListBoxItem)listContacts.SelectedItem).Content).
Children[1]).Children[0]).Text;
call.PhoneNumber = ((TextBlock)((StackPanel)((StackPanel)((ListBoxItem)listContacts.SelectedItem).Content).
Children[1]).Children[1]).Text;
call.Show();
}
else
{
MessageBox.Show("Контакт не выбран.");
}
}
private void MenuSMS_Click(object sender, EventArgs e)
{
int nIndex = listContacts.SelectedIndex;
if (-1 != nIndex)
{
SmsComposeTask sms = new SmsComposeTask();
sms.To = ((TextBlock)((StackPanel)((StackPanel)((ListBoxItem)listContacts.SelectedItem)
.Content).Children[1]).Children[0]).Text;
sms.Body = "Текст сообщения...";
sms.Show();
}
else
{
MessageBox.Show("Контакт не выбран.");
}
}
Теперь добавим в статический класс Contact новое поле, в котором будем хранить ссылку на фотографию контакта:
public static string m_Photo;
Страницу PageEdit.xaml дополним кнопкой и текстовым полем:
<Grid ShowGridLines="False" Margin="0,20,0,0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="340" />
</Grid.ColumnDefinitions>
<TextBlock Text="Имя:" Grid.Column="0" Grid.Row="0"
VerticalAlignment="Center" FontSize="24" />
<TextBlock Text="Телефон:" Grid.Column="0" Grid.Row="1"
VerticalAlignment="Center" FontSize="24" />
<Button Name="btnPhoto" Content="..." Grid.Column="0"
Grid.Row="2" Click="btnPhoto_Click" />
<TextBox Name="txtName" Text="Имя" Grid.Column="1"
Grid.Row="0" />
<TextBox Name="txtPhone" Text="Телефон" InputScope="TelephoneNumber"
Grid.Column="1" Grid.Row="1" />
<TextBox Name="txtPhoto" Text="Фото" IsReadOnly="True"
Grid.Column="1" Grid.Row="2" />
</Grid>
При нажатии на кнопку btnPhoto будем осуществлять задачу выбора картинки из доступных альбомов. Для этого добавим в код директиву:
using Microsoft.Phone.Tasks;
Добавим в класс глобальную переменную:
private PhotoChooserTask photo;
В конструктор добавим инициализацию данной переменной и подпишемся на событие завершения выбора картинки:
photo = new PhotoChooserTask();
photo.Completed += new EventHandler<PhotoResult>(photo_Completed);
При нажатии на кнопку "…" будем вызывать задачу выбора, а при завершении выбора сохранять ссылку на картинку в текстовом поле и статическом поле класса Contact:
private void btnPhoto_Click(object sender, RoutedEventArgs e)
{
bSelectPhoto = true;
photo.Show();
}
void photo_Completed(object sender, PhotoResult e)
{
if (e.TaskResult == TaskResult.OK)
{
txtPhoto.Text = e.OriginalFileName;
Contact.m_Photo = txtPhoto.Text;
}
}
При добавлении нового контакта будем передавать в функцию AddNewContact ссылку на картинку. Затем с помощью класса BitmapImage из пространства имен System.Windows.Media.Imaging будем присваивать свойству Source объекта типа Image (фотография контакта) изображение по ссылке. Аналогичные изменения следует внести и в функцию изменения выбранного контакта:
private void AddNewContact(string strName, string strPhone, string strImg = null)
{
StackPanel stkHorizNew = new StackPanel();
ListBoxItem lstitNew = new ListBoxItem();
StackPanel stkNew = new StackPanel();
TextBlock txtNameNew = new TextBlock();
TextBlock txtPhoneNew = new TextBlock();
txtNameNew.Text = strName;
txtNameNew.FontSize = 28;
txtPhoneNew.Text = strPhone;
txtPhoneNew.FontSize = 22;
txtPhoneNew.Margin = new Thickness(50, 0, 0, 0);
stkNew.Margin = new Thickness(10, 7, 10, 7);
stkNew.Children.Add(txtNameNew);
stkNew.Children.Add(txtPhoneNew);
Image imgNew = new Image();
imgNew.Width = 70;
imgNew.Height = 70;
if (null != strImg) imgNew.Source = new BitmapImage(new Uri(strImg));
stkHorizNew.Orientation = System.Windows.Controls.Orientation.Horizontal;
stkHorizNew.Children.Add(imgNew);
stkHorizNew.Children.Add(stkNew);
lstitNew.Content = stkHorizNew;
listContacts.Items.Add(lstitNew);
listContacts.SelectedIndex = listContacts.Items.Count - 1;
}
Между страницами приложения будем осуществлять передачу ссылки на картинку (так же как это делаем с именем контакта и телефоном). Однако немного изменим поведение при передаче параметров с главной страницы. Поскольку к ссылке на картинку автоматически добавляется "file://" и все обратные слеши "\" заменяются на прямые "/" (а мы не хотим показывать такие изменения пользователю), будем убирать первые 7 символов ссылки и заменять слеши перед передачей.
// Меню - Добавить
private void ApplicationBarMenuAdd_Click(object sender, EventArgs e)
{
NavigationService.Navigate(new Uri("/PageEdit.xaml?mode=add", UriKind.Relative));
}
// Меню - Изменить
private void ApplicationBarMenuEdit_Click(object sender, EventArgs e)
{
int nIndex = listContacts.SelectedIndex;
if (-1 != nIndex)
{
string strName = ((TextBlock)((StackPanel)((StackPanel)((ListBoxItem)listContacts.SelectedItem).Content).
Children[1]).Children[0]).Text;
string strPhone = ((TextBlock)((StackPanel)((StackPanel)((ListBoxItem)listContacts.SelectedItem).Content).
Children[1]).Children[1]).Text;
string strPhoto = "";
if(null != ((Image)((StackPanel)((ListBoxItem)listContacts.SelectedItem).Content).Children.ElementAt(0)).Source)
{
strPhoto = ((BitmapImage)((Image)((StackPanel)((ListBoxItem)listContacts.SelectedItem).Content).
Children.ElementAt(0)).Source).UriSource.AbsoluteUri;
if (strPhoto.Substring(0, 7).Equals("file://"))
{
strPhoto = strPhoto.Substring(7);
strPhoto = strPhoto.Replace('/', '\\');
}
}
Contact.m_Id = nIndex;
Contact.m_Name = strName;
Contact.m_Phone = strPhone;
Contact.m_Photo = strPhoto;
NavigationService.Navigate(new Uri("/PageEdit.xaml?mode=edit", UriKind.Relative));
}
else
{
MessageBox.Show("Контакт не выбран.");
}
}
Теперь можно скомпилировать приложение, запустить на эмуляторе или телефоне и проверить его функциональность.
Задание
Изменить приложение "Учет затрат", добавив работу с локальной базой (загрузка данных из базы должна происходить на основе связывания).
Освоение
Описание
Откроем существующий проект "Учет завтра". Удалим существующий статический класс MyDatabase и структуру MyElement. Создадим новый класс, представляющий таблицу в локальной базе. Таблица будет содержать несколько свойств: идентификатор (ключевое поле), наименование доходов/затрат, флаг, определяющий, актив это или пассив, сумма, версия (на случай обновления данных). Также переопределим обязательные события PropertyChanged, PropertyChanging и функции NotifyPropertyChanged(), NotifyPropertyChanging().
[Table]
public class Cost : INotifyPropertyChanged, INotifyPropertyChanging
{
// Define ID: private field, public property and database column.
private int _CostId;
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity",
CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int CostId
{
get
{
return _CostId;
}
set
{
if (_CostId != value)
{
NotifyPropertyChanging("CostId");
_CostId = value;
NotifyPropertyChanged("CostId");
}
}
}
// Define item name: private field, public property and database column.
private string _Name;
[Column]
public string Name
{
get
{
return _Name;
}
set
{
if (_Name != value)
{
NotifyPropertyChanging("Name");
_Name = value;
NotifyPropertyChanged("Name");
}
}
}
// Define completion value: private field, public property and database column.
private bool _IsActive;
[Column]
public bool IsActive
{
get
{
return _IsActive;
}
set
{
if (_IsActive != value)
{
NotifyPropertyChanging("IsActive");
_IsActive = value;
NotifyPropertyChanged("IsActive");
}
}
}
// Define completion value: private field, public property and database column.
private float _Money;
[Column]
public float Money
{
get
{
return _Money;
}
set
{
if (_Money != value)
{
NotifyPropertyChanging("Money");
_Money = value;
NotifyPropertyChanged("Money");
}
}
}
// Version column aids update performance.
[Column(IsVersion = true)]
private Binary _version;
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify the page that a data context property changed
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
// Used to notify the data context that a data context property is about to change
private void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
#endregion
}
Для работы данного кода добавим следующие директивы:
using System.Data.Linq; using System.Data.Linq.Mapping; using System.ComponentModel;
Для удобной работы с таблицей создадим контекст данных. Основной особенностью данного класса является наличие строки подключения к базе (она должна передаваться в конструктор базового класса) и поля типа Table.
public class CostsDataContext : DataContext
{
// Specify the connection string as a static, used in main page and app.xaml.
public static string DBConnectionString = "Data Source=isostore:/Costs.sdf";
// Pass the connection string to the base class.
public CostsDataContext(string connectionString)
: base(connectionString)
{ }
// Specify a single table for the to-do items.
public Table<Cost> Costs;
}
Теперь перейдем к коду класса главной страницы.
Добавим следующие директивы:
using System.ComponentModel; using System.Collections.ObjectModel;
Добавим наследование от класса INotifyPropertyChanged:
public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
Теперь объявим событие и функцию обработки изменения данных:
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify Silverlight that a property has changed.
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Добавим в класс переменную типа CostsDataContext (класс, который мы создали выше) и свойство Costs типа ObservableCollection (коллекция, поддающаяся наблюдению, реализует интерфейс INotifyCollectionChanged, что позволяет получать уведомления об изменениях коллекции и обновляться).
private CostsDataContext CostsDB;
private ObservableCollection<Cost> _Costs;
public ObservableCollection<Cost> Costs
{
get
{
return _Costs;
}
set
{
if (_Costs != value)
{
_Costs = value;
NotifyPropertyChanged("Costs");
}
}
}
В конструкторе класса добавим инициализацию:
CostsDB = new CostsDataContext(CostsDataContext.DBConnectionString);
this.DataContext = this;
Изменим логику добавления и удаления новых доходов/затрат. При добавлении будем создавать новый объект типа Cost и добавлять его в коллекцию Costs и контекст данных CostsDB. По завершении необходимо сохранить изменения в базе: ContactsDB.SubmitChanges(). При удалении будем удалять контакт из коллекции и контекста данных. По завершении сохранять изменения в базе ContactsDB.SubmitChanges().
private void AddNewCost(string strName, bool isActive, float fMoney)
{
Cost newCost = new Cost { Name = strName, IsActive = isActive, Money = fMoney };
Costs.Add(newCost);
CostsDB.Costs.InsertOnSubmit(newCost);
CostsDB.SubmitChanges();
}
private void DeleteCost(int index)
{
if ((index > 0) (index < listCosts.Items.Count))
{
Cost costForDelete = listCosts.Items[index] as Cost;
Costs.Remove(costForDelete);
CostsDB.Costs.DeleteOnSubmit(costForDelete);
CostsDB.SubmitChanges();
this.Focus();
}
else
{
MessageBox.Show("Индекс вне границ массива.");
}
}
Также изменим логику при переходе на главную страницу. В коде страницы PageAdd.xaml при нажатии на кнопку "Принять" будем осуществлять прямой переход на главную страницу и передавать параметры нового дохода/затраты:
private void btnApply_Click(object sender, RoutedEventArgs e)
{
if (txtElem.Text.Trim().Length > 0)
{
if (txtMoney.Text.Trim().Length > 0)
{
NavigationService.Navigate(new Uri("/MainPage.xaml?name=" + Uri.EscapeDataString(txtElem.Text) +
"isactive=" + Uri.EscapeDataString(checkActive.IsChecked.Value.ToString()) +
"money=" + Uri.EscapeDataString(txtMoney.Text), UriKind.Relative));
}
else
{
MessageBox.Show("Поле суммы не может быть пустым.");
txtMoney.Focus();
}
}
else
{
if(checkActive.IsChecked.Value)
MessageBox.Show("Поле дохода не может быть пустым.");
else
MessageBox.Show("Поле расхода не может быть пустым.");
txtElem.Focus();
}
}
В коде главной страницы в методе OnNavigatedTo() с помощью Linq-запроса будем извлекать данные из таблицы и помещать их в коллекцию. В случае если при переходе были указаны параметры, будем создавать новый элемент:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var CostsInDB = from Cost cost in CostsDB.Costs
select cost;
Costs = new ObservableCollection<Cost>(CostsInDB);
base.OnNavigatedTo(e);
if (NavigationContext.QueryString.ContainsKey("name")
NavigationContext.QueryString.ContainsKey("isactive")
NavigationContext.QueryString.ContainsKey("money"))
{
string strName = NavigationContext.QueryString["name"].ToString();
bool isActive = bool.Parse(NavigationContext.QueryString["isactive"].ToString());
float fMoney = float.Parse(NavigationContext.QueryString["money"].ToString());
AddNewCost(strName, isActive, fMoney);
}
}
Для того чтобы при изменении данных они автоматически менялись в списке на экране телефона, изменим код разметки главной страницы:
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" Canvas.ZIndex="0">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Text="название" FontSize="28" Margin="0,0,5,0" TextAlignment="Center"
Grid.Row="0" Grid.Column="0" />
<TextBlock Text="а|п" FontSize="28" Margin="0,0,5,0" TextAlignment="Center" Grid.Row="0"
Grid.Column="1" />
<TextBlock Text="сумма" FontSize="28" Margin="0,0,5,0" TextAlignment="Center"
Grid.Row="0" Grid.Column="2" />
<ListBox Name="listCosts" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" ItemsSource="{Binding Costs}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,10,0,10">
<TextBlock Text="{Binding Name}" FontSize="28" Width="250" />
<TextBlock Text="{Binding IsActive}" FontSize="28" Width="100"
TextAlignment="Center" />
<TextBlock Text="{Binding Money}" FontSize="28" Width="100"
TextAlignment="Right" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
С помощью строчки ItemsSource="{Binding Costs}" мы связали элементы списка с элементами коллекции Costs. Задав значение свойства Text="{Binding Name}", мы связали содержимое текстового блока с соответствующим полем таблицы. В результате список listCosts будет состоять из элементов типа Cost (класс, который мы создали в самом начале), а текстовые блоки будут автоматически заполняться содержимым полей таблицы.
Теперь можно скомпилировать приложение, запустить на эмуляторе или телефоне и проверить его функциональность.
Наше приложение "Учет затрат" сохраняет контакты в локальной базе, в чем можно легко убедиться, закрыв приложение и открыв его снова.
Задание
Изменить приложение "Блокнот", добавив сохранение его состояния "при захоронении".
Освоение
Описание
В Windows Phone 7 только одно приложение может быть активным. Кроме того существуют т.н. свернутые приложения. Когда приложение перестает быть активным, операционная система автоматически переводит его в бездействующее состояние (dormant). В случае нехватки памяти, бездействующие приложения "захораниваются" (переходят в состояние tombstoned). Однако одновременно система сохраняет состояние tombstoned только для пяти приложений. При активации "захороненного" приложения все изменения теряются. Однако у разработчика есть возможность сохранять состояние.
Примечание. Для активации приложения следует зажать кнопку "Назад" на телефоне. В появившемся списке выбрать нужное приложение. Активировать можно приложения, находящиеся в состоянии dormant или tombstoned.
Откроем приложение "Блокнот". Добавим в него возможность восстановления из состояния "захоронения" приложения.
В коде главной страницы переопределим метод OnNavigatedFrom(). При уходе с нее, в случае если уходим не на страницу настроек, будем сохранять текст и настройки в свойстве State:
protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
if (!e.Uri.OriginalString.Equals("/PropertiesPage.xaml"))
{
this.State.Add("text", txtMain.Text);
SaveProperties();
this.State.Add("FontSize", Properties.nFontSize);
this.State.Add("FontFamily", Properties.strFontFamily);
this.State.Add("Italic", Properties.bItalic);
this.State.Add("Bold", Properties.bBold);
this.State.Add("HAlignment", Properties.taHAlignment);
this.State.Add("colorFont", Properties.colorFont);
this.State.Add("colorBack", Properties.colorBack);
}
}
Добавим в класс глобальную переменную isNewlyCreatedPage типа bool, которой в конструкторе будем присваивать значение true. Эта переменная будет нужна для того, чтобы не восстанавливать состояние при активации бездействующих (dormant) приложений (состояние и так сохранено операционной системой).
Вынесем в отдельную функцию LoadProperties() присвоение свойствам текстового поля значений из статического класса. Изменим метод OnNavigatedTo(). Если приложение новое (или восстановленное из состояния tombstoned), и состояние было сохранено, выгружаем параметры текстового поля и очищаем свойство State. Если приложение старое, то, если мы перешли на главную страницу с другой – просто загружаем параметры, иначе ( приложение восстановлено из состояния dormant) – очищаем свойство State.
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (isNewlyCreatedPage)
{
if (this.State.ContainsKey("text"))
{
txtMain.Text = (string)this.State["text"];
Properties.nFontSize = (int)this.State["FontSize"];
Properties.strFontFamily = (string)this.State["FontFamily"];
Properties.bItalic = (bool)this.State["Italic"];
Properties.bBold = (bool)this.State["Bold"];
Properties.taHAlignment = (TextAlignment)this.State["HAlignment"];
Properties.colorFont = (Color)this.State["colorFont"];
Properties.colorBack = (Color)this.State["colorBack"];
LoadProperties();
this.State.Clear();
}
}
else
{
if (e.IsNavigationInitiator)
{
LoadProperties();
}
else
{
this.State.Clear();
}
}
isNewlyCreatedPage = false;
}
private void LoadProperties()
{
//if (true == Properties.bApply)
{
txtMain.FontSize = Properties.nFontSize;
txtMain.FontFamily = new FontFamily(Properties.strFontFamily);
txtMain.FontStyle = Properties.bItalic ? FontStyles.Italic : FontStyles.Normal;
txtMain.FontWeight = Properties.bBold ? FontWeights.Bold : FontWeights.Normal;
txtMain.TextAlignment = Properties.taHAlignment;
txtMain.Foreground = new SolidColorBrush(Properties.colorFont);
txtMain.Background = new SolidColorBrush(Properties.colorBack);
}
}
Для того чтобы проверить работу новой логики, запустите приложение, измените текст и свойства текстового поля, затем нажмите кнопку "Start" телефона (кнопка с изображением значка Windows). Приложение перейдет в состояние dormant. Чтобы вернуться в приложение зажмите кнопку Back на телефоне и выберете приложение "Блокнот" Рис. 4.1 :
(рис 4.1) Приложение (Блокнот)
Для проверки состояния tombstoned в свойствах проекта в разделе "Debug" поставьте галочку "Tombstone upon deactivation while debugging" (Рис. 4.2 ), после чего повторите вышеописанные действия:
(рис 4.2) Выбор опции (Tombstone upon deactivation while debugging)
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.