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

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

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

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

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

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

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