Основы разработки приложений беспроводных устройств

Лабораторный практикум 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();
    

    Теперь можно скомпилировать приложение, запустить на эмуляторе или телефоне и проверить его функциональность.

    Лабораторная работа №12. Телефонная книжка и локальная база

    Задание

    Изменить приложение "Телефонная книжка", добавив работу с локальной базой (загрузка контактов из базы должна происходить на основе связывания данных).

    Освоение

  • локальная база
  • связывание (Binding)
  • Описание

    Откроем существующий проект "Телефонная книжка". Удалим существующий статический класс 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 (класс, который мы создали в самом начале), а текстовые блоки будут автоматически заполняться содержимым полей таблицы.

    Теперь можно скомпилировать приложение, запустить на эмуляторе или телефоне и проверить его функциональность.

    Наше приложение "Телефонная книжка" сохраняет контакты в локальной базе, в чем можно легко убедиться, закрыв приложение и открыв снова.

    Лабораторная работа №13. Контакты и локальная база

    Задание

    Изменить приложение "Телефонная книжка", добавив возможность позвонить и отправить 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("Контакт не выбран.");
                }
            }
    

    Теперь можно скомпилировать приложение, запустить на эмуляторе или телефоне и проверить его функциональность.

    Лабораторная работа №14. Учет затрат и локальная база

    Задание

    Изменить приложение "Учет затрат", добавив работу с локальной базой (загрузка данных из базы должна происходить на основе связывания).

    Освоение

  • локальная база
  • связывание (Binding)
  • Описание

    Откроем существующий проект "Учет завтра". Удалим существующий статический класс 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 (класс, который мы создали в самом начале), а текстовые блоки будут автоматически заполняться содержимым полей таблицы.

    Теперь можно скомпилировать приложение, запустить на эмуляторе или телефоне и проверить его функциональность.

    Наше приложение "Учет затрат" сохраняет контакты в локальной базе, в чем можно легко убедиться, закрыв приложение и открыв его снова.

    Лабораторная работа №15. Сохранение состояния

    Задание

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

    Освоение

  • сохранение состояния приложения "при захоронении"
  • Описание

    В 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)

    Вернуться к учебному плану