交叉报表
# 交叉报表
这种报表具有表格结构,这意味着它由行和列组成。在设计时,不知道输出表将具有多少行和列。这就是为什么报表不仅会向下发展(如前面所述的报表类型),还会横向发展。交叉表报表的典型示例如下所示。我们来看看表中的元素:
在图示中,我们看到一个包含两行(行)和四列的表,其中“a”和“b”是行标题,“1”,“2”,“3”和“4”是列标题,“a1“......”a4“和”b1“......”b4“是表格中的单元。要构建这样的报表,我们只需要一组数据(来自查询或表),它有三个字段并包含以下值:
a 1 a1
a 2 a2
a 3 a3
a 4 a4
b 1 b1
b 2 b2
b 3 b3
b 4 b4
2
3
4
5
6
7
8
您可以看到第一个字段包含一个行号,第二个字段包含一个列号,第三个字段包含指定行和列交叉处的单元格内容。输出报表时,FastReport在内存中创建一个表并用数据填充它。因此,表格会动态扩展,创建尚不存在的行和列。标题可以出现在多个级别,如下所示:
在此示例中,列的数字或索引是复合的,即它由两个值组成。此报表由以下数据生成:
a 10 1 a10.1
a 10 2 a10.2
a 20 1 a20.1
a 20 2 a20.2
b 10 1 b10.1
b 10 2 b10.2
b 20 1 b20.1
b 20 2 b20.2
2
3
4
5
6
7
8
这里第一个字段包含行索引,如前所述,第二个和第三个字段包含列索引,最后一个字段包含单元格值。在处理具有复杂标题的交叉表数据时,查看FastReport如何构建内存表:
从此内存表输出报表时,FastReport将连接具有相同值且位于同一级别的标题单元格。这是一个更复杂的交叉表报表,包含中间和总计:
此报表源自与以前相同的数据。以新颜色突出显示的单元格中的值将自动计算,并且不会出现在原始数据集中。
# 1. 构建交叉报表
现在让我们从理论转向实践。我们将构建一份简单的交叉表报表。
使用SQL构造以下内容的数据集,将数据集导入至设计器中。
SELECT '李明' AS FName,2018 AS FYear,89300 AS FSalary
UNION
SELECT '李明' AS FName,2019 AS FYear,98430 AS FSalary
UNION
SELECT '李明' AS FName,2020 AS FYear,118300 AS FSalary
UNION
SELECT '刘东' AS FName,2018 AS FYear,79580 AS FSalary
UNION
SELECT '刘东' AS FName,2019 AS FYear,91300 AS FSalary
UNION
SELECT '刘东' AS FName,2020 AS FYear,12300 AS FSalary
2
3
4
5
6
7
8
9
10
11
打开报表设计器界面,使用报表-数据...
菜单项连接至数据源,然后从设计器的对象工具栏中选择DB交叉表对象
,并单击设计页面将对象放置于此,此时会自动打开交叉报表编辑器:
上图中显示项目的相关说明:
- 可用数据源的下拉列表。
- 所选数据源中的字段列表; 此列表中的字段可以拖动到编号为3,4或5的列表中。
- 生成行(行)标题的字段列表。
- 生成列标题的字段列表。
- 生成表格单元格的字段列表。
- 表结构预览。
- 结构选项:显示标题,总数等。
您只能在此编辑器中使用鼠标进行更改。对于我们的示例,只需要将列表2中的字段拖到列表3,4和5(在上图中)。之后,单击[确定]
按钮关闭编辑器。交叉表对象现在显示其结构:
预览报表时,您将看到类似于此的表格:
# 2. 更改外观
让我们修改交叉表对象的外观。我们要做的第一件事是更改标题颜色并显示“总计”。
将标题颜色更改为灰色,依次单击FYear
,FName
和Grand Total
单元格,然后使用工具栏上的背景颜色
按钮/选择灰色。
我们还可以使用一组预定义的样式。这些在交叉表编辑器中可用。在对象列表中选择交叉报表对象使用鼠标右键,点击编辑
按钮打开交叉报表编辑器,在其中单击选择样式
并选择其中的一个样式应用。
要更改两个Grand Total
文本,请双击每个单元格,这将打开熟悉的文本编辑器,我们可以在其中键入合计
:
要格式化货币值,请选择第一个单元格(在我们的示例中为[FName]
和[FYear]
的交集),右键单击以显示上下文菜单,然后选择显示格式...
。
选择所需的格式并关闭格式编辑器。所有这些都会产生报表:
# 3. 使用函数
在我们的示例中,我们在合计
行中看到每个员工三年以上工资的总和。可以使用以下任何聚合函数:
SUM(合计)
:数值的和。MIN(最小值)
:数值的最小值。MAX(最大值)
:数值的最大值。AVG(平均值)
:数值的平均值。COUNT(计数)
:记录的数量。
我们在示例中使用MIN
函数。打开交叉报表编辑器,在列表5(FSalary
字段项)中单击向下箭头。
从下拉列表中选择最小值
功能。现在我们可以将总单元格中的文本从合计
更改为最小值
。完成的报表如下所示:
# 4. 排序
默认情况下,行和列按数字或字母顺序按升序排列,具体取决于数据类型。可以为行和列单独设置排序模式。排序模式是:“按升序排列”,“按降序排列”和“不执行排序”。如果没有排序,则行/列将以默认数据库顺序显示。
让我们在示例中更改列排序。让年份按递减顺序排列。要执行此操作,请打开交叉表编辑器,选择[FYear]
列元素并通过单击向下箭头并选择降序来更改排序模式:
关闭编辑器并预览报表。它看起来像这样:
# 5. 带有复合标题的表
我们之前的示例包含每行和单列标题的单个值。让我们看一下具有多个值的复合头的交叉表设计。我们将以上数据进行修改,显示的SQL内容如下:
SELECT '李明' AS FName,2018 AS FYear,1 AS FMonth,19300 AS FSalary
UNION
SELECT '李明' AS FName,2018 AS FYear,2 AS FMonth,23300 AS FSalary
UNION
SELECT '李明' AS FName,2018 AS FYear,3 AS FMonth,13800 AS FSalary
UNION
SELECT '李明' AS FName,2019 AS FYear,2 AS FMonth,18430 AS FSalary
UNION
SELECT '李明' AS FName,2019 AS FYear,3 AS FMonth,12330 AS FSalary
UNION
SELECT '李明' AS FName,2020 AS FYear,1 AS FMonth,18300 AS FSalary
UNION
SELECT '刘东' AS FName,2018 AS FYear,1 AS FMonth,19580 AS FSalary
UNION
SELECT '刘东' AS FName,2018 AS FYear,3 AS FMonth,18580 AS FSalary
UNION
SELECT '刘东' AS FName,2019 AS FYear,2 AS FMonth,12300 AS FSalary
UNION
SELECT '刘东' AS FName,2020 AS FYear,3 AS FMonth,9800 AS FSalary
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
我们分别添加了包含月份编号和工作日数的“月”字段。可以从这些数据中构建几个不同的报表,例如:每年的员工工资,按月分解。我们会得到什么样的报表?它必须类似于前一个示例中的报表,但将年度数据按月细分。必须以与以前相同的方式设置交叉表对象。这次我们还将FMonth
字段拖到列标题列表中,如下所示:
在预览中,我们看到以下报表:
请注意,FastReport已自动添加一列中间总计,每年后显示一列。通过取消选择[FYear]
列元素的小计
标志,可以在交叉表编辑器中关闭它:
另请注意,列标题列表中的最后一个列元素从不具有小计
标记(包括单个元素的大小写)。在我们的示例中,我们不需要每个月的中间总计,因此可以关闭小计
标记。如果使用中间总计还有另一个特征:最好将中间总数作为“年+年总数”而不是“总计”。在报表页面上的交叉表对象中,双击中间的总单元格,然后在文本编辑器中键入:Total for [Value]
。
在报表构造中,[Value]
表达式将替换为上面单元格中列标题的实际值:
# 6. 调整单元格宽度
查看前面的插图,很明显FastReport会自动调整单元格宽度,以便更大的单元格值确实适合单元格。然而,在某些情况下,这可能并不理想,因为超宽的列可能看起来很难看。
让我们看看控制单元宽度的三种方法。控制单元格宽度的最简单方法是在中间总计的文本中添加换行符,即:
Total
for [Value]
2
这样可以让表格看起来更紧凑。
但是,有些情况下很难或不可能手动地断线。因此,交叉表对象具有MinWidth
和MaxWidth
属性(指单元格宽度)。这两个属性只能通过对象检查器来访问。默认情况下,MinWidth
为0,MaxWidth
为200,这在大多数情况下是足够的。
控制单元格宽度的第二种方法是根据特殊要求改变这些值。因此在我们的示例中,我们可以将MinWidth
和MaxWidth
都设置为50,这意味着数据单元必须至少为50像素宽,即使单元格值适合较少的像素。对于大单元格值,单元格宽度限制为MaxWidth
值,单元格中的文本将根据需要中断。所以我们的例子现在看起来像这样:
控制单元宽度的第三种方法是手动更改单元格宽度。为此,必须将AutoSize
属性设置为False
。然后可以使用鼠标更改交叉表单元格宽度。在报表页面交叉表对象中,鼠标光标在单元格边框上更改形状,因此允许拖动边框。以下是可以实现的示例:
请记住,如果关闭自动调整大小,则交叉表不会自动调整单元格的宽度和高度,您可能会在报表预览中看到类似的内容:
如果是这样,那么只需稍微增加单元格宽度。
# 7. 字体颜色和突出显示
有时需要突出显示值和/或更改字体颜色。我们在组报表示例中查看了这一点,其中我们对文本对象使用了条件突出显示。条件突出显示对我们来说也很有用。要在示例报表中添加突出显示,假设我们需要更改大于16000的值的字体颜色 - 选择表示表格单元格的对象,并通过单击工具栏上的突出显示按钮/来设置突出显示参数。熟悉的突出显示编辑器窗口将打开可以设置此条件的位置:
Value > 16000
并将字体设置为红色:
这就是所需要的。单击[确定]
按钮关闭编辑器并预览报表:
同样,如果需要,可以突出显示总值,还可以使用背景和框架颜色按钮突出显示单元格和线条。
# 8. 在脚本中管理交叉表
如果上面显示的任何方法无法实现所需的报表,则可以使用报表脚本。交叉报表对象具有以下事件:
事件 | 描述 |
---|---|
OnAfterPrint | 在打印表格后调用事件 |
OnBeforePrint | 在打印表之前调用事件 |
OnCalcHeight | 在计算表中行的高度之前调用事件,事件处理程序可以设置为所需的高度,如果需要隐藏行,则为“0” |
OnCalcWidth | 在计算表中的列宽之前调用事件,事件处理程序可以设置为所需的宽度,如果需要隐藏列,则为“0” |
OnPrintCell | 在显示表格单元格之前调用事件,事件处理程序可以修改单元格设计或其内容 |
OnPrintColumnHeader | 在显示列标题之前调用事件,事件处理程序可以修改标题单元格的设计或内容 |
OnPrintRowHeader | 在显示行标题之前调用事件,事件处理程序可以修改标题单元格的设计或内容 |
我们可以在这些事件中使用“交叉表”对象的以下方法:
//返回表中的列数
function ColCount: Integer;
//返回表中的行数
function RowCount: Integer;
//如果'Index'列是总计,则返回True
function IsGrandTotalColumn(Index: Integer): Boolean;
//如果'Index'行是总数,则返回True
function IsGrandTotalRow(Index: Integer): Boolean;
//如果索引列是小计,则返回True
function IsTotalColumn(Index: Integer): Boolean;
//如果'Index'行是小计,则返回True
function IsTotalRow(Index: Integer): Boolean;
//为表添加一个值
procedure AddValue(const Rows, Columns, Cells: array of Variant);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
让我们展示如何突出显示第三列的内容。在报表设计页面上选择交叉表对象,在对象检查器中单击事件选项卡,找到OnPrintCell
事件并通过双击事件右侧的
空列表在代码页上创建处理程序名称。脚本编辑器将显示为您创建的基本声明,然后在声明的空begin ... end
块中添加所需的代码:
procedure Cross1OnPrintCell(Memo: TfrxMemoView;
RowIndex, ColumnIndex, CellIndex: Integer;
RowValues, ColumnValues, Value: Variant);
begin
if ColumnIndex = 2 then
Memo.Color := clRed;
end;
2
3
4
5
6
7
预览报表时,我们会看到以下内容:
要突出显示列标题,请以类似方式创建OnPrintColumnHeader
事件处理程序:
procedure DBCross1OnPrintColumnHeader(Memo: TfrxMemoView;
HeaderIndexes, HeaderValues, Value: Variant);
begin
if (VarToStr(HeaderValues[0]) = '2019') and
(VarToStr(HeaderValues[1]) = '3') then
Memo.Color := clRed;
end;
begin
end.
2
3
4
5
6
7
8
9
10
11
报表预览效果:
这是脚本的工作原理:在表的数据区域中打印单元格之前调用OnPrintCell
事件处理程序(请注意,表标题中的单元格调用OnPrintColumnHeader
或OnPrintRowHeader
处理程序)。OnPrintCell
处理程序参数包括:指向“文本”的链接表示单元格的对象(“备注”参数)和单元格的“地址”作为行,列和单元格的位置(如果交叉表包含多级单元格,则单元格相关)作为RowIndex
,分别为ColumnIndex
和CellIndex
参数。参数列表还具有指定为Variants的标题值(RowValues
和ColumnValues
参数)和保存单元格内容的Value
Variant参数。
在我们的示例中,使用RowIndex
和ColumnIndex
指定“地址”更容易。列和行的编号从“0”开始,因此ColumnIndex = 2
指的是第三列。我们还可以通过查看其数据内容来指定正确的列。
procedureCross1OnPrintCell(Memo: TfrxMemoView;
RowIndex, ColumnIndex, CellIndex: Integer;
RowValues, ColumnValues, Value: Variant);
begin
if (VarToStr(ColumnValues[0]) = '2019') and
(VarToStr(ColumnValues[1]) = '3') then
Memo.Color := clRed;
end;
2
3
4
5
6
7
8
RowValues
和ColumnValues
参数是Variant类型的数组,具有零基数。“0”元素位于表格标题的最高级别,“1”元素位于下一级别,等等。在我们的示例中,ColumnValues [0]
包含年份,ColumnValues [1]
包含月份。
为什么需要VarToStr
功能?这可以防止类型转换期间出错。使用Variant类型时,FastReport尝试自动将字符串转换为数字格式,这反过来可能会在转换Total
和Grand Total
列值时导致错误。
在输出列标题单元格期间调用OnPrintColumnHeader
事件处理程序 。参数列表类似于OnPrintCell
处理程序的参数列表,但在这种情况下,单元格的“地址”(HeaderIndexes
和HeaderValues
参数)采用不同的形式。HeaderValues
参数与ColumnValues
和RowValues
中的值保持相同。OnPrintCell
处理程序。HeaderIndexes
参数也是Variant类型的值数组,并且包含不同形式的标题单元格的地址:“0”元素是表标题中最高级别的索引,“1”是为了阐明单元格编号的原理,请参
考下图:
在我们的示例中,使用HeaderValues
参数更容易,但可以使用以下处理程序:
procedure Cross1OnPrintColumnHeader(Memo: TfrxMemoView;
HeaderIndexes, HeaderValues, Value: Variant);
begin
if (HeaderIndexes[0] = 0) and (HeaderIndexes[1] = 2) then
Memo.Color := clRed;
end;
2
3
4
5
6
# 9. 调整行、列大小
可以使用OnCalcWidth
和OnCalcHeight
事件处理程序调整表列和行的宽度和高度。让我们看看如何通过以下示例增加某一年年月份的列的宽度。 创建一个OnCalcWidth
事件处理程序:
procedure DBCross1OnCalcWidth(ColumnIndex: Integer;
ColumnValues: Variant; var Width: Extended);
begin
if (VarToStr(ColumnValues[0]) = '2019') and
(VarToStr(ColumnValues[1]) = '3') then
Width := 100;
end;
2
3
4
5
6
7
要在我们的示例中隐藏列,只需将“宽度”设置为零。请注意,不会重新计算总计,因为此时表已填充了值。
# 10. 手动填写表格
交叉报表有两中对象类型,DB交叉报表以及交叉报表,到目前为止,我们已经处理了第一个对象,它附加到数据库表中的数据。报表运行后,对象会自动填充。让我们看看第二个对象交叉表。
“交叉表”对象未附加到数据库表。因此必须手动填充数据。“交叉表”对象具有与“DB交叉表”对象类似的编辑器,但不同之处在于必须指定表的标题和单元格的维度,而不是由数据库字段设置:
让我们通过一个例子展示“交叉表”对象的用法。在报表设计页面上放置一个“交叉表”对象,并设置其属性如上所示:行标题中的级别数为1,列标题中为2,单元格中为1.让我们填写表格使用OnBeforePrint
事件处理程序的数据:
procedure Cross1OnBeforePrint(Sender: TfrxComponent);
begin
with Cross1 do
begin
AddValue(['Ann'], [2001, 2], [1500]);
AddValue(['Ann'], [2001, 3], [1600]);
AddValue(['Ann'], [2002, 1], [1700]);
AddValue(['Ben'], [2002, 1], [2000]);
AddValue(['Den'], [2001, 1], [4000]);
AddValue(['Den'], [2001, 2], [4100]);
end;
end;
2
3
4
5
6
7
8
9
10
11
12
在处理程序中,数据通过TfrxCrossView.AddValue
方法添加到表中。此方法有三个参数,每个参数都是Variant类型的数组。第一个参数是行值,第二个参数是列值,第三个参数是单元格值。请注意,每个数组中的值的数量必须与对象的设置匹配!在我们的示例中,对象在行标题中有一个级别,列标题中有两个级别,一个单元格级别,因此AddValue
方法的Variant数组参数需要一个值用于行,两个值用于列,一个值用于单元格。
在预览时,报表输出为:
AddValue
方法也可用于“DB交叉表”对象。这允许插入不是从附加到对象的数据源导出的数据。如果以这种方式添加任何数据,则还会使用数据源中的数据进行汇总。
# 11. 将外部对象添加至表中
外部对象(如线条,形状,图片)可以放在交叉表中。例如,您可能需要以图形形式显示某些数据。让我们看一个使用形状来显示基本进度条的示例:
如果单元格值小于13000,则显示深红色条,如果小于30000,则显示黄色;如果大于30000,则显示绿色。
让我们从我们的报表开始。将以下SQL生成的数据集导入报表设计器:
SELECT '李明' AS FName,2018 AS FYear,1 AS FMonth,19300 AS FSalary
UNION
SELECT '李明' AS FName,2018 AS FYear,2 AS FMonth,23300 AS FSalary
UNION
SELECT '李明' AS FName,2018 AS FYear,3 AS FMonth,13800 AS FSalary
UNION
SELECT '李明' AS FName,2019 AS FYear,2 AS FMonth,18430 AS FSalary
UNION
SELECT '李明' AS FName,2019 AS FYear,3 AS FMonth,12330 AS FSalary
UNION
SELECT '李明' AS FName,2020 AS FYear,1 AS FMonth,18300 AS FSalary
UNION
SELECT '刘东' AS FName,2018 AS FYear,1 AS FMonth,19580 AS FSalary
UNION
SELECT '刘东' AS FName,2018 AS FYear,3 AS FMonth,18580 AS FSalary
UNION
SELECT '刘东' AS FName,2019 AS FYear,2 AS FMonth,12300 AS FSalary
UNION
SELECT '刘东' AS FName,2020 AS FYear,3 AS FMonth,9800 AS FSalary
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在报表页面上放置一个DB交叉报表对象,并设置如下所示的内容:
关闭此对象的Autosize
属性并通过鼠标拖动单元格的竖线以设置列宽:
现在,通过在对象工具栏上选择矩形对象并将其放在单元格中,将形状对象添加到表中:
将其Height
和Width
属性均更改为0.2
并移动使其处于合适的位置。再添加两个相似的矩形如上图所示进行排列。
现在创建一个脚本,显示正确数量的彩色形状(取决于单元格的值)。为此,请选择单元格并双击OnBeforePrint
创建事件处理程序:
在事件处理程序中编写以下代码(注意形状名称,以便它们与您的对象匹配):
procedure DBCross1Cell0OnBeforePrint(Sender: TfrxComponent);
begin
//去掉边框的颜色
DBCross1Object1.Frame.Color := clNone;
DBCross1Object2.Frame.Color := clNone;;
DBCross1Object3.Frame.Color := clNone;;
if Value < 13000 then
begin
// 第一个形状对象
DBCross1Object1.Color := clMaroon; // 深红色
// 第二个形状对象
DBCross1Object2.Color := clWhite;
// 第三个形状对象
DBCross1Object3.Color := clWhite;
end
else if Value < 30000 then
begin
DBCross1Object1.Color := $00CCFF; // 黄色
DBCross1Object2.Color := $00CCFF;
DBCross1Object3.Color := clWhite;
end
else
begin
DBCross1Object1.Color := $00CC98; // 绿色
DBCross1Object2.Color := $00CC98;
DBCross1Object3.Color := $00CC98;
end;
end;
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
预览报表,这与本节顶部显示的类似。
# 12. 交叉报表设置
让我们看一下交叉表编辑器中提供的其他一些设置。
前六个选项允许您显示或隐藏各种表格元素。
自动大小
选项已众所周知,取消此项的勾选后,允许手动设置表格宽度和高度。
围绕单元格的边框
选项允许在单元格周围绘制框架。
往下打印然后交叉
选项确定如何在多个页面上打印表格。以下两个示例显示了此选项的工作原理(请注意页面顺序):
选项关闭时的打印顺序:
选项开启时的打印顺序:
在新页面上打印头
选项确定是否在每个新预览界面上打印表格标题。
如果表格单元格中有两个或更多值,则使用并排单元格
选项。它确定这些单元格值是并排打印还是一个堆叠在另一个之上(默认值)。
如果它们包含相同的值,则连接相等单元格
选项会水平合并单元格:
在对象查看器中还有以下属性可使用:
AddWidth
,AddHeight
:向单元格宽度或高度添加指定的空间量。 FastReport引擎计算单元格大小时会考虑它(必须启用自动大小
选项)。NextCross
:指向另一个交叉表对象的指针,该对象将打印到此一侧。NextCrossGap
:设定两个相邻交叉报表对象之间的间隙。