在处理数据时,经常需要将数据以表格的形式整齐地排列在行和列中,以便小数点能够对齐在一列中。尽管GDI API没有内置的文本右对齐功能,但可以通过巧妙地使用前导空白来模拟,不过这只适用于固定宽度的字体。一旦使用可变宽度的字体,精心设计的格式就会变得混乱。本文介绍的CMatrixPrinter类旨在解决这个问题。它是一个包装类,包装了一个HDC打印机设备上下文句柄。它具有设置打印矩阵的成员函数,以及输出文本到特定行和列的函数,无论是左对齐还是右对齐。几乎可以使用任何字体,因为几乎所有的字体都能很好地工作。
CMatrixPrinter类提供了四个构造函数,它们要么创建打印机设备上下文,要么包装提供的设备上下文。然后调用StartDoc()和StartPage() API,并设置文本背景模式为TRANSPARENT。
CMatrixPrinter(const TCHAR * DocName, LPDEVNAMES pDevNames, LPDEVMODE pDevMode = NULL);
CMatrixPrinter(const TCHAR * DocName, const TCHAR * Device, const TCHAR * Driver = _T("WINSPOOL"), LPDEVMODE pDevMode = NULL);
CMatrixPrinter(const TCHAR * DocName, HDC hDC);
CMatrixPrinter(DOCINFO & DocInfo, HDC hDC);
构造函数参数包括文档文件名、打印机设备名称、打印提供程序名称(默认为"WINSPOOL")和设备上下文句柄。
~CMatrixPrinter(void);
析构函数调用EndPage()和EndDoc() API,并在构造函数中创建设备上下文时调用DeleteDC()。
int SetRows(int Rows, const TCHAR * FontFaceName = NULL);
int SetColumns(int Columns, const TCHAR * FontFaceName = NULL);
这两个函数设置矩阵。它们尝试设置指定的行数或列数,但由于字体的创建方式,可能不会完全精确。SetRows计算所需的列数,SetColumns计算行数。使用CMatrixPrinter::GetMatrixSize()获取确切的行数和列数。
int PlaceTextL(const TCHAR * Text, int Row, int LeftColumn);
int PlaceTextR(const TCHAR * Text, int Row, int RightColumn);
使用这些函数在矩阵上放置文本。文本将在指定的列上左对齐或右对齐。
int NextPage(void);
调用EndPage()和StartPage() API以将打印推进到下一页。
int GetPrintJobID(void);
int GetPageNumber(void);
SIZE GetMatrixSize(void);
RECT GetGlyphRect(int Row, int Column);
这些函数用于检索打印作业标识符、当前页码、打印矩阵的尺寸以及矩阵中字形矩形的位置和大小。
operator HDC(void);
使用此运算符检索包装的打印机设备上下文句柄。
int _tmain(int argc, _TCHAR* argv[]) {
// 设置并调用打印对话框
PRINTDLG pd = {0};
pd.lStructSize = sizeof(PRINTDLG);
pd.Flags |= PD_RETURNDC;
PrintDlg(&pd);
// 将打印机对话框返回的设备上下文包装到CMatrixPrinter类对象中
CMatrixPrinter mp(_T("Matrix Printer Test"), pd.hDC);
// 使用可变宽度字体设置矩阵
mp.SetRows(60, _T("Pristina"));
// 绘制演示网格线以突出显示矩阵
HPEN Pen = CreatePen(PS_DOT, 1, RGB(128, 128, 128));
HPEN OldPen = (HPEN)SelectObject(mp, Pen);
for (int x = 0; x < mp.GetMatrixSize().cx; ++x) {
for (int y = 0; y < mp.GetMatrixSize().cy; ++y) {
RECT rc = mp.GetGlyphRect(y, x);
Rectangle(mp, rc.left, rc.top, rc.right, rc.bottom);
}
}
SelectObject(mp, OldPen);
// 首先在第一行放置正常文本,以显示字体在正常绘制时的外观
RECT rc = mp.GetGlyphRect(1, 1);
TextOut(mp, rc.left, rc.top, Text, _tcslen(Text));
// 然后在矩阵的第二行放置相同的文本,以显示现在的外观
mp.PlaceTextL(Text, 2, 1);
// 现在在第20列放置一些数字,右对齐
for (int i = 0; i < _countof(Numbers); ++i) {
TextColour TC(mp, Numbers[i] < 0.0 ? RGB(255, 0, 0) : RGB(0, 0, 255));
mp.PlaceTextR(pja::CBuilder<>(_T("${0}"), Fixed(Numbers[i], 2)).c_str(), i + 4, 20);
}
return 0;
}