Работа с элементом DataGridView в C#

User Rating: 4 / 5

В этой статье мы рассмотрим основные принципы работы с элементом DataGridView в приложении Windows Forms и напишем тестовую программу на языке C#, которая будет выводить в элемент DataGridView список названий книг, их авторов и год публикации книг. Подобный список мог бы использоваться в какой-то специализированной программе, например, для автоматизации работы книжной библиотеки.

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

В нашей программе мы предусмотрим и реализуем следующие возможности:

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

Вот так будет выглядеть наше приложение:

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

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

При нажатии на второй пункт меню "Редактировать выбранную книгу" будет отображаться отдельная форма с редактированием данных о книге:

 

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

Мы также сможем добавлять книги в список, хранящийся в памяти программы и привязанный к нашей таблице и элементу DataGridView. Для добавления новой книги будет необходимо ввести её название, автора и выбрать дату издания:

Итак, давайте приступим к созданию проекта в среде разработки Microsoft Visual Studio для нашего приложения.

Создание нового проекта в среде Microsoft Visual Studio

Откройте среду разработки Microsoft Visual Studio и создайте новый проект с типом Приложение Windows Forms (.NET Framework), как показано ниже:

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

Переименование главной формы приложения

После создания проекта будет создана главная форма по умолчанию с именем Form1 и соответствующий ей класс Form1.cs.

Выберем в окне "Обозреватель решений" эту форму и в окне "Свойства" изменим название класса для формы на FrmDataGridViewSampleMain.cs.

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

В итоге должно получиться следующее - в окне "Обозреватель решений" форма и зависящие от неё файлы будут переименованы:

А так должно выглядеть окно "Свойства":

Настройка главной формы приложения и её элементов

Далее нам нужно изменить размеры и свойства главной формы и расположить на ней все нужные нам элементы управления.

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

  • FormBorderStyle - FixedSingle
  • Text - [Allineed.Ru] Пример работы с элементом DataGridView
  • Size - 1026; 568
  • StartPosition - CenterScreen
  • Name - FrmDataGridViewSampleMain
  • MaximizeBox - False
  • MinimizeBox - False

Теперь расположим на главной форме нужные элементы. Для этого в левой части Microsoft Visual Studio находим "Панель элементов" и последовательно перетаскиваем с неё на нашу главную форму следующие элементы, а также устанавливаем им описанные ниже свойства:

1) Один элемент ContextMenuStrip для контекстного меню, которое будет отображаться на элементе DataGridView

Перетаскиваем на форму элемент ContextMenuStrip и устанавливаем ему следующие свойства:

  • Name - ContextMenuStripForGrid

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

После этого в представлении конструктора формы появится выпадающее меню с подсказкой "Вводить здесь". Необходимо ввести с клавиатуры названия для двух новых пунктов меню:

  • &Удалить выбранную книгу
  • &Редактировать выбранную книгу

В результате будут созданы два соответствующих элемента ToolStripMenuItem для контекстного меню (знак & перед текстом пунктов меню позволит при работе программы использовать комбинацию клавиш Alt+<подчёркнутая_буква> для выбора пункта меню с клавиатуры без использования мыши):

Теперь снова последовательно выбираем каждый из этих элементов и устанавливаем им свойства:

Для пункта меню "&Удалить выбранную книгу":

  • Name - MenuItemRemoveBook

Для пункта меню "&Редактировать выбранную книгу":

  • Name - MenuItemEditBook

2) Один элемент DataGridView для списка книг

Перетаскиваем на форму элемент DataGridView и устанавливаем ему следующие свойства:

  • CellBorderStyle - RaisedHorizontal
  • ColumnHeadersDefaultCellStyle - открыть окно "Построитель CellStyle" и установить в нём для свойства Font значение: Microsoft Sans Serif; 8,25pt; style=Bold
  • RowsDefaultCellStyle - открыть окно "Построитель CellStyle" и установить в нём для свойства BackColor значение PapayaWhip, а для свойства ForeColor - значение SaddleBrown
  • Location - 12; 41
  • Size - 587; 388
  • ColumnHeadersHeightSizeMode - AutoSize
  • ContextMenuStrip - ContextMenuStripForGrid
  • Name - DataGridViewBooks

3) Один элемент TextBox для текстового представления списка книг в памяти программы

Перетаскиваем на форму элемент TextBox и устанавливаем ему следующие свойства:

  • ScrollBars - Vertical
  • Location - 605; 12
  • Size - 391; 417
  • Multiline - True
  • ReadOnly - True
  • Name - TextBoxBooks

4) Один элемент Label с текстом "Поиск:"

Перетаскиваем на форму элемент Label и устанавливаем ему следующие свойства:

  • Text - Поиск:
  • Location - 12; 15
  • Size - 42; 13
  • Name - LabelSearch

5) Один элемент TextBox для быстрого поиска книги или автора и фильтрации данных в элементе DataGridView

