Reports for Silverlight или построитель отчетов c шаблонами и с группировкой

WPF, MVVM, Silverlight | создано: 28.07.2011 | опубликовано: 28.07.2011 | обновлено: 13.01.2024 | просмотров: 7824 | всего комментариев: 8

Эта статья является всего лишь демонстрацией работы с отчетами в Silverlight. В статье показано как строить отчет с использованием шаблонов и как включить группировку данных, а также как задавать результаты группировки (aggregations).

Установка Reports

Библиотека Calabonga.Silverlight.Reports специально упакована в nuget-пакет чтобы можно было с легкостью устанавливать/обновлять/удалять эту библиотеку. Давайте предположим, что Nuget Manager у Вас уже установлен, и тогда можно сразу перейти к установке пакет отчетов. Я создал новый Silverlight-проект чтобы продемонстрировать работу библиотеки. Нажимаем на проекте правую кнопку мыши и запускаем менеджер Nuget:

image

В открывшемся окне менеджера в поле поиска вбиваем “calabonga” и перед нами все пакеты, которые доступны под брендом “calabonga”:

image

На данный момент нам потребуется первый в списке “Silverlight Reporting”. А следом за ним я установлю “Samlpe classes and data” чтобы было “что” печать и группировать.

Кнопка “Печать” и “Предварительный просмотр”

На главную страницу проекта добавлю две кнопки, а перед этим разобью на области сетку. А также добавлю ContentControl для того чтобы в него поместить предварительный просмотр сформированного отчета перед печатью:

<Grid x:Name="LayoutRoot"
            Background="White">
    <Grid.RowDefinitions>
        <RowDefinition Height="36" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Button Content="Печать"
                    Height="23"
                    HorizontalAlignment="Left"
                    Margin="13,6,0,0"
                    Name="print"
                    VerticalAlignment="Top"
                    Width="75"
                    Click="print_Click" />
    <Button Content="Просмотр"
                    Height="23"
                    HorizontalAlignment="Left"
                    Margin="94,6,0,0"
                    Name="preview"
                    VerticalAlignment="Top"
                    Width="75"
                    Click="preview_Click" />
    <ContentControl Grid.Row="1"
                    VerticalContentAlignment="Stretch"
                    HorizontalContentAlignment="Stretch"
                    Name="previewBox" />
</Grid>

Разметка простая, давай те теперь создадим шаблон.

Создаем шаблон печати (ItemTemplate)

Добавлю в проект новый Silverlight UserControl. Пусть называется он Report1.xaml:

image

Надо добавить namespace для того чтобы Report стал доступен:

xmlns:clb="http://schemas.calabonga.com"

Я во всех своих библиотеках предпочитаю использовать такой namespace, что не приходилось “бегать, искать” что в какой сборке. Вы можете использовать такой же подход в своих сборках.

Теперь пришло время подготовить данные для печати. Создадим пару-тройку переменных:

private List<Person> printData;
private Report1 report1;

Теперь вернемся в разметку самого шаблона, надо же к нему как-нибудь обращаться и для этого дадим ему имя, пусть зовут его CalaReport:

<Grid x:Name="LayoutRoot"   Background="White">
   <clb:Report Title="Первый отчет" x:Name="CalaReport">  </clb:Report>
</Grid>

А теперь добавим шаблон (ItemTemplate), который будет “рисовать” Person. Если посмотреть в библиотеку SilverlightSampleData, то можно увидеть свойства класса, которые можно отобразить в шаблоне:

image

Мой шаблон будет такой:

