Практикум прикладного программирования на C# в среде VS.NET 2005

Элементы управления Data в приложениях на C#

Показывать лекцию целиком

Файлы к лабораторной работе Вы можете скачать здесь.

Однажды в студентную зимнюю пору Виталя Биктимиров сказал про Visual Studio .NET 2003 такие мудрые слова: "Я знал, что там все разложено по полочкам, но я не подозревал, что этих полок окажется так много" !!! А вот тебе, Виталя, еще целый стеллаж...

Студент Панасюк, если серьезно заниматься разработкой приложений, то чтобы добыть достаточное количество сала, без знаний о базах данных не обойтись. В каждом потенциальном Заказчике Вы обязательно встретите лукавую ухмылку и коварный вопрос, а можете ли Вы разрабатывать приложения для управления большими объемами данных. Поэтому знание методов доступа к данным и наличие навыков работы с ними жизненно необходимы, и даже больше, чем сало.

Данные имеют разные формы и размеры и в мире существует великое множество способов хранения данных. Вероятность появления у клиента желания перейти на базу данных другого производителя очень велика. Поэтому, чтобы судорожно не переучиваться на голодный желудок, необходимо владеть знанием универсального механизма доступа и управления данными. И это средство в последнее время появилось - оно называется ADO.NET.

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