Перетаскиваем на форму элемент TextBox и устанавливаем ему следующие свойства:

  • Text - <оставить поле пустым>
  • Location - 53; 12
  • Size - 546; 20
  • Name - TextBoxSearch

6) Один элемент GroupBox для группировки элементов в нижней части формы

Перетаскиваем на форму элемент GroupBox и устанавливаем ему следующие свойства:

  • Text - Добавить новую книгу в коллекцию:
  • Location - 12; 435
  • Size - 984; 59
  • Name - GroupBoxAddNewBook

Теперь внутрь этого элемента GroupBox перетаскиваем дополнительные элементы и устанавливаем им свойства:

6.1) один элемент Label для названия книги

  • Text - Название:
  • Location - 16; 25
  • Size - 60; 13
  • Name - LabelTitle

6.2) один элемент TextBox для ввода названия книги

  • Text - <оставить поле пустым>
  • Location - 82; 22
  • Size - 294; 20
  • Name - TextBoxTitle

6.3) один элемент Label для автора книги

  • Text - Автор:
  • Location - 393; 25
  • Size - 40; 13
  • Name - LabelAuthor

6.4) один элемент TextBox для ввода автора книги

  • Text - <оставить поле пустым>
  • Location - 439; 22
  • Size - 175; 20
  • Name - TextBoxAuthor

6.5) один элемент Label для года издания книги

  • Text - Год издания:
  • Location - 630; 25
  • Size - 73; 13
  • Name - LabelDatePublished

6.6) один элемент DateTimePicker для ввода года издания книги

  • Format - Custom
  • Location - 717; 22
  • Size - 66; 20
  • CustomFormat - yyyy
  • Name - DateTimePickerDatePublished

6.7) один элемент Button для добавления книги в таблицу

  • Text - &Добавить книгу
  • Location - 823; 20
  • Size - 147; 23
  • Name - ButtonAddBook

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

Добавление новой формы для редактирования книги

Добавьте к проекту новую форму и назовите её FrmEditBook. Для добавления новой формы в окне "Обозреватель решений" кликните на проекте DataGridViewSample правой кнопкой мыши (маркер 1 на рисунке ниже), затем выберите пункт меню "Добавить" (маркер 2 на рисунке ниже), а затем выберите пункт меню "Форма (Windows Forms)..." (маркер 3 на рисунке ниже):

В открывшемся диалоговом окне вместо имени класса по умолчанию Form1.cs введите имя нового класса для формы FrmEditBook.cs и нажмите кнопку "Добавить".

После добавления формы к проекту установите ей следующие свойства:

  • FormBorderStyle - FixedSingle
  • Text - Редактировать книгу
  • Size - 548; 342
  • StartPosition - CenterScreen
  • Name - FrmEditBook
  • MaximizeBox - False
  • MinimizeBox - False 

Далее расположите на форме следующие элементы управления и задайте им соответствующие свойства:

1) Один элемент Label (для текстового поля с названием книги):

  • Text - Название книги:
  • Location - 23; 20
  • Size - 92; 13
  • Name - LabelTitle

2) Один элемент TextBox (для отображения редактируемого названия книги):

  • Text - <оставить поле пустым>
  • Location - 26; 36
  • Size - 476; 20
  • Name - TextBoxTitle

3) Один элемент Label (для текстового поля с автором книги):

  • Text - Автор книги:
  • Location - 23; 81
  • Size - 72; 13
  • Name - LabelAuthor

4) Один элемент TextBox (для отображения редактируемого автора книги):

  • Text - <оставить поле пустым>
  • Location - 26; 97
  • Size - 476; 20
  • Name - TextBoxAuthor

5) Один элемент Label (для поля с выбором года издания книги):

  • Text - Год издания:
  • Location - 23; 144
  • Size - 73; 13
  • Name - LabelDatePublished

6) Один элемент DateTimePicker (поле с выбором года издания книги):

  • Format - Custom
  • Location - 26; 160
  • Size - 89; 20
  • CustomFormat - yyyy
  • Name - DateTimePickerDatePublished

7) Один элемент Button (кнопка "OK"):

  • Text - &OK
  • Location - 346; 236
  • Size - 75; 23
  • DialogResult - OK
  • Name - ButtonOK

8) Один элемент Button (кнопка "Отмена"):

  • Text - &Отмена
  • Location - 427; 236
  • Size - 75; 23
  • DialogResult - Cancel
  • Name - ButtonCancel

Теперь все основные элементы расположены на нашей второй форме "Редактировать книгу". Для завершения дизайна формы нам осталось выставить ещё два дополнительных свойства на форме.

Выберите форму в представлении конструктора и установите свойства из категории "Прочее":

  • AcceptButton - ButtonOK
  • CancelButton - ButtonCancel

Должно было получиться следующее:

Теперь дизайн второй формы полностью готов. Нажмите комбинацию Ctrl+Shift+S, чтобы сохранить все изменения в проекте.

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

