Сводный отчет с расположение точек учета в колонках
Рассмотрим порядок создания сводного отчета параметров потребления по точкам учета с возможностью расположения точек учета в колонках. В качестве примера будем создавать отчетную формы для отображения двух точек учета. Если нужно отображать больше точек, увеличьте количество колонок в шапке, теле и подвале отчета.
ШАГ 1. Откройте копию системной отчетной формы Параметры потребления по точкам учета. Удалите из нее все секции, группировку и таблицу из секции detailBand1. Добавьте секции: заголовок отчета с именем ReportHeader, заголовок группы с именем PageHeader, подвал отчета с именем ReportFooter, переименуйте секцию detailBand1 на Detail. Добавьте таблицу из 5 строк в секцию ReportHeader и таблицы из одной строки в секции PageHeader, Detail и ReportFooter Для всех ячеек в колонках кроме первой, задайте свойство Видимость = Нет, чтобы не отображать колонки, которые не будут связаны с точками учета.
Задайте следующие наименования строк в таблицах (код скриптов привязан к этим наименованиям): measurePointTitleRow - для строки Точка учета в таблице секции ReportHeader measurePointAddressRow - для строки Адрес в таблице секции ReportHeader deviceSerialNumberRow - для строки № прибора учета в таблице секции ReportHeader deviceModelRow - - для строки Модель прибора учета в таблице секции ReportHeader startEndTotalsRow - для строки Показания интеграторов на начало и конец отчетного периода в таблице секции ReportHeader pageHeaderRow - для строки Дата в таблице секции PageHeader detailRow - для строки DataDate в таблице секции Detail footerRow - для строки Итого в таблице секции ReportFooter
ШАГ 2. Откройте вкладку Скрипты и скопируйте в нее следующий код:
// Скрипт для изменения структуры источника данных позволяющий отображать точки учета в колонках
using System.Data;
using System.Collections.Generic;
using System.Linq;
// Количество отображаемых точек учета в данной отчетной форме не может быть больше 2, а количество параметров - 2.
// Чтобы увеличить, надо добавить колонки в отчетную форму.
// Для добавляемых колонок надо задать свойство Видимость = Нет, чтобы скрывать колонки, если их количество больше числа выбранных точек учета
// Наименование отображаемых параметров потребления (из узла Параметры потребления в Списке полей)
// ВАЖНО!!! Размер массива должен быть согласован с количеством ячеек в строках detailRow и footerRow
private string[] CONSUMPTION_FIELD_NAMES = new string[] {"M1", "M2"};
// Наименование параметров с показаниями интеграторов (из узла Показания интеграторы на начало и конец отчетного периода в Списке полей)
// ВАЖНО!!! Размер массива должен быть согласован с количеством ячеек в строке startEndTotalsRow
private string[] TOTALS_FIELD_NAMES = new string[] {"M1_Start", "M1_End", "M2_Start", "M2_End"};
// Экземпляр отчетной формы
private XtraReport _xtraReport;
// Рабочая таблица с данными для секции ReportHeader
private DataTable _wrkHeaderDataTable;
// Рабочая таблица с данными для секции Detail
private DataTable _wrkDetailDataTable;
// Рабочая табочая таблица с данными для секции ReportFooter
private DataTable _wrkFooterDataTable;
// Наименования колонок в рабочих таблицах формируются динамически
// в зависимости от значения присваемому полям _number, _consumption и _totals
private string _number, _consumption, _totals;
// Наименование колонок в создаваемых рабочих таблицах
private string Title_Number { get { return "Title_" + _number; } }
private string Address_Number { get { return "Address_" + _number; } }
private string SerialNumber_Number { get { return "SerialNumber_" + _number; } }
private string DeviceModel_Number { get { return "DeviceModel_" + _number; } }
private string Value_Number { get { return "Value_" + _consumption + "_" + _number; } }
private string Totals_Number { get { return "Totals_" + _totals + "_" + _number; } }
// Тип данных, по которым формируется отчет
private int _dataType = 64;
// Параметры выбранных точек учета (ключ - номер точки учета)
private Dictionary<int, MeasurePointInfo> _measurePoints = new Dictionary<int, MeasurePointInfo>();
// Параметры точки учета используемые в секции ReportHeader
private class MeasurePointInfo
{
// Параметры точки учета
public string Title {get; set;}
public string Address {get; set;}
public string DeviceModel {get; set;}
public string SerialNumber {get; set;}
public int MeasurePointId {get; set;}
// Берется из пользовательского атрибута с кодом ValueForSorting
// Используется для задания порядка следования колонок с точками учета
// При необходимости сортировку можно задать по любому параметру точки учета (см. строки 131 и 156), закомментировав строку 139.
public object ValueForSorting {get; set;}
// Показания интеграторов на начало и конец отчетного периода
public Dictionary<string, double?> Totals_Value {get; set;}
}
// В обработчике BaseReport_DataSourceDemanded() создаются рабочие таблицы источника данных и к ним привязываются ячейки отчетной формы
private void BaseReport_DataSourceDemanded(object sender, System.EventArgs e)
{
_xtraReport = (XtraReport)sender;
var dataSet = (DataSet)_xtraReport.DataSource;
if (dataSet == null)
{
return;
}
// Тип данных
_dataType = (int)_xtraReport.Parameters["DATA_TYPE"].Value;
// Заполняем информацию по точкам учета, используемую в шапке отчета
FillMeasurePointInfo(dataSet);
// Создаем рабочие таблицы и добавляем их в dataSet (источник данных отчета)
CreateWorkTables(dataSet);
// Привязываем ячейки к полям рабочих таблиц
BindCellsToWrkTables();
// Заполняем рабочие таблицы значениями задаваемых параметров потребления
FillWorkTables(dataSet);
// Меняем имя таблицы, из которой берутся данные для секции Detail.
// Данные будут браться из рабочей таблицы содержащей в каждой строке с меткой времени параметры всех точек учета выбранных при формировании отчета.
_xtraReport.DataMember = "DetailDataTable";
}
// Формирует отсортированный Dictionary точек учета (в _measurePoints ключ - номер точки учета).
// Сортировка точек учета выполняется по значению свойства MeasurePointInfo.ValueForSorting
private void FillMeasurePointInfo(DataSet dataSet)
{
// Исходная таблица c параметрами точек учета
var measurePointTable = dataSet.Tables["MeasurePointTable"];
measurePointTable.PrimaryKey = new DataColumn[1] { measurePointTable.Columns["MeasurePointId"] };
// Исходная таблица с пользовательскими атрибутами точек учета
var attributeTable = dataSet.Tables["MeasurePointAttributeTable"];
attributeTable.PrimaryKey = new DataColumn[1] { attributeTable.Columns["MeasurePointId"]};
// Исходная таблица с показаниями интеграторов на начало и конец отчетного периода
var startEndTotalsTable = dataSet.Tables["StartEndTotalsTable"];
startEndTotalsTable.PrimaryKey = new DataColumn[1] { startEndTotalsTable.Columns["MeasurePointId"]};
// Формируем Dictionary c параметрами точек учета для шапки отчета
foreach(DataRow row in measurePointTable.Rows)
{
int measurePointId = (int)row["MeasurePointId"];
int number = (int)row["Number"];
// Получаем строку с пользовательскими атрибутами для текущей точки учета
var attributeRow = attributeTable.Rows.Find(new object[] { measurePointId });
// Добавляем строку в Dictionary c информацией по точкам учета используемой в шапке отчета
_measurePoints[number] = new MeasurePointInfo()
{
Title = (string)row["MeasurePointTitle"],
Address = Convert.ToString(row["Address"]),
SerialNumber = Convert.ToString(row["SerialNumber"]),
DeviceModel = Convert.ToString(row["DeviceModel"]),
MeasurePointId = (int)row["MeasurePointId"],
ValueForSorting = int.MaxValue // Если у точки учета атрибут для сортировки не задан, то она будет в последних колонках
};
if (attributeRow != null)
{
if (attributeTable.Columns.Contains("ValueForSorting") && attributeRow["ValueForSorting"] != DBNull.Value)
{
// Задаем свойство, которое определяет порядок следования колонок с точками учета
_measurePoints[number].ValueForSorting = Convert.ToInt32(attributeRow["ValueForSorting"]);
}
}
// Показания интеграторов на начало и конец отчетного периода
var startEndTotalsRow = startEndTotalsTable.Rows.Find(measurePointId);
if (startEndTotalsRow != null)
{
_measurePoints[number].Totals_Value = new Dictionary<string, double?>();
foreach(string fieldName in TOTALS_FIELD_NAMES)
{
_measurePoints[number].Totals_Value[fieldName] = startEndTotalsRow[fieldName] != DBNull.Value ? (double?)startEndTotalsRow[fieldName] : null;
}
}
}
// Сортировка точек учета по значению свойства MeasurePointInfo.ValueForSorting
_measurePoints = _measurePoints.OrderBy(x => x.Value.ValueForSorting).ToDictionary(x => x.Key, x => x.Value);
}
// Создает рабочие таблицы.
private void CreateWorkTables(DataSet dataSet)
{
// Таблица из одной строки со свойствами точки учета
// К колонкам этой таблицы будут привязаны ячейки секции ReportHeader
_wrkHeaderDataTable = dataSet.Tables["HeaderDataTable"];
// Проверка нужна для возможности повторных запусков из редактор отчетов
if (_wrkHeaderDataTable == null)
{
_wrkHeaderDataTable = new DataTable("HeaderDataTable");
dataSet.Tables.Add(_wrkHeaderDataTable);
}
_wrkHeaderDataTable.Columns.Clear();
// Таблица содержащая количество строк равное количеству меток времени
// К колонкам этой таблицы будут привязаны ячейки секции Detail.
// В каждой строке с меткой времени содержатся параметры всех точек учета выбранных при формировании отчета.
_wrkDetailDataTable = dataSet.Tables["DetailDataTable"];
// Проверка нужна для возможности повторных запусков из редактор отчетов
if (_wrkDetailDataTable == null)
{
_wrkDetailDataTable = new DataTable("DetailDataTable");
dataSet.Tables.Add(_wrkDetailDataTable);
}
_wrkDetailDataTable.Columns.Clear();
_wrkDetailDataTable.Columns.Add("DataDate", typeof(DateTime));
// Таблица из одной строки с итогами по точкам учета.
// К колонкам этой таблицы будут привязаны ячейки секции ReportFooter
_wrkFooterDataTable = dataSet.Tables["FooterDataTable"];
// Проверка нужна для возможности повторных запусков из редактор отчетов
if (_wrkFooterDataTable == null)
{
_wrkFooterDataTable = new DataTable("FooterDataTable");
dataSet.Tables.Add(_wrkFooterDataTable);
}
_wrkFooterDataTable.Columns.Clear();
// Формируем структуру рабочих таблиц
foreach(int number in _measurePoints.Keys)
{
// Используется при получении наименований колонок
_number = number.ToString();
// Таблица с данными для шапки отчета
_wrkHeaderDataTable.Columns.Add(Title_Number, typeof(string));
_wrkHeaderDataTable.Columns.Add(Address_Number, typeof(string));
_wrkHeaderDataTable.Columns.Add(SerialNumber_Number, typeof(string));
_wrkHeaderDataTable.Columns.Add(DeviceModel_Number, typeof(string));
// Поля для показаний интеграторов на начало и конец отчетного периода в шапке отчета
foreach(string fieldName in TOTALS_FIELD_NAMES)
{
// Используется при получении наименований колонок
_totals = fieldName;
_wrkHeaderDataTable.Columns.Add(Totals_Number, typeof(double));
}
// Таблицы с данными для тела и подвала отчета
foreach(string fieldName in CONSUMPTION_FIELD_NAMES)
{
// Используется при получении наименований колонок
_consumption = fieldName;
_wrkDetailDataTable.Columns.Add(Value_Number, typeof(double));
_wrkFooterDataTable.Columns.Add(Value_Number, typeof(double));
}
}
}
// Привязываем ячейки к полям рабочих таблиц
private void BindCellsToWrkTables()
{
// Настраиваем привязку и формат для меток времени
detailRow.Cells[0].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[DetailDataTable].[DataDate]"));
detailRow.Cells[0].TextFormatString = "{0:dd.MM.yyyy}"; // суточные данные
if (_dataType == 128)
{
detailRow.Cells[0].TextFormatString = "{0:dd.MM.yyyy HH:mm}"; // часовые данные
}
else if (_dataType == 32)
{
detailRow.Cells[0].TextFormatString = "{0:dd MMMM yyyy}"; // месячные данные
}
// Формат вывода чисовых значений
string valueFormat = "{0:F2}";
// Номер колонки в строке шапки отчета
int k = 1;
// Сдвиг номеров колонок
int shiftTotals = 1, shiftConsumption = 1;
// Привязываем ячейки к полям рабочих таблиц для заданных точек учета
foreach(var number in _measurePoints.Keys)
{
// Количество отображаемых точек учета определяется количеством колонок в данной отчетной форме.
// Чтобы увеличить, надо добавить колонки в отчетную форму
if (k + shiftTotals > startEndTotalsRow.Cells.Count)
{
break;
}
// Наименование отображаемого параметра
int i = 0;
foreach(string fieldName in CONSUMPTION_FIELD_NAMES)
{
pageHeaderRow.Cells[shiftConsumption + i].Text = fieldName;
pageHeaderRow.Cells[shiftConsumption + i].Visible = true;
i++;
}
// Используется при получении наименований колонок
_number = number.ToString();
measurePointTitleRow.Cells[k].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[HeaderDataTable].[" + Title_Number + "]"));
measurePointTitleRow.Cells[k].Visible = true;
measurePointAddressRow.Cells[k].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[HeaderDataTable].[" + Address_Number + "]"));
measurePointAddressRow.Cells[k].Visible = true;
deviceSerialNumberRow.Cells[k].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[HeaderDataTable].[" + SerialNumber_Number + "]"));
deviceSerialNumberRow.Cells[k].Visible = true;
deviceModelRow.Cells[k].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[HeaderDataTable].[" + DeviceModel_Number + "]"));
deviceModelRow.Cells[k].Visible = true;
int j = 0;
foreach(string fieldName in TOTALS_FIELD_NAMES)
{
_totals = fieldName;
startEndTotalsRow.Cells[shiftTotals + j].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[HeaderDataTable].[" + Totals_Number + "]"));
startEndTotalsRow.Cells[shiftTotals + j].Visible = true;
startEndTotalsRow.Cells[shiftTotals + j].TextFormatString = fieldName + " " + valueFormat;
j++;
}
j = 0;
foreach(string fieldName in CONSUMPTION_FIELD_NAMES)
{
_consumption = fieldName;
detailRow.Cells[shiftConsumption + j].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[DetailDataTable].[" + Value_Number + "]"));
detailRow.Cells[shiftConsumption + j].TextFormatString = valueFormat;
detailRow.Cells[shiftConsumption + j].Visible = true;
footerRow.Cells[shiftConsumption + j].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[FooterDataTable].[" + Value_Number + "]"));
footerRow.Cells[shiftConsumption + j].TextFormatString = valueFormat;
footerRow.Cells[shiftConsumption + j].Visible = true;
j++;
}
shiftTotals = shiftTotals + TOTALS_FIELD_NAMES.Length;
shiftConsumption = shiftConsumption + CONSUMPTION_FIELD_NAMES.Length;
k++;
}
}
// Заполняет рабочие таблицы данными для отчета
private void FillWorkTables(DataSet dataSet)
{
// Исходная таблица c параметрами потребления точек учета
var consumptionTable = dataSet.Tables["ConsumptionTable"];
consumptionTable.PrimaryKey = new DataColumn[2] { consumptionTable.Columns["MeasurePointId"], consumptionTable.Columns["DataDate"] };
var headerDataRow = _wrkHeaderDataTable.NewRow();
_wrkHeaderDataTable.Rows.Add(headerDataRow);
var footerDataRow = _wrkFooterDataTable.NewRow();
_wrkFooterDataTable.Rows.Add(footerDataRow);
var data = (DateTime)_xtraReport.Parameters["DATE_START"].Value;
_wrkDetailDataTable.Rows.Clear();
// Заполняем строки таблиц связанных с ячеками секций DetailReportHeader, DetailReportData и DetailReportFooter
// В КАЖДОЙ СТРОКЕ с меткой времени заполняем данные для ВСЕХ заданных точек учета
while (data <= (DateTime)_xtraReport.Parameters["DATE_END"].Value)
{
var detailRow = _wrkDetailDataTable.NewRow();
_wrkDetailDataTable.Rows.Add(detailRow);
// Проходим по ВСЕМ заданным точкам учета
foreach(int number in _measurePoints.Keys)
{
_number = number.ToString();
int measurePointId = _measurePoints[number].MeasurePointId;
headerDataRow[Title_Number] = _measurePoints[number].Title;
headerDataRow[Address_Number] = _measurePoints[number].Address;
headerDataRow[SerialNumber_Number] = (object)_measurePoints[number].SerialNumber ?? DBNull.Value;
headerDataRow[DeviceModel_Number] = (object)_measurePoints[number].DeviceModel ?? DBNull.Value;
// Заполняем метку времени
detailRow["DataDate"] = data;
foreach(string fieldName in TOTALS_FIELD_NAMES)
{
_totals = fieldName;
headerDataRow[Totals_Number] = (object)_measurePoints[number].Totals_Value[_totals] ?? DBNull.Value;
}
// Заполняет сроку с потреблениями и накапливает сумму для итогов
var row = consumptionTable.Rows.Find(new object[] { measurePointId, data});
if (row != null)
{
foreach(string fieldName in CONSUMPTION_FIELD_NAMES)
{
_consumption = fieldName;
// Значение, которое отображается в теле отчета
var value = (object)row[fieldName] ?? DBNull.Value;
if (value != DBNull.Value)
{
detailRow[Value_Number] = (double)value;
// Суммирование итогов по каждой колонке
if (footerDataRow[Value_Number] == DBNull.Value)
{
footerDataRow[Value_Number] = 0;
}
footerDataRow[Value_Number] = (double)footerDataRow[Value_Number] + (double)value;
}
}
}
}
// Переход к следующей метки времени
if (_dataType == 32)
{
data = data.AddMonths(1);
}
else if (_dataType == 128)
{
data = data.AddHours(1);
}
else
{
data = data.AddDays(1);
}
}
}
ШАГ 3. Переключитесь на вкладку Обозреватель отчета и для узла MeasurePointSummaryReport в окне свойств задайте скрипт Запрос источника данных равным BaseReport_DataSourceDemanded.
ШАГ 4. Сохраните изменения.
ШАГ 5. Пример сформированного отчета с отображением точек учета в колонках: