Опубликован: 05.08.2010 | Уровень: специалист | Доступ: свободно
Самостоятельная работа 6:

Редактирование данных OLE DB средствами ADO.NET

Упражнение 3. Полномасштабное редактирование одной таблицы

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

На сей раз мы будем работать с таблицей Products готовой БД Northwind.mdb, потому что там есть столбец первичного ключа числового типа и удобно сделать этот столбец автозаполняющимся (AutoIncrement), чтобы не редактировать. В качестве элемента представления и редактирования данных применим объект DataGridView, который свяжем с загруженной таблицей, но не напрямую. Дело в том, что мы еще будем использовать объект-навигатор BindingNavigator, который обеспечит удобное перемещение по сетке DataGridView. Для обеспечения синхронной их работы с одной и той же таблицей, мы не сможем их сразу оба связать с ней. Но это можно легко сделать через объект-разветвитель BindingSource, к которому подключим таблицу, а уже через него подключим и другие объекты.

Этап визуального проектирования интерфейса

  • Добавьте к решению ADO новый проект оконного приложения с именем WinForms3 и назначьте его стартовым
  • В панели Solution Explorer курсором мыши перетащите каталог дата из предыдущего проекта WinForms2 на узел нового проекта WinForms3, мастеру настройки конфигурации скажите Cancel
  • В панели Solution Explorer выделите файл БД Northwind.mdb и в панели Properties установите для него
    • Build Action=None
    • Copy to Output Directory=Copy if newer
  • Перетащите на форму из вкладки Data панели Toolbox объекты DataGridView, BindingNavigator, BindingSource. Проследите, чтобы объект BindingNavigator попал на форму, а не внутрь объекта DataGridView как дочерний (тогда лучше вначале перетащить BindingNavigator, а потом DataGridView )
  • Настройте свойства объектов в соответствии с таблицей свойств. Имена экземпляров задайте в точности, иначе будет нестыковка с приведенным далее кодом
Объект Свойство Значение
Form Text Упражнение 3.
  Size 333 ; 300
DataGridView (Name) dataGridView
  Dock Fill
BindingNavigator (Name) bindingNavigator
  CountItemFormat из {0}
BindingSource (Name) bindingSource
  • Растяните форму по ширине раза в два, выделите сам объект bindingNavigator (можно щелкнуть на пиктограмме в самом низу рабочей области оболочки) и раскройте его интеллектуальный дескриптор (smart tag = смарт-тег) щелчком на маленьком квадратике с треугольничком в правом верхнем углу выделенного компонента
  • Щелкните на ссылке Insert Standard Items окна смарт-тега, чтобы добавить ряд встроенных пиктограмм
  • Из добавленных пиктограмм оставьте Save и Help, а остальные удалите

Лишние пиктограммы можно удалять по одной, выделяя их, а можно воспользоваться окном редактора Items Collection Editor.

  • Щелкните в окне смарт-тега объекта bindingNavigator на ссылку Edit Items, чтобы открыть редактор элементов. Этот редактор с типовым интерфейсом и с ним легко работать

  • Удалите лишние пиктограммы и добавьте сепаратор (разделитель) между кнопкой Save и предыдущими 'родными' пиктограммами навигатора
  • Установите прежнюю ширину формы, прописанную в таблице свойств

В конечном итоге на этапе проектирования должен получиться такой интерфейс


Этап кодирования и настройки всего остального

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

  • Заполните файл Form1.cs проекта WinForms3 следующим кодом
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
       
// Дополнительные пространства имен
using System.Data.OleDb;
using System.Data.Common;
    