Создаём класс Book для хранения данных о книге

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

Для добавления нового класса в окне "Обозреватель решений" кликните на проекте DataGridViewSample правой кнопкой мыши (маркер 1 на рисунке ниже), затем выберите пункт меню "Добавить" (маркер 2 на рисунке ниже), а затем выберите пункт меню "Класс..." (маркер 3 на рисунке ниже):

В открывшемся диалоговом окне вместо Class1.cs введите имя нового класса: Book.cs и нажмите кнопку "Добавить". В результате будет открыт редактор кода со сгенерированным начальным кодом класса Book. Всё содержимое нужно будет заменить представленным ниже кодом.

Ниже полный код класса Book, который имеет свойства для хранения названия книги (Title), автора книги (Author), а также даты публикации книги (DatePublished). Мы также отдельно будем возвращать год публикации книги (YearPublished), без возможности его редактирования:

using System;

namespace DataGridViewSample {
    public class Book {
        public string Title { get; set; }
        public string Author { get; set; }
        public DateTime DatePublished { get; set; }

        public string YearPublished { get { return DatePublished.ToString("yyyy"); } }

        public Book(string title, string author, DateTime datePublished) {
            Title = title;
            Author = author;
            DatePublished = datePublished;
        }

        public override string ToString() {
            return "\tBook@" + GetHashCode() + "{\r\n" +
                "\t\tTitle: " + Title + "\r\n" +
                "\t\tAuthor: " + Author + "\r\n" +
                "\t\tDatePublished: " + DatePublished + "\r\n" +
                "\t\tYearPublished: " + YearPublished + "\r\n" +
                "\t}";
        }
    }
}

Вставьте весь представленный выше код в редактор и сохраните изменения, нажав Ctrl+S.

Привязка полей формы FrmEditBook к источнику данных

Теперь, когда класс книги Book у нас готов, мы привяжем текстовые поля формы редактирования книги (FrmEditBook) и её элемент DateTimePicker к источнику данных, которым будет являться экземпляр книги.

Чтобы сделать это, выделите в представлении конструктора формы первое текстовое поле TextBoxTitle и в окне "Свойства" для него найдите категорию "Данные", а под ним свойство DataBindings:

 

Раскройте напротив свойства Text выпадающий список и в открывшемся окне раскройте узел "Другие источники данных" и вложенный в него "Источники данных проекта". Вы увидите созданный ранее класс Book и его поля, как показано ниже на рисунке. Выберите поле Title для его привязки к свойству Text для текстового поля:

В результате вы должны увидеть следующее - напротив свойства Text появился индикатор привязки к источнику данных - bookBindingSource - Title:

Также обратите внимание на нижнюю часть под формой в представлении конструктора - можно увидеть добавленный к форме источник данных с именем bookBindingSource:

Проделайте аналогичную привязку к полям класса Book для остальных элементов формы:

Для элемента TextBoxAuthor необходимо выбрать привязку данных для свойства Text к полю Author в классе Book. Но в этот раз нужно уже выбирать поле из созданного источника bookBindingSource, поскольку теперь он есть на форме:

 

Для элемента DateTimePickerDatePublished необходимо выбрать привязку данных для свойства Value к полю DatePublished в классе Book:

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

Программируем главную форму приложения (FrmDataGridViewSampleMain)

Добавление полей формы

В начало класса формы мы добавим список книг с именем books, а также флаг cancelContextMenu, который будет отвечать за то, чтобы не показывать контекстное меню для элемента DataGridView при определённых условиях (какие именно это условия мы узнаем чуть позже):

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace DataGridViewSample {
    public partial class FrmDataGridViewSampleMain : Form {
        /// <summary>
        /// список книг
        /// </summary>
        private List<Book> books = new List<Book>();

        /// <summary>
        /// флаг отмены отображения контекстного меню для элемента DataGridView
        /// </summary>
        private bool cancelContextMenu = false;

        // ... остальной код формы
    }
}

Вставьте код для этих двух полей в начало класса формы FrmDataGridViewSampleMain, как показано в листинге и сохраните изменения, нажав Ctrl+S.

Инициализация списка книг тестовыми данными