<clb:Report.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition />
                    <ColumnDefinition />
                    <ColumnDefinition />
                    <ColumnDefinition />
                    <ColumnDefinition />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <Border Grid.Column="0"
                                Style="{StaticResource border}">
                    <TextBlock Text="{Binding Path=[Name]}"
                                         Grid.Column="0" />
                </Border>
                <Border Grid.Column="1"
                                Style="{StaticResource border}">
                    <TextBlock Text="{Binding Path=[Gender]}"
                                         Grid.Column="1" />
                </Border>
                <Border Grid.Column="2"
                                Style="{StaticResource border}">
                    <TextBlock Text="{Binding Path=[IsMember]}"
                                         Grid.Column="2" />
                </Border>
                <Border Grid.Column="3"
                                Style="{StaticResource border}">
                    <TextBlock Text="{Binding Path=[Age]}"
                                         Grid.Column="3" />
                </Border>
                <Border Grid.Column="4"
                                Style="{StaticResource border}">
                    <TextBlock Text="{Binding Path=[Description]}"
                                         Grid.Column="4" />
                </Border>
                <Border Grid.Column="5"
                                Style="{StaticResource border}">
                    <TextBlock Text="{Binding Path=[Weight]}"
                                         Grid.Column="5" />
                </Border>
                <Border Grid.Column="6"
                                Style="{StaticResource border}">
                    <TextBlock Text="{Binding Path=[Country]}"
                                         Grid.Column="6" />
                </Border>
            </Grid>

        </DataTemplate>
    </clb:Report.ItemTemplate>

Обратите внимание на то, как осуществляется привязка – в квадратных скобках. Просто мне кажется “так красивее” :)

И добавим еще и стили, которые использованы в шаблоне для отображения.

<UserControl.Resources>
    <Style TargetType="TextBlock"
                    x:Key="header">
        <Setter Property="FontSize"
                        Value="11" />
        <Setter Property="HorizontalAlignment"
                        Value="Center" />
        <Setter Property="FontWeight"
                        Value="Bold" />
    </Style>
    <Style TargetType="Border"
                    x:Key="border">
        <Setter Property="BorderBrush"
                        Value="Gray" />
        <Setter Property="BorderThickness"
                        Value="1" />
        <Setter Property="Margin"
                        Value="1" />
        <Setter Property="Padding"
                        Value="3" />
    </Style>
</UserControl.Resources>

Напишем код обработки нажатия кнопок, я приведу весь код, чтобы больше к нему не возвращаться, потому что он не измениться:

public partial class MainPage : UserControl
{
    private List<Person> printData;
    private Report1 report1;

    public MainPage()
    {
        InitializeComponent();
        Loaded += new RoutedEventHandler(MainPage_Loaded);
    }

    void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        report1 = new Report1();
        printData = People.GetPeople();
        report1.CalaReport.ItemsSource = printData;
    }

    private void print_Click(object sender, RoutedEventArgs e)
    {
        report1.CalaReport.Print();
    }

    private void preview_Click(object sender, RoutedEventArgs e)
    {
        previewBox.Content = report1.CalaReport.GetPreview(previewBox);
    }
}

Ну а теперь, давайте попробуем напечатать. Ура! Что-то видно на превью, да и на принтер что-то улетело… зажужжал гадина:

image

Надо бы HeaderTemplate и FooterTemplate добавить – для красоты и эстетики:

<clb:Report.PageHeaderTemplate>
    <DataTemplate>
        <Grid MinHeight="40"
                    Background="Wheat">
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Border Grid.Column="0"
                            Style="{StaticResource border}">
                <TextBlock Text="Имя"
                                        Style="{StaticResource header}" />
            </Border>
            <Border Grid.Column="1"
                            Style="{StaticResource border}">
                <TextBlock Text="Пол"
                                        Style="{StaticResource header}" />
            </Border>
            <Border Grid.Column="2"
                            Style="{StaticResource border}">
                <TextBlock Text="Участник"
                                        Style="{StaticResource header}" />
            </Border>
            <Border Grid.Column="3"
                            Style="{StaticResource border}">
                <TextBlock Text="Возраст"
                                        Style="{StaticResource header}" />
            </Border>
            <Border Grid.Column="4"
                            Style="{StaticResource border}">
                <TextBlock Text="Описание"
                                        Style="{StaticResource header}" />
            </Border>
            <Border Grid.Column="5"
                            Style="{StaticResource border}">
                <TextBlock Text="Вес"
                                        Style="{StaticResource header}" />
            </Border>
            <Border Grid.Column="6"
                            Style="{StaticResource border}">
                <TextBlock  Text="Страна"
                                        Style="{StaticResource header}" />
            </Border>
        </Grid>
    </DataTemplate>
