Лабораторный практикум 3
Лабораторная работа №14. Учет затрат и локальная база
Задание
Изменить приложение "Учет затрат", добавив работу с локальной базой (загрузка данных из базы должна происходить на основе связывания).
Освоение
- локальная база
- связывание (Binding)
Описание
Откроем существующий проект "Учет завтра". Удалим существующий статический класс MyDatabase и структуру MyElement. Создадим новый класс, представляющий таблицу в локальной базе. Таблица будет содержать несколько свойств: идентификатор (ключевое поле), наименование доходов/затрат, флаг, определяющий, актив это или пассив, сумма, версия (на случай обновления данных). Также переопределим обязательные события PropertyChanged, PropertyChanging и функции NotifyPropertyChanged(), NotifyPropertyChanging().
[Table] public class Cost : INotifyPropertyChanged, INotifyPropertyChanging { // Define ID: private field, public property and database column. private int _CostId; [Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)] public int CostId { get { return _CostId; } set { if (_CostId != value) { NotifyPropertyChanging("CostId"); _CostId = value; NotifyPropertyChanged("CostId"); } } } // Define item name: private field, public property and database column. private string _Name; [Column] public string Name { get { return _Name; } set { if (_Name != value) { NotifyPropertyChanging("Name"); _Name = value; NotifyPropertyChanged("Name"); } } } // Define completion value: private field, public property and database column. private bool _IsActive; [Column] public bool IsActive { get { return _IsActive; } set { if (_IsActive != value) { NotifyPropertyChanging("IsActive"); _IsActive = value; NotifyPropertyChanged("IsActive"); } } } // Define completion value: private field, public property and database column. private float _Money; [Column] public float Money { get { return _Money; } set { if (_Money != value) { NotifyPropertyChanging("Money"); _Money = value; NotifyPropertyChanged("Money"); } } } // Version column aids update performance. [Column(IsVersion = true)] private Binary _version; #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; // Used to notify the page that a data context property changed private void NotifyPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } #endregion #region INotifyPropertyChanging Members public event PropertyChangingEventHandler PropertyChanging; // Used to notify the data context that a data context property is about to change private void NotifyPropertyChanging(string propertyName) { if (PropertyChanging != null) { PropertyChanging(this, new PropertyChangingEventArgs(propertyName)); } } #endregion }
Для работы данного кода добавим следующие директивы:
using System.Data.Linq; using System.Data.Linq.Mapping; using System.ComponentModel;
Для удобной работы с таблицей создадим контекст данных. Основной особенностью данного класса является наличие строки подключения к базе (она должна передаваться в конструктор базового класса) и поля типа Table.
public class CostsDataContext : DataContext { // Specify the connection string as a static, used in main page and app.xaml. public static string DBConnectionString = "Data Source=isostore:/Costs.sdf"; // Pass the connection string to the base class. public CostsDataContext(string connectionString) : base(connectionString) { } // Specify a single table for the to-do items. public Table<Cost> Costs; }
Теперь перейдем к коду класса главной страницы.
Добавим следующие директивы:
using System.ComponentModel; using System.Collections.ObjectModel;
Добавим наследование от класса INotifyPropertyChanged:
public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
Теперь объявим событие и функцию обработки изменения данных:
public event PropertyChangedEventHandler PropertyChanged; // Used to notify Silverlight that a property has changed. private void NotifyPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } }
Добавим в класс переменную типа CostsDataContext (класс, который мы создали выше) и свойство Costs типа ObservableCollection (коллекция, поддающаяся наблюдению, реализует интерфейс INotifyCollectionChanged, что позволяет получать уведомления об изменениях коллекции и обновляться).
private CostsDataContext CostsDB; private ObservableCollection<Cost> _Costs; public ObservableCollection<Cost> Costs { get { return _Costs; } set { if (_Costs != value) { _Costs = value; NotifyPropertyChanged("Costs"); } } }
В конструкторе класса добавим инициализацию:
CostsDB = new CostsDataContext(CostsDataContext.DBConnectionString); this.DataContext = this;
Изменим логику добавления и удаления новых доходов/затрат. При добавлении будем создавать новый объект типа Cost и добавлять его в коллекцию Costs и контекст данных CostsDB. По завершении необходимо сохранить изменения в базе: ContactsDB.SubmitChanges(). При удалении будем удалять контакт из коллекции и контекста данных. По завершении сохранять изменения в базе ContactsDB.SubmitChanges().
private void AddNewCost(string strName, bool isActive, float fMoney) { Cost newCost = new Cost { Name = strName, IsActive = isActive, Money = fMoney }; Costs.Add(newCost); CostsDB.Costs.InsertOnSubmit(newCost); CostsDB.SubmitChanges(); } private void DeleteCost(int index) { if ((index > 0) && (index < listCosts.Items.Count)) { Cost costForDelete = listCosts.Items[index] as Cost; Costs.Remove(costForDelete); CostsDB.Costs.DeleteOnSubmit(costForDelete); CostsDB.SubmitChanges(); this.Focus(); } else { MessageBox.Show("Индекс вне границ массива."); } }
Также изменим логику при переходе на главную страницу. В коде страницы PageAdd.xaml при нажатии на кнопку "Принять" будем осуществлять прямой переход на главную страницу и передавать параметры нового дохода/затраты:
private void btnApply_Click(object sender, RoutedEventArgs e) { if (txtElem.Text.Trim().Length > 0) { if (txtMoney.Text.Trim().Length > 0) { NavigationService.Navigate(new Uri("/MainPage.xaml?name=" + Uri.EscapeDataString(txtElem.Text) + "&isactive=" + Uri.EscapeDataString(checkActive.IsChecked.Value.ToString()) + "&money=" + Uri.EscapeDataString(txtMoney.Text), UriKind.Relative)); } else { MessageBox.Show("Поле суммы не может быть пустым."); txtMoney.Focus(); } } else { if(checkActive.IsChecked.Value) MessageBox.Show("Поле дохода не может быть пустым."); else MessageBox.Show("Поле расхода не может быть пустым."); txtElem.Focus(); } }
В коде главной страницы в методе OnNavigatedTo() с помощью Linq-запроса будем извлекать данные из таблицы и помещать их в коллекцию. В случае если при переходе были указаны параметры, будем создавать новый элемент:
protected override void OnNavigatedTo(NavigationEventArgs e) { var CostsInDB = from Cost cost in CostsDB.Costs select cost; Costs = new ObservableCollection<Cost>(CostsInDB); base.OnNavigatedTo(e); if (NavigationContext.QueryString.ContainsKey("name") && NavigationContext.QueryString.ContainsKey("isactive") && NavigationContext.QueryString.ContainsKey("money")) { string strName = NavigationContext.QueryString["name"].ToString(); bool isActive = bool.Parse(NavigationContext.QueryString["isactive"].ToString()); float fMoney = float.Parse(NavigationContext.QueryString["money"].ToString()); AddNewCost(strName, isActive, fMoney); } }
Для того чтобы при изменении данных они автоматически менялись в списке на экране телефона, изменим код разметки главной страницы:
<!--ContentPanel - place additional content here--> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" Canvas.ZIndex="0"> <Grid.RowDefinitions> <RowDefinition Height="50" /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="250" /> <ColumnDefinition Width="100" /> <ColumnDefinition Width="100" /> </Grid.ColumnDefinitions> <TextBlock Text="название" FontSize="28" Margin="0,0,5,0" TextAlignment="Center" Grid.Row="0" Grid.Column="0" /> <TextBlock Text="а|п" FontSize="28" Margin="0,0,5,0" TextAlignment="Center" Grid.Row="0" Grid.Column="1" /> <TextBlock Text="сумма" FontSize="28" Margin="0,0,5,0" TextAlignment="Center" Grid.Row="0" Grid.Column="2" /> <ListBox Name="listCosts" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" ItemsSource="{Binding Costs}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" Margin="0,10,0,10"> <TextBlock Text="{Binding Name}" FontSize="28" Width="250" /> <TextBlock Text="{Binding IsActive}" FontSize="28" Width="100" TextAlignment="Center" /> <TextBlock Text="{Binding Money}" FontSize="28" Width="100" TextAlignment="Right" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid>
С помощью строчки ItemsSource="{Binding Costs}" мы связали элементы списка с элементами коллекции Costs. Задав значение свойства Text="{Binding Name}", мы связали содержимое текстового блока с соответствующим полем таблицы. В результате список listCosts будет состоять из элементов типа Cost (класс, который мы создали в самом начале), а текстовые блоки будут автоматически заполняться содержимым полей таблицы.
Теперь можно скомпилировать приложение, запустить на эмуляторе или телефоне и проверить его функциональность.
Наше приложение "Учет затрат" сохраняет контакты в локальной базе, в чем можно легко убедиться, закрыв приложение и открыв его снова.