精通
英语
和
开源
,
擅长
开发
与
培训
,
胸怀四海
第一信赖
锐英源精品开源心得,转载请注明出处:锐英源,www.wisestudy.cn,孙老师作品,联系电话13803810136。
2010年7月份,锐英源承接了个VC++报表项目,这个项目细节请点击。本文描述了这个项目里使用的开源例子。例子出处为:http://www.codeproject.com/KB/miscctrl/easyreports.aspx。经过我们修改,这个报表模型具备以下特点:中国式报表:坚向,格内嵌入多行,适合于定制查询数据集类结果,MFC框架和视图。锐英源是VC++报表开发技术专家。
One of the problems while writing programs in C++ is that the tools and API that one can use to generate reports is very limited. For instance, there are a plethora of classes to view (CView, CHtmlView etc.) but no reporting API. 用C++开发,报表工具很少。
The present tool here is a simple API that can be used to print fairly complex business style reports.
Almost all reports have the following standard elements: 这里提供的例子是个简单的报表接口,它能打印相当复杂业务类型的报表,同样其它报表里有的元素,这个接口也会有:
Given the above, we present a simple yet powerful layout class. This class is called CEasyReport. There are basically three functions that are really the key: 元素描述说过了,说说我们强大的布局类吧。这个类命名为CEasyReport。关键的三个功能如下:
1、SetDataCols, which starts a new tabular section. The arguments are a array of CColInfo items describing each column in the section. The description includes the column name, the column width and alignment. The column name can contain '\n', which sets up a multiple row column heading. 它开始一个新的数据节。参数是CColInfo数组,数组里每一项描述了数据节里的一列。CColInfo里的成员有列名称,列宽度和对齐方式。列名称里包含有'\n',让它能设置多行列标题。
AtTab(0,"Lal, Vipul"); // write the name in col 0 AtTab(1,"12/04/1980"); // write DOB in col 1We could have overloaded this function to print a long, double, a CTime etc, but we left that to the user to suit his/her own requirements. 对不同类型的重载实现,请自己酌情考虑。
3、Call NextRow to advance the printing to the next row. Typically, you would do this when you have printed all the columns in one row. This function checks to see if there is enough space on the current page to print another row, and ejects the page if not. 调用NextRow来滚动到下行进行打印输出。典型情况下,当你打印过行内所有列后,才调用这个函数。这个函数检查看当前页是否有足够的空间来打印另外一行,如果没有,则离开当前页。
Whenever you need to start a new section, simply call SetDataCols. Thus, if your report consists of a main section followed by a summary section for each group, your typical code loop would be: 当你需要开始新的数据区时,只需要简单地调用SetDataCols。这样,如果你的报表在主数据区后面后续的有每个分组需要的统计数据区,你典型的循环代码如下:
m_Recordset.Open(...);
while(! m_Recordset.IsEof())
{
SetDataCols(ColsInMainSection...)
m_CurGroup = m_Recordset.GroupColumnData;
do { AtTab(0,DataForGroupCol); ... other columns... NextRow(); // advance the printer row m_Recordset.next; // compute summary items
} while( !m_Recordset.IsEof() && m_Recordset.GroupColumnData == m_CurGroup);
// Start a summary section...
SetDataCols(ColsInSummarySection);
AtTab(...); // print summary data
NextRow();
}
While generating a report content, no actual image is generated. Rather, the generation process simply generates "report objects". For instance, a CTextObject contains the text to be written and the rectangular co-ordinates for the text. Thus, every page consists of a set of "report objects". Later, when we are called to print or preview the report, we simply call upon the report object to draw itself. Since every such object has the co-ordinates with respect to the top of the page, we can draw any page. Thus, the user can select to print or preview page 3,5 and 7 of the report and we simply draw all objects for those pages. The current version supports a Text object, a Line object and a Date and PageNumber object. 这里只是生成了报表内容,并不是实际的打印输出内容。相反,生成过程只是生成“报表对象”。比如,一个CTextObject包含要输出的文本和包围文本的矩形坐标。这样,每页包含了一组“报表对象”。随后,当我们调用来打印或预览报表,我们简单地调用报表对象来绘制自己。因为每个这样的报表对象都有相对于页面顶部的坐标,我们能绘制任何页面。这样,用户能够选择打印或预览3,5,7页,我们简单地只用绘制这些页面的对象。当前的版本支持Text对象,线对象和日期和PageNumber对象。
Note: The current version simply generates these report objects in memory. You might want to serialize these to disk if the report is really long. 注意:当前版本只是在内存里生成报表对象。如果你的报表非常长,有必要把它序列化到磁盘上。
The API has a RepeatHdr flag, which forces the column headings to be printed on every page. When this flag is off, the column header is printed every time the tabular section is set up. Therefore, if you have only one section, i.e your report consists of one long list, and you like the column headings to be printed on every page, set this flag on. API函数里有一个RepeatHdr标记,它会强制在每页打印列标题。哪标志为假,列标题只在表格化的数据区设置时才打印。这样,如果你只有一个数据区,也就是说,你的报表包含了很长的列表,你愿意让每页都打印标题,设置这个标记。
The report also has "SuppressBlankHdr" flag. When this flag is set, the column headings are not printed unless you print something in one of the columns. "SuppressBlankHdr"标记让空的列标题不进行打印。
The report also has a mode in which one can print a "paragraph" of text. The text is aligned within the page margins. 报表有"word-wrap"模式,在这个模式下可以按“段落”来打印文本。文本在页面边距位置上对齐。
The demo report project generates a report of all employees, grouped by department. The demo project also has a small Access database which has two tables - an Employee table and a Departments table. The SQL Query used for the report is:演示的报表对象生成一个所有雇员的报表,以部门进行分组。演示工程同样有一个小型的Access数据库,库里有2个表-雇员表和部门表。SQL查询如下:
SELECT * FROM employees INNER JOIN departments ON employees.DeptID = departments.DeptID GROUP BY employees.DeptID
我们添加了新的CTextArrayBox类,这个类支持了格内嵌入多行的处理,同时对于行高自动调整方面进行了代码修改。CTextArrayBox类声明如下:
/***********************************************************************
* CTextArrayBox class is a 字符串数组 element in the report
by shw为了支持子格化,一个单元格能够垂直输出多行文本
锐英源软件,www.wisestudy.cn,孙红伟,使用请注明出处
**********************************************************************/
class CTextArrayBox : public CElement
{
DECLARE_SERIAL( CTextArrayBox )
virtual void Serialize(CArchive & ar);
protected:
CTextArrayBox(); // default constructor is protected and used only while seralizing
int m_FontIndex;
CStringArray m_AryText;
int m_Align;
int m_iRowHeight;//行高,孙红伟添加
public:
CTextArrayBox( CRect *inRect, CStringArray &inStr, int inFontIndex, int iRowHeight,int inAlign = DT_LEFT )
: CElement(inRect), m_FontIndex(inFontIndex)
{
m_iRowHeight=iRowHeight;
m_AryText.Append(inStr);
m_Align = inAlign;
}
virtual ~CTextArrayBox();
void SetAlign(int inAlign) { m_Align = inAlign;}
virtual void Draw(CDC *inDC);
};
为报表准备数据的代码如下:
long aCurDept;
int aCount;
double aTotSalary, aMaxSalary;
CEmpList aList;
CString aTemp;
try
{
aList.Open();
}
catch(CDaoException *ex)
{
AfxMessageBox(ex->m_pErrorInfo->m_strDescription);
throw ex;
}
aList.MoveFirst();
m_Report.SetReportTitle("Employees Salaries, by Department");
m_Report.Start();
// print a long paragraph
m_Report.AtTab(0,
"Hello World ! This is a simple long paragraph "
"which needs to be Word-wrapped. Basically, if "
"we set the data colums to 0, the report goes into "
"a paragraph mode. In this mode, long paragraphs of "
"text can be inserted and the text will be wrapped "
"between the left and the right margins. You can insert "
"a paragraph of text anywhere on the report. Comming soon "
"bullet and numbered paragraph styles !");
m_Report.NextRow();
while(!aList.IsEOF())
{
// Initalize all totals etc at the start of a group
aCurDept = aList.m_DeptID;
aTotSalary = aMaxSalary = 0;
aCount = 0;
//m_Report.SetDataCols(NULL);
//m_Report.AtTab(0,aTemp);
// Set up a tabular section for the main section
m_Report.SetDataCols(s_Cols,4);
m_Report.AtTab(0,aList.m_DeptName);
do
{
aTotSalary += aList.m_Salary;
if( aList.m_Salary > aMaxSalary)
aMaxSalary = aList.m_Salary;
aTemp.Format("%s,%s",(LPCSTR)aList.m_LastName,(LPCSTR)aList.m_FirstName);
m_Report.AtTab(1,aTemp);
aTemp.Format("%5.2lf",aList.m_Salary);
m_Report.AtTab(2,aTemp);
//添加子格化的单元格by shw
CStringArray sarrr;
sarrr.Add("123");
sarrr.Add("321");
sarrr.Add("442");
sarrr.Add("444lk");
int iAH=m_Report.AtTab(3,sarrr);//保存高度用于行高的指定by shw
m_Report.NextRow(iAH);//by shw指定行高
aList.MoveNext();
++aCount;
}
while( !aList.IsEOF() && aList.m_DeptID == aCurDept);
// write a summary for this department
m_Report.SetDataCols(s_SummaryCols,3);
aTemp.Format("%3d",aCount);
m_Report.AtTab(0,aTemp );
aTemp.Format("%5.2lf",aTotSalary);
m_Report.AtTab(1,aTemp);
aTemp.Format("%5.2lf",aMaxSalary);
m_Report.AtTab(2,aTemp);
m_Report.SetDataCols(NULL);
m_Report.NextRow();
m_Report.NextRow(); // insert a blank row between two groups
}
m_Report.End(); // close report
aList.Close(); // close database
m_Report.GotoPage(0);