</clb:Report.PageHeaderTemplate>

и нижняя часть:

<clb:Report.ReportFooterTemplate>
   <DataTemplate>
       <Grid HorizontalAlignment="Stretch"
                   MinHeight="30"
                   Background="LightGray">
           <TextBlock Text="Этот отчет сгенерирован при помощи
                           библиотеки Calabonga.Silverlight.Reports." />
       </Grid>
   </DataTemplate>
</clb:Report.ReportFooterTemplate>

Нажимаем печать… И!…

Untitled-3

Страница уже выглядит по другому. Про Вас не обращать внимание на шапку, немного кривая вышла, что, кстати, подтверждает чистоту эксперимента.

Группировка и Агрегирующие функции

Эта библиотека была изначально задумана как “способная группировать и агрегировать данные при печати. Пришло время проверить на что она способна. Перед тем как добавить группировку надо подключить mscorlib.dll:

xmlns:sys="clr-namespace:System;assembly=mscorlib"

Добавим группировку по стране (Country):

<clb:Report.Groups>
    <clb:GroupDefinitions>
        <clb:GroupDefinitions.GroupedFields>
            <sys:String>Country</sys:String>
        </clb:GroupDefinitions.GroupedFields>
    </clb:GroupDefinitions>
</clb:Report.Groups>

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

<sys:String>Gender</sys:String>

Между 4 и 5 строками. Теперь раз добавлена группировка следует добавить и шаблоны для группировки (Header и Footer), иначе прилетит ошибка. Шаблон группировки выглядит так:

<clb:Report.GroupHeaderTemplate>
   <DataTemplate>
       <Border BorderBrush="Black"
                       BorderThickness="1"
                       Background="Silver"
                       MinHeight="40">
           <StackPanel Orientation="Horizontal">
               <TextBlock Text="{Binding Path=[Country]}"
                                       Margin="30,0,20,0"
                                       Style="{StaticResource header}" />
               <TextBlock Text="{Binding Path=[Gender]}"
                                       Margin="30,0,20,0"
                                       Style="{StaticResource header}" />
               <TextBlock Text="{Binding Path=[Weight]}"
                                       Margin="30,0,20,0"
                                       Style="{StaticResource header}" />
               <TextBlock Text="{Binding Path=[Age]}"
                                       Margin="30,0,20,0"
                                       Style="{StaticResource header}" />
           </StackPanel>
       </Border>
   </DataTemplate>
</clb:Report.GroupHeaderTemplate>
<clb:Report.GroupFooterTemplate>
           <DataTemplate>
               <Border BorderBrush="Black"
                               BorderThickness="1"
                               Background="Gainsboro"
                               MinHeight="30">
                   <StackPanel Orientation="Horizontal">
                       <TextBlock Text="Итого вес:"
                                            Margin="0,0,20,0" />
                       <TextBlock Text="{Binding Path=[Weight]}"
                                            Style="{StaticResource header}"
                                            Margin="0,0,60,0" />
                       <TextBlock Text="Возраст средний:"
                                            Margin="0,0,20,0" />
                       <TextBlock Text="{Binding Path=[Age]}"
                                            Style="{StaticResource header}" />
                   </StackPanel>
               </Border>
           </DataTemplate>
       </clb:Report.GroupFooterTemplate>

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

