Элемент управления DataGridView является последней новинкой в серии табличных элементов DataGrid, позволяющих отображать таблицы. Главное назначение этих элементов - связывание с таблицами внешних источников данных, прежде всего с таблицами баз данных. Мы же сейчас рассмотрим другое его применение - в интерфейсе, позволяющем пользователю вводить и отображать матрицы - двумерные массивы.
Рассмотрим классическую задачу умножения прямоугольных матриц C=A*B. Построим интерфейс, позволяющий пользователю задавать размеры перемножаемых матриц, вводить данные для исходных матриц A и B, перемножать матрицы и видеть результаты этой операции. На рис. 6.6 показан возможный вид формы, поддерживающей работу пользователя. Форма показана в тот момент, когда пользователь уже задал размеры и значения исходных матриц, выполнил умножение матриц и получил результат.
увеличить изображение Рис. 6.6. Форма с элементами DataGridView, поддерживающая работу с матрицами
На форме расположены три текстовых окна для задания размеров матриц, три элемента DataGridView для отображения матриц, три командные кнопки для выполнения операций, доступных пользователю. Кроме того, на форме присутствуют 9 меток (элементов управления label), семь из которых видимы на рис. 6.6. В них отображается информация, связанная с формой и отдельными элементами управления. Текст у невидимых на рисунке меток появляется тогда, когда обнаруживается, что пользователь некорректно задал значение какого-либо элемента исходных матриц.
А теперь перейдем к описанию того, как этот интерфейс реализован. В классе Form2, которому принадлежит наша форма, зададим поля, определяющие размеры матриц, и сами матрицы:
//поля класса Form int m, n, p; //размеры матриц double[,] A, B, C; //сами матрицы
Рассмотрим теперь, как выглядит обработчик события "Click" командной кнопки "Создать DataGridView". Предполагается, что пользователь разумен и, прежде чем нажать эту кнопку, задает размеры матриц в соответствующих текстовых окнах. Напомню, что при перемножении матриц размеры матриц должны быть согласованы - число столбцов первого сомножителя должно совпадать с числом строк второго сомножителя, а размеры результирующей матрицы определяются размерами сомножителей. Поэтому для трех матриц в данном случае достаточно задать не шесть, а три параметра, определяющие размеры.
Обработчик события выполняет три задачи - создает сами матрицы, осуществляет чистку элементов управления DataGridView, удаляя предыдущее состояние, затем добавляет столбцы и строки в эти элементы в полном соответствии с заданными размерами матриц. Вот текст обработчика:
private void button1_Click(object sender, EventArgs e) { //создание матриц m = Convert.ToInt32(textBox1.Text); n = Convert.ToInt32(textBox2.Text); p = Convert.ToInt32(textBox3.Text); A = new double[m, n]; B = new double[n, p]; C = new double[m, p]; //Чистка DGView, если они не пусты int k =0; k = dataGridView1.ColumnCount; if (k != 0) for (int i = 0; i < k; i++) dataGridView1.Columns.RemoveAt(0); dataGridView2.Columns.Clear(); dataGridView3.Columns.Clear(); //Заполнение DGView столбцами AddColumns(n, dataGridView1); AddColumns(p, dataGridView2); AddColumns(p, dataGridView3); //Заполнение DGView строками AddRows(m, dataGridView1); AddRows(n, dataGridView2); AddRows(m, dataGridView3); }
Прокомментирую этот текст.
Прием размеров и создание матриц, надеюсь, не требует дополнительных комментариев.
Чистка предыдущего состояния элементов DataGridView сводится к удалению столбцов. Продемонстрированы два возможных способа выполнения этой операции. Для первого элемента показано, как можно работать с коллекцией столбцов. Организуется цикл по числу столбцов коллекции, и в цикле выполняется метод RemoveAt, аргументом которого является индекс удаляемого столбца. Поскольку после удаления столбца происходит перенумерация столбцов, на каждом шаге цикла удаляется первый столбец, индекс которого всегда равен нулю. Удаление столбцов коллекции можно выполнить одним махом - вызывая метод Clear() коллекции, что и делается для остальных двух элементов DataGridView.
После чистки предыдущего состояния, можно задать новую конфигурацию элемента, добавив в него вначале нужное количество столбцов, а затем и строк. Эти задачи выполняют специально написанные процедуры AddColumns и AddRows. Вот их текст:
private void AddColumns(int n, DataGridView dgw) { //добавляет n столбцов в элемент управления dgw //Заполнение DGView столбцами DataGridViewColumn column; for (int i = 0; i < n; i++) { column = new DataGridViewTextBoxColumn(); column.DataPropertyName = "Column" + i.ToString(); column.Name = "Column" + i.ToString(); dgw.Columns.Add(column); } } private void AddRows(int m, DataGridView dgw) { //добавляет m строк в элемент управления dgw //Заполнение DGView строками for (int i = 0; i < m; i++) { dgw.Rows.Add(); dgw.Rows[i].HeaderCell.Value = "row" + i.ToString(); } }
Приведу краткий комментарий.
Создаются столбцы в коллекции Columns по одному. В цикле по числу столбцов матрицы, которую должен отображать элемент управления DataGridView, вызывается метод Add этой коллекции, создающий очередной столбец. Одновременно в этом же цикле создается и имя столбца (свойство Name), отображаемое в форме. Показана возможность формирования еще одного имени (DataPropertyName), используемого при связывании со столбцом таблицы внешнего источника данных. В нашем примере это имя не используется.
Создав столбцы, нужно создать еще и нужное количество строк у каждого из элементов DataGridView. Делается это аналогичным образом, вызывая метод Add коллекции Rows. Чуть по-другому задаются имена строк - для этого используется специальный объект HeaderCell, имеющийся у каждой строки и задающий ячейку заголовка.
После того как сформированы строки и столбцы, элемент DataGridView готов к тому, чтобы пользователь или программа вводила значения в ячейки сформированной таблицы.
Рассмотрим теперь, как выглядит обработчик события "Click" следующей командной кнопки "Перенести данные в массив". Предполагается, что пользователь разумен и, прежде чем нажать эту кнопку, задает значения элементов перемножаемых матриц в соответствующих ячейках подготовленных таблиц первых двух элементов DataGridView. Обработчик события выполняет следующие задачи - в цикле читает элементы, записанные пользователем в таблицы DataGridView, проверяет их корректность и в случае успеха переписывает их в матрицы. Вот текст обработчика:
Этот программный код нуждается в подробных комментариях.
Основная задача переноса данных из таблицы элемента DataGridView в соответствующий массив не вызывает проблем. Конструкция Rows[i].Cells[j] позволяет добраться до нужного элемента таблицы, после чего остается присвоить его значение элементу массива.
Как всегда при вводе основной проблемой является обеспечение корректности вводимых данных. Схема, рассматриваемая нами ранее, нуждается в корректировке. Дело в том, что ранее проверка корректности осуществлялась сразу же после ввода пользователем значения элемента. Теперь проверка корректности выполняется после того, как пользователь полностью заполнил таблицы, при этом некоторые элементы он мог задать некорректно. Просматривая таблицу, необходимо обнаружить некорректно заданные значения и предоставить возможность их исправления. В программе предлагаются два различных подхода к решению этой проблемы.
Первый подход демонстрируется на примере ввода элементов матрицы A. Как обычно, преобразование данных, введенных пользователем, в значение, допустимое для элементов матрицы А, помещается в охраняемый блок. Если данные некорректны и возникает исключительная ситуация, то она перехватывается универсальным обработчиком catch(Exception). Заметьте, в данном варианте нет цикла, работающего до тех пор, пока не будет введено корректное значение. Обработчик исключения просто прерывает работу по переносу данных, вызывая оператор return. Но предварительно он формирует информационное сообщение об ошибке и выводит его в форму. (Помните, специально для этих целей у формы были заготовлены две метки). В сообщении пользователю предлагается исправить некорректно заданный элемент и повторить ввод - повторно нажать командную кнопку "перенести данные в массив". Этот подход понятен и легко реализуем. Недостатком является его неэффективность, поскольку повторно будут переноситься в массив все элементы, в том числе и те, что были введены вполне корректно. У программиста такая ситуация может вызывать чувство неудовлетворенности своей работой.
На примере ввода элементов матрицы В продемонстрируем другой подход, когда исправляется только некорректно заданное значение. Прежде чем читать дальше, попробуйте найти собственное решение этой задачи. Это не так просто, как может показаться с первого взгляда. Для организации диалога с пользователем пришлось организовать специальное диалоговое окно, представляющее обычную форму с двумя элементами управления - меткой для выдачи информационного сообщения и текстовым окном для ввода пользователем корректного значения. При обнаружении ошибки ввода открывается диалоговое окно, в которое пользователь вводит корректное значение элемента и закрывает окно диалога. Введенное пользователем значение переносится в нужную ячейку таблицы DataGridView, а оттуда в матрицу.
При проектировании диалогового окна значение свойства формы FormBorderStyle, установленное по умолчанию как "sizeable", следует заменить значением "FixedDialog", что влияет на внешний вид и поведение формы. Важно отметить, что форма, представляющая диалоговое окно, должна вызываться не методом Show, а методом ShowDialog. Иначе произойдет зацикливание, начнут порождаться десятки диалоговых окон, прежде чем вы успеете нажать спасительную в таких случаях комбинацию Ctrl+ Alt + Del.
Обработчик события "Click" командной кнопки "Умножить матрицы" выполняет ответственные задачи - реализует умножение матриц и отображает полученный результат в таблице соответствующего элемента DataGridView. Но оба эти действия выполняются естественным образом, не требуя, кроме циклов, никаких специальных средств и программистских ухищрений. Я приведу программный код без дополнительных комментариев:
private void button3_Click(object sender, EventArgs e) { MultMatr(A, B, C); FillDG(); } void MultMatr(double[,] A, double[,] B, double[,] C) { int m = A.GetLength(0); int n = A.GetLength(1); int p = B.GetLength(1); double S =0; for(int i=0; i < m; i++) for (int j = 0; j < p; j++) { S = 0; for (int k = 0; k < n; k++) S += A[i, k] * B[k, j]; C[i, j] = S; } } void FillDG() { for (int i = 0; i < m; i++) for (int j = 0; j < p; j++) dataGridView3.Rows[i].Cells[j].Value = C[i, j].ToString(); }