Далее нам нужно инициализировать некоторую тестовые данные с книгами, добавив их в список books. Эти данные будут затем загружаться в элемент DataGridView сразу при запуске приложения. Добавим в класс формы новый метод InitTestBooksData(), который будет инициализировать список книг и добавлять туда некоторые известные произведения классиков литературы с их авторами и годами публикации книг. Для даты публикации книги в нашем приложении будет важен только год издания, поэтому число и месяц публикации произведения мы будем принимать везде по умолчанию за 1 января того самого года, в котором была издана книга:

        private void InitTestBooksData() {
            books.Add(new Book("Война и мир", "Лев Николаевич Толстой", DateTime.Parse("1867-01-01")));
            books.Add(new Book("Анна Каренина", "Лев Николаевич Толстой", DateTime.Parse("1877-01-01")));
            books.Add(new Book("После бала", "Лев Николаевич Толстой", DateTime.Parse("1911-01-01")));
            books.Add(new Book("Юность", "Лев Николаевич Толстой", DateTime.Parse("1857-01-01")));
            books.Add(new Book("Кавказский пленник", "Лев Николаевич Толстой", DateTime.Parse("1872-01-01")));
            books.Add(new Book("Преступление и наказание", "Фёдор Михайлович Достоевский", DateTime.Parse("1866-01-01")));
            books.Add(new Book("Евгений Онегин", "Александр Сергеевич Пушкин", DateTime.Parse("1833-01-01")));
            books.Add(new Book("Капитанская дочка", "Александр Сергеевич Пушкин", DateTime.Parse("1836-01-01")));
            books.Add(new Book("Руслан и Людмила", "Александр Сергеевич Пушкин", DateTime.Parse("1820-01-01")));
            books.Add(new Book("Метель", "Александр Сергеевич Пушкин", DateTime.Parse("1831-01-01")));
            books.Add(new Book("Пиковая дама", "Александр Сергеевич Пушкин", DateTime.Parse("1834-01-01")));
            books.Add(new Book("Мать", "Максим Горький", DateTime.Parse("1906-01-01")));
            books.Add(new Book("Мёртвые души", "Николай Васильевич Гоголь", DateTime.Parse("1842-01-01")));
            books.Add(new Book("Шинель", "Николай Васильевич Гоголь", DateTime.Parse("1842-01-01")));
            books.Add(new Book("Тарас Бульба", "Николай Васильевич Гоголь", DateTime.Parse("1835-01-01")));
            books.Add(new Book("Нос", "Николай Васильевич Гоголь", DateTime.Parse("1836-01-01")));
            books.Add(new Book("Вечера на хуторе близ Диканьки", "Николай Васильевич Гоголь", DateTime.Parse("1830-01-01")));
            books.Add(new Book("Ночь перед рождеством", "Николай Васильевич Гоголь", DateTime.Parse("1832-01-01")));
            books.Add(new Book("Вий", "Николай Васильевич Гоголь", DateTime.Parse("1835-01-01")));
            books.Add(new Book("Повесть о капитане Копейкине", "Николай Васильевич Гоголь", DateTime.Parse("1842-01-01")));
            books.Add(new Book("Заколдованное место", "Николай Васильевич Гоголь", DateTime.Parse("1832-01-01")));
            books.Add(new Book("Портрет", "Николай Васильевич Гоголь", DateTime.Parse("1835-01-01")));
            books.Add(new Book("Тихий Дон", "Михаил Александрович Шолохов", DateTime.Parse("1928-01-01")));
        }
Метод для фильтрации списка книг

Теперь создадим метод GetFilteredBooks(). Он будет возвращать отфильтрованный список книг, который мы будем формировать из нашего исходного списка books, в зависимости от содержимого текстового поля с фильтром TextBoxSearch. Как только мы будем что-то вводить в поле-фильтр TextBoxSearch, то этот введённый текст будет приводиться к нижнему регистру, и мы будем просматривать все книги из списка books, пытаясь найти среди их названий, авторов и годов публикации введённый текст в поле-фильтре. Если текст будет найден, то книга будет добавлена в результирующий список result, в противном же случае она будет проигнорирована, что в дальнейшем придаст элементу DataGridView эффект динамической фильтрации при каждом вводе текста в поисковый фильтр. Код этого метода представлен ниже, добавим его в класс формы:

        private List<Book> GetFilteredBooks() {
            List<Book> result = new List<Book>();
                       
            string searchString = TextBoxSearch.Text;

            if (searchString.Length == 0) {
                return books;
            }

            searchString = searchString.ToLower();

            foreach (Book book in books) {                
                if ((book.Title.Length > 0 && book.Title.ToLower().Contains(searchString)) || 
                    (book.Author.Length > 0 && book.Author.ToLower().Contains(searchString)) ||
                    (book.YearPublished.Length > 0 && book.YearPublished.Contains(searchString))) {
                    result.Add(book);
                }
            }
            return result;
        }
Метод для обновления содержимого элемента DataGridView

Далее мы добавим в код формы метод RefreshDataGridView(), который будет обновлять данные в элементе DataGridView главной формы. В качестве источника данных (свойство DataSource элемента DataGridView) мы будем устанавливать результат вызова метода GetFilteredBooks(). При обновлении элемента DataGridView мы также установим названия его столбцов и их ширину. Ниже код этого метода:

        private void RefreshDataGridView() {
            DataGridViewBooks.DataSource = null;
            DataGridViewBooks.DataSource = GetFilteredBooks();

            DataGridViewBooks.Columns["Title"].HeaderText = "Название";
            DataGridViewBooks.Columns["Title"].Width = 200;

            DataGridViewBooks.Columns["Author"].HeaderText = "Автор";
            DataGridViewBooks.Columns["Author"].Width = 200;

            DataGridViewBooks.Columns["DatePublished"].HeaderText = "Дата издания";
            DataGridViewBooks.Columns["DatePublished"].Width = 200;
            DataGridViewBooks.Columns["DatePublished"].Visible = false;

            DataGridViewBooks.Columns["YearPublished"].HeaderText = "Год публикации";
            DataGridViewBooks.Columns["YearPublished"].Width = 126;
        }