<clb:GroupDefinitions.Aggregations>
    <clb:AggregateFieldDefinition Field="Weight">
        <clb:AggregateFieldDefinition.AggregateFunctions>
            <clb:AggregateFunctionDefinition Function="Sum" />
        </clb:AggregateFieldDefinition.AggregateFunctions>
    </clb:AggregateFieldDefinition>
    <clb:AggregateFieldDefinition Field="Age">
        <clb:AggregateFieldDefinition.AggregateFunctions>
            <clb:AggregateFunctionDefinition Function="Average" />
        </clb:AggregateFieldDefinition.AggregateFunctions>
    </clb:AggregateFieldDefinition>
</clb:GroupDefinitions.Aggregations>

Компилируем… Запускаем…. Нажимаем предварительный просмотр… И… вуаля:

image

Шаблоны подготовленные мной конечно оставляют желать лучшего по части эстетического вида, но как бы там не было отчет на бумаге.

Так же хотелось бы добавить, что существуют еще и другие варианты шаблонов. А так же достаточное количество событий.

image

Надеюсь, что контрол будет востребован. На этом всё. Пишите комментарии.

Обновление Версия 1.1  от 18.08.2011: Добавлено ReportHeaderTemplate (Отображется один раз на первой странце отчета).

Обновление Версия 1.2 от 23.08.2011: Теперь если вы хотите  отобразить номер строки, то для этого достаточно просто указать в щаблоне ItemTemplate текстовое поле с привязкой к данным по свойству [Index], например:

<TextBlock Text="{Binding Path=[Index]}" />

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

Обновление Версия 1.3 29.09.2011: Добавлены свойства к отчету, в которых можно хранить данные, например, для заголовков и для оснований отчета. Фамилия должностного лица, название компании, место для подписи с инициалами.

В коде:

view3.Report.Properties.Add("buhgalter","Страшная Татьяна Леонидовна");

В шаблоне:

<clb:Report.ReportHeaderTemplate>
  <DataTemplate>
    <Grid HorizontalAlignment="Stretch"
        VerticalAlignment="Stretch"
        Background="Magenta">
      <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
      </Grid.RowDefinitions>
      <TextBlock Text="Главный бухгалтер:"
            FontSize="34"
            Grid.Row="0" />
      <TextBlock Text="{Binding Path=Properties[buhgalter]}"
            FontSize="23"
            Grid.Row="1" />
    </Grid>
  </DataTemplate>
</clb:Report.ReportHeaderTemplate>

Ссылки по теме

Как установить Nuget Manager.
Описание стандарта

Комментарии к статье (8)

Hi, i trying your examples but it has some errors in code. Can you please send me a simple example. 

Tnx

Hi, Ervin, I uploaded demo project which showed in article. You can download it and if you have questions I'll answer with pleasure.

Здравствуйте! У меня вопрос по библиотеке Calabonga.Silverlight.Reports.
привязка данных к шаблону <TextBlock Text="{Binding Path=[Age]}" Grid.Column="3" /> в квадратных скобках [Age] - нужна не только для красоты  она просто не работает без квадратных скобок!  Также при добавлении формата Text="{Binding Path=[Weight], StringFormat='0:N2'}" - форматирование не происходит! Как-то можно ето исправить ?

Для os...
Вот не готов я ответить на ваш вопрос. Первое что приходит в голову, так это "лишние одиночные кавычки"... В крайнем случае, прошу обратиться к MSDN
http://msdn.microsoft.com/en-us/library/system.windows.data.bindingbase.stringformat(v=vs.110).aspx
или
в этой статье
 

Уважаемый Calabonga, вопрос по печати отчета. Если ли возможность допечатывать  не умещающиеся столбцы (минимальная ширина столбца обязательна)? Хотелось бы выводить на печать как можно больше информации :) Буду рад любой подсказке, в какую сторону копать.

К сожалению, не очень понимаю о чем идет речь. Что значит "допечатывать"?

Calabonga, ситуация такова, есть отчет в десять столбцов фиксированной ширины  MinWidth="145" (уже делать не вижу смысла). При попытке печати данного отчета не умещающиеся столбцы "обрезаются", как-нибудь можно распечатать не уместившиеся столбцы?