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

Лабораторный практикум 2

Показывать лекцию целиком

Лабораторная работа №6. Учет затрат

Задание

Создать приложение "Учет затрат" для Windows Phone 7 с возможностью добавления новых доходов и расходов и подсчетом итоговой суммы.

Освоение

  • навигация между страницами
  • основные элементы управления и разметки (Grid, StackPanel, Canvas, TextBox, TextBlock, ListBox, Button)
  • события
  • контекст ввода
  • меню приложения
  • Описание

    Создадим новый проект Silverlight for Windows Phone – Windows Phone Application.

    Приложение будет состоять из двух окон:

  • главное окно со списком доходов/затрат
  • окно добавления новых доходов/затрат
  • Откроем файл разметки главной страницы MainPage.xaml. В самом верху страницы разметки изменим выводимое название приложения, например, на "УЧЁТ ЗАТРАТ". Заголовок страницы удалим или закомментируем с целью экономии места:

        <!--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="УЧЁТ ЗАТРАТ" Style="{StaticResource PhoneTextNormalStyle}"/>
                <!--TextBlock x:Name="PageTitle" Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/-->
            </StackPanel>
    

    Внутри элемента ContentPanel поместим список доходов/затрат. Список будет иметь вид таблицы с 3 столбцами: название, актив/пассив (актив – доход, пассив – расход) и сумма. Итак, код элемента ContentPanel будет выглядеть следующим образом:

        <!--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="300" />
                    <ColumnDefinition Width="50" />
                    <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="lstElements" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3">
                    <ListBoxItem>
                        <StackPanel Orientation="Horizontal" Margin="0,10,0,10">
                            <TextBlock Text="Элемент списка" FontSize="28" Width="300" />
                            <TextBlock Text="А" FontSize="28" Width="50" TextAlignment="Center" />
                            <TextBlock Text="3000" FontSize="28" Width="100" TextAlignment="Right" />
                        </StackPanel>
                    </ListBoxItem>
                </ListBox>
            </Grid>
    

    Внутри элемент Grid сначала помещаем объявление строк и столбцов, а затем и сами элементы управления. Внутри элементов управления с помощью атрибутов Grid.Row и Grid.Column задаются индексы, соответственно, строки и столбца.

    В первой строке поместим заголовки столбцов, во второй – сам список. С помощью атрибута Grid.ColumnSpan можно объединять столбцы. Элементы списка объединим с помощью StackPanel с горизонтальной ориентацией.

    В конце страницы раскомментируем фрагмент кода, отвечающий за меню приложения. Добавим еще одну кнопку. Объявим обработчики нажатия на кнопки меню и добавим значки. Значки можно найти в папке C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.0\Icons (лучше всего брать из папки dark) или нарисовать самостоятельно. Добавим в проект папку "Images" и поместим в нее необходимые изображения. Для каждого из изображений в свойствах назначим полю Build Action значение Content (если этого не сделать, ApplicationBar не сможет найти изображения).

        <!--Sample code showing usage of ApplicationBar-->
        <phone:PhoneApplicationPage.ApplicationBar>
            <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
                <shell:ApplicationBarIconButton IconUri="Images/appbar.check.rest.png" Text="Подсчет"
                 Click="ApplicationBarMenuCount_Click" />
                <shell:ApplicationBarIconButton IconUri="Images/appbar.add.rest.png" Text="Добавить" 
                Click="ApplicationBarMenuAdd_Click" />
                <shell:ApplicationBarIconButton IconUri="Images/appbar.cancel.rest.png" Text="Удалить"
                 Click="ApplicationBarMenuRemove_Click" />
                
                <!--shell:ApplicationBar.MenuItems>
                    <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
                    <shell:ApplicationBarMenuItem Text="MenuItem 2"/>
                </shell:ApplicationBar.MenuItems-->
            </shell:ApplicationBar>
        </phone:PhoneApplicationPage.ApplicationBar>
    

    Перейдем к написанию кода.

    Поскольку работать с локальной базой и файлами мы пока не умеем, для сохранения данных будем использовать статический класс MyDatabase. Создадим его. Он будет содержать список элементов типа MyElement, который нам также необходимо создать:

        public struct MyElement
        {
            public string Element;
            public bool Active;
            public float Money;
        }
    

    Кроме того статический класс будет содержать функцию инициализации списка (несколько элементов зададим заранее в этой функции):

       public static class MyDatabase
        {
            public static List<MyElement> Values;
    
            public static void Init()
            {
                Values = new List<MyElement>();
    
                MyElement tmp;
                tmp.Element = "зарплата";
                tmp.Active = true;
                tmp.Money = 15000f;
                Values.Add(tmp);
    
                tmp.Element = "донор крови";
                tmp.Active = true;
                tmp.Money = 2000f;
                Values.Add(tmp);
    
                tmp.Element = "ресторан";
                tmp.Active = false;
                tmp.Money = 3500f;
                Values.Add(tmp);
            }
        }
    

    Теперь откроем файл MainPage.xaml.cs. В самый верх для обеспечения перехода между страницами добавим директиву:

      using System.Windows.Navigation;
    

    В класс MainPage добавим функцию формирования элементов ListBox страницы из списка класса MyDatabase. Для этого будем последовательно создавать экземпляры класса TextBlock, задавать свойства и помещать их в экземпляры класса StackPanel, которые, в свою очередь, будем помещать в ListBoxItem, а те уже – в список.

      private void DrawList()
            {
                lstElements.Items.Clear();
    
                for (int i = 0; i < MyDatabase.Values.Count; i++)
                {
                    ListBoxItem lstitNew = new ListBoxItem();
                    StackPanel spNew = new StackPanel();
                    TextBlock tbElem = new TextBlock();
                    TextBlock tbAP = new TextBlock();
                    TextBlock tbMoney = new TextBlock();
    
                    tbElem.Text = MyDatabase.Values[i].Element;
                    tbElem.FontSize = 28;
                    tbElem.Width = 300;
    
                    tbAP.Text = MyDatabase.Values[i].Active ? "А" : "П";
                    tbAP.FontSize = 28;
                    tbAP.Width = 50;
                    tbAP.TextAlignment = TextAlignment.Center;
    
                    tbMoney.Text = MyDatabase.Values[i].Money.ToString();
                    tbMoney.FontSize = 28;
                    tbMoney.Width = 100;
                    tbMoney.TextAlignment = TextAlignment.Right;
    
                    spNew.Orientation = System.Windows.Controls.Orientation.Horizontal;
                    spNew.Margin = new Thickness(0, 10, 0, 10);
                    spNew.Children.Add(tbElem);
                    spNew.Children.Add(tbAP);
                    spNew.Children.Add(tbMoney);
    
                    lstitNew.Content = spNew;
    
                    lstElements.Items.Add(lstitNew);
                }
            }
    

    В конструкторе класса MainPage проинициализируем список и "нарисуем" его:

      // Constructor
            public MainPage()
            {
                InitializeComponent();
    
                MyDatabase.Init();
                DrawList();
            }
    

    Переход на главную страницу может быть осуществлен только в самом начале при загрузке приложения и при переходе со страницы добавления доходов/затрат. Таким образом, при переходе список может быть изменен. Поэтому нам следует перерисовывать его. Для этого переопределим стандартный метод OnNavigatedTo():

      // Вызывается при переходе на текущую страницу
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                base.OnNavigatedTo(e);
    
                if (null != MyDatabase.Values)
                {
                    DrawList();
                }
            }
    

    Теперь напишем обработчики нажатий на кнопки меню. При нажатии на кнопку "Подсчет" просто будем выводить в MessageBox-е сводку по доходам, расходам и общей сумме. При нажатии на кнопку "Добавить" просто будем осуществлять переход на страницу добавления доходов/расходов (PageAdd.xaml), которую создадим далее. При нажатии на кнопку "Удалить" будем удалять элемент с выделенных индексом из списка и перерисовывать его.

      // Меню - Подсчет
            private void ApplicationBarMenuCount_Click(object sender, EventArgs e)
            {
                float fActive = 0f;
                float fPassive = 0f;
                float fItog = 0f;
    
                for (int i = 0; i < MyDatabase.Values.Count; i++)
                {
                    if (MyDatabase.Values[i].Active)
                    {
                        fActive += MyDatabase.Values[i].Money;
                    }
                    else
                    {
                        fPassive += MyDatabase.Values[i].Money;
                    }
                }
    
                fItog = fActive - fPassive;
    
                MessageBox.Show(
                            "Доходы:  " + fActive.ToString() + "\n" +
                            "Расходы: " + fPassive.ToString() + "\n\n" +
                            "Итого:   " + fItog.ToString()
                            );
            }
    
            // Меню - Добавить
            private void ApplicationBarMenuAdd_Click(object sender, EventArgs e)
            {
                NavigationService.Navigate(new Uri("/PageAdd.xaml", UriKind.Relative));
            }
    
            // Меню - Удалить
            private void ApplicationBarMenuRemove_Click(object sender, EventArgs e)
            {
                int nIndex = lstElements.SelectedIndex;
    
                if (-1 != nIndex)
                {
                    MyDatabase.Values.RemoveAt(nIndex);
                    DrawList();
                }
                else
                {
                    MessageBox.Show("Элемент не выбран.");
                }
            }
    

    Теперь добавим в проект приложения новую страницу – Windows Phone Portrait Page. Назовем ее PageAdd. В файле PageAdd.xaml определим разметку страницы. Она будет включать два текстовых поля (для названия дохода/расхода и суммы), один CheckBox (для определения расход это или доход) и две кнопки – "Принять" и "Отмена". Также не забудем про элементы TextBlock с пояснительными подписями к текстовым полям.

     <!--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="БАЗА ПАРОЛЕЙ" 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">
                <StackPanel Margin="5,5,5,5">
                    <Grid ShowGridLines="False" Margin="0,20,0,0">
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition />
                            <RowDefinition />
                        </Grid.RowDefinitions>
    
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="120" />
                            <ColumnDefinition Width="320" />
                        </Grid.ColumnDefinitions>
    
                        <TextBlock Name="lblElem" Text="Расход:" Grid.Column="0" Grid.Row="0" 
                        VerticalAlignment="Center" FontSize="24" />
                        <TextBlock Text="Сумма:" Grid.Column="0" Grid.Row="1" VerticalAlignment="Center" FontSize="24" />
                        <TextBox Name="txtElem" Text="Элемент" Grid.Column="1" Grid.Row="0" />
                        <TextBox Name="txtMoney" Text="0" InputScope="Number" Grid.Column="1" Grid.Row="1" />
                        <CheckBox Name="checkActive" Content="Прибыль" Grid.Column="1" Grid.Row="2"
                         Click="checkActive_Click" />
                    </Grid>
    
                    <StackPanel Orientation="Horizontal">
                        <Button Name="btnApply" Height="90" Width="220" Grid.Column="0" Grid.Row="0" Content="Принять" 
                        Click="btnApply_Click" />
                        <Button Name="btnCancel" Height="90" Width="220" Grid.Column="1" Grid.Row="0" Content="Отмена" 
                        Click="btnCancel_Click" />
                    </StackPanel>
                </StackPanel>
            </Grid>
        </Grid>
    

    Перейдем к коду страницы PageAdd.xaml.cs.

    Точно так же в самый верх добавим директиву:

      using System.Windows.Navigation;
    

    При переходе на данную страницу будем очищать текстовые поля и снимать галочку с CheckBox-а:

       protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                base.OnNavigatedTo(e);
    
                txtElem.Text = "";
                txtMoney.Text = "";
                checkActive.IsChecked = false;
    
                txtElem.Focus();
            }
    

    При нажатии на CheckBox просто будем менять текст в текстовом блоке lblElem для удобства пользователя:

       private void checkActive_Click(object sender, RoutedEventArgs e)
            {
                if (checkActive.IsChecked.Value)
                    lblElem.Text = "Доход:";
                else
                    lblElem.Text = "Расход:";
            }
    

    При нажатии на кнопку "Отмена" будем переходить на предыдущую главную страницу, ничего не изменяя. При нажатии на кнопку "Принять" будем добавлять новый элемент в список MyDatabase.Values:

       private void btnCancel_Click(object sender, RoutedEventArgs e)
            {
                MessageBoxResult result = MessageBox.Show("Изменения будут потеряны.
                 Продолжить?", "Отмена", MessageBoxButton.OKCancel);
                if (result == MessageBoxResult.OK)
                {
                    NavigationService.GoBack();
                }
            }
    
            private void btnApply_Click(object sender, RoutedEventArgs e)
            {
                if (txtElem.Text.Trim().Length > 0)
                {
                    if (txtMoney.Text.Trim().Length > 0)
                    {
                        MyElement tmp;
                        tmp.Element = txtElem.Text;
                        tmp.Active = checkActive.IsChecked.Value;
                        tmp.Money = float.Parse(txtMoney.Text);
                        MyDatabase.Values.Add(tmp);
    
                        NavigationService.GoBack();
                    }
                    else
                    {
                        MessageBox.Show("Поле суммы не может быть пустым.");
                        txtMoney.Focus();
                    }
                }
                else
                {
                    if(checkActive.IsChecked.Value)
                        MessageBox.Show("Поле дохода не может быть пустым.");
                    else
                        MessageBox.Show("Поле расхода не может быть пустым.");
    
                    txtElem.Focus();
                }
            }
    

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

    Лабораторная работа №7. Список задач

    Задание

    Создать приложение "Список задач" для Windows Phone 7 с возможностью добавления новых задач. Для установки даты и времени использовать элементы DatePicker и TimePicker библиотеки Microsoft.Phone.Controls.Toolkit.

    Освоение

  • навигация между страницами
  • основные элементы управления и разметки (StackPanel, TextBox, TextBlock, ListBox, CheckBox, Button)
  • расширенные элементы управления (DatePicker, TimePicker)
  • события
  • меню приложения
  • Описание

    Создадим новый проект Silverlight for Windows Phone – Windows Phone Application.

    Приложение будет состоять из двух окон:

  • главное окно со списком задач
  • окно добавления новых задач
  • Откроем файл разметки главной страницы MainPage.xaml. В самом верху страницы разметки изменим выводимое название приложения, например, на "СПИСОК ЗАДАЧ". Заголовок страницы удалим или закомментируем с целью экономии места:

      <!--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="СПИСОК ЗАДАЧ" 
                Style="{StaticResource PhoneTextNormalStyle}"/>
                <!--TextBlock x:Name="PageTitle" Text="page name" Margin="9,-7,0,0" 
                Style="{StaticResource PhoneTextTitle1Style}"/-->
            </StackPanel>
    

    Внутри элемента ContentPanel поместим элемент ListBox, который будем заполнять динамически из кода приложения (обязательно следует указать свойство Name, чтобы обращаться к списку из кода):

      <!--ContentPanel - place additional content here-->
            <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
                <ListBox Name="listToDo" />
            </Grid>
    

    Каждый элемент списка ListBoxItem будет включать панель StackPanel, которая будет объединять CheckBox с названием задачи, TextBlock с описанием и еще один TextBlock с датой и временем.

    В конце страницы раскомментируем фрагмент кода, отвечающий за меню приложения. Зададим текст кнопок и объявим обработчики нажатия Click:

     <!--Sample code showing usage of ApplicationBar-->
        <phone:PhoneApplicationPage.ApplicationBar>
            <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
                <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" 
                Text="добавить" Click="MenuAdd_Click" />
                <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" 
                Text="удалить" Click="MenuRemove_Click" />
    
                <!--shell:ApplicationBar.MenuItems>
                    <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
                    <shell:ApplicationBarMenuItem Text="MenuItem 2"/>
                </shell:ApplicationBar.MenuItems-->
            </shell:ApplicationBar>
        </phone:PhoneApplicationPage.ApplicationBar>
    

    Теперь перейдем к написанию кода.

    Чтобы при создании новых задач передавать данные от страницы добавления к основной странице, создадим статический класс с 4 полями (заголовок, описание, дата и логический флаг, обозначающий, нужно ли создавать новый элемент):

     public static class ToDoItems
    {
        public static bool NewItem;
        public static string Title;
        public static string Description;
        public static string DateTime;
    }
    

    Теперь откроем файл MainPage.xaml.cs. В самый верх для обеспечения перехода между страницами добавим директиву:

     using System.Windows.Navigation;
    

    Переопределим метод OnNavigatedTo(), который вызывается при переходе на текущую страницу. В этом методе будем создавать новый элемент списка. Для этого последовательно будем создавать экземпляры CheckBox, TextBlock, StackPanel и ListBoxItem и задавать им необходимые свойства. Для того чтобы избежать добавления лишних элементов, будем использовать флаг ToDoItems.NewItem.

    protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                base.OnNavigatedTo(e);
    
                if (ToDoItems.NewItem)
                {
                    CheckBox chbNew = new CheckBox();
                    chbNew.Content = ToDoItems.Title;
                    chbNew.FontSize = 26;
    
                    TextBlock tbNew1 = new TextBlock();
                    tbNew1.Text = ToDoItems.Description;
                    tbNew1.FontSize = 24;
                    tbNew1.TextWrapping = TextWrapping.Wrap;
    
                    TextBlock tbNew2 = new TextBlock();
                    tbNew2.Text = ToDoItems.DateTime;
                    tbNew2.FontSize = 24;
                    tbNew2.FontStyle = FontStyles.Italic;
    
                    StackPanel spNew = new StackPanel();
                    spNew.Children.Add(chbNew);
                    spNew.Children.Add(tbNew1);
                    spNew.Children.Add(tbNew2);
    
                    ListBoxItem lbiNew = new ListBoxItem();
                    lbiNew.Content = spNew;
    
                    listToDo.Items.Add(lbiNew);
    
                    ToDoItems.NewItem = false;
                }
            }
    

    В конструкторе проинициализируем флаг:

     // Constructor
            public MainPage()
            {
                ToDoItems.NewItem = false;
    
                InitializeComponent();
            }
    

    Определим обработчики нажатий на кнопки меню. При нажатии на кнопку "Добавить" будем просто переходить на страницу добавления новой задачи (AddItem.xaml). При нажатии на кнопку "Удалить" будем сначала определять, выделен ли какой-нибудь элемент списка.

     private void MenuAdd_Click(object sender, EventArgs e)
            {
                NavigationService.Navigate(new Uri("/AddItem.xaml", UriKind.Relative));
            }
    
            private void MenuRemove_Click(object sender, EventArgs e)
            {
                if (-1 != listToDo.SelectedIndex)
                {
                    listToDo.Items.RemoveAt(listToDo.SelectedIndex);
                }
                else
                {
                    MessageBox.Show("Нет выделенных элементов.");
                }
            }
    

    Теперь добавим в проект приложения новую страницу – Windows Phone Portrait Page. Назовем ее AddItem. В файле AddItem.xaml определим разметку страницы. Она будет включать два текстовых поля (для названия и описания задачи), DatePicker (для выбора даты), TimePicker (для выбора времени), 4 TextBlock (для пояснительных подписей) и две кнопки – "Принять" и "Отмена". Все элементы управления объединим с помощью StackPanel.

    Чтобы элементы DatePicker и TimePicker стали доступны, необходимо скачать расширение Windows Phone Toolkit, что можно сделать, пройдя по ссылке:

    http://silverlight.codeplex.com/releases

    Затем в проект необходимо добавить в References библиотеку Microsoft.Phone.Controls.Toolkit Рис. 3.1:

    (рис 3.1) Добавление в Reference библиотеку

    В самый верх файла разметки необходимо добавить строчку:

     xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
    

    Примечание: Вместо "toolkit" можно придумать любое другое название. В этом случае на странице разметки при использовании элементов управления из данного пространства имен нужно использовать соответствующее указанное название.

    Итак, страница разметки теперь примет вид:

     <!--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="СПИСОК ЗАДАЧ"
                 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">
                <StackPanel Margin="15,15,0,0">
                    <TextBlock Name="NotificationTitleCaption" Text="Заголовок:" FontSize="26" />
                    <TextBox Name="txtTitle" FontSize="26" />
    
                    <TextBlock Text="Текст:"  FontSize="26" />
                    <TextBox Name="txtText" FontSize="26" />
    
                    <TextBlock Text="Дата:" FontSize="26" VerticalAlignment="Center" />
                    <toolkit:DatePicker Name="ToDoItemDate"/>
                    <TextBlock Text="Время:" FontSize="26" VerticalAlignment="Center" />
                    <toolkit:TimePicker Name="ToDoItemTime"/>
    
                    <StackPanel Orientation="Horizontal" Margin="0,20,0,0">
                        <Button Name="btnSet" Content="Принять" Width="220" Click="btnSet_Click" />
                        <Button Name="btnBack" Content="Отмена" Width="220" Click="btnBack_Click" />
                    </StackPanel>
                </StackPanel>
            </Grid>
    

    Перейдем к коду страницы AddItem.xaml.cs.

    Определим обработчики нажатий на кнопки. При нажатии на кнопку "Назад" будем спрашивать у пользователя подтверждения и переходить на главную страницу посредством вызова метода NavigationService.GoBack(). При нажатии на кнопку "Принять" будем проверять, что текстовые поля заполнены, объединять дату и время и в случае, если выбранное время еще не наступило, переходить на главную страницу. Переход будем осуществлять посредством метода NavigationService.GoBack(). В этом случае список на главной странице не будет очищаться.

    private void btnBack_Click(object sender, RoutedEventArgs e)
            {
                MessageBoxResult result = MessageBox.Show("Изменения будут потеряны. Продолжить?", 
    "Отмена", MessageBoxButton.OKCancel);
                if (result == MessageBoxResult.OK)
                {
                    NavigationService.GoBack();
                }
            }
    
            private void btnSet_Click(object sender, RoutedEventArgs e)
            {
                if (!txtTitle.Text.Trim().Equals("")  !txtText.Text.Trim().Equals(""))
                {
                    DateTime date = (DateTime)ToDoItemDate.Value;
                    DateTime time = (DateTime)ToDoItemTime.Value;
                    DateTime beginTime = date + time.TimeOfDay;
    
                    if (beginTime > DateTime.Now)
                    {
                        ToDoItems.NewItem = true;
                        ToDoItems.Title = txtTitle.Text.Trim();
                        ToDoItems.Description = txtText.Text;
                        ToDoItems.DateTime = beginTime.ToString();
    
                        NavigationService.GoBack();
                    }
                    else
                    {
                        MessageBox.Show("Невозможно установить задачу на прошлое.");
                    }
                }
                else
                {
                    MessageBox.Show("Не все поля заполнены.");
                }
            }
    

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

    Лабораторная работа №8. Веб-браузер

    Задание

    Создать простой веб-браузер для Windows Phone 7 с адресной строкой, историей посещений и сохранением истории в изолированном хранилище.

    Освоение

  • элемент управления WebBrowser
  • изолированное хранилище
  • Описание

    Создадим новый проект Silverlight for Windows Phone – Windows Phone Application.

    Откроем файл разметки главной страницы MainPage.xaml. Добавим текстовое поле (для адресной строки), кнопку (при нажатии на нее будет происходить переход) и элемент WebBrowser. Обратите внимание, что браузер находится в пространстве имен "phone", т.о. не требуется подключение дополнительных библиотек. В меню добавим 3 кнопки для навигации по истории (вперед, назад и просмотр). При нажатии на кнопку просмотра истории будем переходить на другую страницу, где в списке будем выводить посещенные страницы.

    Полностью разметка главной страницы примет следующий вид:

     <phone:PhoneApplicationPage 
        x:Class="Wp7IUSLab11.HistoryPage"
        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: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"
        FontFamily="{StaticResource PhoneFontFamilyNormal}"
        FontSize="{StaticResource PhoneFontSizeNormal}"
        Foreground="{StaticResource PhoneForegroundBrush}"
        SupportedOrientations="Portrait" Orientation="Portrait"
        mc:Ignorable="d" d:DesignHeight="696" d:DesignWidth="480"
        shell:SystemTray.IsVisible="True">
    
        <!--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="ОБОЗРЕВАТЕЛЬ (история)" 
                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">
                <ListBox Name="lstHistory">
                    
                </ListBox>
            </Grid>
        </Grid>
     
        <!--Sample code showing usage of ApplicationBar-->
        <phone:PhoneApplicationPage.ApplicationBar>
            <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
                <shell:ApplicationBarIconButton IconUri="/Images/appbar.back.rest.png" 
                Text="назад" Click="Menu1_Click" />
                <shell:ApplicationBarIconButton IconUri="/Images/appbar.cancel.rest.png" 
                Text="очистить" Click="Menu2_Click" />
                <shell:ApplicationBarIconButton IconUri="/Images/appbar.next.rest.png" 
                Text="перейти" Click="Menu3_Click" />
                
                <!--shell:ApplicationBar.MenuItems>
                    <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
                    <shell:ApplicationBarMenuItem Text="MenuItem 2"/>
                </shell:ApplicationBar.MenuItems-->
            </shell:ApplicationBar>
        </phone:PhoneApplicationPage.ApplicationBar>
    
    </phone:PhoneApplicationPage>
    

    Разметка второй страницы будет состоять только из пустого списка (мы будем заполнять его динамически в коде страницы). Меню будет состоять из 3 кнопок (назад, перейти по выделенной ссылке и очистить историю).

     <phone:PhoneApplicationPage 
        x:Class="Wp7IUSLab11.HistoryPage"
        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: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"
        FontFamily="{StaticResource PhoneFontFamilyNormal}"
        FontSize="{StaticResource PhoneFontSizeNormal}"
        Foreground="{StaticResource PhoneForegroundBrush}"
        SupportedOrientations="Portrait" Orientation="Portrait"
        mc:Ignorable="d" d:DesignHeight="696" d:DesignWidth="480"
        shell:SystemTray.IsVisible="True">
    
        <!--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="ОБОЗРЕВАТЕЛЬ (история)" 
                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">
                <ListBox Name="lstHistory" />
            </Grid>
        </Grid>
     
        <!--Sample code showing usage of ApplicationBar-->
        <phone:PhoneApplicationPage.ApplicationBar>
            <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
                <shell:ApplicationBarIconButton IconUri="/Images/appbar.back.rest.png" 
                Text="назад" Click="Menu1_Click" />
                <shell:ApplicationBarIconButton IconUri="/Images/appbar.cancel.rest.png" 
                Text="очистить" Click="Menu2_Click" />
                <shell:ApplicationBarIconButton IconUri="/Images/appbar.next.rest.png" 
                Text="перейти" Click="Menu3_Click" />
                
                <!--shell:ApplicationBar.MenuItems>
                    <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
                    <shell:ApplicationBarMenuItem Text="MenuItem 2"/>
                </shell:ApplicationBar.MenuItems-->
            </shell:ApplicationBar>
        </phone:PhoneApplicationPage.ApplicationBar>
    
    </phone:PhoneApplicationPage>
    

    Для того чтобы упростить взаимодействие между страницами приложения создадим статический класс History, который будет содержать статический список ссылок, индекс текущего элемента списка и несколько функций:

     public static class History
        {
            public static List<Uri> listHistory;
            public static int nCurHistoryPos;
    
            public static void Init()
            {
                listHistory = new List<Uri>();
                nCurHistoryPos = 0;
            }
    
            public static string ListToString()
            {
                string strRes = "";
    
                for (int i = 0; i < listHistory.Count; i++)
                {
                    if (i != 0)
                    {
                        strRes += "|";
                    }
    
                    strRes += listHistory[i].ToString();
                }
    
                return strRes;
            }
    
            public static void StringToList(string strHist)
            {
                string[] strMas = strHist.Split( '|');
    
                for (int i = 0; i < strMas.GetLength(0); i++)
                {
                    listHistory.Add(new Uri(strMas[i]));
                }
            }
        }
    

    Вернемся к файлу MainPage.xaml.cs. При нажатии на кнопку btnRun будем осуществлять загрузку в браузере страницы, адрес которой указан в текстовом поле. При этом в случае если ссылка не содержит "http://", то его обязательно будем добавлять:

     private void btnRun_Click(object sender, RoutedEventArgs e)
            {
                try
                {
                    //убираем пробелы и приводим к нижнему регистру
                    txtAddress.Text = txtAddress.Text.Trim().ToLower();
    
                    //добавляем, если надо, "http://"
                    if (txtAddress.Text.Length < 7)
                    {
                        txtAddress.Text = "http://" + txtAddress.Text;
                    }
                    else
                    {
                        if (!txtAddress.Text.Substring(0, 7).Equals("http://"))
                        {
                            txtAddress.Text = "http://" + txtAddress.Text;
                        }
                    }
    
                    //переходим
                    Uri url = new Uri(txtAddress.Text);
                    wBrowser.Navigate(url);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
    

    Как только страница в браузере загружена, будем добавлять ее в историю. При этом, если история полна, будем удалять последний элемент:

    private void wBrowser_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
            {
                //если мы движемся не по истории
                if (!bHistoryNavigate)
                {
                    if (History.listHistory.Count > 0)
                    {
                        //в случае, если бродили по истории - подтираем ее до текущей позиции
                        History.listHistory.RemoveRange(0, History.nCurHistoryPos);
    
                        if (!History.listHistory[0].Equals(wBrowser.Source))
                        {
                            AddToHistoryUri();
                        }
                    }
                    else
                    {
                        AddToHistoryUri();
                    }
                }
                else
                {
                    //устанавливаем флаг назад в фолс
                    bHistoryNavigate = false;
                }
    
                txtAddress.Text = wBrowser.Source.AbsoluteUri;
            }
    

    При добавлении ссылки в историю будем сохранять всю историю в изолированном хранилище:

    private void AddToHistoryUri()
            {
                if (History.listHistory.Count >= MAX_HISTORY_LEN)
                {
                    History.listHistory.RemoveAt(History.listHistory.Count - 1);
                }
    
                History.listHistory.Insert(0, wBrowser.Source);
    
                string strHistoryText = History.ListToString();
                SaveToIsolatedStorage(strHistoryText);
            }
    

    Создадим константу для хранения имени файла, в который будем сохранять историю:

    private const string strIStorageName = "Wp7IUSLab11.txt";
    

    В конструкторе класса будем загружать историю из изолированного хранилища - файла (в случае, если файл существует):

    // Constructor
            public MainPage()
            {
                InitializeComponent();
    
                History.Init();
    
                if (IsIsolatedStorageExist())
                {
                    string strText = LoadFromIsolatedStorage();
                    History.StringToList(strText);
    
                    if (History.listHistory.Count > 0)
                    {
                        txtAddress.Text = "идет загрузка...";
                        wBrowser.Navigate(History.listHistory[0]);
                    }
                }
            }
    
            private void SaveToIsolatedStorage(string histText)
            {
                IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
                IsolatedStorageFileStream fileStream = fileStorage.CreateFile(strIStorageName);
    
                StreamWriter sw = new StreamWriter(fileStream);
                sw.Write(histText);
                sw.Close();
    
                fileStream.Close();
            }
    
            private string LoadFromIsolatedStorage()
            {
                IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
                IsolatedStorageFileStream fileStream = fileStorage.OpenFile(strIStorageName, System.IO.FileMode.Open);
    
                StreamReader sr = new StreamReader(fileStream);
                string strRes = sr.ReadToEnd();
                sr.Close();
                fileStream.Close();
    
                return strRes;
            }
    
            private bool IsIsolatedStorageExist()
            {
                IsolatedStorageFile sileStorage = IsolatedStorageFile.GetUserStoreForApplication();
                return sileStorage.FileExists(strIStorageName);
            }
    
            private void RemoveIsolatedStorage()
            {
                if (IsIsolatedStorageExist())
                {
                    IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
                    fileStorage.DeleteFile(strIStorageName);
                }
            }
    

    Для работы с изолированным хранилищем в код необходимо добавить директивы:

    using System.IO.IsolatedStorage;
    using System.IO;
    

    При переходе на главную страницу будем загружать указанную страницу. В случае если история пуста, будем очищать изолированное хранилище:

    protected override void OnNavigatedTo(NavigationEventArgs e)
            {
                base.OnNavigatedTo(e);
    
                if (NavigationContext.QueryString.ContainsKey("hist"))
                {
                    History.nCurHistoryPos = int.Parse(NavigationContext.QueryString["hist"].ToString());
                    bHistoryNavigate = true;
                    wBrowser.Navigate(History.listHistory[History.nCurHistoryPos]);
                }
                else
                {
                    if (0 == History.listHistory.Count)
                    {
                        RemoveIsolatedStorage();
                    }
                }
            }
    

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

    Лабораторная работа №9. Игра "Загони шар в лузу"

    Задание

    Создать игру "Загони шар в лузу" для Windows Phone 7 с использованием акселерометра (шар меняет направление и скорость при наклоне телефона) и возможностью сохранения наилучшего времени в изолированном хранилище.

    Освоение

  • акселерометр
  • работа с потоками
  • изолированное хранилище
  • Описание

    Создадим новый проект Silverlight for Windows Phone – Windows Phone Application.

    Откроем файл разметки главной страницы MainPage.xaml. Разместим элемент Canvas, в котором будут находиться два эллипса (элемент управления Ellipse). В зависимости от положения телефона будем менять свойства Canvas.Left и Canvas.Top. Добавим также текстовый блок для вывода времени игры и 3 кнопки: новая игра, просмотр рекорда и сброс рекорда.

    <!--ContentPanel - place additional content here-->
            <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
                <StackPanel>
                    <Canvas Name="cnvMain" Height="700" Width="450" Background="White">
                        <Ellipse Name="ellBall" Height="30" Width="30" Canvas.Left="50" Canvas.Top="200" Canvas.ZIndex="2" 
                        Stroke="Black" StrokeThickness="1" VerticalAlignment="Top" Fill="Red" />
                        <Ellipse Name="ellHole" Height="50" Width="50" Canvas.Left="200" Canvas.Top="50" Canvas.ZIndex="1"
                         Stroke="Black" StrokeThickness="1" VerticalAlignment="Top" Fill="Black" />
                    </Canvas>
    
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Name="lblTime" Text="00:00" FontSize="26" VerticalAlignment="Center" />
                        <Button Name="btnNewGame" Content="Новая" Click="btnNewGame_Click" />
                        <Button Name="btnRecord" Content="Рекорд" Click="btnRecord_Click" />
                        <Button Name="btnResetRecord" Content="Сброс" Click="btnResetRecord_Click" />
                    </StackPanel>
                </StackPanel>
            </Grid>
    

    Добавим константы для определения радиусов шарика и лузы:

            private const double BALL_RAD = 15d;
            private const double HOLE_RAD = 20d;
    

    В конструкторе класса проинициализируем размеры элепсов:

           ellBall.Width = BALL_RAD * 2d;
                ellBall.Height = BALL_RAD * 2d;
                ellHole.Width = HOLE_RAD * 2d;
                ellHole.Height = HOLE_RAD * 2d;
    

    Для работы с акселерометром добавим в код директиву:

      using Microsoft.Devices.Sensors;     
    

    Создадим глобальную переменную акселерометра:

        private Accelerometer myAccel;   
    

    В конструкторе класса страницы проинициализируем акселерометр и подпишемся на событие изменения положения телефона:

       myAccel = new Accelerometer();
            myAccel.ReadingChanged += new EventHandler<AccelerometerReadingEventArgs>(myAccel_ReadingChanged);
    

    Для изменения скорости шарика определим глобальную константу и переменные в классе:

      private const double KOEFF_ACCEL = 20d;
            private double nSpeedUpZ, nSpeedUpX;
            private bool bGameStopped;
    

    При изменении положения телефона будем менять координаты шарика. Поскольку из отдельного потока нельзя напрямую работать с элементами управления, будем пользоваться диспетчерами. После изменения координат будем проверять игру на победу (попадание центра шарика в лузу).

     void myAccel_ReadingChanged(object sender, AccelerometerReadingEventArgs e)
            {
                if (myAccel.IsDataValid  !bGameStopped)
                {
                    nSpeedUpZ = e.Z * KOEFF_ACCEL;
                    nSpeedUpX = e.X * KOEFF_ACCEL;
    
                    if (e.Z < 0)
                    {
                        Dispatcher.BeginInvoke(() => HandleUp());
                    }
                    else
                    {
                        Dispatcher.BeginInvoke(() => HandleDown());
                    }
    
                    if (e.X < 0)
                    {
                        Dispatcher.BeginInvoke(() => HandleLeft());
                    }
                    else
                    {
                        Dispatcher.BeginInvoke(() => HandleRight());
                    }
    
                    Dispatcher.BeginInvoke(() => WinChecking());
                }
            }
    
            private void HandleUp()
            {
                if ((double)ellBall.GetValue(Canvas.TopProperty) > 0d)
                {
                    ellBall.SetValue(Canvas.TopProperty, (double)ellBall.GetValue(Canvas.TopProperty) + nSpeedUpZ);
                }
            }
    
            private void HandleDown()
            {
                if ((double)ellBall.GetValue(Canvas.TopProperty) < (cnvMain.Height - ellBall.Height))
                {
                    ellBall.SetValue(Canvas.TopProperty, (double)ellBall.GetValue(Canvas.TopProperty) + nSpeedUpZ);
                }
            }
    
            private void HandleLeft()
            {
                if ((double)ellBall.GetValue(Canvas.LeftProperty) > 0d)
                {
                    ellBall.SetValue(Canvas.LeftProperty, (double)ellBall.GetValue(Canvas.LeftProperty) + nSpeedUpX);
                }
            }
    
            private void HandleRight()
            {
                if ((double)ellBall.GetValue(Canvas.LeftProperty) < (cnvMain.Width - ellBall.Width))
                {
    
                    ellBall.SetValue(Canvas.LeftProperty, (double)ellBall.GetValue(Canvas.LeftProperty) + nSpeedUpX);
                }
            }
    
            private void WinChecking()
            {
                if (IsWin())
                {
                    bGameStopped = true;
                    dTimer.Stop();
                    myAccel.Stop();
    
                    if (IsRecord())
                    {
                        nTimeRecord = nTime;
                        MessageBox.Show("Победа!" + Environment.NewLine + "Новый рекорд: " + FormatTime(nTimeRecord) + "!");
    
                        SaveToIsolatedStorage(nTimeRecord.ToString());
                    }
                    else
                    {
                        MessageBox.Show("Победа!");
                    }
                }
            }
    
            private bool IsWin()
            {
                bool bRes = false;
    
                if ((((double)ellBall.GetValue(Canvas.TopProperty) + BALL_RAD) > 
                ((double)ellHole.GetValue(Canvas.TopProperty))) 
                    (((double)ellBall.GetValue(Canvas.TopProperty) + BALL_RAD) < 
                    ((double)ellHole.GetValue(Canvas.TopProperty) + HOLE_RAD + HOLE_RAD)))
                {
                    if ((((double)ellBall.GetValue(Canvas.LeftProperty) + BALL_RAD) > 
                    ((double)ellHole.GetValue(Canvas.LeftProperty))) 
                    (((double)ellBall.GetValue(Canvas.LeftProperty) + BALL_RAD) < 
                    ((double)ellHole.GetValue(Canvas.LeftProperty) + HOLE_RAD + HOLE_RAD)))
                    {
                        bRes = true;
                    }
                }
    
                return bRes;
            }
    

    Введем в игру счетчик времени. Добавим константу и переменные:

     private const int MAX_TIME = 3599; //59:59
            private int nTimeRecord;
            private int nTime;
            System.Windows.Threading.DispatcherTimer dTimer;
    

    В конструкторе класса объявим новый поток и подпишемся на событие срабатывания таймера (каждую секунду):

                 dTimer = new System.Windows.Threading.DispatcherTimer();
                dTimer.Interval = new TimeSpan(0, 0, 0, 1, 0); //1 сек
                dTimer.Tick += new EventHandler(Timer_Tick);
    

    По событию будем увеличивать таймер и с помощью диспетчера менять время в текстовом блоке на странице. А в случае, если максимальное время достигнуто, останавливаем игру.

        private void Timer_Tick(object sender, EventArgs e)
            {
                nTime++;
                Dispatcher.BeginInvoke(() => ChangeTimerLabel());
    
                if (IsLose())
                {
                    Dispatcher.BeginInvoke(() => Lose());
                }
            }
    
            private void ChangeTimerLabel()
            {
                lblTime.Text = FormatTime(nTime);
            }
    
            private string FormatTime(int time)
            {
                return String.Format("{0:00}", (int)(time / 60)) + ":" + String.Format("{0:00}", (time % 60));
            }
    
            private bool IsLose()
            {
                bool bRes = false;
    
                if (nTime >= MAX_TIME)
                {
                    bRes = true;
                }
    
                return bRes;
            }
    
            private void Lose()
            {
                bGameStopped = true;
                dTimer.Stop();
                myAccel.Stop();
    
                MessageBox.Show("Скучно...");
            }
     

    В случае успешного попадания шарика в лузу, если достигнут рекорд по времени, сохраняем его в файле в изолированном хранилище:

      private const string strIStorageName = "Wp7IUSLab12.txt";
    

    При этому в конструкторе класса, в случае если файл с рекордом существует, будем загружать рекорд:

    if (IsIsolatedStorageExist())
       {
           nTimeRecord = int.Parse(LoadFromIsolatedStorage());
       }
       else
       {
           nTimeRecord = MAX_TIME;
       }
    

    При нажатии на кнопку "Сброс" будем очищать хранилище:

    Для работы с хранилищем определим директивы:

            using System.IO.IsolatedStorage;
            using System.IO;
    

    Создадим функции сохранения рекорда в хранилище, загрузки, удаления и проверки файла на существование:

        private void SaveToIsolatedStorage(string histText)
            {
                IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
                IsolatedStorageFileStream fileStream = fileStorage.CreateFile(strIStorageName);
    
                StreamWriter sw = new StreamWriter(fileStream);
                sw.Write(histText);
                sw.Close();
    
                fileStream.Close();
            }
    
            private string LoadFromIsolatedStorage()
            {
                IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
                IsolatedStorageFileStream fileStream = fileStorage.OpenFile(strIStorageName, System.IO.FileMode.Open);
    
                StreamReader sr = new StreamReader(fileStream);
                string strRes = sr.ReadToEnd();
                sr.Close();
                fileStream.Close();
    
                return strRes;
            }
    
            private bool IsIsolatedStorageExist()
            {
                IsolatedStorageFile sileStorage = IsolatedStorageFile.GetUserStoreForApplication();
                return sileStorage.FileExists(strIStorageName);
            }
    
            private void RemoveIsolatedStorage()
            {
                if (IsIsolatedStorageExist())
                {
                    IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
                    fileStorage.DeleteFile(strIStorageName);
                }
            }
    

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

    Лабораторная работа №10. Определение местоположения

    Задание

    Создать приложение для Windows Phone 7 с использованием карты, определения местоположения и возможностью сохранения и демонстрации сохраненных мест на карте.

    Освоение

  • карта и определение местоположения
  • изолированное хранилище
  • Описание

    Создадим новый проект Silverlight for Windows Phone – Windows Phone Application.

    Откроем файл разметки главной страницы MainPage.xaml . Для использования карт (элемента управления Map) необходимо подключить библиотеку Microsoft.Phone.Controls.Maps и добавить в файл разметки пространство имен:

    xmlns:my="clr-namespace:Microsoft.Phone.Controls.Maps;assembly=Microsoft.Phone.Controls.Maps"
    

    Наше приложение будем основано на элементах Pivot. Для этого подключим библиотеку Microsoft.Phone.Controls и добавим пространство имен:

    xmlns:ctrl="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"   
    

    В первый PivotItem поместим карту и элементы для сохранения названия места. Во второй PivotItem – список сохраненных мест и 2 кнопки для удаления и отображения выбранного места.

          <!--ContentPanel - place additional content here-->
            <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
                <ctrl:Pivot Name="myPivot" SelectionChanged="myPivot_SelectionChanged">
                    <ctrl:PivotItem Header="карта">
                        <Canvas>
                            <my:Map Name="myMap" Height="529" Width="450" />
    
                            <Border Name="msgPlaceName" Visibility="Collapsed" BorderThickness="4" BorderBrush="White" 
     Canvas.Left="0" Canvas.Top="75" Canvas.ZIndex="2">
                                <StackPanel Width="430" Background="Black">
                                    <StackPanel Orientation="Horizontal">
                                        <TextBlock Text="Введите название места:" FontSize="26" 
    Width="350" Margin="10,10,10,10" />
                                        <TextBlock Text="X" TextAlignment="Right" FontSize="26" 
    Width="30" Margin="10,10,10,10" MouseEnter="lblPlaceNameX_MouseEnter" />
                                    </StackPanel>
                                    <TextBox Name="txtPlaceName" />
                                    <Button Name="btnPlaceNameApply" Content="Принять" 
    Margin="0,0,5,0" Click="btnPlaceNameApply_Click" />
                                </StackPanel>
                            </Border>
                        </Canvas>
                    </ctrl:PivotItem>
                    
                    <ctrl:PivotItem Name="piPlaces" Header="мои места">
                        <StackPanel>
                            <ListBox Name="lstPlaces" Height="550" />
    
                            <StackPanel Orientation="Horizontal">
                                <Button Name="btnPlacesShow" Content="Показать"
                                  Width="220" 
                                Click="btnPlacesShow_Click" />
                                <Button Name="btnPlacesDelete" Content="Удалить" 
                                    Width="220" 
                                Click="btnPlacesDelete_Click"  />
                            </StackPanel>
                        </StackPanel>
                    </ctrl:PivotItem>
                </ctrl:Pivot>
            </Grid>
     

    Добавим также меню с 3 кнопками (увеличить масштаб, уменьшить, показать текущее местоположение) и еще 2 контекстными элементами (переключение режима и сохранение местоположения):

            <!--Sample code showing usage of ApplicationBar-->
        <phone:PhoneApplicationPage.ApplicationBar>
            <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
                <shell:ApplicationBarIconButton IconUri="/Images/appbar.minus.rest.png" 
                Text="Масштаб -" Click="MenuZoomSub_Click" />
                <shell:ApplicationBarIconButton IconUri="/Images/appbar.add.rest.png" 
                Text="Масштаб +" Click="MenuZoomAdd_Click" />
                <shell:ApplicationBarIconButton IconUri="/Images/appbar.feature.search.rest.png" 
                Text="Где я?" Click="MenuCurrentPos_Click" />
                
                <shell:ApplicationBar.MenuItems>>
                    <shell:ApplicationBarMenuItem Text="режим" Click="MenuMapMode_Click" />
                    <shell:ApplicationBarMenuItem Text="сохранить" Click="MenuSavePos_Click" />
                </shell:ApplicationBar.MenuItems>
            </shell:ApplicationBar>
    
        </phone:PhoneApplicationPage.ApplicationBar>
    

    Перейдем к написанию кода.

    Для начала опишем структуру для сохранения мест. Она будет содержать имя места и координаты:

    struct Coords
     {
       public string Name;         //название места
       public double Latitude;     //широта
       public double Longitude;    //долгота
     }
    

    В классе определим глобальную переменную – список:

    private List<Coords> Places; 
    

    Для работы с определением местоположения и отметками на карте необходимо прописать следующие директивы:

    using Microsoft.Phone.Controls.Maps;
    using System.Device.Location;
    

    Определим две глобальные переменные:

    private GeoCoordinateWatcher m_GeoWatcher;
    private Pushpin myPushpin;
    

    Немного изменим конструктор класса (проинициализируем некоторые переменные) и добавим обработчик события изменения местоположения:

    // Constructor
    public MainPage()
    {
        InitializeComponent();
    
        myMap.ZoomLevel = 1.0d;
    
        Places = new List<Coords>();
    
        m_GeoWatcher = new GeoCoordinateWatcher();
        m_GeoWatcher.MovementThreshold = 100.0f;
        m_GeoWatcher.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<
        GeoCoordinate>>(m_GeoWatcher_PositionChanged);
    
        myPushpin = new Pushpin();
        new Thread(startMyGeoWatcher).Start();
    }
    
    private void m_GeoWatcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
    {
        myPushpin.Location = e.Position.Location;
    }
    
    void startMyGeoWatcher()
    {
        m_GeoWatcher.TryStart(false, TimeSpan.FromSeconds(60));
    }
    

    При нажатии на кнопку меню "Где я?" будем добавлять Pushpin с текущими координатами на карту:

         private void ShowPosition(GeoCoordinate coords)
    {
        myMap.Center = coords;
        myMap.Children.Clear();
    
        if (!myMap.Children.Contains(myPushpin))
        {
    Pushpin pshpn = new Pushpin();
    pshpn.Location = coords;
    
    myMap.Children.Add(pshpn);
        }
    }
     

    При нажатии на кнопки изменения масштаба будем изменять свойство ZoomLevel карты на 1. При нажатии на кнопку смены режима будем менять свойство Mode. При сохранении текущего положения будем отображать элементы, объединенные элементом msgPlaceName .

        private void MenuZoomAdd_Click(object sender, EventArgs e)
            {
                myMap.ZoomLevel += 1;
            }
    
            private void MenuZoomSub_Click(object sender, EventArgs e)
            {
                myMap.ZoomLevel -= 1;
            }
    
            private void MenuCurrentPos_Click(object sender, EventArgs e)
            {
                ShowPosition(myPushpin.Location);
            }
    
            private void MenuMapMode_Click(object sender, EventArgs e)
            {
                if (myMap.Mode is RoadMode)
                {
                    myMap.Mode = new AerialMode(true);
                }
                else
                {
                    myMap.Mode = new RoadMode();
                }
            }
    
            private void MenuSavePos_Click(object sender, EventArgs e)
            {
                if (0 == myMap.Children.Count)
                {
                    MessageBox.Show("Неверное действие.");
                }
                else
                {
                    msgPlaceName.Visibility = System.Windows.Visibility.Visible;
                    txtPlaceName.Text = "";
                    myMap.IsEnabled = false;
                    ApplicationBar.IsVisible = false;
                    piPlaces.IsEnabled = false;
                }
            }
    

    При выборе элемента из списка сохраненных мест, будем отображать координаты на карте:

        private void btnPlacesShow_Click(object sender, RoutedEventArgs e)
            {
                if (-1 != lstPlaces.SelectedIndex)
                {
                    GeoCoordinate coords = new GeoCoordinate(Places[lstPlaces.SelectedIndex].Latitude,
                                Places[lstPlaces.SelectedIndex].Longitude);
                    ShowPosition(coords);
    
                    myPivot.SelectedIndex = 0;
                    myPivot.UpdateLayout();
                }
                else
                {
                    MessageBox.Show("Элемент не выбран.");
                }
            }
    

    Сохранять список мест будем в файле в изолированном хранилище:

        private const string strIStorageName = "Wp7IUSLab13.txt";
    

    В конструкторе в случае, если файл существует, будем загружать список из изолированного хранилища:

        if (IsIsolatedStorageExist())
            {
                FromStringToList(LoadFromIsolatedStorage());
            }
    

    Сохранять список в изолированное хранилище лучше всего при сохранении нового места. А удалять файл при удалении, если число сохраненных мест равно 0.

    Работу с изолированным хранилищем обеспечивают следующие четыре функции:

       private void SaveToIsolatedStorage(string histText)
            {
                IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
                IsolatedStorageFileStream fileStream = fileStorage.CreateFile(strIStorageName);
    
                StreamWriter sw = new StreamWriter(fileStream);
                sw.Write(histText);
                sw.Close();
    
                fileStream.Close();
            }
    
            private string LoadFromIsolatedStorage()
            {
                IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
                IsolatedStorageFileStream fileStream = fileStorage.OpenFile(strIStorageName, System.IO.FileMode.Open);
    
                StreamReader sr = new StreamReader(fileStream);
                string strRes = sr.ReadToEnd();
                sr.Close();
                fileStream.Close();
    
                return strRes;
            }
    
            private bool IsIsolatedStorageExist()
            {
                IsolatedStorageFile sileStorage = IsolatedStorageFile.GetUserStoreForApplication();
                return sileStorage.FileExists(strIStorageName);
            }
    
            private void RemoveIsolatedStorage()
            {
                if (IsIsolatedStorageExist())
                {
                    IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication();
                    fileStorage.DeleteFile(strIStorageName);
                }
            }
    

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

    Для тестирования приложения на эмуляторе можно использовать эмулятор определения местоположения Рис. 3.2 :

    (рис 3.2) Эмулятор определения местоположения

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