Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Все статьи »

Обсудить на форуме Обсудить на форуме

Печать отчетов в Windows-формах
Как создавать собственные отчеты, применяя функции печати GDI+ в Microsoft .NET.
Аннотация

Как создавать собственные отчеты, применяя функции печати GDI+ в Microsoft .NET.

Исходный код к этой статье можно скачать по ссылке printwinforms.exe sample code.

Содержание

Введение

Средства печати в .NET Framework

Создание реальных отчетов

Класс TabularReport

Заключение

Введение

Я не собираюсь разглагольствовать о технологиях безбумажного документооборота, отмечу лишь, что времена этих технологий еще не настали. Я создал много разных систем, отчасти избавляющих компании от работы с бумажными документами и превращающих эти документы в хранящиеся на компьютере данные, но (независимо от того, насколько хороша система) одним из основных требований всегда оставалась функция перевода данных обратно в бумажную форму. Начиная с систем, написанных на языке программирования Clipper, затем Microsoft® FoxPro®, Microsoft® Access, Microsoft® Visual Basic®, а теперь и Microsoft .NET, при создании бизнес-систем неизменно одно: создание и тестирование отчетов - один из самых длительных периодов в графике проекта. Предполагая, что это справедливо не только для меня, а для всех, я собираюсь показать, как использовать средства вывода графики GDI+ и классы GDI+ Printing для создания табличного отчета. Значительную часть всей функциональности вывода, которую вам когда-либо предстоит разработать, составляют отчеты этого типа (рис. 1).

 

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

Хотя я не рассматриваю другой распространенный тип отчетов - форму-счет (рис. 2), многие принципы создания отчетов, обсуждаемые в этой статье, относятся и к таким отчетам.

 

Рис. 2. Счета, налоговые декларации и другие документы этого типа часто выводятся в отчетах-формах

Примечание - Если вы уже изучали возможности Microsoft® Visual Studio® .NET, то скорее всего знаете, что вместе с ним поставляется Crystal Reports - полноценная среда для разработки отчетов, содержащая множество средств, и вы, вероятно, удивлены, почему я не рассматриваю ее в этой статье. Crystal Reports - отличное средство создания отчетов, но бесплатно его не развернешь, поэтому я хочу, чтобы вы поняли, чего можно добиться, используя лишь средства .NET Framework.

Средства печати в .NET Framework

Прежде чем углубиться в разработку двух отчетов-примеров, я хочу сделать обзор возможностей печати в .NET. Платформа .NET Framework предоставляет набор средств печати, которые опираются на существующие классы GDI+, позволяющие выполнять печать, предварительный просмотр и другие операции с принтерами. Для программного использования этих средств предназначены классы System.Drawing.Printing и визуальные компоненты (PrintDialog, PrintPreviewDialog, PrintDocument и т. д.), доступные в приложениях Windows Forms. Благодаря этим классам писать код для печати практически не нужно - достаточно лишь пары компонентов Windows Forms.

Попробуйте сами

