Россия, г. Санкт-Петербург |
Лабораторный практикум 3
Лабораторная работа №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 (класс, который мы создали в самом начале), а текстовые блоки будут автоматически заполняться содержимым полей таблицы.
Теперь можно скомпилировать приложение, запустить на эмуляторе или телефоне и проверить его функциональность.
Наше приложение "Телефонная книжка" сохраняет контакты в локальной базе, в чем можно легко убедиться, закрыв приложение и открыв снова.