Метод для отображения внутреннего содержимого списка книг

Теперь нам будет нужен отдельный метод для обновления текстового поля TextBoxBooks, которое используется для отображения внутреннего содержимого основного списка books. Назовём новый метод UpdateBooksListDetails() и добавим к коду главной формы:

        private void UpdateBooksListDetails() {
            StringBuilder sb = new StringBuilder();
            sb.Append("Books: [\r\n");
            foreach (Book b in books) {
                sb.Append(b.ToString());
                sb.Append("\r\n");
            }            
            sb.Append("]");

            TextBoxBooks.Text = sb.ToString();
        }

Как видим, он просто пробегает циклом foreach по списку книг и добавляет текстовое представление очередной книги к строковому буферу (переменная sb). В конце метода содержимое строкового буфера sb мы записываем в свойство Text текстового поля TextBoxBooks

Метод для обновления всех данных (список книг в элементе DataGridView и внутреннее содержимое списка книг)

Создадим ещё один небольшой метод RefreshGridAndBookDetails(), который будет вызывать два уже ранее добавленных метода. Этот метод будет сразу обновлять и сам элемент DataGridView, и текстовое поле с содержимым списка books:

        private void RefreshGridAndBookDetails() {
            RefreshDataGridView();
            UpdateBooksListDetails();
        }
Метод для управления доступностью кнопки "Добавить книгу"

Добавим метод UpdateButtonAddBookState(), который будет регулировать доступность кнопки "Добавить книгу" . Кнопка должна быть доступна только тогда, когда оба текстовых поля с названием и автором книги содержат текст:

        private void UpdateButtonAddBookState() {
            ButtonAddBook.Enabled = TextBoxTitle.Text.Length > 0 && TextBoxAuthor.Text.Length > 0;
        }
Пишем логику при загрузке главной формы 

Нам нужно добавить код, который будет выполняться сразу при загрузке формы. Для этого нужно сгенерировать в коде формы обработчик события Load. Это можно сделать перейдя к представлению конструктора формы и дважды кликнув по форме - будет добавлен пустой метод FrmDataGridViewSampleMain_Load. Теперь мы добавим в него следующий код с вызовом трёх методов InitTestBooksData(), RefreshGridAndBookDetails() и UpdateButtonAddBookState():

        private void FrmDataGridViewSampleMain_Load(object sender, EventArgs e) {
            InitTestBooksData();
            RefreshGridAndBookDetails();
            UpdateButtonAddBookState();
        }

Как видим, при загрузке программы и главной формы будут производиться следующие действия:

  1. список с книгами books будет наполнен тестовыми данными - это достигается вызовом метода InitTestBooksData()
  2. будет обновлено содержимое элемента DataGridView и текстового поля TextBoxBooks - это достигается вызовом метода RefreshGridAndBookDetails()
  3. будет установлена доступность кнопки "Добавить книгу" - это достигается вызовом метода UpdateButtonAddBookState()

Теперь вернёмся в представление конструктора формы и дважды кликнем по кнопке "Добавить книгу", чтобы сгенерировался метод-обработчик для события Click (т.е. события клика по кнопке). Добавим в него следующий код:

        private void ButtonAddBook_Click(object sender, EventArgs e) {
            DateTime selectedDate = DateTimePickerDatePublished.Value;
            DateTime truncatedDate = DateTime.Parse(selectedDate.ToString("dd.MM.yyyy"));

            Book newBook = new Book(TextBoxTitle.Text, TextBoxAuthor.Text, truncatedDate);
            books.Add(newBook);
            RefreshGridAndBookDetails();

            TextBoxTitle.Text = "";
            TextBoxAuthor.Text = "";
            DateTimePickerDatePublished.Value = DateTime.Now;

            TextBoxTitle.Focus();
        }

В переменную selectedDate мы получаем значение даты и времени, выбранное в элементе DateTimePickerDatePublished. Это значение содержит выбранную дату вместе с текущим временем. Время нам не нужно, поэтому мы записываем в переменную truncatedDate значение, которое содержит только дату. Далее остаётся только создать новый экземпляр книги newBook со значениями названия, автора и даты публикации и добавить его в основной список books, после чего обновить данные в элементе DataGridView и текстовом поле с содержимым всего списка книг. Напоследок мы также очищаем все поля в нижней части формы и устанавливаем дату в текущую, а также явно устанавливаем фокус на текстовое поле с названием книги - это необходимо для удобства последовательного ввода нескольких книг.