Чтобы получить первое впечатление о средствах печати, выполните следующие действия и создайте небольшое тестовое приложение.

 

  1. Создайте новое Windows-приложение в Visual Studio .NET на Microsoft® Visual C#® или Visual Basic .NET.
  2. На автоматически созданную форму Form1 добавьте по одному из следующих компонентов: PrintDocument, PrintPreviewDialog и Button.
  3. Теперь выберите компонент PrintPreviewDialog и просмотрите его свойства. Установите свойство Document, равное (none), в PrintDocument1 ('printDocument1', если вы пишете на C#). Тем самым вы свяжете два компонента, и когда диалогу предварительного просмотра потребуется вывести страницу в окно предварительного просмотра или на принтер, он вызовет событие PrintPage указанного PrintDocument.
  4. Добавьте код в обработчик события PrintPage объекта PrintDocument1. Независимо от языка для доступа к событию два раза щелкните компонент PrintDocument. Далее приведен фрагмент кода, печатающий на странице имя зарегистрированного в системе пользователя.

     
    //C#
    
    private void printDocument1_PrintPage(object sender,
    
        System.Drawing.Printing.PrintPageEventArgs e)
    
    {
    
        Graphics g = e.Graphics;
    
        String message = System.Environment.UserName;
    
        Font messageFont = new Font("Arial",
    
                 24,System.Drawing.GraphicsUnit.Point);
    
        g.DrawString(message,messageFont,Brushes.Black,100,100);
    
    }
    
    'Visual Basic .NET
    
    Private Sub PrintDocument1_PrintPage(ByVal sender As System.Object, _
    
            ByVal e As System.Drawing.Printing.PrintPageEventArgs) _
    
            Handles PrintDocument1.PrintPage
    
        Dim g As Graphics = e.Graphics
    
        Dim message As String = System.Environment.UserName
    
        Dim messageFont As New Font("Arial", 24, _
    
              System.Drawing.GraphicsUnit.Point)
    
        g.DrawString(message, messageFont, Brushes.Black, 100, 100)
    
    End Sub
    
  5. Добавьте код к обработчику события щелчка кнопки (чтобы попасть в обработчик события click, дважды щелкните кнопку в дизайнере формы), вызывающий диалог предварительного просмотра. Когда диалогу предварительного просмотра потребуется вывести страницу, он вызовет только что созданный обработчик события PrintPage.

     
    //C#
    
    private void button1_Click(object sender, System.EventArgs e)
    
    {
    
        printPreviewDialog1.ShowDialog();
    
    }
    
    
    'Visual Basic .NET
    
    Private Sub Button1_Click(ByVal sender As System.Object, _
    
            ByVal e As System.EventArgs) _
    
            Handles Button1.Click
    
        PrintPreviewDialog1.ShowDialog()
    
    End Sub
    

Запустите приложение. Если все правильно, при щелчке кнопки откроется диалоговое окно предварительного просмотра, содержащее почти пустую страницу с именем пользователя.

Создание реальных отчетов

Короткого упражнения, приведенного выше, вполне достаточно, чтобы понять, как создавать собственные отчеты; вся остальная работа - это просто рисование средствами GDI+ и решение задач разметки, связанных с границами и другими параметрами. Для создания законченных отчетов, управляемых данными (data-driven reports), достаточно добавить код к обработчику события PrintPage. Конечно, для одного никогда не меняющегося отчета это неплохо, но если вы создаете отчеты постоянно, поддерживать весь код в PrintPage будет трудновато. Вместо этого разбейте печать страницы на отдельные задачи и для каждой задачи напишите функцию, выполняющую свою часть работы. Если вам удастся сделать этот код повторно используемым, вы сможете генерировать любые отчеты без написания нового кода.

В прошлом я пользовался разными инструментами для генерации отчетов и обнаружил, что все они разбивают отчеты на одни и те же общие элементы:

 

  • верхние и нижние колонтитулы страницы;
  • верхние и нижние колонтитулы отчета;
  • верхние и нижние колонтитулы группы;
  • строки с данными.

Чтобы облегчить себе задачу, я решил обойтись без реализации разделов отчета (Report) и группы (Group) и сосредоточиться на верхних и нижних колонтитулах страницы, а также, конечно, на строках с данными. Я начал с новой Windows-формы, добавил PrintDocument точно так же, как в приведенном выше упражнении, а затем создал несколько других функций - PrintPageFooter, PrintPageHeader и PrintDetailRow, - каждая их которых возвращает размер (высоту в пикселах) созданного ею раздела. Каждой из этих функций я предоставил объект PrintPageEventArgs, передаваемый обработчику события PrintPage, чтобы они получили доступ к информации о параметрах принтера и к объекту Graphics, нужному им для отрисовки. Кроме того, я передаю прямоугольник, bounds, описывающий площадь страницы, на которой требуется вывести раздел, и общий аргумент sizeOnly, указывающий, надо ли печатать раздел или достаточно вычислить и вернуть его размер. Функция PrintDetailRow принимает некоторые другие аргументы, которые я опишу, когда мы перейдем к рассмотрению ее кода. После того как функция для каждого отдельного раздела написана, отчет создается быстро. Как я обнаружил, независимо от конкретного раздела приходится решать несколько основных задач, в том числе настройка разрешения и обработка нескольких страниц.

Проблемы разрешения: преобразование дюймов в пикселы

Имея дело с GDI+, вы всегда работаете в каких-то конкретных единицах измерения, т. е. дюймах, пикселах, точках или в чем-то еще, причем на связь между единицами измерения влияет устройство вывода (например, принтер). Я обнаружил, что легче измерять все в аппаратно-независимых дюймах (дюйм - всегда дюйм) и вообще избегать проблем с разрешением. Для этого (поскольку дюйм не является единицей измерения по умолчанию) вам придется установить свойство PageUnit объекта Graphics в начале обработчика события PrintPage.

 

Public Sub PrintPage(ByVal sender As Object, _

           ByVal e As PrintPageEventArgs)

        Dim g As Graphics = e.Graphics

        g.PageUnit = GraphicsUnit.Inch

Единственное неудобство при работе в дюймах - такие параметры Page и Printer, как поля страницы (margins), измеряются в сотых долях дюйма. Конвертировать из одних значений в другие легко, но помните, что всегда следует использовать тип данных Single, чтобы после конвертирования не потерять точность. В начале функции PrintPage, я получаю значения границ и во избежание путаницы немедленно их конвертирую.

 

Dim leftMargin As Single = e.MarginBounds.Left / 100

Dim rightMargin As Single = e.MarginBounds.Right / 100

Dim topMargin As Single = e.MarginBounds.Top / 100

Dim bottomMargin As Single = e.MarginBounds.Bottom / 100

Dim width As Single = e.MarginBounds.Width / 100

Dim height As Single = e.MarginBounds.Height / 100

Работа с несколькими страницами

Если ваш отчет занимает несколько страниц, нужно следить за номером текущей страницы и текущей строкой данных, так как событие PrintPage вызывается для печати каждой страницы. В любом случае переменная, существующая вне кода обработчика события, позволяет сохранять текущую позицию между вызовами PrintPage.

 

Dim currentPage As Integer = 0

Dim currentRow As Integer = 0

Public Sub PrintPage(ByVal sender As Object, _

       ByVal e As PrintPageEventArgs)

    ...

    currentPage += 1

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

 

Private Sub PrintDocument1_BeginPrint(ByVal sender As Object, _

        ByVal e As PrintEventArgs) _

        Handles PrintDocument1.BeginPrint

    currentPage = 0

    currentRow = 0

End Sub

Когда я только начинал изучение возможностей печати в .NET, я обнулял счетчик страниц в обработчике нажатия кнопки Print, перед печатью документа. Все отлично работало, пока я не решил просмотреть документ перед печатью. В предварительном просмотре страницы нумеровались с 1 по 3, но на бумаге номера шли с 4 по 6, так как документ на самом деле печатался два раза (один раз в окне предварительного просмотра, второй раз - на принтере). На самом деле, так как из диалогового окна Print Preview можно печатать сколько угодно раз, номера продолжат расти. Cобытие BeginPrint позволяет избежать этих проблем.

Кроме того, через свойства HasMorePages объекта PrintPageEventArgs следует указывать, закончена печать отчета или еще остались страницы. Перед завершением обработчика события PrintPage установите это свойство в false, если печать закончена, или в true, если напечатаны не все строки. При печати на нескольких страницах нужно знать, когда достигнут конец страницы, поэтому все мои процедуры печати (например, PrintDetailRow) принимают аргумент "sizeOnly". Передавая True в параметре sizeOnly, я выясняю размер строки, которую необходимо напечатать, что позволяет определить, есть ли для этой строки место на странице (конечно, не забывая про нижний колонтитул). Если места больше нет, я не печатаю строку и не увеличиваю значение переменной currentRow; вместо этого я устанавливаю свойство HasMorePages в True и печатаю строку на следующей странице.

Примечание - Если размер строки превышает размер страницы, вы никогда не сможете ее напечатать и войдете в бесконечный цикл, печатая пустые страницы. После того как вы выясните размер раздела, проверьте, разумно ли это значение (меньше ли оно свободного места на пустой странице) до того, как работать с ним. Если оно окажется слишком велико, генерируйте исключение.

Вывод текста

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

Сочетание параметров StringFormat с LayoutRectangle

Каждый из этих рисунков - результат вызова DrawString со строкой "The quick brown fox jumped high over the slow and lazy black dog" и разными параметрами формата строки. Ограничивающий прямоугольник обычно не виден, поэтому я выводил его потом вызовом функции DrawRectangle - так легче понять смысл иллюстраций.

 

Рис. 3. Результат работы с параметрами StringFormat по умолчанию

На первой из этих иллюстраций показано поведение по умолчанию. Текст переносится, видны неполные строки, а все, что находится за границами прямоугольника, отсекается (не рисуется).

 

Рис. 4. Включение LineLimit предотвращает появление неполных строк

При включенном параметре LineLimit выводятся только полные строки. Отсутствие дополнительной строки слегка меняет перенос слов.

 

Рис. 5. При NoClip текст может появляться вне ограничивающего прямоугольника

Как показано на рис. 5, при отключении параметра LineLimit и включении NoClip части строк, отсеченные ограничивающим прямоугольником, становятся видны.

Автоматическое добавление многоточий

Если текст обрезается ограничительным прямоугольником, с помощью объекта StringFormat (через свойство Trimming) можно добавлять многоточия взамен последних нескольких символов (EllipseCharacter), за последним полностью видимым словом (EllipseWord) или взамен средней части строки, так чтобы начало и конец строки были видимы (EllipsePath). Все три варианта (слева направо: EllipseCharacter, EllipseWord и EllipsePath) показаны на рис. 6.

 

Рис. 6. Если строка не помещается полностью в ограничивающий прямоугольник, с помощью свойства Trimming можно автоматически добавлять многоточия

Индикаторы "горячих" клавиш

Еще одно настраиваемое свойство этого класса - HotkeyPrefix, очень полезное для самостоятельной отрисоввки элементов управления Windows Forms. Оно влияет на то, как DrawString обрабатывает символы &, и может принимать значения Hide, Show или None. Если оно равно Show, символ & перед буквой означает, что буква является "горячей клавишей", и тогда буква подчеркивается. При указании Hide буква не подчеркивается, но и сам символ & не выводится, а в случае None этот символ обрабатывается как обычный текст. На рисунке ниже показаны результаты действия трех значений (Show, Hide и None) для строки "&Print".

 

Рис. 7. Свойство HotkeyPrefix полезно при создании собственных элементов управления

Выравнивание текста

Наконец, класс StringFormat позволяет задавать выравнивание (alignment) с использованием far, near и center вместо left, right и center, так что эти значения будут работать в любом языке независимо от направления чтения текста. В английском языке текст читается слева направо, там эти значения преобразуются в выравнивание по правому (far) и левому (near) краю, а center так и остается выравниванием по центру. Независимо от способа выравнивания оно влияет на положение текста относительно ограничивающего прямоугольника. Если ограничивающий прямоугольник не задан, выравнивание влияет на интерпретацию координат x и y, заданных для вывода строки. Без ограничивающего прямоугольника параметр Alignment = Center центрирует строку относительно координаты x; когда Alignment = Far, координата x считается конечной точкой строки, а при значении Alignment = Near (по умолчанию) строка начинается с позиции x. На рис. 8 показано три варианта этого параметра (near, far и center), при этом для примеров в верхней половине рисунка указан ограничивающий прямоугольник, а в нижней задана только пара координат x и y (верхний левый угол нарисованной линии). Обратите внимание: в системе с направлением текста справа налево результаты были бы другими.

 

Рис. 8. Эффект от применения свойства Alignment зависит от наличия или отсутствия ограничивающего прямоугольника

Все примеры DrawString из этого раздела включены во второй проект, PlayingWithDrawString (его можно скачать вместе с исходным кодом, прилагаемым к этой статье). Фактически я использовал этот проект и для создания всех изображений, представленных в данном разделе, поэтому то, что вы видите, действительно соответствует тому, что вы получаете!

Обработка столбцов

Во многих отчетах, особенно в табличных, данные состоят из столбцов. Для каждого столбца необходимо определить ряд параметров, в том числе источник данных столбца (скажем, поле в источнике данных), ширину столбца на странице, шрифт, выравнивание содержимого столбца и т. д. Так как основным содержанием моего табличного отчета являются столбцы, я решил создать специальный класс ColumnInformation, а затем пользоваться набором (collection) объектов этого типа, чтобы определить, какие столбы (точнее - поля) должны быть в каждой строке данных. Этот класс содержит всю информацию, необходимую для правильного отображения всех столбцов, и даже два свойства (HeaderFont и HeaderText), зарезервированные на случай, если в будущем мне захочется добавить к коду отчета строку заголовка. Для облегчения восприятия листинга я не привожу закрытые члены свойств и сам код процедур. Полный исходный код см. в материалах, которые можно скачать для этой статьи.

 

Public Class ColumnInformation

    Public Event FormatColumn(ByVal sender As Object, _

        ByRef e As FormatColumnEventArgs)



    Public Function GetString(ByVal Value As Object)

        Dim e As New FormatColumnEventArgs()

        e.OriginalValue = Value

        e.StringValue = CStr(Value)

        RaiseEvent FormatColumn(CObj(Me), e)

        Return e.StringValue

    End Function



    Public Sub New(ByVal Field As String, _

            ByVal Width As Single, _

            ByVal Alignment As StringAlignment)

        m_Field = Field

        m_Width = Width

        m_Alignment = Alignment

    End Sub



    Public Property Field() As String

    Public Property Width() As Single

    Public Property Alignment() As StringAlignment

    Public Property HeaderFont() As Font

    Public Property HeaderText() As String

    Public Property DetailFont() As Font



End Class

Public Class FormatColumnEventArgs

    Inherits EventArgs

    Public Property OriginalValue() As Object

    Public Property StringValue() As String

End Class

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

 

Public Sub FormatCurrencyColumn(ByVal sender As Object, _

    ByRef e As FormatColumnEventArgs)

    Dim incomingValue As Decimal

    Dim outgoingValue As String

    incomingValue = CDec(e.OriginalValue)

    outgoingValue = String.Format("{0:C}", incomingValue)

    e.StringValue = outgoingValue

End Sub

Прежде чем выводить отчет, я заполняю ArrayList объектами ColumnInformation, при необходимости присоединяя обработчики FormatColumn.

 

Dim Columns As New ArrayList()

Public Sub PrintDoc()

    Columns.Clear()



    Dim titleInfo As _

           New ColumnInformation("title", 2, StringAlignment.Near)

    Columns.Add(titleInfo)



    Dim authorInfo As _

           New ColumnInformation("author", 2, StringAlignment.Near)

    Columns.Add(authorInfo)



    Dim bookPrice As _

           New ColumnInformation("author", 2, StringAlignment.Near)

    AddHandler bookPrice.FormatColumn, AddressOf FormatCurrencyColumn

    Columns.Add(bookPrice)



    Me.PrintPreviewDialog1.ShowDialog()

End Sub

Для каждой строки в источнике данных код PrintDetailRow перебирает объекты в ArrayList и выводит содержимое каждого столбца. Код этой функции см. в материалах для скачивания; загляните в него, чтобы понять, как там используются возможности, описанные в разделе Вывод текста, для формирования столбцов.

Класс TabularReport

До сих пор я рассматривал отдельные части задачи создания отчета, но в конечном итоге нужно собрать все эти части воедино и создать компонент, позволяющий легко генерировать табличные отчеты (рис. 1). Я создал новый класс, наследующий от PrintDocument, так что его можно использовать со всеми существующими средствами печати типа PrintDialog, PrintPreviewDialog и PrintPreviewControl. Полный исходный код достаточно длинный, так что я расскажу об этапах его разработки, а затем поясню, как применять такой класс в ваших приложениях.

Добавление свойств

Чтобы вы могли настраивать табличный отчет, мне пришлось добавить к классу массу свойств, позволяющих настраивать столбцы, указывать источник данных и изменять общий вид всего отчета.

Настройка столбцов

В дополнение к классу ColumnInformation я мог бы создать строго типизированный класс набора для хранения нескольких экземпляров ColumnInformation, что сравнительно несложно сделать с помощью Strongly Typed Collection Generator с Web-сайта GotDotNet. Я не стал двигаться в этом направлении, так как мне хотелось, чтобы доступ ко всем столбцам осуществлялся через мой класс; вместо этого я решил просто воспользоваться ArrayList и создать в классе отчета несколько методов для строго типизированного доступа к этому списку.

 

Protected m_Columns As New ArrayList()

Public Function AddColumn(ByVal ci As ColumnInformation) As Integer

    Return m_Columns.Add(ci)

End Function



Public Sub RemoveColumn(ByVal index As Integer)

    m_Columns.RemoveAt(index)

End Sub



Public Function GetColumn(ByVal index As Integer) As ColumnInformation

    Return CType(m_Columns(index), ColumnInformation)

End Function



Public Function ColumnCount() As Integer

    Return m_Columns.Count

End Function



Public Sub ClearColumns()

    m_Columns.Clear()

End Sub

Настройка внешнего вида отчета

Мой класс отчета довольно гибок, так как его пользователи могут настроить все шрифты и кисти, а также высоту каждого раздела отчета. Для этого я предусмотрел открытые процедуры-свойства и внутренние переменные, инициализируемые значениями по умолчанию. Я также создал свойства DefaultReportFont и DefaultReportBrush, позволяющие указывать объекты Font и Brush, применяемые по умолчанию ко всему отчету, если не заданы более специфичные свойства. Код первого из них приведен ниже, а второго - опущен, поскольку он делает практически те же, что и первый (только с объектом Brush).

 

Protected m_DefaultReportFont As Font = _

    New Font("Arial", 12, FontStyle.Bold, GraphicsUnit.Point)

Protected m_HeaderFont As Font

Protected m_FooterFont As Font

Protected m_DetailFont As Font



Public Property DefaultReportFont() As Font

    Get

        Return m_DefaultReportFont

    End Get

    Set(ByVal Value As Font)

        If Not Value Is Nothing Then

            m_DefaultReportFont = Value

        End If

    End Set

End Property



Public Property HeaderFont() As Font

    Get

        If m_HeaderFont Is Nothing Then

            Return m_DefaultReportFont

        Else

            Return m_HeaderFont

        End If

    End Get

    Set(ByVal Value As Font)

        m_HeaderFont = Value

    End Set

End Property



Public Property FooterFont() As Font

    Get

        If m_FooterFont Is Nothing Then

            Return m_DefaultReportFont

        Else

            Return m_FooterFont

        End If

    End Get

    Set(ByVal Value As Font)

        m_FooterFont = Value

    End Set

End Property



Public Property DetailFont() As Font

    Get

        If m_DetailFont Is Nothing Then

            Return m_DefaultReportFont

        Else

            Return m_DetailFont

        End If

    End Get

    Set(ByVal Value As Font)

        m_DetailFont = Value

    End Set

End Property

Кроме объектов Font и Brush, я реализовал свойства, позволяющие управлять высотой разделов отчета (устанавливая минимальную и максимальную высоту для раздела Detail).

 

Protected m_HeaderHeight As Single = 1

Protected m_FooterHeight As Single = 1

Protected m_MaxDetailRowHeight As Single = 1

Protected m_MinDetailRowHeight As Single = 0.5



Public Property HeaderHeight() As Single

    Get

        Return m_HeaderHeight

    End Get

    Set(ByVal Value As Single)

        m_HeaderHeight = Value

    End Set

End Property



Public Property FooterHeight() As Single

    Get

        Return m_FooterHeight

    End Get

    Set(ByVal Value As Single)

        m_FooterHeight = Value

    End Set

End Property



Public Property MaxDetailRowHeight() As Single

    Get

        Return m_MaxDetailRowHeight

    End Get

    Set(ByVal Value As Single)

        m_MaxDetailRowHeight = Value

    End Set

End Property



Public Property MinDetailRowHeight() As Single

    Get

        Return m_MinDetailRowHeight

    End Get

    Set(ByVal Value As Single)

        m_MinDetailRowHeight = Value

    End Set

End Property

Настройка источника данных

Чтобы создать отчет, нужен доступ к данным, поэтому я добавил свойство, принимающее экземпляр DataView, строки которого перебираются в перегруженной версии OnPrintPage.

 

Protected m_DataView As DataView

Public Property DataView() As DataView

    Get

        Return m_DataView

    End Get

    Set(ByVal Value As DataView)

        m_DataView = Value

    End Set

End Property



Protected Function GetField(ByVal row As DataRowView, ByVal fieldName As String) As Object

    Dim obj As Object = Nothing

    If Not m_DataView Is Nothing Then

        obj = row(fieldName)

    End If

    Return obj

End Function



'важный фрагмент из OnPrintPage

Dim rowCounter As Integer



e.HasMorePages = False



For rowCounter = currentRow To Me.DataView.Count - 1

    Dim currentRowHeight As Single = _

         PrintDetailRow(leftMargin, _

            currentPosition, Me.MinDetailRowHeight, _

            Me.MaxDetailRowHeight, width, _

            e, Me.DataView(rowCounter), True)



     If currentPosition + currentRowHeight < footerBounds.Y Then

        'поместится на странице

        currentPosition += _

         PrintDetailRow(leftMargin, currentPosition, _

            MinDetailRowHeight, MaxDetailRowHeight, _

            width, e, Me.DataView(rowCounter), False)

     Else

        e.HasMorePages = True

        currentRow = rowCounter

        Exit For

     End If

Next

Печать отдельных разделов отчета

Одних только свойств недостаточно, в конце концов отчет нужно напечатать. Но я постоянно обращаюсь ко всем этим свойствам, проверяя, что результат печати совпадает с указанными пользователем параметрами. Печать отчета контролируется процедурой OnPrintPage базового класса (PrintDocument), перегруженной в моем классе-потомке; именно оттуда вызываются процедуры печати отдельных разделов (PrintPageHeader, PrintPageFooter и PrintDetailRow). Как и в ранее приведенных примерах, эти процедуры достаточно велики и в силу этого их код не приводится в статье. Вместо этого предлагаю скачать код и запустить тестовое приложение.

Применение класса TabularReport

После того как я закончил класс TabularReport, для создания отчета-примера понадобилось всего несколько шагов.

Шаг 1: создание экземпляра TabularReport

Сначала создайте экземпляр моего класса. Вообще-то это можно сделать и прямо в коде, но, так как этот класс - наследник PrintDocument, вы можете добавить мой компонент в окно инструментария (toolbox) и перетащить его на разрабатываемую Windows-форму.

Шаг 2: получение данных

Чтобы получить данные для отчета, вам потребуется экземпляр DataView, который вы можете получить, заполнив DataTable результатами, возвращенными хранимой процедурой или SQL-запросом.

 

Private Function GetData() As DataView

        Dim Conn As New OleDbConnection(connectionString)

        Conn.Open()



        'Вариант с базой данных Access

        Dim getOrdersSQL As String = _

        "SELECT Customers.ContactName, Orders.OrderID, Orders.OrderDate,

Orders.ShippedDate, Sum([UnitPrice]*[Quantity]) AS Total FROM (Customers

INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID) INNER JOIN

[Order Details] ON Orders.OrderID = [Order Details].OrderID GROUP BY

Customers.ContactName, Orders.OrderID, Orders.OrderDate,

Orders.ShippedDate ORDER BY Orders.OrderDate"



        Dim getOrders As New OleDbCommand(getOrdersSQL, Conn)

        getOrders.CommandType = CommandType.Text

        Dim daOrders As New OleDbDataAdapter(getOrders)

        Dim orders As New DataTable("Orders")

        daOrders.Fill(orders)

        Return orders.DefaultView

    End Function
	

В этом примере я получаю данные от базы данных Access, поэтому я применяю классы OleDB, но вы можете использовать любую базу по своему выбору: все, что нужно моему отчету, - результирующий DataView.

Шаг 3: настройка столбцов отчета

На этом шаге надо создать объекты ColumnInformation для всех столбцов отчета и добавить их в набор столбцов TabularReport. Я поместил этот код в процедуру SetupReport, настраивающую столбцы и другие детали внешнего вида отчета. Я уже рассматривал объекты ColumnInformation в разделе Обработка столбцов, а здесь покажу, как они настраиваются в приложении-примере.

 

Private Sub SetupReport()

    'Получение данных

    Dim orders As DataView

    orders = GetData()



    'Настройка столбцов

    Dim contactName _

        As New ColumnInformation("ContactName", 2, _

               StringAlignment.Near)

    Dim orderID _

        As New ColumnInformation("OrderID", 1, _

               StringAlignment.Near)

    Dim orderDate _

        As New ColumnInformation("OrderDate", 1, _

               StringAlignment.Center)

    AddHandler orderDate.FormatColumn, AddressOf FormatDateColumn



    Dim shippedDate _

        As New ColumnInformation("ShippedDate", 1, _

               StringAlignment.Center)

    AddHandler shippedDate.FormatColumn, AddressOf FormatDateColumn



    Dim total _

        As New ColumnInformation("Total", 1.5, _

               StringAlignment.Far)

    AddHandler total.FormatColumn, AddressOf FormatCurrencyColumn



    With TabularReport1

        .ClearColumns()

        .AddColumn(contactName)

        .AddColumn(orderID)

        .AddColumn(orderDate)

        .AddColumn(shippedDate)

        .AddColumn(total)

        .DataView = orders

        .HeaderHeight = 0.5

        .FooterHeight = 0.3

        .DetailFont = New Font("Arial", _

                          12, FontStyle.Regular, _

                          GraphicsUnit.Point)

        .DetailBrush = Brushes.DarkKhaki

        .DocumentName = "Order Summary From Northwinds Database"

    End With

End Sub

При настройке трех столбцов - orderDate, shippedDate и total - для каждого из них вызывается AddHandler, связывающий столбцы с процедурами, форматирующими их данные в виде денежного значения или даты.

 

Public Sub FormatCurrencyColumn(ByVal sender As Object, _

    ByRef e As FormatColumnEventArgs)

    Dim incomingValue As Decimal

    Dim outgoingValue As String

    If Not IsDBNull(e.OriginalValue) Then

        incomingValue = CDec(e.OriginalValue)

    Else

        incomingValue = 0

    End If

    outgoingValue = String.Format("{0:C}", incomingValue)

    e.StringValue = outgoingValue

End Sub



Public Sub FormatDateColumn(ByVal sender As Object, _

    ByRef e As FormatColumnEventArgs)

    Dim incomingValue As Date

    Dim outgoingValue As String

    If Not IsDBNull(e.OriginalValue) Then

        incomingValue = CDate(e.OriginalValue)

        outgoingValue = incomingValue.ToShortDateString()

    Else

        outgoingValue = "-"

    End If

    e.StringValue = outgoingValue

End Sub

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

Шаг 4: печать или предварительный просмотр документа

Теперь вызывая метод Print класса TabularReport или один из элементов управления Windows Forms (а именно PrintDialog, PrintPreviewDialog или PrintPreviewControl), способных взаимодействовать с объектом PrintDocument, вы можете печатать или просматривать настроенный отчет. Любой элемент управления, содержащий свойство типа PrintDocument, прекрасно работает с экземпляром TabularReport, так как наследует от класса PrintDocument.

Заключение

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


Может пригодится:


Автор: Дункан Мак-Кензи
Прочитано: 7852
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру