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

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

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >

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

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

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

< Лекция 3 || Лекция 4: 12345 || Лекция 5 >