namespace WinForms3
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            // Инициализировать созданное в режиме дизайнера
            InitializeComponent();
            this.WindowState = FormWindowState.Maximized;// На полный экран
    
            // Создать все остатки от дизайнера и загрузить таблицу
            LoadProducts();
            // Перед закрытием формы проверяем несохраненные изменения
            this.FormClosing += new FormClosingEventHandler(Form1_FormClosing);
        }
    
        // Проверяем при закрытии формы несохраненные изменения
        void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if(tableProducts.GetChanges() != null)
            {
                DialogResult result =
                   MessageBox.Show("Вы не сохранили изменения в БД\nСохранить?",
                   "Внимание!",
                   MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning);
                if (result == DialogResult.Yes)
                    Update();   // Сохраняем данные
                else if (result == DialogResult.Cancel)
                    e.Cancel = true;// Отменяем закрытие формы
            }
        }
    
        // Строка соединения к БД с абсолютным путем, определяемым сборкой
        String ConnectionString()
        {
            // Используем построитель строки подключения 
            OleDbConnectionStringBuilder objConnectionStringBuilder =
                new OleDbConnectionStringBuilder();
            objConnectionStringBuilder.Provider = "Microsoft.Jet.OLEDB.4.0";
            objConnectionStringBuilder.DataSource =
                Application.StartupPath.ToString() + @"\Data\Northwind.mdb";
                //Application.ExecutablePath + @"\Data\Northwind.mdb";// + имя сборки
    
            return objConnectionStringBuilder.ToString();
        }
    
        // Выносим в поля для видимости в методах
        DataTable tableProducts;
        OleDbDataAdapter adapter;
    
        // Все загружаем и все настраиваем
        void LoadProducts()
        {
            // Загружаем таблицу Products целиком
            OleDbConnection connection = new OleDbConnection(ConnectionString());
            adapter = new OleDbDataAdapter(
                new OleDbCommand("SELECT * FROM Products", connection));
            tableProducts = new DataTable();// Имя можно не указывать!
    
            // Регистрируем обработчик события изменения поля таблицы для
            // выделения ячейки после редактирования через DataGridView
            tableProducts.ColumnChanged += 
                new DataColumnChangeEventHandler(tableProducts_ColumnChanged);
    
            // Указать столбец с автозаполнением, но обязательно до включения в таблицу
            DataColumn col = new DataColumn("ProductID", typeof(int));
            col.AutoIncrement = true;
            tableProducts.Columns.Add(col);
            col.ReadOnly = true;// Ключевой столбец не давать редактировать напрямую
    
            adapter.Fill(tableProducts);// Сам откроет соединение и сам закроет
            // Указать первичный ключ можно уже и в заполненной таблице
            // Синтаксис определения первичного ключа требует использовать массив
            // на случай, если в первичный ключ войдут несколько столбцов 
            tableProducts.PrimaryKey =
                new DataColumn[] { tableProducts.Columns["ProductID"] };
    
            // 
            // Разветвитель, навигатор и сетку создали визуально
            //
            // Связали таблицу с разветвителем
            bindingSource.DataSource = tableProducts;
            // Поключили к разветвителю навигатор
            bindingNavigator.BindingSource = bindingSource;
            // Подключили к разветвителю элемент редактирования
            dataGridView.DataSource = bindingSource;
    
            //
            //Настраиваем DataGridView
            //
            // Сделаем ключевой столбец с автозаполнением только для чтения
            //dataGridView.Columns["ProductID"].ReadOnly = true;// Уже сделали в таблице
            // Ширина столбцов по ширине содержимого
            dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            dataGridView.RowHeadersWidth = 25;// Ширина первого (служебного) столбца
            //dataGridView.RowHeadersVisible = false;// Скрываем первый (служебный) столбец        
            //dataGridView.Columns["ProductID"].Visible = false;// Скрываем рабочий столбец
    
            // Запретим выделение многих строк, все-равно 
            // навигатор удаляет только по одной строке
            dataGridView.MultiSelect = false;
    
            // Настраиваем стиль строки заголовков сетки
            DataGridViewCellStyle columnHeaderStyle = new DataGridViewCellStyle();// Создали стиль
            columnHeaderStyle.BackColor = Color.Beige;// Цвет фона заголовка
            columnHeaderStyle.Font = new Font("Verdana", 10, FontStyle.Bold);// Шрифт
            columnHeaderStyle.Alignment = 
                DataGridViewContentAlignment.MiddleCenter;// Выравнивать по центру столбца
            dataGridView.ColumnHeadersDefaultCellStyle = columnHeaderStyle;// Настроили и прицепили 
            // Фоновый цвет всех ячеек таблицы по умолчанию белый. Но явно
            // задаем потому, что позже будем восстанавливать белым же цветом
            DataGridViewCellStyle defaultCellStyle = new DataGridViewCellStyle();
            defaultCellStyle.BackColor = Color.White;
            dataGridView.DefaultCellStyle = defaultCellStyle;// Настроили и прицепили
    
            //
            // Настраиваем кнопки объекта BindingNavigator здесь
            // или в панели Properties дизайнера
            //
            bindingNavigatorMoveFirstItem.ToolTipText = "На первую запись";
            bindingNavigatorMovePreviousItem.ToolTipText = "На предыдущую запись";
            bindingNavigatorPositionItem.ToolTipText = "Текущая запись";
            bindingNavigatorCountItem.ToolTipText = "Всего записей";
            bindingNavigatorMoveNextItem.ToolTipText = "На следующую запись";
            bindingNavigatorMoveLastItem.ToolTipText = "На последнюю запись";
            bindingNavigatorAddNewItem.ToolTipText = "Добавить запись";
            bindingNavigatorDeleteItem.ToolTipText = "Удалить запись";
            saveToolStripButton.ToolTipText = "Сохранить изменения";
            helpToolStripButton.ToolTipText = "Помощь";
            helpToolStripButton.Enabled = false;// Затычка для Справки, пока не хотим...
            // Регистрируем обработчик события сохранения данных в БД
            saveToolStripButton.Click += new EventHandler(saveToolStripButton_Click);
    
            //
            // Создаем и настраиваем контекстное меню для дополнительных команд
            //
            // Создаем объект контекстного меню
            System.Windows.Forms.ContextMenuStrip menu = new ContextMenuStrip();
            // Прицепляем к DataGridView
            dataGridView.ContextMenuStrip = menu;
            // Создаем объект команды и сразу с названием
            ToolStripMenuItem item = new ToolStripMenuItem("Восстановить");
            // Всплывающая подсказка
            item.ToolTipText = "Откатить все несохраненные изменения";
            // Добавляем команду в коллекцию команд контекстного меню
            menu.Items.Add(item);
            // Регистрируем обработчик команды для события Click
            item.Click += new EventHandler(item_RestoreClick);
        }
    
        // Список для хранения ячеек с измененными стилями
        // для последующего восстановления в ClearStyleModifiedCells()
        // (Ничего приличнее мне в голову не пришло! НаноСидоркин - помоги!
        // Надо искать оптовое решение в самом DataGridView)
        System.Collections.ArrayList listCell = new System.Collections.ArrayList();
    
        // Срабатывает при модификации любого поля
        void tableProducts_ColumnChanged(object sender, DataColumnChangeEventArgs e)
        {
            DataGridViewCellStyle style = new DataGridViewCellStyle();
            style.BackColor= Color.Aqua;
            dataGridView.CurrentCell.Style = new DataGridViewCellStyle(style);
            listCell.Add(dataGridView.CurrentCell);// Складываем ссылки для восстановления
        }
    
        // Обработчик команды контекстного меню
        void item_RestoreClick(object sender, EventArgs e)
        {
            // Если нечего откатывать
            if (tableProducts.GetChanges() == null)
                return;
    
            // Есть изменения, спрашиваем желание пользователя
            DialogResult result =
                MessageBox.Show("Вы действительно хотите откатить\n"
                + "последние несохраненные изменения?",
                "Внимание!",
                MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
            if (result == DialogResult.Yes)
            {
                tableProducts.RejectChanges();// Откатываем таблицу
                ClearStyleModifiedCells(); // Снимаем выделение с ячеек
            }
        }
    
        // Сохраняем изменения в БД
        new void Update()
        {
            // Чтобы лишний раз не дергать проверку на уровне БД
            if (tableProducts.GetChanges() == null)
                return;
    
            // Создаем построитель команд по настройкам нашего адаптера
            OleDbCommandBuilder commandBuilder =
                new OleDbCommandBuilder(adapter);
            // Не реагировать на конфликты параллелизма, а записывать поверх
            commandBuilder.ConflictOption = ConflictOption.OverwriteChanges;
            // Отправить изменения таблицы в БД. Необходимые 
            // команды генерирует созданный объект commandBuilder
            adapter.Update(tableProducts);
        }
    
        // Обработчик кнопки сохранения изменений в БД
        void saveToolStripButton_Click(object sender, EventArgs e)
        {
            // Проверка достоверности завершает редактирование текушего поля
            this.Validate();        
            bindingSource.EndEdit();// Останавливаем разветвитель
    
            Update();// Отправляем изменения в БД
            ClearStyleModifiedCells();// Восстанавливаем цвет ячеек сетки
        }
    
        // Все содержимое ArrayList имеет тип Object - надо явно приводить
        void ClearStyleModifiedCells()
        {
            // Снимаем выделение с измененных ячеек сетки
            DataGridViewCellStyle style = new DataGridViewCellStyle();
            style.BackColor = Color.White;
            foreach (Object ob in listCell)
            {
                if (ob != null)// На случай, если строку с ячейками удалили!
                    ((DataGridViewCell)ob).Style = new DataGridViewCellStyle(style);
            }
    
            listCell.Clear();// Очистить список, начать с чистого листа!
        }
    }
}
  • Запустите приложение, разберитесь с кодом, что не достроено - достройте, что недокрашено - докрасьте. Вот результат


Алексей Бабушкин
Алексей Бабушкин

При выполнении в лабораторной работе упражнения №1 , а именно при выполнении нижеследующего кода:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using Microsoft.Xna.Framework.Graphics;

   

namespace Application1

{

    public partial class MainForm : Form

    {

        // Объявим поле графического устройства для видимости в методах

        GraphicsDevice device;

   

        public MainForm()

        {

            InitializeComponent();

   

            // Подпишемся на событие Load формы

            this.Load += new EventHandler(MainForm_Load);

   

            // Попишемся на событие FormClosed формы

            this.FormClosed += new FormClosedEventHandler(MainForm_FormClosed);

        }

   

        void MainForm_FormClosed(object sender, FormClosedEventArgs e)

        {

            //  Удаляем (освобождаем) устройство

            device.Dispose();

            // На всякий случай присваиваем ссылке на устройство значение null

            device = null;       

        }

   

        void MainForm_Load(object sender, EventArgs e)

        {

            // Создаем объект представления для настройки графического устройства

            PresentationParameters presentParams = new PresentationParameters();

            // Настраиваем объект представления через его свойства

            presentParams.IsFullScreen = false; // Включаем оконный режим

            presentParams.BackBufferCount = 1;  // Включаем задний буфер

                                                // для двойной буферизации

            // Переключение переднего и заднего буферов

            // должно осуществляться с максимальной эффективностью

            presentParams.SwapEffect = SwapEffect.Discard;

            // Устанавливаем размеры заднего буфера по клиентской области окна формы

            presentParams.BackBufferWidth = this.ClientSize.Width;

            presentParams.BackBufferHeight = this.ClientSize.Height;

   

            // Создадим графическое устройство с заданными настройками

            device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, DeviceType.Hardware,

                this.Handle, presentParams);

        }

   

        protected override void OnPaint(PaintEventArgs e)

        {

            device.Clear(Microsoft.Xna.Framework.Graphics.Color.CornflowerBlue);

   

            base.OnPaint(e);

        }

    }

}

Выбрасывается исключение:

Невозможно загрузить файл или сборку "Microsoft.Xna.Framework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=6d5c3888ef60e27d" или один из зависимых от них компонентов. Не удается найти указанный файл.

Делаю все пунктуально. В чем может быть проблема?

Юрий Макушин
Юрий Макушин
Россия, Москва, РЭА им. Плеханова, 2004