Вернёмся к представлению конструктора главной формы. Нам нужно сгенерировать в коде главной формы обработчики следующих событий для элемента DataGridView:

  • CellEndEdit - это событие возникает при завершении редактирования содержимого ячейки
  • DataError - это событие возникает при появлении ошибки при редактировании ячейки. В нашем случае оно будет вызываться при попытке установить некорректное значение года публикации книги (к примеру, если попытаемся ввести некорректную дату)
  • MouseDown - событие возникает при клике мышью на элементе DataGridView. Его мы будем обрабатывать в случае нажатия правкой кнопки мыши в целях полного выделения строки, над которой произошёл клик, а также установки специального флага скрытия контекстного меню

Для этого выбираем на форме элемент DataGridView, а затем в окне "Свойства" кликаем на иконку "молнии". В списке событий находим сначала CellEndEdit и дважды кликаем напротив имени этого события, в результате чего в коде формы будет сгенерирован пустой метод-обработчик DataGridViewBooks_CellEndEdit. Аналогичное действие проделываем, дважды кликая напротив событий DataError и MouseDown.

Теперь пора написать код для этих сгенерированных методов-обработчиков.

Начнём с первого - DataGridViewBooks_CellEndEdit. Внутри него мы напишем код, который из входного параметра e с типом DataGridViewCellEventArgs получит индексы строки и столбца в переменные row и col, соответственно. После этого в переменную cell мы получим ссылку на ячейку элемента DataGridView, которая и была отредактирована. Далее нам нужно получить содержимое ячейки с помощью свойства Value - его мы устанавливаем в переменную cellValue с типом object (на этом этапе мы не знаем информацию о точном типе данных для значения отредактированной ячейки). Теперь мы можем получить ссылку на редактируемую книгу в переменную book - индекс отредактированной книги в списке books совпадает с индексом строки в элементе DataGridView. А чтобы обновить конкретное свойство экземпляра книги мы смотрим на индекс столбца col и приводим cellValue уже к конкретному типу данных и устанавливаем в конкретное свойство для экземпляра книги. После этого мы можем вызывать метод UpdateBooksListDetails() для обновления содержимого текстового поля TextBoxBooks:

        private void DataGridViewBooks_CellEndEdit(object sender, DataGridViewCellEventArgs e) {
            int row = e.RowIndex;
            int col = e.ColumnIndex;

            DataGridViewCell cell = DataGridViewBooks[col, row];

            object cellValue = cell.Value;

            Book book = books[row];
            switch (col) {
                case 0:
                    book.Title = (string)cellValue;
                    break;
                case 1:
                    book.Author = (string)cellValue;
                    break;
                case 2:                    
                    book.DatePublished = (DateTime)cellValue;                    
                    break;
            }

            UpdateBooksListDetails();
        }

Теперь перейдем к методу-обработчику DataGridViewBooks_DataError и напишем код для него.