Для решения задач программного управления данными нами будет использована типовая учебная база Northwind. Если на вашем компьютере она отсутствует, то в каталоге Data этой лабораторной работы запустите программу SampDBInstall.exe и введите по запросу имя сервера localhost. Возможно, перед этим файлы

  • northwind.sql
  • pubs.sql
  • SampDBInstall.exe
  • придется скопировать в папку C:\MSSQL7\Data. Установите обе учебных базы, т.к. они нам в дальнейшем понадобятся именно обе.

    Провильность установки баз данных можно проверить, если открыть панель Server Explorer оболочки через меню View

    В этой панели отображается дерево доступных сервисов, среди которых можно увидеть установленные базы данных, в том числе нужные нам Northwind и pubs (на данном этапе нам понадобится только база Northwind, но в дальнейшем, даст Бог, доберемся и до второй)

    Далее мы будем употреблять термины, в которые вложен одинаковый смысл:

  • Компонент
  • Элемент управления
  • Класс
  • Пространство имен BCL

    BCL - Basic Class Library разделена на множество пространств имен, которые по аналогии можно сравнить с каталогами файловой системы, внутри которых размещены конкретные классы. Для ознакомления и поиска нужного пространства имен можно использовать панель Object Browser (команда меню View/Object Browser) оболочки Visual Studio .NET 2003.

    Для создания приложений с использованием баз данных в режиме визуального проектирования Design во вкладке Data панели Toolbox предусмотрен ряд компонентов для решения различных задач управления данными. Эти компоненты являются визуальным представлением соответствующих классов в пространстве имен BCL.

    Если известно, что для приложения должна использоваться база данных типа SQL Server, то для обеспечения более высокой производительности этого приложения следует применять классы, которые относятся к пространству имен System.Data.SqlClient. Но если тип базы данных, применяемой в приложении заранее не известен, то следует использовать классы пространства имен System.Data.OleDb

    На вкладке Data панели Toolbox соответствующие компоненты имеют префиксы Sql и OleDb

    Назначение некоторых из компонентов
    Компонент Назначение
    DataSet Используется в сочетании с другими компонентами и позволяет хранить результаты, возвращенные командами и адаптерами данных. В отличие от наборов записей в технологиях ADO (не путать с ADO.NET) и DAO, этот компонент фактически позволяет вернуться к иерархическому представлению данных. Свойства и коллекции объекта DataSet дают возможность получить полный доступ к отдельным таблицам, строкам и столбцам.
    OleDbDataAdapter Дает возможность управлять командами, которые должны быть применены к такому провайдеру данных OleDb, как Jet, Oracle или SQL Server, и хранить их. В частности, могут применяться команды выборки, обновления, вставки и удаления записей. Отслеживается также объект соединения Connection, к которому применяются эти команды.
    OleDbConnection Позволяет сопровождать информацию о соединении для провайдера данных OleDb. Он применяется в сочетании с компонентом OleDbDataAdapter.
    OleDbCommand Как и объект команды ADO, позволяет передавать в базу данных операторы SQL или хранимые процедуры для выполнения трудоемких операций или выборки данных.
    SqlDataAdapter Аналогичен компоненту OleDbDataAdapter за исключением того, что он применяется для доступа только к хранилищам данных SQL Server.
    SqlConnection Аналогичен компоненту OleDbConnection за исключением того, что он применяется для доступа только к хранилищам данных SQL Server.
    SqlCommand Аналогичен компоненту OleDbCommand за исключением того, что он применяется для доступа только к хранилищам данных SQL Server.
    DataView Позволяет создавать несколько представлений одной и той же таблицы. В частности, он обеспечивает возможность просматривать данные, находящиеся в различных состояниях, например, удаленные, модифицированные или как-то отсортированные.

    В качестве упражнения создадим соединение с учебной базой данных Northwind, которая поставлется вместе с оболочкой Visual Studio .NET 2003, но которая есть в каталоге Data данной работы.

    Подключение к данным связанного списка

  • Войдите в каталог предполагаемого размещения своей работы и создайте новый проект приложения рабочего стола с именем WndAppAdo
  • Раскройте вкладку Data панели Toolbox и перенесите на форму компонент OleDbDataAdapter.
  • Откроется окно мастера настройки конфигурации адаптера данных

    В раскрывающемся списке ("Which data connection should data adapter use? - Какое подключение (связь) данных адаптер данных должен использовать?) должны сохраняться имена используемых ранее подключений и можно выбрать интересующую нас базу Northwind. Если список пуст, нужно создать новое подключение. Для этого

  • Щелкните на кнопке New Connection..., после чего откроется окно Data Link Properties свойств канала передачи данных. Его заполните так
  • После щелчка на кнопке OK вновь появится предыдущее окно мастера
  • в котором выберите созданное соединение и щелкните на кнопке Next.

  • Появится новое окно мастера, в котором оставьте опцию по умолчанию Use SQL statements (использовать SQL-запросы)
  • и щелкните по кнопке Next.

    Откроется поле ввода, обозначенное вопросом What data should the data adapter load into the dataset? (Какие данные должны загружаться этим адаптером данных в набор данных?)

  • Введите в поле ввода SQL-запрос к базе Northwind
  • Select CustomerID, CompanyName From Customers

  • Щелкните на кнопке Advanced Options (Дополнительные опции) и в появившемся окне снимите отметку с верхнего флажка, чтобы запретить модификацию базы (сейчас нам нужно только прочитать данные из базы)
  • Щелкните на кнопке Next для просмотра результатов выполнения оператора выборки. Если Вы все сделали без ошибок, то окно должно иметь точно такой вид, как приведено ниже
  • Щелкните на кнопке Finish, чтобы мастер создал адаптер данных и объект соединения. После этого оболочка вернется опять к режиму Form Designer
  • Обратите внимание, что под формой расположена область Components (иногда для удобства будем эту область называть подвалом формы ), в которой оболочка располагает невизуальные компоненты приложения. Это намного удобнее, чем в Builder, поскольку не портится внешний вид формы в режиме проектирования. В области Components появился объект соединения, созданный мастером.

    После создания двух объектов перейдем к созданию объекта класса DataSet - универсального источника данных.

  • Щелкните правой кнопкой мыши на объекте oleDbDataAdapter1 и из контекстного меню выполните команду Generate Dataset
  • Откроется диалоговое окно Generate Dataset, в котором все установки оставьте по умолчанию
  • После щелчка на кнопке OK будет создан объект набора данных с именем dataSet11 (для соединения с индексом 1 можно создать несколько наборов данных с индексами 11, 12, ...). Теперь окно Design оболочки имеет область Components следующего вида
  • Теперь приступим к созданию средств отображения данных пользователю. Для этого используем компонент ListBox.

  • Перенесите (двойным щелчком) на форму компонент ListBox из вкладки Windows Forms панели Toolbox
  • В панели Properties установите свойства компонента ListBox в соответствии с таблицей
  • Свойства компонента ListBox
    Свойство Значение
    Dock Fill
    DataSource dataSet11
    DisplayMember Customers.CompanyName
    ValueMember Customers.CustomerID

    Хоть мы и правильно связали ListBox с набором данных, но если откомпилировать и запустить форму, данных в списке не будет. Для того, чтобы они загрузились в связанный с ними список ListBox, нужно написать код C#.

  • Щелкните правой кнопкой мыши в любом месте редактора Design и в появившемся контекстном меню выберите команду View Code, чтобы перейти в режим редактирования кода.
  • Через раскрывающийся список редактора кода перейдите к конструктору класса Form1()
  • Вставьте строку кода, заполняющего объект класса ListBox, как приведено ниже
  • ..........................................................    
        public Form1()
        {
          //
          // Required for Windows Form Designer support
          //
          InitializeComponent();
    
          //
          // TODO: Add any constructor code after InitializeComponent call
          //
          oleDbDataAdapter1.Fill(dataSet11);
        }
    ..........................................................
  • Постройте проект, результат должен быть таким
  • Обратите внимание на код, который генерировала оболочка, пока мы манипулировали с проектом, находясь в режиме проектирования. Основная его часть спрятана в разделе

    #region Windows Form Designer generated code
    .....................................................
    #endregion

    и мы, как разработчики, не должен вносить туда изменения (по возможности воздерживаться - пока)!!!

    Управление составом данных, отображаемых в связанном списке

    Если в таблице содержится много данных, то заполнение списка данными, как это мы только что проделали, даже из нескольких столбцов требует больших затрат вычислительных ресурсов. Ниже мы применим параметризованный (с параметрами) оператор SQL для ограничения количества элементов, отображаемых в списке. Это позволит уменьшить потребность в ресурсах и тем самым повысит производительность выполнения формы.

    Предположим, что в базе данных содержатся данные о сотнях тысяч заказчиков и поэтому не следует загружать в элемент управления списком сразу всю таблицу заказчиков. Рассмотрим способы ограничения данных, отображаемых в списке.

    План

    Прежде всего необходимо сделать копию предыдущей формы. Затем в форму нужно ввести элементы управления Label и TextBox, из которых в оператор Select, содержащийся в элементе управления OleDbDataAdapter, будет поступать информация, позволяющая ограничить объем данных, отображаемых в списке. В форму нужно ввести кнопку Button, при щелчке на которой будет вызываться метод Fill() элемента управления OleDbDataAdapter после введения новых данных в текстовое поле.

    Здесь мы чуть облегчим себе работу, чтобы заново не создавать и не настраивать элементы соединения с базой. Мы просто скопируем код предыдущей формы вместе с объектами и их настройками в новую форму. После этого достаточно будет дополнить новую форму необходимыми элементами и настроить только их.

    Порядок действий

  • Откройте панель Solution Explorer и, вызвав контекстное меню для файла Form1.cs, выполните команду Copy
  • Затем щелкните правой кнопкой мыши на корневом узле текущего проекта и выполните команду Paste контекстного меню
  • К проекту добавится файл с дежурным именем Copy of Form1.cs

  • Присвойте вновь созданной форме имя Form2.cs и, вызвав для этого файла контекстное меню, выполните команду View Code , чтобы перейти в режим редактирования кода
  • Измените для вновь созданной копии имя класса, имя конструктора и точку входа с Form1 на Form2, поскольку оболочка не делает это автоматически
  • public class Form2 : System.Windows.Forms.Form
      .......................
      public Form2()
      .......................
      static void Main() 
      {
        Application.Run(new Form2());
      }
  • Перейдите в режим Design файла Form2.cs, выделите форму и в панели Properties измените значение свойства Text на Form2
  • Убедитесь, что все помещенные ранее на первую форму элементы управления сохранились и на второй форме, в том числе и элемент ListBox, растянутый по всей клиентской области формы. Внутри нового класса Form2 за ними можно оставить прежние имена, поскольку в рамках приложения префикс Form2 делает их уникальными.

    Теперь можно приступать к выполнению дальнейших этапов плана. Начнем с перестройки параметров адаптера данных.

  • Закройте вкладки окна редактора для первой формы (чтобы не мешали и случайно не подвернулись под горячую руку)
  • В подвале формы выберите объект oleDbDataAdapter1 и в панели Properties найдите его свойство SelectCommand. Раскройте этот узел щелчком по значку "+" и измените текущее значение свойства CommandText, которое мы задавали мастеру при создании соединения, на SQL-команду
  • SELECT CustomerID, CompanyName FROM Customers WHERE (CompanyName LIKE ? + '%')

    В применяемой здесь инструкции WHERE выполняется сравнение значений поля CompanyName с заданным параметром, значение которого будет передано из текстового поля элемента TextBox, еще не помещенного нами на форму. Знак процента представляет собой подстановочный символ, который указывает серверу, что в значении поля должен быть выполнен поиск подстроки.

    В приведенном выражении отражается существенная разница в использовании элементов управления OleDbDataAdapter и SqlDataAdapter. Элемент управления OleDbDataAdapter предусматривает использование операции ? для указания места вставки передаваемого извне параметра в SQL-оператор SELECT, а для элемента управления SqlDataAdapter требуется именованный параметр, такой как @parCustLimit. Фактическое имя параметра зависит от конкретной программы. Приведем оба варианта одной и той же SQL-команды

    OleDbDataAdapter SELECT CustomerID, CompanyName FROM Customers WHERE (CompanyName LIKE ? + '%')
    SqlDataAdapter SELECT CustomerID, CompanyName FROM Customers WHERE (CompanyName LIKE @parCustLimit + '%')
  • Установите у элемента управления ListBox свойство Dock=Bottom и освободите место верхней части формы для элементов управления Label, TextBox и Button. Поместите на форму эти три элемента управления из вкладки Windows Forms панели Toolbox и откорректируйте значения их свойств, как показано в таблице
  • Значения свойств дополнительных элементов управления
    Компонент Свойство Значение
    Label Text Клиент:
      TextAlign MiddleRight
      Size/Height 20
    TextBox Name txtCustLimit
      Text A (латинское!)
    Button Name btnLoadList
      Text Заполнить
  • Дважды щелкните на кнопке btnLoadList и в создавшийся обработчик введите код
  • private void btnLoadList_Click(object sender, System.EventArgs e)
    {
      oleDbDataAdapter1.SelectCommand.Parameters[0].Value
                      = txtCustLimit.Text;
      dataSet11.Clear();
      oleDbDataAdapter1.Fill(dataSet11);
    }
  • Удалите из конструктора формы перекочевавший из предыдущей формы код заполнения списка
  • public Form2()
    {
      //
      // Required for Windows Form Designer support
      //
      InitializeComponent();
      
      //
      // TODO: Add any constructor code after InitializeComponent call
      //
      oleDbDataAdapter1.Fill(dataSet11);
    }
  • В панели Solution Explorer щелкните правой кнопкой мыши на файле Form1.cs и выполните команду контекстного меню Exclude From Project (Исключить из проекта). Файл будет отключен от проекта, но физически существовать останется. Позднее его можно включить командой Add Existing Item
  •  

    Если первую форму не отключить от проекта, то в приложении будут существовать две точки входа, которые определяются функциями Main(), что не допустимо.

  • Постройте приложение и поэкспериментируйте с полем ввода. Первоначальный после запуска результат должен выглядеть так
  • Просмотр отдельных текстовых полей по выбранному элементу списка

    Списки являются удобным средством отображения ряда полей формы, но иногда возникает потребность ознакомиться со значениями других полей для конкретной записи, выделив ее в списке.Для решения этой задачи нам необходимо расширить нашу предыдущую форму и ввести в нее дополнительные элементы управления данными, в частности, еще один адаптер данных и набор данных.

    В новый адаптер нужно добавить SQL-оператор SELECT, который будет принимать в качестве параметра выбранный элемент списка. После этого набор данных будет заполнен данными, а в текстовых полях отобразятся значения полей текущей записи, полученные с использованием рассматриваемого набора данных в качестве источника данных.

    Создадим копию предыдущей формы и будем работать с ней. Для удобства переименуем адаптер данных, набор данных и список, присвоив им более содержательные имена. Хорошим стилем программирования является переименование объектов сразу после их создания. Оболочка не производит автоматическую корректировку многих участков кода, которые генерируются мастерами или правятся программистом. Поэтому переименование объектов на поздних этапах разработки проекта может быть затруднено и ведет к появлению ошибок.

  • В панели Solution Explorer сделайте копию формы Form2.cs, как это мы делали с предыдущей формой
  • Отключите файл Form2.cs от проекта, а копию переименуйте в файл Form3.cs
  • Для файла Form3.cs в панели Solution Explorer вызовите контекстное меню и выполните команду View Code, чтобы открыть файл в режиме редактирования кода C#
  • Измените в коде файла Form3.cs имена с Form2 на Form3 в классе, конструкторе и точке входа Main(), используя окно Replace in Files , открытое щелчком на пиктограмме Find in Files панели инструментов Standard оболочки
  • В панели Solution Explorer вызовите контекстное меню для файла Form3.cs и выполните команду View Designer (или выполните двойной щелчок на узле этого файла), чтобы открыть файл в режиме Design визуального проектирования
  • Измените имена объектов работы с данными в соответствии с таблицей. Это можно сделать через панель свойств, но лучше через окно Replace in Files, поскольку мы уже добавляли код вручную с использованием этих имен и оболочка в этих местах его автоматически не поменяет. Но при этом для безопасности замены ее лучше осуществлять по одиночке и с включенными опциями Match whole word (по целому слову) и Match case (с учетом регистра)
  • Изменения имен существующих объектов
    Объект Свойство Старое значение Новое значение
    ListBox Name listBox1 lstCustomers
    OleDbDataAdapter Name oleDbDataAdapter1 odaCustomerList
    DataSet Name dataSet11 dsCustomerList
  • Разместите в подвале формы элементы в хаотичном порядке затем через контекстное меню подвала выполните команду Line Up Icons, чтобы упорядочить значки элементов
  • Перенесите на форму двойным щелчком еще один компонент OleDbDataAdapter из вкладки Data панели Toolbox.
  • Укажите мастеру подключения соединение с базой данных Northwind
  • Укажите, что будут применяться операторы SQL
  • Вспомните, что SQL - регистронезависимый язык и присвойте адаптеру команду SELECT для использования параметра времени выполнения по выбранному элементу списка следующего вида
  • SELECT CustomerID, CompanyName, ContactName, ContactTitle,
           Address, City, Region, PostalCode, Country, Phone, Fax
    FROM Customers
    WHERE (CustomerID = ?)

    в окне мастера

  • Щелкните на кнопке Advanced Options и сбросьте первый сверху флажок, чтобы убрать возможность редактирования данных
  • На последнем этапе должно появиться приведенное ниже окно, свидетельствующее об успешном подключении к базе

    Список полей, участвующих в команде SELECT, можно посмотреть в панели Server Explorer, т.е. мы в поставщик данных выбрали все поля таблицы Customers

  • Переименуйте в панели Properties созданный объект oleDbDataAdapter1 на odaCustomerIndividual
  • Щелкните правой кнопкой мыши на элементе odaCustomerIndividual и выполните команду Generate Dataset из контекстного меню
  • Создайте новый набор данных с именем dsCustomerIndividual
  • Мастер создаст набор данных, автоматически добавив к указанному имени единичку.

  • Отредактируйте через панель Properties имя созданного набора данных с dsCustomerIndividual1 на dsCustomerIndividual
  • Теперь в подвале нашей формы должны находиться два адаптера данных ( odaCustomerList и odaCustomerIndividual ) и два набора данных ( dsCustomerList и dsCustomerIndividual ).

    Теперь пришла пора модифицировать интерфейс пользователя на форме, дополнив ее необходимыми компонентами.

  • Отключите свойство Dock компонента ListBox ( Name = lstCustomers ) списка
  • Освободите место справа от списка и поместите на него элементы Label и TextBox (лучше поместить один экземпляр, настроить его на общие свойства, затем накопировать нужное количество ). Для выравнивания элементов пользуйтесь панелью инструментов Dialog Editor оболочки
  • Для всех текстовых меток Label назначьте следующие значения общих свойств: Size/Width=96; Size/Height=20; TextAlign=MiddleRight
  • Для всех текстовых полей TextBox назначьте следующие значения общих свойств: Text=Пусто; ReadOnly=True; BorderStyle=FixedSingle
  • Индивидуальные значения других свойств приведены в таблице
  • Таблица значений свойств компонентов
    Label TextBox
    Text= Name= (DataBindings)/Text/dsCustomerIndividual/Customers=
    Customer ID: txtCustomerID CustomerID
    Company Name: txtCompanyName CompanyName
    Contact Name: txtContactName ContactName
    Contact Title: txtContactTitle ContactTitle
    Address: txtAddress Address
    City: txtCity City
    Region: txtRegion Region
    Postal Code: txtPostalCode PostalCode
    Country: txtCountry Country
    Phone: txtPhone Phone
    Fax: txtFax Fax

    Значения свойств мы выбрали в соответствии с именами полей таблицы данных Customers, чтобы мозги не задымились.

  • После разработки дизайна форма должна выглядеть так (студент Панасюк! Именно так. Я "жирный" пользователь и не приму неряшливый интерфейс. Хочешь получить зачет, а потом и кучу денежков на сало, - выравнивай все компоненты. Только студент Жук может не выравнивать - он сало не любит, он любит студенток!)
  • Теперь пора приступить к разработке кода. Прежде всего откорректируем обработчик кнопки btnLoadList.

  • Перейдите в режим View Code файла Form3.cs, найдите обработчик btnLoadList_Click() и откорректируйте его так
  • // Обработчик кнопки
    private void btnLoadList_Click(object sender, System.EventArgs e)
    {
      // Присвоить параметру адаптера данных значение,
      // введенное для ограничения объема списка
      odaCustomerList.SelectCommand.Parameters[0].Value
                      = txtCustLimit.Text;
      // Удалить текущие данные в наборе данных
      dsCustomerList.Clear();
      // Заполнить набор данных списком заказчиков
      odaCustomerList.Fill(dsCustomerList);
      // Заполнить отдельный набор данных для начальной записи
      RefreshIndividual();
    }
      
    // Обновление отдельного набора данных
    private void RefreshIndividual()
    {
      // Очистить отдельный набор данных
      dsCustomerIndividual.Clear();
      // Определить, выбран ли текущий элемент
      if(lstCustomers.SelectedIndex != -1)
      {
        // Присвоить параметру SQL адаптера данных отдельного
        // набора выбранный идентификатор заказчика
        odaCustomerIndividual.SelectCommand.Parameters[0].Value
          = lstCustomers.SelectedValue;
        // Заполнить набор данных
        odaCustomerIndividual.Fill(dsCustomerIndividual);
      }
    }

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

  • Перейдите в режим Design и выделите элемент lstCustomers. В панели Properties найдите событие SelectedIndexChanged и двойным щелчком на поле значений создайте новый обработчик
  • Заполните обработчик следующим кодом
  • // Обработчик кнопки
    private void btnLoadList_Click(object sender, System.EventArgs e)
    {
      // Присвоить параметру адаптера данных значение,
      // введенное для ограничения объема списка
      odaCustomerList.SelectCommand.Parameters[0].Value
                      = txtCustLimit.Text;
      // Удалить текущие данные в наборе данных
      dsCustomerList.Clear();
      // Заполнить набор данных списком заказчиков
      odaCustomerList.Fill(dsCustomerList);
      // Заполнить отдельный набор данных для начальной записи
      RefreshIndividual();
    }
      
    // Обновление отдельного набора данных
    private void RefreshIndividual()
    {
      // Очистить отдельный набор данных
      dsCustomerIndividual.Clear();
      // Определить, выбран ли текущий элемент
      if(lstCustomers.SelectedIndex != -1)
      {
        // Присвоить параметру SQL адаптера данных отдельного
        // набора выбранный идентификатор заказчика
        // Заполнить набор данных
        try
        {
          odaCustomerIndividual.SelectCommand.Parameters[0].Value
                    = lstCustomers.SelectedValue;
          odaCustomerIndividual.Fill(dsCustomerIndividual);
        }
        catch(Exception e1)
        {
          MessageBox.Show(e1.Message);
        }
      }
    }
      
    // Обработчик выбора элемента списка
    private void lstCustomers_SelectedIndexChanged(object sender, System.EventArgs e)
    {
      // Заполнить отдельный набор данных значениями
      // полей текущего элемента списка
      RefreshIndividual();
    }

    Полный текст файла Form3.cs

  • Выделите элемент TextBox текстового поля с именем txtCustLimit в самом верху формы и присвойте его свойству Text пустое значение, чтобы иметь возможность закачивать все содержимое таблицы Customers.
  • Постройте приложение и проверьте его работоспособность. Экран должен быть примерно таким
  • Редактирование и обновление данных с использованием связанных элементов управления

    До сих пор мы создавали формы, позволяющие просматривать данные, но более важной задачей является обеспечение возможности для пользователя редактировать и обновлять данные в базе данных. Займемся этим.

    План действий

    Чтобы не терять прежние наработки, нужно создать копию формы и ее наращивать, как мы это делали в предыдущих случаях. Причем нужно иметь ввиду, что в конце выполнения этой лабораторной работы мы, возможно, создадим презентационную форму для запуска из нее любой из разработанных здесь форм. Да и промежуточные результаты иметь всегда неплохо под рукой.

    Работу предыдущей формы в режиме просмотра данных будем считать естественным до тех пор, пока пользователь не пожелает начать редактирование. Для обеспечения этой возможности предусмотрим три кнопки, с помощью которых можно выполнить следующие действия:

  • Кнопка Edit. Должна позволять переключиться из режима просмотра в режим редактирования текстовых полей. При этом должен меняться внешний вид полей, чтобы пользователь знал, какой из режимов включен.
  • Кнопка Save. Должна обеспечить сохранение на сервере изменений, внесенных в данные. После записи нужно перейти в режим просмотра, считая, что пользователь закончил этап редактирования.
  • Кнопка Cancel: Должна отменять режим редактирования с очисткой кэша без сохранения данных в базе.
  • На данном этапе мы задействуем класс BindingContext. Этот класс упрощает работу с элементами управления, связанных с данными. В дальнейшем можно применить также и класс BindingManagerBase, который обеспечивает синхронизацию наборов данных с элементами управления.

  • Создайте копию предудущего файла Form3.cs и назовите его Form4.cs.
  • Отключите из проекта файл Form3.cs
  • Замените в коде Form4.cs все значения Form3 на Form4, используя окно Replace in Files
  • Для проверки постройте проект и убедитесь, что функциональность предыдущей формы сохранилась - значит мы не сделали ошибок.
  • Поместите на форму три кнопки, как показано на рисунке (свободное место зарезервировано), и задайте им значения свойств, приведенные в таблице
  • Свойства дополнительных кнопок
    Объект Свойство Значение
    Button Name btnEdit
      Text Edit
    Button Name btnSave
      Text Save
    Button Name btnCancel
      Text Cancel

  • Выполните команду Format/Lock Controls, чтобы предотвратить случайные изменения элементов интерфейса на форме
  • Создайте обработчики события Click для кнопок и заполните их так
  • // Обработчик события Click кнопки Edit
    private void btnEdit_Click(object sender, System.EventArgs e)
    {
      // Разрешить редактирование данных в кэше
      ActivateEditing(true);
    }
      
    // Функция изменения свойств текстовых полей
    private void ActivateEditing(bool bEnable)
    {
      // Только для чтения
      txtCustomerID.ReadOnly = 
      txtCompanyName.ReadOnly = 
      txtContactName.ReadOnly = 
      txtContactTitle.ReadOnly = 
      txtAddress.ReadOnly = 
      txtCity.ReadOnly = 
      txtRegion.ReadOnly = 
      txtPostalCode.ReadOnly = 
      txtCountry.ReadOnly = 
      txtPhone.ReadOnly = 
      txtFax.ReadOnly = !bEnable;
      
      // Стиль текстовых полей
      if(bEnable)
      {
        txtCustomerID.BorderStyle = 
        txtCompanyName.BorderStyle = 
        txtContactName.BorderStyle = 
        txtContactTitle.BorderStyle = 
        txtAddress.BorderStyle = 
        txtCity.BorderStyle = 
        txtRegion.BorderStyle = 
        txtPostalCode.BorderStyle = 
        txtCountry.BorderStyle = 
        txtPhone.BorderStyle = 
        txtFax.BorderStyle = BorderStyle.Fixed3D;
      }
      else
      {
        txtCustomerID.BorderStyle = 
        txtCompanyName.BorderStyle = 
        txtContactName.BorderStyle = 
        txtContactTitle.BorderStyle = 
        txtAddress.BorderStyle = 
        txtCity.BorderStyle = 
        txtRegion.BorderStyle = 
        txtPostalCode.BorderStyle = 
        txtCountry.BorderStyle = 
        txtPhone.BorderStyle = 
        txtFax.BorderStyle = BorderStyle.FixedSingle;
      }
    }
      
    // Обработчик события Click кнопки Save
    private void btnSave_Click(object sender, System.EventArgs e)
    {
      SaveRecord();
      ActivateEditing(false);
    }
      
    // Сохранить изменения в базе данных
    private void SaveRecord()
    {
      // Применить класс BindingContext для завершения
      // текущего сеанса редактирования, чтобы иметь
      // возможность записать обновления в базу данных
      this.BindingContext[dsCustomerIndividual, "Customers"].EndCurrentEdit();
      
      // Обновить данные в кэше из набора данных dsCustomerIndividual
      // в поставщик данных odaCustomerIndividual
      odaCustomerIndividual.Update(dsCustomerIndividual, "Customers");
      
      // Передать данные из кэша поставщика данных в базу
      dsCustomerIndividual.AcceptChanges();
    }
      
    // Обработчик события Click кнопки Cancel
    private void btnCancel_Click(object sender, System.EventArgs e)
    {
      // Для отмены текущей операции 
      // применяется класс BindingContext
      this.BindingContext[dsCustomerIndividual, "Customers"].CancelCurrentEdit();
      ActivateEditing(false);
    }
  • Выделите текстовое поле txtCustLimit и очистите в нем свойство Text, чтобы список изначально заполнялся полностью
  • Вспомните, что при создании объекта odaCustomerIndividual мы настраивали мастер поставщика данных

    При нажатии кнопки Advanced Options мы вызвали соответствующее окно и запретили изменение данных в самой базе

    В результате выдавалось окно с возможностями поставщика данных, которое имело такой вид

    Теперь пришла пора снять с адаптера свойство защиты изменений данных в базе!!!

  • В режиме дизайна выделите в подвале формы поставщик данных odaCustomerIndividual и в нижней части панели Properties щелкните по ссылке Configure Data Adapter...
  • Появится мастер настройки поставщика данных, в котором дойдите до вкладки с кнопкой Advanced Options, щелкните на ней и установите режим обновления данных. Вид вкладки с результатами настройки перед нажатием кнопки Finish должен выглядеть так

  • Постройте проект и удостоверьтесь в правильной работе добавленных возможностей. При проверке добавьте пару символов в поле Company Name, а затем сотрите их, чтобы база соответствовала оригиналу для получения одинаковых со мной результатов.
  • При разработке кода функции SaveRecord() мы использовали обновление данных в базе в два этапа: вначале сохранили изменения в поставщике данных, и лишь потом дали команду на окончательное внесение изменений в саму базу. Такой подход реализован в среде ADO.NET для обеспечения отсоединенного подключения к базе данных.

    Механизм работы таков: поставщик данных при выполнении запроса SELECT на выборку данных кратковременно подключается к базе данных, загружает в кэш найденные данные и сразу же отключается от базы. Это дает возможность работать с базой данных максимальному количеству пользователей. Поставщик данных после разъединения имитирует теперь собой базу данных в кэше оперативной памяти и пользователь работает с данными не замечая, что фактически работает с копией данных.

    Пользователь в отсоединенном режиме может сохранять данные якобы в базе, но при каждом сохранении поставщик данных фактически создает у себя по несколько версий изменений, которые можно всегда отменить (типа уровней откатов). При окончательном сохранении изменений в базе данных разработчик через программирование поставщика данных может реализовать необходимую стратегию обновления и разрешения конфликтных ситуаций.

    Механизм ADO.NET работы в отсоединенном режиме опирается на принцип оптимистического параллелизма обработки данных. Данные в самой базе не блокируются и могут потребляться сразу несколькими пользователями. Поставщики данных каждого пользователя периодически проверяют изменения подведомственных данных в базе и позволят сохранить изменения только тех данных, с которыми не работают в данный момент другие пользователи.

    В SQL Server наиболее распространены следующие типы проверки нарушения параллелизма и стратегий обновления данных

  • Изменения заносятся в базу данных в том случае, если значение первичного ключа обновленной строки совпадает со значением первичного ключа существующей строки.
  • Изменения заносятся в базу данных в том случае, если ни один из столбцов, обновленных данным пользователем, не был изменен никем другим с тех пор, как строка была считана данным пользователем.
  • Изменения заносятся в базу данных в том случае, если ни один из столбцов строки не был изменен никем другим с тех пор, как строка была считана данным пользователем.
  • Изменения заносятся в базу данных в том случае, если метка времени последнего обновления строки не изменилась с тех пор, как строка была считана данным пользователем. По своему смыслу этот тип проверки аналогичен предыдущему.
  • Функционирование среды ADO.NET в отсоединенном режиме основано на создании временных данных в файле на языке XML. Чтобы убедиться в этом, загляните в панель Solution Explorer и вы увидите файлы с расширением .xsd и с именами созданных нами наборов данных. Откройте файл DataSet1.xsd через команду контекстного меню Open With/Source Code (Text) Editor и можно увидеть схему на XML, приведенную ниже

    <?xml version="1.0" standalone="05_57"?>
    <xs:schema id="DataSet1" 
      targetNamespace="http://www.tempuri.org/DataSet1.xsd" 
      xmlns:mstns="http://www.tempuri.org/DataSet1.xsd" 
      xmlns="http://www.tempuri.org/DataSet1.xsd" 
      xmlns:xs="http://www.w3.org/2001/XMLSchema" 
      xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" 
      attributeFormDefault="qualified" elementFormDefault="qualified">
      <xs:element name="DataSet1" 
      msdata:IsDataSet="true" msdata:Locale="ru-RU">
        <xs:complexType>
          <xs:choice maxOccurs="unbounded">
            <xs:element name="Customers">
              <xs:complexType>
                <xs:sequence>
                  <xs:element name="CustomerID" type="xs:string" />
                  <xs:element name="CompanyName" type="xs:string" />
                </xs:sequence>
              </xs:complexType>
            </xs:element>
          </xs:choice>
        </xs:complexType>
        <xs:unique name="Constraint1" msdata:PrimaryKey="true">
          <xs:selector xpath=".//mstns:Customers" />
          <xs:field xpath="mstns:CustomerID" />
        </xs:unique>
      </xs:element>
    </xs:schema>

    Добавление и удаление записей с использованием связанных элементов управления

    Не менее важной задачей, чем редактирование данных, является возможность добавления новых и удаления существующих записей в таблице. Нужно поместить на форму две новых кнопки New и Delete и ввести переменную области действия класса для контроля за тем, происходит добавление или редактирование записи. Если будет вводиться новая запись, то при обновлении данных в приложении необходимо перегрузить данные в списке в том случае, если значение поля CompanyName попадает в область данных, загруженных в список с учетом фильтра поля txtCustLimit.

  • Создайте копию формы файла Form4cs, переименуйте ее в файл Form5.cs и отключите от проекта файл Form4.cs
  • Просмотрите код файла Form5.cs и выполните замену слов Form4 на Form5 в поисковике оболочки.
  • Постройте проект и убедитесь, что функциональность новой формы сохранилась.
  • Под кнопкой btnEdit поместите две новых кнопки со значениями Name=btnNew; Text=New; Name=btnDelete; Text=Delete
  • Выделите в подвале формы поставщик данных odaCustomerList и внизу панели Properties щелкните по ссылке Configure Data Adapter..., вызвав мастер для установки режима обновления данных закрепленной таблицы базы данных, как это мы делали ранее для поставщика данных odaCustomerIndividual.
  • Создайте для новых кнопок обработчики и заполните их, используя окончательный для данного этапа код C#
  • .........................................................
    static void Main() 
    {
      Application.Run(new Form5());
    }
      
    //*****************************************************************
    // Переменная-член класса для контроля над тем,
    // происходит ли добавление или редактирование записи
    private bool mbAddNew; 
      
    //*****************************************************************
    // Обработчик кнопки загрузки данных
    // в список ListBox с именем lstCustomers
    private void btnLoadList_Click(object sender, System.EventArgs e)
    {
      LoadList();
    }
      
    //*****************************************************************
    // Загрузка данных в список ListBox с именем lstCustomers
    private void LoadList()
    {
      // Присвоить параметру адаптера данных значение,
      // введенное для ограничения объема списка
      odaCustomerList.SelectCommand.Parameters[0].Value
                      = txtCustLimit.Text;
      // Удалить текущие данные в наборе данных
      dsCustomerList.Clear();
      // Заполнить набор данных списком заказчиков
      odaCustomerList.Fill(dsCustomerList);
      // Заполнить отдельный набор данных для начальной записи
      RefreshIndividual();
    }
      
    //*****************************************************************
    // Обновление отдельного набора данных
    private void RefreshIndividual()
    {
      // Очистить отдельный набор данных
      dsCustomerIndividual.Clear();
      // Определить, выбран ли текущий элемент
      if(lstCustomers.SelectedIndex != -1)
      {
        // Присвоить параметру SQL адаптера данных отдельного
        // набора выбранный идентификатор заказчика
        // Заполнить набор данных
        try
        {
          odaCustomerIndividual.SelectCommand.Parameters[0].Value
                    = lstCustomers.SelectedValue;
          odaCustomerIndividual.Fill(dsCustomerIndividual);
        }
        catch(Exception e1)
        {
          MessageBox.Show(e1.Message);
        }
      }
    }
      
    //*****************************************************************
    // Обработчик выбора элемента списка
    private void lstCustomers_SelectedIndexChanged(object sender, System.EventArgs e)
    {
      // Заполнить отдельный набор данных значениями
      // полей текущего элемента списка
      RefreshIndividual();
    }
      
    //*****************************************************************
    // Обработчик события Click кнопки Edit
    private void btnEdit_Click(object sender, System.EventArgs e)
    {
      // Разрешить редактирование данных в кэше
      ActivateEditing(true);
    }
      
    //*****************************************************************
    // Функция изменения свойств текстовых полей
    private void ActivateEditing(bool bEnable)
    {
      // Только для чтения
      txtCustomerID.ReadOnly = 
      txtCompanyName.ReadOnly = 
      txtContactName.ReadOnly = 
      txtContactTitle.ReadOnly = 
      txtAddress.ReadOnly = 
      txtCity.ReadOnly = 
      txtRegion.ReadOnly = 
      txtPostalCode.ReadOnly = 
      txtCountry.ReadOnly = 
      txtPhone.ReadOnly = 
      txtFax.ReadOnly = !bEnable;
      
      // Стиль текстовых полей
      if(bEnable)
      {
        txtCustomerID.BorderStyle = 
          txtCompanyName.BorderStyle = 
          txtContactName.BorderStyle = 
          txtContactTitle.BorderStyle = 
          txtAddress.BorderStyle = 
          txtCity.BorderStyle = 
          txtRegion.BorderStyle = 
          txtPostalCode.BorderStyle = 
          txtCountry.BorderStyle = 
          txtPhone.BorderStyle = 
          txtFax.BorderStyle = BorderStyle.Fixed3D;
      }
      else
      {
        txtCustomerID.BorderStyle = 
          txtCompanyName.BorderStyle = 
          txtContactName.BorderStyle = 
          txtContactTitle.BorderStyle = 
          txtAddress.BorderStyle = 
          txtCity.BorderStyle = 
          txtRegion.BorderStyle = 
          txtPostalCode.BorderStyle = 
          txtCountry.BorderStyle = 
          txtPhone.BorderStyle = 
          txtFax.BorderStyle = BorderStyle.FixedSingle;
      }
    }
      
    //*****************************************************************
    // Обработчик события Click кнопки Save
    private void btnSave_Click(object sender, System.EventArgs e)
    {
      SaveRecord();
      ActivateEditing(false);
      
      // Добавлен код для загруки новой записи
      if(mbAddNew)
      {
        // Загрузка данных новой записи
    
        LoadList(); // Наша функция
        // Обновление отдельного набора данных
        RefreshIndividual(); // Наша функция
        mbAddNew = false;
      }
    }
      
    //*****************************************************************
    // Сохранить изменения в базе данных
    private void SaveRecord()
    {
      // Применить класс BindingContext для завершения
      // текущего сеанса редактирования, чтобы иметь
      // возможность записать обновления в базу данных
      this.BindingContext[dsCustomerIndividual, "Customers"].EndCurrentEdit();
      
      // Обновить данные в кэше из набора данных dsCustomerIndividual
      // в поставщик данных odaCustomerIndividual
      odaCustomerIndividual.Update(dsCustomerIndividual, "Customers");
      
      // Передать данные из кэша поставщика данных в базу
      dsCustomerIndividual.AcceptChanges();
    }
      
    //*****************************************************************
    // Обработчик события Click кнопки Cancel
    private void btnCancel_Click(object sender, System.EventArgs e)
    {
      // Для отмены текущей операции 
      // применяется класс BindingContext
      this.BindingContext[dsCustomerIndividual, "Customers"].CancelCurrentEdit();
      ActivateEditing(false);
      mbAddNew = false;
    }
      
    //*****************************************************************
    private void btnNew_Click(object sender, System.EventArgs e)
    {
      mbAddNew = true;
      
      // Добавить новую запись с использованием класса BindingContext
      this.BindingContext[dsCustomerIndividual, "Customers"].AddNew();
      ActivateEditing(true);
    }
      
    //*****************************************************************
    private void btnDelete_Click(object sender, System.EventArgs e)
    {
      // Отменить строку для удаления с использованием
      // метода RemoveAt() класса BindingContext
      this.BindingContext[dsCustomerIndividual, "Customers"]
        .RemoveAt(BindingContext[dsCustomerIndividual, "Customers"].Position);
      
      // Обновить данные в кэше из набора данных dsCustomerIndividual
      // в поставщик данных odaCustomerIndividual
      odaCustomerIndividual.Update(dsCustomerIndividual, "Customers");
      
      // Передать данные из кэша поставщика данных в базу
      dsCustomerIndividual.AcceptChanges();
      
      // Перезагрузить список
      LoadList();
      
      // Отобразить первую запись
      RefreshIndividual();
      
      // Отключить текстовые поля
      ActivateEditing(false);
    }
  • Постройте проект и удостоверьтесь в правильной работе добавленных возможностей. При проверке добавляйте строки, а затем удаляйте их, но не трогайте существующие записи, чтобы база соответствовала оригиналу для получения одинаковых со мной результатов.
  • При анализе кода в редакторе кода оболочки используйте его возможность, показанную на рисунке (очень удобно!)
  • Приведенный код полность рабочий, снабжен подробными комментариями, поэтому попробуйте проанализировать его самостоятельно (при необходимости для удобства можете распечатать).

    Обработка ошибок с помощью связанных элементов управления

    В процессе эксплуатации приложений и баз данных иногда приходится сталкиваться с ошибками времени выполнения. Обычно такие ошибки называют исключениями. При работе с базами данных исключения возникают в процессе ввода данных пользователем и обновления базы данных на сервере. Их правильная обработка является важной составляющей создания надежных приложений.

    Если в программе не предусмотрено применение соответствующих средств обработки исключений, то в лучшем случае система выводит стандартное сообщение о возникшей ошибке, а в худшем - аварийно завершает свою работу или записывает в базу данных ошибочные данные. В среде Visual Studio .NET 2003 для обработки ошибок предусмотрены специальные классы типа Exception.

    Исключения времени выполнения могут возникать почти в любой части приложения. Разработчику для их управления предлагаются конструкции языка try-catch-finally-throw (попытка-ловушка-наконец-бросок). Программа, имеющая такие конструкции, называется защищенной. При работе с этими конструкциями следует иметь ввиду следующее:

  • Для объекта исключения может быть выбрано любое имя.
  • Конкретные типы исключений происходят от базового класса System.Exception. Одним из таких производных классов является класс System.Data.OleDb.OleDbException, который мы будем ниже использовать.
  • Оператор throw предназначен для прямого вызова из программы исключительной ситуации, чтобы при выполнении каких-то условий направить обработку по цепочке вызовов к ближайшему обработчику.
  • Конструкция catch используется для перехвата конкретных исключений. В программе может быть предусмотрено несколько таких блоков для обработки всех возможных исключений, которые могут в ней возникнуть.
  • Разработчику не стоит скупиться на применение средств управления исключениями, и вставлять их в код при каждом удобном случае по принципу: "чего бы не случилось - хуже не будет - до пенсии далеко", а также из соображений защиты от пользователя-дурака или системы-дуры. Он не должен надеяться на русское "авось пронесет и это не случится".
  • Билл из Редмонда выполнил большую работу по созданию средств обработки исключений в среде .NET Framework на разных языках. Правильное примененение конструкций try-catch-finally-throw и классов поддержки из BCL позволяет разработчику обеспечить обработку ошибок любого уровня сложности.
  • Исключения несут в себе большой объем информации, поэтому в программе может быть обеспечена исчерпывающая обработка всех возможных ошибок. В частности, может быть предусмотрена процедура централизованной регистрации ошибок или даже автоматическая передача сообщений разработчику по электронной почте (или на оленях) при возникновении ошибок в его приложении. Возможно, вы сталкивались в своих лабораторных работах с такой ситуацией, когда оболочка Visual Studio .NET 2003 от ваших праведных действий вдруг начинает метаться, дымиться, чесать затылок, рычать, морщить лоб и, наконец, в предсмертных судорогах тихо просить, чтобы студент Панасюк срочно написал письмо Большому Биллу.
  • Блок try - это фрагмент кода, во время выполнения которого происходит отслеживание возникающих исключений. При возникновении исключения выполнение кода, определенного в блоке try, прерывается, и начинает выполняться код, определенный в ближайшем блоке цепочки блоков catch с подходящей настройкой. Если исключение не возникло при выполнении всего блока try, то следующие за ним блоки catch игнорируются.
  • Необязательный блок finaly следует в конце блоков try/catch и выполняется всегда, не зависимо от того, сработало исключение или нет. Его главное назначение - гарантировать, что ресурсы, которые открыты потенциально опасным методом, будут обязательно освобождены. В реальных приложениях этот блок используется для освобождения памяти, закрытия файла, отключения от источника и данных - выполнения любых необходимых операций, связанных с корректным завершением программы при возникновении исключения.
  • Добавим средства защиты в наше приложение, пытаясь повысить его живучесть. Уязвимым местом в коде проекта является операция сохранения изменений в базе данных и возможность некорректного завершения приложения. В нашем коде эти операции находятся в обработчиках кнопок btnSave, btnDelete и во вспомогательной функции SaveRecord().

  • Найдите в коде файла Form5.cs функции
  • private void btnSave_Click(object sender, System.EventArgs e)
  • private void SaveRecord()
  • private void btnDelete_Click(object sender, System.EventArgs e)
  • Для быстрого поиска нужных разделов кода пользуйтесь раскрывающимся списком вверху редактора кода
  • Вставьте в функции необходимый код, который приведен ниже и выделен синим цветом.
  • //*****************************************************************
    // Обработчик события Click кнопки Save
    private void btnSave_Click(object sender, System.EventArgs e)
    {
      // Блок обработки исключений
      try
      {
        SaveRecord();
      }
      catch(Exception saveException) // Имя объекта исключения произвольное
      {
        MessageBox.Show("Произошла следующая ошибка:\n" 
          + saveException.Message
          , "\nСохранение текущих изменений отклонено!");
        return;
      }
        
      ActivateEditing(false);
      
      // Добавлен код для загруки новой записи
      if(mbAddNew)
      {
        // Загрузка данных новой записи
        LoadList(); // Наша функция
        // Обновление отдельного набора данных
        RefreshIndividual(); // Наша функция
        mbAddNew = false;
      }
    }
      
    //*****************************************************************
    // Сохранить изменения в базе данных
    private void SaveRecord()
    {
      // Блок обработки исключений
      try
      {
        // Применить класс BindingContext для завершения
        // текущего сеанса редактирования, чтобы иметь
        // возможность записать обновления в базу данных
        this.BindingContext[dsCustomerIndividual, "Customers"].EndCurrentEdit();
      
        // Обновить данные в кэше из набора данных dsCustomerIndividual
        // в поставщик данных odaCustomerIndividual
        odaCustomerIndividual.Update(dsCustomerIndividual, "Customers");
      
        // Передать данные из кэша поставщика данных в базу
        dsCustomerIndividual.AcceptChanges();
      }
      catch(Exception saveException)
      {
        // Генерируем исключение, которое будет обработано 
        // во внешнем блоке try-catch, вызывающем эту функцию
        throw saveException;
      }
    }
      
    //*****************************************************************
    private void btnDelete_Click(object sender, System.EventArgs e)
    {
      // Блок обработки исключений
      try
      {
        // Отменить строку для удаления с использованием
        // метода RemoveAt() класса BindingContext
        this.BindingContext[dsCustomerIndividual, "Customers"]
          .RemoveAt(BindingContext[dsCustomerIndividual, "Customers"].Position);
      
        // Обновить данные в кэше из набора данных dsCustomerIndividual
        // в поставщик данных odaCustomerIndividual
        odaCustomerIndividual.Update(dsCustomerIndividual, "Customers");
      
        // Передать данные из кэша поставщика данных в базу
        dsCustomerIndividual.AcceptChanges();
      }
      catch(System.Data.OleDb.OleDbException delException)
      {
        // Проверяем, не относится ли возникшее исключение
        // к конкретному типу 3621, которое свидетельствует
        // об ошибочной попытке удалить запись, тогда как
        // в другой таблице существуют связанные с ней записи
        if(delException.Errors[0].NativeError == 3621)
          MessageBox.Show("Призошла ошибка попытки \n удаления связанной записи!"
            ,"Ошибка удаления в таблице Customer"
            ,MessageBoxButtons.OK
            ,MessageBoxIcon.Exclamation);
        else
          MessageBox.Show("Неопознанная ошибка \n при удалении записи!"
            ,"Ошибка удаления в таблице Customer"
            ,MessageBoxButtons.OK
            ,MessageBoxIcon.Exclamation);
            
        return;
      }
      catch(Exception delException)
      {
        MessageBox.Show(delException.Message
          ,"Ошибка удаления в таблице Customer"
          ,MessageBoxButtons.OK
          ,MessageBoxIcon.Exclamation);
            
        return;
      }
      
      // Перезагрузить список
      LoadList();
      
      // Отобразить первую запись
      RefreshIndividual();
      
      // Отключить текстовые поля
      ActivateEditing(false);
    }
  • Запретите в одном или обоих поставщиках данных изменения данных, постройте приложение и испытайте срабатывание установленной нами защиты от исключений. При проверке добавляйте строки, а затем удаляйте их, но не трогайте существующие записи, чтобы база соответствовала оригиналу для получения одинаковых со мной результатов.
  • При попытке сохранения или удаления данных наши обработчики исключений будут выдавать примерно такие сообщения

    А само приложение, при этом, будет сохранять полную работоспособность, бодрость духа и даже не кашлять.

  • Верните свойства поставщиков данных изменять данные для возможности выполнения дальнейших этапов построения проекта.
  • Окончательное оформление формы, связанной с данными

    В форме ввода данных должны быть предусмотрены не только основные средства обработки данных, но и учтены такие второстепенные нюансы, как возможность запретить или разрешить использование элементов управления с учетом тех операций, которые выполняются в настоящий момент с записью. Нужно своевременно включать или отключать важные кнопки, чтобы пользователь в критические дни чувствовал себя защищенным - без кариеса, угрей, дурного запаха, перхоти, жирных пятен, вредных бактерий и насекомых и т.д. и т.п.

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

    В этом разделе мы реализуем следующие функции в нашем приложении:

  • Разрешение или запрещение свойства Enabled кнопок btnSave, btnCancel, btnNew и btnDelete в соответствующих состояниях программы, когда нажатие на них может быть нежелательно.
  • Добавление кнопки для закрытия формы.
  • Ввод определенного кода в обработчик события Closing формы, который перед завершение программы позволяет проверить, внесены ли изменения в текущую запись, и нужно ли их в таком случае сохранить.
  • Начнем с того, что откорректируем две ранее созданные нами функции RefreshIndividual() и ActivateEditing(). Затем обеспечим проверку того, была ли выбрана запись в списке заказчиков lstCustomers. В случае отрицательного ответа должно быть запрещено применение двух кнопок, btnEdit и btnDelete. Если же запись в списке заказчиков выбрана, то эти две кнопки должны быть разрешены и обновлено содержимое текстовых полей набором данных dsCustomerIndividual.

  • Разместите в конце тела функций RefreshIndividual() и ActivateEditing() код управления доступностью кнопок, как приведено ниже
  • //*****************************************************************
    // Обновление отдельного набора данных
    private void RefreshIndividual()
    {
    ......................................................
      // Изменить свойство доступности кнопок
      if(lstCustomers.SelectedIndex == -1)
      {
        btnEdit.Enabled = false;
        btnDelete.Enabled = false;
      }
      else
      {
        btnEdit.Enabled = true;
        btnDelete.Enabled = true;
      }
    }
      
    //*****************************************************************
    // Функция изменения свойств текстовых полей
    private void ActivateEditing(bool bEnable)
    {
    ......................................................
      // Разрешить или запретить применение соответствующих кнопок
      btnEdit.Enabled = !bEnable;
      btnNew.Enabled = !bEnable;
      btnDelete.Enabled = !bEnable;
          
      btnCancel.Enabled = bEnable;
      btnSave.Enabled = bEnable;
          
      // Установить фокус ввода на первое текстовое поле CustomerID
      if(bEnable)
        txtCustomerID.Focus();
    }
  • Разместите внизу формы кнопку завершения работы приложения и назначьте ей свойства Name=btnClose; Text=Close
  • Создайте обработчик для кнопки btnClose и заполните его кодом так
  • //*****************************************************************
    private void btnClose_Click(object sender, System.EventArgs e)
    {
      this.Close();
    }

    Теперь пришла пора создать обработчик события Closing формы. В нем нужно проверить состояние кнопки btnSave в момент закрытия приложения. Если она доступна, значит существуют несохраненные изменения данных. В этом случае нужно спросить у пользователя, желает ли он сохранить внесенные изменения.

  • Перейдите в режим Design, откройте панель Properties и в раскрывающемся списке установите текущим элемент формы Form5. Затем в режиме Events панели Properties найдите событие Closing и двойным щелчком на поле значений создайте для него обработчик
  • Заполните обработчик следующим кодом
  • //*****************************************************************
    private void Form5_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
      if(!btnSave.Enabled) return; // Ничего не надо - все сохранено!
      
      string caption = "Сохранение результатов редактирования";
      string message = "Хотели бы Вы сохранить текущие изменения?";
      MessageBoxButtons buttons = MessageBoxButtons.05_57No;
      DialogResult result;
      
      // Вывести пользователю предупреждение.
      result = MessageBox.Show(this, message, caption, buttons);
      
      // Проанализировать выбор пользователя
      if(result == DialogResult.No) return; // Не желает сохранять
      
      // Пытаемся сохранить
      try
      {
        SaveRecord();
      }
      catch(Exception saveException)
      {
        caption = "Ошибка записи данных";
        message = "Произошла следующая ошибка:\n"
          + saveException.Message
          + "\nПродолжить закрытие формы?";
      
        // Вывести пользователю предупреждение.
        result = MessageBox.Show(this, message, caption, buttons);
      
        // Проанализировать выбор пользователя
        if(result == DialogResult.No)
          e.Cancel = true; // Прекратить закрытие формы
                   // e - аргумент этого обработчика
      }
    }
  • Добавьте в конструктор формы Form5() вызов нашей функции начальной загрузки списка данными
  • public Form5()
    {
      //
      // Required for Windows Form Designer support
      //
      InitializeComponent();
      
      //
      // TODO: Add any constructor code after InitializeComponent call
      //
      LoadList();
    }
  • Постройте приложение и проверьте заложенную в нем функциональность
  • Вернуться к учебному плану