Опубликован: 11.01.2013 | Доступ: свободный | Студентов: 623 / 124 | Длительность: 12:06:00
Лекция 2:

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

< Лекция 1 || Лекция 2: 12345 || Лекция 3 >
Аннотация: Лабораторные работы: телефонная книжка, блокнот, меню ресторана, калькулятор, база паролей.

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

Задание

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

Освоение

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

Описание

Приложение, которое мы будем создавать, основано на 4 основных функциях управления данными – CRUD (create, read, update, delete).

Для начала определимся, что хотим получить. Приложение будет состоять из 2 окон: основного со списком контактов и окна редактирования/добавления контактов Рис. 2.1 .

Внешний вид приложения

Рис. 2.1. Внешний вид приложения

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

Откроем файл разметки главной страницы MainPage.xaml. Изменим текст названия приложения ApplicationTitle, например, на "КОНТАКТЫ". Для того чтобы выделить больше места для будущего списка контактов, закомментируем или удалим блок заголовка страницы PageTitle.

<!--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>

Внутрь объекта Grid с именем ContentPanel поместим элемент ListBox. Назовем его listContacts (атрибут Name – необходим тем элементам, к которым будет непосредственное обращение из кода программы) и определим высоту списка (атрибут Height). Внутрь ListBox поместим несколько первых контактов, которые будут доступны сразу после запуска приложения. Каждый элемент списка необходимо заключить в тег ListBoxItem. Т.к. контакт состоит из имени и номера телефона, добавим внутрь объекта ListBoxItem два текстовых блока TextBlock. Определим для них атрибуты FontSize (размер шрифта) и Margin (отступы). Поскольку. данные блоки идут последовательно друг за другом, их необходимо заключить в тег StackPanel.

<!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <ListBox Name="listContacts" Height="615">
                <ListBoxItem>
                    <StackPanel Margin="10,7,10,7">
                        <TextBlock Text="Андреев Андрей" FontSize="28" />
                        <TextBlock Text="+79215555555" FontSize="22" Margin="50,0,0,0" />
                    </StackPanel>
                </ListBoxItem>
                <ListBoxItem>
                    <StackPanel Margin="10,7,10,7">
                        <TextBlock Text="Иванов Иван" FontSize="28" />
                        <TextBlock Text="+79114444444" FontSize="22" Margin="50,0,0,0" />
                    </StackPanel>
                </ListBoxItem>
                <ListBoxItem>
                    <StackPanel Margin="10,7,10,7">
                        <TextBlock Text="Алексеев Алексей" FontSize="28" />
                        <TextBlock Text="+79656666666" FontSize="22" Margin="50,0,0,0" />
                    </StackPanel>
                </ListBoxItem>
            </ListBox>
        </Grid>

Создадим меню на главной странице нашего приложения. Для этого в самом низу файла MainPage.xaml уберем комментарии, в которые заключен тег phone:PhoneApplicationPage.ApplicationBar и его содержимое. Добавим еще один элемент shell:ApplicationBarIconButton и изменим текст (атрибут Text) для всех пунктов меню. Также можно поменять картинки для пунктов меню (атрибут IconUri). В данном примере мы этого делать не будем. Добавим обработчики нажатия на кнопки меню. Для этого добавим в теги shell:ApplicationBarIconButton атрибут Click. При этом в файле MainPage.xaml.cs автоматически будут добавлены соответствующие пустые обработчики.

Элементы shell:ApplicationBar.MenuItems закомментируем или удалим – для данного приложения они нам не понадобятся.

<!--Sample code showing usage of ApplicationBar-->
    <phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" 
            Text="Добавить" Click="ApplicationBarMenuAdd_Click" />
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" 
            Text="Изменить" Click="ApplicationBarMenuEdit_Click" />
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button3.png" 
            Text="Удалить" Click="ApplicationBarMenuRemove_Click" />
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>

Добавим в проект приложения новую страницу – Windows Phone Portrait Page. Назовем ее PageEdit. Откроем файл PageEdit.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="добавление" Margin="9,-7,0,0" 
            Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

Внутрь объекта Grid с именем ContentPanel поместим элементы управления. Пусть текстовые поля будут находиться в одной таблице (Grid), а кнопки в другой:

текстовый блок "Имя:" текстовое поле "txtName"
текстовый блок "Телефон:" текстовое поле "txtPhone"
кнопка btnApply кнопка btnCancel

Таблицы поместим в тег StackPanel. Элемент Grid состоит из части объявления строк и столбцов и части содержимого. Элементы содержимого должны содержать в себе атрибуты Grid.Column и Grid.Row, которые обозначают индексы столбца и строки таблицы, в которой находятся (индексы нумеруются с нуля). Атрибут ShowGridLines позволяет сделать видимой или невидимой границу таблицы. Определим также вертикальное выравнивание элементов (VerticalAlignment) и размер шрифта (FontSize). Для текстового поля txtPhone определим контекст ввода (InputScope). Контекст ввода служит для того, чтобы пользователю было удобнее вводить те или иные данные в текстовое поле. Если установить значение атрибута InputScope в "TelephoneNumber", то клавиатура будет установлена в телефонный режим ввода Рис. 2.2 .

Клавиатура в телефонном режиме ввода

Рис. 2.2. Клавиатура в телефонном режиме ввода

<Grid ShowGridLines="False" Margin="0,20,0,0">
                    <Grid.RowDefinitions>
                        <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" />
                    <TextBox Name="txtName" Text="Имя" Grid.Column="1" Grid.Row="0" />
                    <TextBox Name="txtPhone" Text="Телефон" InputScope="TelephoneNumber" Grid.Column="1" Grid.Row="1" />
               </Grid>

Для кнопок btnApply и btnCancel определим атрибут Click – обработчик кнопок.

Итак, весь код содержимого ContentPanel в итоге должен выглядеть примерно так:

<!--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 />
                    </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" />
                    <TextBox Name="txtName" Text="Имя" Grid.Column="1" Grid.Row="0" />
                    <TextBox Name="txtPhone" Text="Телефон" InputScope="TelephoneNumber" Grid.Column="1" Grid.Row="1" />
                </Grid>

                <Grid Margin="0,20,0,0">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="90"/>
                    </Grid.RowDefinitions>

                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="220" />
                        <ColumnDefinition Width="220" />
                    </Grid.ColumnDefinitions>

                    <Button Name="btnApply" Grid.Column="0" Grid.Row="0" Content="Принять" Click="btnApply_Click" />
                    <Button Name="btnCancel" Grid.Column="1" Grid.Row="0" Content="Отмена" Click="btnCancel_Click" />
                </Grid>
            </StackPanel>
        </Grid>

Теперь перейдем к программированию логики нашего приложения.

Откроем файл MainPage.xaml.cs. При нажатии на кнопку "Добавить" должен происходить переход на страницу PageEdit.xaml. Для этого в самом верху кода пропишем директиву, облегчит написание переходов между страницами:

using System.Windows.Navigation;

Перейдем к обработчику нажатия на кнопку "Добавить". Напишем здесь код перехода на страницу добавления/изменения контактов:

private void ApplicationBarMenuAdd_Click(object sender, EventArgs e)
        {
            NavigationService.Navigate(new Uri("/PageEdit.xaml", UriKind.Relative));
        }

В обработчике нажатия на кнопку "Удалить" следует получить индекс выделенного элемента списка и, если он не равен -1 (если есть выделенный элемент), удалить данный элемент. Поскольку в элементе ListBox мы задали значение атрибута Name="listContacts", то обращение к списку из кода программы следует осуществлять через объект listContacts.

private void ApplicationBarMenuRemove_Click(object sender, EventArgs e)
{
    int nIndex = listContacts.SelectedIndex;

    if (-1 != nIndex)
    {
       listContacts.Items.RemoveAt(nIndex);
    }
    else
    {
       MessageBox.Show("Контакт не выбран.");
            }
        }