Аналогичным образом из параметра e можно извлечь индексы столбца и строки в переменные col и row. Далее нас будет интересовать свойство EditedFormattedValue для ячейки по этим индексам - его мы получим в переменную editedValue. В конце метода мы проверим, что если тип исключения - это FormatException и при этом индекс столбца равен 2 (т.е. мы редактируем какую-то ячейку в столбце "Год публикации"), то мы выводим сообщение о необходимости ввода корректной даты, а также отменяем событие, устанавливая свойство Cancel в true. Ниже код этого метода:

        private void DataGridViewBooks_DataError(object sender, DataGridViewDataErrorEventArgs e) {
            int col = e.ColumnIndex;
            int row = e.RowIndex;
            
            object editedValue = DataGridViewBooks[col, row].EditedFormattedValue;

            if (e.Exception is FormatException fe && col == 2) {
                MessageBox.Show("Введите корректную дату, т.к. значение '" + editedValue.ToString() + "' не является датой", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
                e.Cancel = true;
            }
        }

Перейдем к последнему методу-обработчику для элемента DataGridView - DataGridViewBooks_MouseDown и напишем код для него. Прежде всего мы должны проверить, что клик по таблице с книгами произведён именно правой кнопкой мыши (MouseButtons.Right). Далее мы используем специальный метод HitTest, доступный для элемента DataGridView - он поможет вернуть специальную информацию о координатах клика в переменную hitTestInfo, в частности, мы сможем получить доступ к индексу строки и столбца, по которым произошёл клик мышью. Мы проверим, что если эти индексы - неотрицательные, то это значит, что текущее выделение для элемента DataGridView необходимо очистить и выделить только ту строку, над которой находился курсор мыши в момент клика. В этом случае мы также сбросим флаг cancelContextMenu в false - это означает, что контекстное меню показать надо. В противном же случае - если мы кликнули где-то по заголовкам или служебным областям элемента DataGridView - индексы будут отрицательными и в развилке else мы выставим флаг cancelContextMenu в true:

        private void DataGridViewBooks_MouseDown(object sender, MouseEventArgs e) {
            if (e.Button == MouseButtons.Right) {
                var hitTestInfo = DataGridViewBooks.HitTest(e.X, e.Y);
                if (hitTestInfo.RowIndex >= 0 && hitTestInfo.ColumnIndex >= 0) {
                    DataGridViewBooks.ClearSelection();
                    DataGridViewBooks.Rows[hitTestInfo.RowIndex].Selected = true;
                    cancelContextMenu = false;
                } else {                    
                    cancelContextMenu = true;
                }
            }
        }

Раз уж мы заговорили про специальный флаг cancelContextMenu - пора им воспользоваться и написать логику, которая будет отменять показ контекстного меню. Для этого нужно вернуться на главную форму в представление конструктора, выбрать контекстное меню ContextMenuStripForGrid и сгенерировать для него обработчик события Opening. Это событие возникает в момент открытия контекстного меню, при этом его можно отменить, тогда контекстное меню не будет отображено. В коде мы как раз проверим наш специальный флаг необходимости отмены показа контекстного меню и отменим само событие, если флаг установлен:

        private void ContextMenuStripForGrid_Opening(object sender, System.ComponentModel.CancelEventArgs e) {
            if (cancelContextMenu) {
                e.Cancel = true;
            }
        }
Обработка событий контекстного меню

Теперь мы напишем код, который будет обрабатывать события клика на элементах контекстного меню ContextMenuStripForGrid. Для этого нужно перейти к представлению конструктора формы, выбрать в нижней части элемент ContextMenuStripForGrid и дважды кликнуть по пункту меню "&Удалить выбранную книгу". В результате будет сгенерирован обработчик клика на этом пункте меню с названием MenuItemRemoveBook_Click. Затем нужно повторить то же самое для пункта меню "&Редактировать выбранную книгу" и сгенерировать обработчик MenuItemEditBook_Click.

Теперь напишем для них код:

        private void MenuItemRemoveBook_Click(object sender, EventArgs e) {
            DataGridViewSelectedRowCollection selectedRows = DataGridViewBooks.SelectedRows;
            foreach (DataGridViewRow selectedRow in selectedRows) {
                int rowIndex = selectedRow.Index;

                if (rowIndex < 0) {
                    continue;
                }
                
                Book book = books[rowIndex];
                
                DialogResult dlgConfirm = MessageBox.Show("Удалить эту книгу?\r\n\r\nАвтор: " + book.Author + "\r\nНазвание: " + book.Title + "\r\nГод публикации: " + book.YearPublished, "Подтвердите", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
                if (dlgConfirm == DialogResult.Yes) {
                    books.RemoveAt(rowIndex);
                }
            }

            RefreshGridAndBookDetails();
        }

        private void MenuItemEditBook_Click(object sender, EventArgs e) {
            DataGridViewSelectedRowCollection selectedRows = DataGridViewBooks.SelectedRows;

            foreach (DataGridViewRow selectedRow in selectedRows) {
                int rowIndex = selectedRow.Index;

                if (rowIndex < 0) {
                    continue;
                }

                Book book = books[rowIndex];

                FrmEditBook frmEdit = new FrmEditBook();
                frmEdit.EditedBook = book;
                frmEdit.BookUpdatedEvent += FrmEdit_BookUpdatedEvent;
                frmEdit.Show();
            }
        }

Как видим, в обработчике MenuItemRemoveBook_Click происходит получение выбранных строк таблицы в коллекцию selectedRows, а затем цикл по этой коллекции. На самом деле выбранная строка всегда будет одна, но для надёжности мы делаем цикл и в цикле проверяем, что индекс выбранной строки неотрицательный. В этом случае мы получаем выбранную книгу из списка books (её индекс в списке равен индексу выбранной строки таблицы) и выдаём диалоговое окно с подтверждением необходимости удаления книги. Если в окне диалога удаление подтверждено, то книга удаляется из списка.

Во втором обработчике MenuItemEditBook_Click у нас немного другая задача. Мы должны отобразить вторую форму для редактирования выбранной в таблице записи. Легко заметить, что логика с получением выбранных строк таблицы и проверки в цикле индекса аналогична первому обработчику, только в этот раз мы вместо удаления книги создаём экземпляр формы FrmEditBook, устанавливаем её свойство EditedBook текущей выбранной книге, а также подписываемся на событие BookUpdatedEvent, после чего отображаем форму.

Ниже код нового метода FrmEdit_BookUpdatedEvent, который надо добавить в код главной формы. Всё, что он делает, - это вызывает метод обновления данных на форме:

        private void FrmEdit_BookUpdatedEvent(Book updatedBook) {
            RefreshGridAndBookDetails();
        }

Последнее, что нам осталось добавить в код главной формы - это реакция на изменения текстовых полей. У нас их на форме три:

  • TextBoxTitle - для ввода названия книги (при добавлении новой)
  • TextBoxAuthor - для ввода автора книги (при добавлении новой)
  • TextBoxSearch - поисковое поле-фильтр для быстрого поиска по списку книг

Кликните дважды по каждому из них из представления конструктора формы - должны сгенерироваться пустые методы-обработчики для события TextChanged, которое возникает при изменении текста в текстовом поле.

Наполним эти пустые методы следующим образом:

        private void TextBoxTitle_TextChanged(object sender, EventArgs e) {
            UpdateButtonAddBookState();
        }

        private void TextBoxAuthor_TextChanged(object sender, EventArgs e) {
            UpdateButtonAddBookState();
        }

        private void TextBoxSearch_TextChanged(object sender, EventArgs e) {
            RefreshDataGridView();
        }

При изменении названия или автора книги будет вызываться UpdateButtonAddBookState() - это необходимо, чтобы делать кнопку "Добавить книгу" доступной при наличии всех данных по книге и наоборот - делать её недоступной, если хотя бы одно из полей не заполнено. А вот при изменении поля-фильтра мы будем вызывать RefreshDataGridView(), который "перерисует" элемент DataGridView - в зависимости от текста в поле-фильтре.

На этом код главной формы полностью готов.

Программируем форму редактирования книги (FrmEditBook)

Теперь, когда код для главной формы полностью готов, перейдем к написанию кода для второй формы, которая будет отображаться при редактировании выбранной в списке книги.

Сначала дважды кликнем по форме из представления конструктора - это сгенерирует пустой метод-обработчик FrmEditBook_Load для события Load формы.

Затем возвращаемся снова к конструктору формы и дважды кликаем по кнопке ButtonCancel - в результате будет сгенерирован пустой метод-обработчик ButtonCancel_Click, который будет отвечать за обработку события клика по кнопке "&Отмена".

Аналогичное действие делаем для второй кнопки ButtonOK - из представления конструктора формы дважды кликаем по ней, чтобы сгенерировать пустой метод-обработчик ButtonOK_Click.

Далее необходимо наполнить пустые сгенерированные методы представленным ниже кодом или просто выделить весь фрагмент и заменить им код в редакторе:

using System;
using System.Windows.Forms;

namespace DataGridViewSample {
    public partial class FrmEditBook : Form {
        /// <summary>
        /// делегат, который описывает контракт для события обновления данных для книги
        /// </summary>
        /// <param name="updatedBook">отредактированная книга</param>
        public delegate void DelegateUpdateBook(Book updatedBook);

        /// <summary>
        /// событие обновления данных для книги
        /// </summary>
        public event DelegateUpdateBook BookUpdatedEvent;

        /// <summary>
        /// свойство содержит ссылку на редактируемую книгу
        /// </summary>
        public Book EditedBook { get; set; }

        public FrmEditBook() {
            InitializeComponent();
        }

        private void FrmEditBook_Load(object sender, EventArgs e) {
            // Помещаем в источник данных экземпляр для редактируемой книги
            bookBindingSource.Add(EditedBook);
        }

        private void ButtonCancel_Click(object sender, EventArgs e) {
            Close();
        }

        private void ButtonOK_Click(object sender, EventArgs e) {
            // Получаем текущую обновлённую книгу из свойства Current
            // для источника данных bookBindingSource
            Book bookUpdated = (Book)bookBindingSource.Current;

            // Если событие BookUpdatedEvent не равно null, это значит, что
            // на него есть подписчики. Следовательно, вызовем это событие, передавая 
            // в него экземпляр обновлённой книги
            if (BookUpdatedEvent != null) {
                BookUpdatedEvent(bookUpdated);
            }
            Close();
        }
    }
}

Код формы для редактирования книги снабжён комментариями, к тому же он совсем несложный, поэтому разбирать его отдельно не будем.

Наше приложение полностью готово, можем запустить его из среды разработки при помощи клавиши F5 или нажав на кнопку "Пуск".

Заключение

Попробуйте поработать со списком книг - отфильтровать его содержимое, вводя различный текст в поисковое поле-фильтр. Также можно редактировать отдельные записи прямо в таблице - либо при помощи двойного клика по ячейкам, либо нажимая клавишу F2 для входа в режим редактирования ячейки. Обратите внимание, что ячейки таблицы с годом издания книги невозможно отредактировать. Год издания можно поменять только через вторую форму "Редактировать книгу", которая открывается из контекстного меню при клике правой кнопкой мыши по строкам таблицы.

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

Друзья, на этом всё, надеюсь, что урок вам понравился, при наличии вопросов/пожеланий - используйте форму комментариев внизу статьи.

Ссылка на готовый проект из этой статьи:

https://allineed.ru/our-products/download/4-allineed-ru-examples/15-csharp-demo-working-with-datagridview

Удачи и успехов в написании хороших программ!

Яндекс.Метрика