На новой работе мне было поручено разобраться с генератором отчетов FastReport .NET. Раньше мне приходилось иметь дело с другими системами отчетности, например, Crystal Reports и Microsoft Reporting Services. Их функционала было достаточно для меня, до того, как я познакомился с FastReport.
Действительно мощный продукт с широким функционалом. Одна из наиболее понравившихся мне особенностей FastReport .NET – возможность создания отчетов прямо из кода пользовательского приложения. В этой статье я хочу рассмотреть пример использования такой «фичи». Это действительно удобно, когда не нужна куча файлов, поставляемых вместе с exe-шником. Кроме того, можно полностью контролировать создание отчета самостоятельно, изменяя вид объекта отчета в зависимости от логики приложения.
Прежде всего, объясню, чем же отличается построение отчета из кода пользовательского приложения от классической разработки шаблона в специальном дизайнере.
Обычно, генератор отчетов предоставляет специальный дизайнер для разработки шаблона отчета. Это может быть компонент IDE, либо просто внешняя программа. Разработчик размещает компоненты вывода данных на странице отчета, задает их свойства. Это похоже на конструирование формы приложения в проекте Windows Forms.
Помимо такого классического способа создания шаблона отчета, FastReport дает возможность создать шаблон, используя те же компоненты, но не визуальным редактором, а привычным для программиста кодом в приложении. Создается класс отчета, в него добавляются объекты отчета, настраивается источник данных. При определенной практике, создание отчета из кода займет немногим больше времени, чем из визуального редактора. Интересно, что в итоге такой шаблон отчета можно просмотреть все в том же визуальном редакторе и сохранить как файл.
Итак, рассмотрим все на примере.
Создаем приложение Windows Forms на языке C# (конечно же FastReport .Net должен быть установлен к этому моменту).
На форме размещаем одну кнопку, которая будет запускать наш отчет. Забегая вперед, скажу, что мы будем не только показывать отчет в режиме предварительного просмотра, но и делать его экспорт в PDF. Поэтому предусмотрим эту опцию добавив CheckBox:
Создаем обработчик события нажатия кнопки. Здесь будет весь код нашего приложения.
Перво-наперво, добавляем ссылку на библиотеку FastReport.dll (которая находится в паке FastReport .Net).
Также, добавляем библиотеки FastReport, FastReport.Utils и FastReport.Data в using.
Создаем экземпляр класса Report:
1 2 3 4 5 6 7 8 9 |
private void RunBtn_Click(object sender, EventArgs e) { //Create instance of class Report Report report = new Report(); } |
В нашем отчете будут выводиться данные из базы, так что необходимо подключить источник данных:
1 2 3 4 5 |
//load data DataSet ds = new DataSet(); ds.ReadXml(AppFolder + "\\nwind.xml"); |
Базу данных я взял из поставки FastReport .Net в папке Reports.
Теперь необходимо зарегистрировать источник данных в отчете:
1 2 3 |
//Register data source report.RegisterData(ds); |
Чтобы использовать таблицу из зарегистрированного источника данных, нужно включить ее:
1 2 3 |
//Enable data table report.GetDataSource("Products").Enabled = true; |
Подготовительные работы считаю на этом законченными. Приступаем непосредственно к созданию шаблона отчета. Создаем страницу отчета:
1 2 3 |
//Add report page ReportPage page = new ReportPage(); |
И добавляем ее в отчет:
1 |
report.Pages.Add(page);
|
Всем объектам отчета нужно давать уникальные имена. Можно придумывать их самостоятельно и присваивать свойству Name, а можно использовать функцию, которая сама сгенерирует уникальное имя:
1 |
page.CreateUniqueName();
|
Итак, страница отчета готова к наполнению. Создаем бэнд «Заголовок группы»:
1 2 3 |
//Create GroupHeader band GroupHeaderBand group = new GroupHeaderBand(); |
Созданный бэнд добавляем на страницу:
1 2 3 |
page.Bands.Add(group); group.CreateUniqueName(); |
Задаем высоту бэнда:
1 |
group.Height = Units.Centimeters * 1;
|
Условие группировки и порядок сортировки:
1 2 3 |
group.Condition = "[Products.ProductName].Substring(0,1)"; group.SortOrder = FastReport.SortOrder.Ascending; |
Теперь нужно наполнить созданный бэнд данными. Для этого нужно создать текстовый объект со ссылкой на поле из источника данных:
1 2 3 |
// create group text TextObject groupTxt = new TextObject(); |
Важный параметр Parent, который указывает на бенд, где будет размещен текстовый объект:
1 2 3 |
groupTxt.Parent = group; groupTxt.CreateUniqueName(); |
Задаем размер и границы текстового объекта:
1 |
groupTxt.Bounds = new RectangleF(0, 0, Units.Centimeters * 10, Units.Centimeters * 1);
|
И, собственно, сам текст:
1 |
groupTxt.Font = new Font("Arial", 14, FontStyle.Bold);
|
Остальные настройки относятся к внешнему виду текста:
1 2 3 4 5 |
groupTxt.Text = "[[Products.ProductName].Substring(0,1)]"; groupTxt.VertAlign = VertAlign.Center; groupTxt.Fill = new LinearGradientFill(Color.LightGoldenrodYellow, Color.Gold, 90, 0.5f, 1); |
Теперь самое интересное - создание бэнда «Данные»:
1 2 3 |
// create data band DataBand data = new DataBand(); |
Присваиваем бэнд данных группе:
1 2 3 |
group.Data = data; data.CreateUniqueName(); |
Назначаем источник данных для бэнда «Данные»:
1 2 3 |
data.DataSource = report.GetDataSource("Products"); data.Height = Units.Centimeters * 0.5f; |
Тут же можно задать фильтрацию бэнда с помощью свойства Filter. Теперь заполним бэнд данными с помощью текстового объекта:
Подвал группы создается для конкретного экземпляра группы. Это очень удобно, так как не позволит запутаться, если в отчете несколько заголовков групп. Итак, создаем подвал группы:
1 2 3 4 5 6 7 |
// create group footer group.GroupFooter = new GroupFooterBand(); group.GroupFooter.CreateUniqueName(); group.GroupFooter.Height = Units.Centimeters * 1; |
Чтобы подвал группы не пустовал, добавим в него итоговое значение, которое будет отображать количество продуктов в группе:
1 2 3 4 5 |
// create total Total groupTotal = new Total(); groupTotal.Name = "TotalRows"; |
Задаем тип вычислений, бэнд, для которого проводятся вычисления, и бэнд, в котором будет отображен результат. Так как мы считаем количество элементов, задавать конкретное поле для расчетов не нужно (делается с помощью groupTotal.Expression).
1 2 3 4 5 |
groupTotal.TotalType = TotalType.Count; groupTotal.Evaluator = data; groupTotal.PrintOn = group.GroupFooter; |
Созданный итог нужно добавить в каталог итогов отчета. Так сказать, зарегистрировать:
1 |
report.Dictionary.Totals.Add(groupTotal);
|
Как и любые выражения, которые нужно отобразить, итог выводится посредством текстового объекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// show total in the group footer TextObject totalText = new TextObject(); totalText.Parent = group.GroupFooter; totalText.CreateUniqueName(); totalText.Bounds = new RectangleF(0, 0, Units.Centimeters * 10, Units.Centimeters * 0.5f); totalText.Text = "Rows: [TotalRows]"; totalText.HorzAlign = HorzAlign.Right; totalText.Border.Lines = BorderLines.Top; |
Вот и все. Отчет готов. Теперь мы можем либо отобразить его, либо запустить в дизайнере. А можно сразу же экспортировать в нужный нам формат данных. Задействуем CheckBox, который мы добавили на форму:
Если CheckBox отмечен, то будет показано диалоговое окно сохранения файла pdf. Иначе будет запущен отчет в режиме предварительного просмотра. Тут надо отметить, что можно сделать экспорт и без отображения диалогового окна. Так сказать, в сохранение в скрытом режиме. Тогда экспорт будет выглядеть так:
1 |
export.Export(report, @"C:\Temp\ReportFromCode.pdf");
|
где первый параметр – экземпляр отчета, а второй – итоговый файл.
Что же у нас получилось в итоге:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
//Create instance of class Report Report report = new Report(); //load data DataSet ds = new DataSet(); ds.ReadXml(AppFolder + "\\nwind.xml"); //Register data source report.RegisterData(ds); //Enable data table report.GetDataSource("Products").Enabled = true; //Add report page ReportPage page = new ReportPage(); report.Pages.Add(page); page.CreateUniqueName(); //Create GroupHeader band GroupHeaderBand group = new GroupHeaderBand(); page.Bands.Add(group); group.CreateUniqueName(); group.Height = Units.Centimeters * 1; group.Condition = "[Products.ProductName].Substring(0,1)"; group.SortOrder = FastReport.SortOrder.Ascending; // create group text TextObject groupTxt = new TextObject(); groupTxt.Parent = group; groupTxt.CreateUniqueName(); groupTxt.Bounds = new RectangleF(0, 0, Units.Centimeters * 10, Units.Centimeters * 1); groupTxt.Text = "[[Products.ProductName].Substring(0,1)]"; groupTxt.Font = new Font("Arial", 14, FontStyle.Bold); groupTxt.VertAlign = VertAlign.Center; groupTxt.Fill = new LinearGradientFill(Color.LightGoldenrodYellow, Color.Gold, 90, 0.5f, 1); // create data band DataBand data = new DataBand(); group.Data = data; data.CreateUniqueName(); data.DataSource = report.GetDataSource("Products"); data.Height = Units.Centimeters * 0.5f; // create product name text TextObject productText = new TextObject(); productText.Parent = data; productText.CreateUniqueName(); productText.Bounds = new RectangleF(0, 0, Units.Centimeters * 10, Units.Centimeters * 0.5f); productText.Text = "[Products.ProductName]"; // create group footer group.GroupFooter = new GroupFooterBand(); group.GroupFooter.CreateUniqueName(); group.GroupFooter.Height = Units.Centimeters * 1; // create total Total groupTotal = new Total(); groupTotal.Name = "TotalRows"; groupTotal.TotalType = TotalType.Count; groupTotal.Evaluator = data; groupTotal.PrintOn = group.GroupFooter; report.Dictionary.Totals.Add(groupTotal); // show total in the group footer TextObject totalText = new TextObject(); totalText.Parent = group.GroupFooter; totalText.CreateUniqueName(); totalText.Bounds = new RectangleF(0, 0, Units.Centimeters * 10, Units.Centimeters * 0.5f); totalText.Text = "Rows: [TotalRows]"; totalText.HorzAlign = HorzAlign.Right; totalText.Border.Lines = BorderLines.Top; if (PDFCheckBox.Checked) { report.Prepare(); FastReport.Export.Pdf.PDFExport export = new FastReport.Export.Pdf.PDFExport(); export.Export(report); //export.Export(report, @"C:\Temp\ReportFromCode.pdf"); } else report.Show(); |
И сам отчет:
Подведем итоги. FastReport .NET порадовал еще одной интересной фичей – создание отчета из кода. Когда это может быть полезно? Если вы не хотите плодить кучу отдельных файликов с шаблонами отчетов или хотите скрыть шаблон отчета внутри программы во избежание порчи или изменения шаблона. Также удобно менять шаблон отчета прямо во время выполнения вашего приложения. Это дает большую гибкость отчетам и возможность использовать один шаблон, изменяя его в зависимости от логики программы.
Лично мне привычно и удобно использовать объекты в коде программы. Так что создание отчета практически ничем не отличается от написания основного кода оконного приложения.