Теперь перейдем к обработчику нажатия на кнопку "Изменить". Здесь также следует получить индекс выделенного элемента списка и проверить, что он не равен -1. Поскольку элементы списка контактов в приложении задаются динамически, то обращение по имени к этим элементам затруднено. Поэтому для получения значений имени контакта и телефона необходимо:

  • получить выделенный элемент списка и привести его к типу ListBoxItem;
  • получить содержимое элемента ListBoxItem и привести его к типу StackPanel;
  • получить коллекцию элементов StackPanel и из нее взять элемент с индексом 0 (для имени контакта) или 1 (для телефона контакта) и привести его к типу TextBlock;
  • получить значение свойства Text элемента TextBlock.

Данная процедура множественного преобразования типов на C# будет выглядеть следующим образом:

string strName = ((TextBlock)((StackPanel)((ListBoxItem)listContacts.SelectedItem)
.Content).Children.ElementAt(0)).Text;
     string strPhone = ((TextBlock)((StackPanel)((ListBoxItem)listContacts.SelectedItem)
     .Content).Children.ElementAt(1)).Text;

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

Передача параметров между страницами осуществляется так же, как и в web-приложениях GET-запрос. Поскольку после адреса страницы ставится символ "?", за которым следуют пары "ключ=значение", разделенные символом "&". Передаваемые строковые значения следует преобразовывать с помощью функции Uri.EscapeDataString(). В этом случае будет обеспечена правильная передача специальных символов, таких как "&".

private void ApplicationBarMenuEdit_Click(object sender, EventArgs e)
        {
            int nIndex = listContacts.SelectedIndex;

            if (-1 != nIndex)
            {
                string strName = ((TextBlock)((StackPanel)((ListBoxItem)listContacts.SelectedItem)
                .Content).Children.ElementAt(0)).Text;
                string strPhone = ((TextBlock)((StackPanel)((ListBoxItem)listContacts.SelectedItem)
                .Content).Children.ElementAt(1)).Text;

                NavigationService.Navigate(new Uri("/PageEdit.xaml?name=" + Uri.EscapeDataString(strName) +
                        "&phone=" + Uri.EscapeDataString(strPhone) +
                        "&id=" + Uri.EscapeDataString(nIndex.ToString()),
                        UriKind.Relative));
            }
            else
            {
                MessageBox.Show("Контакт не выбран.");
            }
        }

Перейдем к файлу PageEdit.xaml.cs. В самый верх добавим директиву:

using System.Windows.Navigation;

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

private int nCurrentId = -1;

Добавим в класс перегруженный метод перехвата пришедших запросов OnNavigatedTo(NavigationEventArgs e). В этом методе будем считывать и обрабатывать пришедшие имена, телефоны и индексы контактов.

Если в полученном запросе есть ключи id, name и phone, значит, пользователь нажал кнопку "Редактировать". В этом случае просто следует присвоить свойствам Text текстовых полей пришедшие значения.

Если полученный запрос не содержит таких ключей, значит, пользователь нажал кнопку "Добавить". В этом случае следует очистить свойства Text.

protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            //если есть ключи, значит - режим редактирования
            if (NavigationContext.QueryString.ContainsKey("id") &&
                NavigationContext.QueryString.ContainsKey("name") &&
                NavigationContext.QueryString.ContainsKey("phone"))
            {
                if (!int.TryParse(NavigationContext.QueryString["id"].ToString(), out nCurrentId))
                {
                    nCurrentId = -1;
                }

                txtName.Text = NavigationContext.QueryString["name"].ToString();
                txtPhone.Text = NavigationContext.QueryString["phone"].ToString();

                PageTitle.Text = "изменение";
            }
            else
            {
                //режим добавления

                nCurrentId = -1;
                txtName.Text = "";
                txtPhone.Text = "";

                PageTitle.Text = "добавление";
            }

            txtName.Focus();
        }

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

