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