Генератор отчетов на C# - Часть 3 (Создание отчета, Работа с несколькими dataGridView)

Мы уже научили программу загружать файлы с расширением XLSX и CSV в свои dataGridView. Теперь мы будем работать с загруженными данными. Создадим еще одну таблицу, но уже с подписанными районами (согласно ОГРН), а также создадим и заполним общий свод по всем районам области.


Для начала напишем метод для заполнения свода шаблоном данных:

public void WriteToSummary() // Метод заполнения сводной таблицы шаблоном данных
{
    dgvSummary.Rows.Clear();
    dgvSummary.Columns.Clear();

    dgvSummary.Columns.Add("ateID", "Код АТЕ");
    dgvSummary.Columns.Add("ateName", "Наименование АТЕ");
    dgvSummary.Columns.Add("not", "Не заполнили совсем");
    dgvSummary.Columns.Add("partially", "Заполнили частично");
    dgvSummary.Columns.Add("full", "Заполнили полностью");
    dgvSummary.Columns.Add("only", "Всего в выборке");

    dgvSummary.Rows.Add(510, "Арбажский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(520, "Афанасьевский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(530, "Белохолуницкий", 0, 0, 0, 0);
    dgvSummary.Rows.Add(540, "Богородский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(550, "Верхнекамский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(560, "Верхошижемский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(570, "Вятскополянский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(580, "Даровской", 0, 0, 0, 0);
    dgvSummary.Rows.Add(590, "Зуевский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(600, "Кикнурский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(610, "Кильмезский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(620, "Кирово-Чепецкий", 0, 0, 0, 0);
    dgvSummary.Rows.Add(630, "Котельнический", 0, 0, 0, 0);
    dgvSummary.Rows.Add(640, "Куменский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(650, "Лебяжский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(660, "Лузский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(670, "Малмыжский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(680, "Мурашинский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(690, "Нагорский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(700, "Немский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(710, "Нолинский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(720, "Омутнинский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(730, "Опаринский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(740, "Оричевский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(750, "Орловский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(760, "Пижанский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(770, "Подосиновский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(780, "Санчурский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(790, "Свечинский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(800, "Слободской", 0, 0, 0, 0);
    dgvSummary.Rows.Add(810, "Советский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(820, "Сунский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(830, "Тужинский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(840, "Унинский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(850, "Уржумский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(860, "Фаленский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(870, "Шабалинский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(880, "Юрьянский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(890, "Яранский", 0, 0, 0, 0);
    dgvSummary.Rows.Add(900, "Вятские Поляны (город)", 0, 0, 0, 0);
    dgvSummary.Rows.Add(910, "Кирово-Чепецк (город)", 0, 0, 0, 0);
    dgvSummary.Rows.Add(920, "Котельнич (город)", 0, 0, 0, 0);
    dgvSummary.Rows.Add(930, "Слободской (город)", 0, 0, 0, 0);
    dgvSummary.Rows.Add(940, "Киров (город)", 0, 0, 0, 0);
    dgvSummary.Rows.Add(950, "ЗАТО Первомайский", 0, 0, 0, 0);
}

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

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

public void WriteAteAndSummary() // Метод рабочей и сводной таблиц
{
    bool status = false;
    progressBar.Value = 0;
    progressBar.Minimum = 0;
    progressBar.Maximum = dgvInitialData.ColumnCount + (dgvInitialData.RowCount * 4);
    // Добавляем столбцы в рабочую таблицу
    tbLogs.Text += Environment.NewLine + Environment.NewLine + DateTime.Now + ": Копирование столбцов из выборки в рабочую таблицу...";
    for (int i = 0; i < dgvInitialData.Columns.Count; i++)
    {
        dgvWorkTable.Columns.Add(dgvInitialData.Columns[i].Name, dgvInitialData.Columns[i].HeaderText);
        progressBar.Value++;
    }
    tbLogs.Text += "ОК";

    // Копируем данные из выборки в рабочую таблицу
    tbLogs.Text += Environment.NewLine + DateTime.Now + ": Копирование строк из выборки в рабочую таблицу...";
    for (int i = 0; i < dgvInitialData.RowCount; i++)
    {
        dgvWorkTable.Rows.Add();
        for (int j = 0; j < dgvWorkTable.Columns.Count; j++)
        {
            dgvWorkTable[j, i].Value = dgvInitialData[j, i].Value;
        }
        progressBar.Value++;
    }
    tbLogs.Text += "ОК";

    // Добавляем колонки с кодом и наименованием АТЕ, перемещаем их на 2 и 3 позиции
    tbLogs.Text += Environment.NewLine + DateTime.Now + ": Присваиваем АТЕ по ОГРН...";
    dgvWorkTable.Columns.Add("ooAteId", "Код АТЕ");
    dgvWorkTable.Columns.Add("ooAteName", "Наименование АТЕ");
    dgvWorkTable.Columns[dgvWorkTable.Columns.Count - 2].DisplayIndex = 2;
    dgvWorkTable.Columns[dgvWorkTable.Columns.Count - 1].DisplayIndex = 3;

    // Присваиваем код и наименование АТЕ
    for (int i = 0; i < dgvWorkTable.RowCount; i++) // Счетчик строк для листа с выгрузкой
    {
        for (int j = 0; j < dgvBase.RowCount; j++)
        {
            if (Convert.ToInt64(dgvWorkTable[1, i].Value) == Convert.ToInt64(dgvBase[0, j].Value)) // Если ОГРН совпадают
            {
                dgvWorkTable[dgvWorkTable.Columns.Count - 2, i].Value = dgvBase[1, j].Value;
                dgvWorkTable[dgvWorkTable.Columns.Count - 1, i].Value = dgvBase[2, j].Value;
            }
        }
        progressBar.Value++;
    }
    tbLogs.Text += "ОК";

    tbLogs.Text += Environment.NewLine + DateTime.Now + ": Проверяем целостность...";
    // Проверяем целостность данных

    for (int i = 0; i < dgvWorkTable.RowCount; i++)
    {
        if (dgvWorkTable[dgvWorkTable.ColumnCount - 2, i].Value == null)
        {
            for (int cCol = 0; cCol < dgvWorkTable.ColumnCount; cCol++)
            {
                dgvWorkTable[cCol, i].Style.BackColor = System.Drawing.Color.Red;
            }
            status = true;
            tabControlPreview.SelectTab(4);
            tbLogs.Text += Environment.NewLine + Environment.NewLine + DateTime.Now + ": ОШИБКА! ОГРН (" + dgvWorkTable[1, i].Value + ") не найден в базе. Строка в рабочей таблице выделена красным.";
        }
        progressBar.Value++;
    }
    if (status == false)
    {
        tabControlPreview.SelectTab(3);
        tbLogs.Text += "ОК";
    }


    tbLogs.Text += Environment.NewLine + Environment.NewLine + DateTime.Now + ": Заполняем сводную таблицу...";
    for (int i = 0; i < dgvWorkTable.RowCount; i++)
    {
        for (int j = 0; j < dgvSummary.RowCount; j++)
        {
            if (Convert.ToInt32(dgvWorkTable[dgvWorkTable.ColumnCount - 2, i].Value) == Convert.ToInt32(dgvSummary[0, j].Value)) // Если код АТЕ совпадает
            {
                long pr = 1;
                for (int colC = 2; colC < (dgvWorkTable.ColumnCount - 3); colC++)
                {
                    pr *= Convert.ToInt32(dgvWorkTable[colC, i].Value);
                }
                if (pr == 0 && Convert.ToInt32(dgvWorkTable[dgvWorkTable.ColumnCount - 3, i].Value) == 0)
                {
                    dgvSummary[2, j].Value = Convert.ToInt32(dgvSummary[2, j].Value) + 1;
                }
                else if (pr == 0 && Convert.ToInt32(dgvWorkTable[dgvWorkTable.ColumnCount - 3, i].Value) != 0)
                {
                    dgvSummary[3, j].Value = Convert.ToInt32(dgvSummary[3, j].Value) + 1;
                }
                else if (pr != 0 && Convert.ToInt32(dgvWorkTable[dgvWorkTable.ColumnCount - 3, i].Value) != 0)
                {
                    dgvSummary[4, j].Value = Convert.ToInt32(dgvSummary[4, j].Value) + 1;
                }
                // Подсчитываем количество ОО в выборке по данному АТЕ
                dgvSummary[5, j].Value = Convert.ToInt32(dgvSummary[5, j].Value) + 1;
            }
        }
        progressBar.Value++;
    }
    tbLogs.Text += "ОК";
    tbLogs.Text += Environment.NewLine + Environment.NewLine + DateTime.Now + ": Отчет сгенерирован.";
}

Алгоритм работы кода:

  1. Инициализируем переменную status типа bool со значением false. Я решил добавить проверку целостности данных в программу. То есть, если в выгрузке будет присутствовать организация, неизвестная базе ОГРН, то программа об этом сообщит. Для этого нужна переменная status.
  2. Обнуляем значение поля Value элемента progressBar. Значение Minimum устанавливаем по-умолчанию (0). А также устанавливаем новое значение поля Maximum, равное [количеству столбцов в таблице с исходной выгрузкой] + [количество строк в таблице * 4] - именно столько итераций будет циклов в нашем методе, каждая итерация будет прибавлять единицу к значению Value.
  3. С помощью цикла for копируем столбцы выгрузки в таблицу обработки. Оповещаем о начале и завершении операции в tbLogs, прибавляем единицу к progressBar.Value после каждой итерации.
  4. С помощью двух циклов for (по строкам таблицы выгрузки и по столбцам таблицы обработки) копируем все данные в таблицу для обработки. Оповещаем пользователя в tbLogs о начале и завершении операции. Здесь после каждой итерации первого(!) цикла for прибавляем единицу в progressBar.
  5. Присваиваем название и код района для каждой организации. Для этого сперва добавляем два столбца в таблицу обработки (Код и Наименование), а также для удобства пользователя меняем индекс отображения (Свойство столбца DisplayIndex) этих столбцов на 2 и 3. Таким образом новые столбцы будут отображаться 3 и 4 по счету. Обратите внимание, что индекс столбца меняется только для отображения пользователю, реальный индекс остается прежним! После этого с помощью двух циклов for (первый по строкам таблицы обработки, второй - по строкам таблицы базы ОГРН) в случае совпадения ОГРН в таблице обработки в столбцах для наименования и кода района организации проставляются соответствующие данному ОГРН сведения из базы. Оповещаем о начале и окончании операции в tbLogs.
  6. Проверяем целостность данных. С помощью цикла for проверяем, есть ли пустые ячейки в столбце с кодом района. Если такие есть, это означает, что в базе данный ОГРН не был найден. В случае обнаружения, меняем значение переменной status на true, с помощью еще одного цикла for проходимся по ячейкам этой строки и меняем их фон на красный, а также оповещаем пользователя через tbLogs о том, что данный ОГРН не был найден, а также сразу делаем активной вкладку с логами, чтобы пользователь сразу мог увидеть, что что-то пошло не так. Если за время прохождения первого цикла переменная статус не изменила своего значения (осталась false), то оповещаем пользователя через tbLogs об успешном завершении операции и делаем активной вкладку со сводной таблицей.
  7. Заполняем сводную таблицу. Первый цикл for заходит в стркоу i и ищет совпадения по коду района в таблице свода с помощью вложенного в него второго цикла for. Если совпадение найдено, прибавляем единицу в столбце "Всего", инициализируем переменную pr типа long со значением 1 для вычисления произведения значений столбцов с данными за все года с помощью еще одного цикла for (по ячейкам с данными по годам текущей строки в таблице обработки). Если произведение и значение ячейки с суммой по всем годам равны 0, то прибавляем единицу в столбец "Не заполнили". Если произведение равно 0, но сумма не равна 0, значит организация предоставила данные частично и в соответствующем столбце свода прибавляется единица. Если же произведение и сумма НЕ равны 0, это означает что организация полностью предоставила данные за все годы и прибавляем единицу в столбец "Заполнили полностью". Оповещаем пользователя через tbLogs о начале и завершении данной операции, а также о том, что отчет был сгенерирован полностью. При каждой итерации цикла по строкам сводной таблицы прибавляем единицу к progressBar.Value.
Методы успешно написаны. Теперь нужно, чтобы все это добро мог запустить пользователь. Для этого в обработчике клика по кнопке butCreateReport напишем следующие строки:


if (!String.IsNullOrEmpty(tbSelectionFilePath.Text))
{
    butCreateReport.Enabled = false;
    WriteToSummary(); // Создаем шаблон свода
    WriteAteAndSummary(); // Создание отчета
    butSaveReport.Enabled = true; // Активируем кнопку сохранения отчета
}


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

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