private void btnCancel_Click(object sender, RoutedEventArgs e)
        {
            MessageBoxResult result = MessageBox.Show("Изменения будут потеряны. 
            Продолжить?", "Отмена", MessageBoxButton.OKCancel);
            if (result == MessageBoxResult.OK)
            {
                NavigationService.GoBack();
            }
        }

Во втором случае будем проверять свойство Text текстовых полей на пустоту. Если же они непустые, будем передавать в главную страницу значения этих свойств. Если осуществлять переход на главную страницу посредством методы NavigationService.Navigate(), то страница MainPage.xaml будет перезагружаться, и все изменения будут потеряны. Поскольку мы пока что не умеем сохранять данные в базе или файле, будем использовать статический класс для передачи значений.

Создадим в проекте новый класс. Назовем его Contact:

public static class Contact
    {
        public static int m_Id;
        public static string m_Name;
        public static string m_Phone;
    }

Теперь в обработчике нажатия на кнопку "Принять" просто будем заполнять поля статические поля класса, а переход осуществлять методом NavigationService.GoBack().

private void btnApply_Click(object sender, RoutedEventArgs e)
        {
            if (txtName.Text.Trim().Length > 0)
            {
                if (txtPhone.Text.Trim().Length > 0)
                {
                    Contact.m_Id = nCurrentId;
                    Contact.m_Name = txtName.Text;
                    Contact.m_Phone = txtPhone.Text;

                    NavigationService.GoBack();
                }
                else
                {
                    MessageBox.Show("Поле телефона не может быть пустым.");
                    txtPhone.Focus();
                }
            }
            else
            {
                MessageBox.Show("Поле имени не может быть пустым.");
                txtName.Focus();
            }
        }

Перейдем снова в файл MainPage.xaml.cs. Добавим в класс перегруженный метод перехвата пришедших запросов OnNavigatedTo(NavigationEventArgs e).

Если значения полей статического класса Contact не равны null, то, если индекс равен -1, то добавляем новый контакт, иначе изменяем контакт. В конце следует обнулить поля класса Contact, чтобы при обновлении страницы (например, при переходе на страницу добавления/изменения и нажатия кнопки "Отмена") контакты не дублировались.

protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            if ((null != Contact.m_Name) && (null != Contact.m_Phone))
            {
                if (-1 == Contact.m_Id)
                {
                    //добавление нового контакта
                    AddNewContact(Contact.m_Name, Contact.m_Phone);
                }
                else
                {
                    //редактирование контакта
                    EditContact(Contact.m_Id, Contact.m_Name, Contact.m_Phone);
                }

                //обнуляем добавленный контакт (чтоб избежать дублирование контакта)
                Contact.m_Id = 0;
                Contact.m_Name = null;
                Contact.m_Phone = null;
            }
        }

Остается написать функции добавления нового контакта и изменения имеющего.

При добавлении необходимо создать объекты ListBoxItem, StackPanel и TextBlock, заполнить необходимые свойства и добавить объекты TextBlock в StackPanel, StackPanel в ListBoxItem, а ListBoxItem в listContacts:

private void AddNewContact(string strName, string strPhone)
        {
            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);

            lstitNew.Content = stkNew;

            listContacts.Items.Add(lstitNew);
            listContacts.SelectedIndex = listContacts.Items.Count - 1;
        }

При изменении придется снова воспользоваться множественным приведение типов:

private void EditContact(int nIndex, string strName, string strPhone)
        {
            if ((nIndex >= 0) && (nIndex < listContacts.Items.Count))
            {
                ((TextBlock)((StackPanel)((ListBoxItem)listContacts.Items[nIndex]).Content)
                .Children.ElementAt(0)).Text = strName;
                ((TextBlock)((StackPanel)((ListBoxItem)listContacts.Items[nIndex]).Content)
                .Children.ElementAt(1)).Text = strPhone;
            }
            else
            {
                MessageBox.Show("Ошибка. Индекс за границами массива.");
            }
        }

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

< Лекция 1 || Лекция 2: 12345 || Лекция 3 >