在Windows中使用打印机时,在调用一系列与打印相关的GDI绘图函数的背后,实际上启动了一系列模块之间复杂的交互过程,包括 GDI32库模块、打印机设备驱动程序库模块(带.DRV后缀的文件)、Windows后台打印处理程序(print spooler)和其他有关模块。在开始为打印机编程之前,让我们先来看一下这个过程是如何工作的。
本节必须掌握的知识点:
打印和后台处理
打印机设备环境
第79练:获取显示器及打印机设备信息
第80练:最简单的打印程序
13.1.1打印和后台处理
■打印的一般处理过程
●应用程序首先调用CreateDC或者PrintDIg函数得到打印机设备环境的句柄。加载打印机设备驱动程序,并且进行初始化。
●然后再调用StartDoc函数,开始一个新文档。StartDoc 会由GDI模块处理。GDI模块调用打印机驱动程序中的Control函数,通知设备驱动程序做好打印准备。
●调用StartPage函数开始一页。GDI函数则把文字或图形显示到每个文档页面里。GDI模块会把应用程序针对打印机设备环境使用的任何GDI函数存储到硬盘上的图元文件(打印程序生成的临时文件)。这些图元文件的文件名以字符串“EMF”(表示“增强型图元文件”)开头,后缀是.TMP。我们将在第十七章详细讲述图元文件。
●完成一个页面后,调用EndPage函数结束这一页。
●打印机驱动程序必须把存储在图元文件中的各个绘图命令转化成适用于打印机的输出。打印机驱动程序把每一条带(页码扫描块)的打印机输出传给GDI模块,而GDI模块则把这个输出存到另一个临时文件中。GDI模块采用进程间调用告诉后台打印处理程序新的打印作业已就绪。
●是否继续处理下一页?否,打印输出。当应用程序完成了定义第一个页面的GDI函数调用以后,调用EndPage函数。然后再次循环处理下一页。
●打印完毕,调用EndDoc函数结束此过程。
■图元文件的打印输出的过程
●打印机驱动程序通常都采用“分带”技术,将页面分成多个长方形的带。GDI 模块从打印机驱动程序得到每条带的尺寸,然后设置一个大小与这条带相等的剪裁区域;
●对图元文件中每一个绘图函数都调用一次打印机驱动程序中的Output函数。这个过程被称为“把图元文件送入设备驱动程序”。对于设备驱动程序定义在页面上的每一条带, GDI模块都必须把整个图元文件送入设备驱动程序。这个过程完成后,图元文件就可以删掉了。
●对于每条带,打印机驱动程序把这些绘图函数转换成相应的需要在打印机上实现的输 出。这些输出的格式将根据打印机的不同而不同。对于点阵打印机,输出格式是一个控制系列集合,包括图形系列。(为了帮助构造这个输出,打印机驱动程序可以调用一些GDI 模块中的辅助例程。)对于支持高级页面合成语言(比如PostScript)的激光打印机,打印机的输出将会用这种语言表达。由GDI模块负责与不同打印机驱动之间的兼容,应用程序只需调用相关的GDI函数就可以了。
■打印驱动程序
打印机驱动程序把每一条带的打印机输出传给GDI模块,而GDI模块则把这个输出存到另一个临时文件中。这个文件以字符串“SPL”开头,以“.TMP”作为后缀。在整个页完成以后,GDI模块采用进程间调用(inter-process call)告诉后台打印处理程序新的打印作业已就绪。应用程序然后可以处理下一个页面。当应用程序处理完所有要打印的页面以后,要调用EndDoc函数来表示打印作业己经完成。图13-1显示了应用程序、GDI模块和打印机驱动程序之间的交互。
图13-1 应用程序、GDI模块、打印驱动程序与后台处理程序之间的交互
■打印后台处理程序
Windows后台打印处理程序实际上是由下面几个组件构成的:
后台打印处理程序组件 | 描述 |
打印请求程序 | 把数据流转发给打印提供程序 |
本地打印提供程序 | 创建用于本地打印机的后台处理程序 |
网络打印提供程序 | 创建用于网络打印机的后台处理程序 |
打印处理器 | 进行后台反向处理,也就是将进入后台的“与设备无关的”数据转换成适用于目标打印机的形式 |
端口监视程序 | 控制打印机连接的端口 |
语言监视程序 | 控制具备双向通信能力的打印机去设置设备配置和监视打印机状态 |
后台处理程序将应用程序从一些涉及打印的工作中解脱出来。Windows在启动时加载后台打印处理程序,所以应用程序开始打印的时候后台打印处理程序就已经在运行中。当程序打印文档时,GDI模块创建包含打印机输出的文件。后台打印处理程序的工作就是把这些文件发给打印机。它从GDI模块那儿得知有新的打印作业,于是开始读入文件,并把文件直接传给打印机。为了传输这些文件,后台处理程序使用了各种通信函数与连接打印机的并口、串口、USB端口或网络端口进行通信。后台处理程序把文件传输给打印机以后,就会删除输出时产生的临时文件。这个过程如图13-2所示。
图13-2 后台打印处理程序的操作
这个过程的绝大部分对于应用程序来说都是透明的。从应用程序的角度来说,“打印” 只发生在GDI模块把所有的打印机输出存储到硬盘文件这个阶段。在那之后,或者甚至是在那之前(如果打印是由另一个线程处理的话应用程序已经可以随便做其他事情了。文档的真正打印不再是应用程序的责任,而是后台打印处理程序的责任了。用户则负责暂停打印作业、
改变作业优先级或者在必要情况下取消打印。比起让应用程序实时打印并且等打印完一页再打印下一页的做法,这种安排使得程序可以更快地“打印”。
13.1.2打印机设备环境
在 Windows 操作系统中,打印机设备环境(Printer Device Context)用于绘制和输出图形到打印机的设备。
打印机设备环境是通过使用 GDI 函数 CreateDC 或 CreateDCW 来创建的,其中指定了打印机的驱动程序、打印机名称和其他相关参数。创建打印机设备环境后,可以使用 GDI 函数对其进行绘图操作,如绘制图形、文本和图像,实现打印输出。
■以下是一些常用的打印机设备环境相关的函数:
CreateDC 或 CreateDCW:用于创建打印机设备环境的函数,需要指定打印机驱动程序和打印机名称。
●CreateDC 函数或其变体函数CreateDCW,可以用于创建显示器设备上下文或打印机设备上下文,具体取决于传递给函数的参数。
以下是 CreateDC 函数的声明:
HDC CreateDC(
LPCTSTR lpszDriver, // 驱动程序名称或驱动程序文件路径
LPCTSTR lpszDevice, // 设备名称或设备文件路径
LPCTSTR lpszOutput, // 输出设备名称
const DEVMODE *lpInitData // 设备模式信息
);
返回值:
如果函数调用成功,将返回创建的设备上下文的句柄(HDC)。如果函数调用失败,将返回 NULL。
示例用法:
以下示例展示了如何使用 CreateDCW 函数创建打印机设备上下文:
#include <windows.h>
void PrintTextToPrinter(const wchar_t* printerName, const wchar_t* text) {
HDC hdcPrinter = CreateDCW(L"WINSPOOL", printerName, NULL, NULL);
if (hdcPrinter != NULL) {
// 在打印机设备上下文中绘制文本或进行其他打印操作
TextOutW(hdcPrinter, 100, 100, text, lstrlenW(text));
// 释放打印机设备上下文
DeleteDC(hdcPrinter);
}
}
int main() {
const wchar_t* printerName = L"Printer Name";
const wchar_t* text = L"Hello, Printer!";
PrintTextToPrinter(printerName, text);
return 0;
}
在上述示例中,我们使用 CreateDCW 函数创建了一个打印机设备上下文(hdcPrinter),然后使用 TextOutW 函数在打印机上绘制文本。最后,我们使用 DeleteDC 函数释放了打印机设备上下文。
【注意】示例中的 printerName 应该替换为实际的打印机名称。
●StartDoc:开始一个打印文档,标识一个打印作业的开始。
StartDoc 函数用于告诉打印机驱动程序开始打印一个文档,并指定文档的名称。
以下是 StartDoc 函数的声明:
int StartDoc(
HDC hdc, //设备上下文(DC)的句柄
const DOCINFO *lpdi //指向一个 DOCINFO 结构体的指针
);
返回值:
如果函数调用成功,返回值为大于零的唯一标识符,表示打印作业的标识。如果函数调用失败,返回值为小于零的错误代码。
DOCINFO 结构体的定义如下:
typedef struct _DOCINFO {
int cbSize; // 结构体的大小,用于指定结构体的字节数
LPCTSTR lpszDocName; // 文档的名称,将在打印队列中显示
LPCTSTR lpszOutput; // 输出设备的名称,可以为 NULL
LPCTSTR lpszDatatype; // 打印数据的类型,可以为 NULL
DWORD fwType; // 保留字段,应该设置为 0
} DOCINFO, *LPDOCINFO;
示例用法:
以下示例展示了如何使用 StartDoc 函数开始打印作业:
#include <windows.h>
void PrintTextToPrinter(const wchar_t* printerName, const wchar_t* docName, const wchar_t* text) {
HDC hdcPrinter = CreateDCW(L"WINSPOOL", printerName, NULL, NULL);
if (hdcPrinter != NULL) {
DOCINFO di = { sizeof(DOCINFO), docName, NULL, NULL, 0 };
int jobId = StartDoc(hdcPrinter, &di);
if (jobId > 0) {
// 在打印作业中绘制文本或进行其他打印操作
TextOutW(hdcPrinter, 100, 100, text, lstrlenW(text));
// 结束打印作业
EndDoc(hdcPrinter);
}
// 释放打印机设备上下文
DeleteDC(hdcPrinter);
}
}
int main() {
const wchar_t* printerName = L"Printer Name";
const wchar_t* docName = L"My Document";
const wchar_t* text = L"Hello, Printer!";
PrintTextToPrinter(printerName, docName, text);
return 0;
}
在上述示例中,我们使用 CreateDCW 函数创建了一个打印机设备上下文(hdcPrinter),然后使用 StartDoc 函数开始一个打印作业,并指定了文档名称。在打印作业中,我们使用 TextOutW 函数在打印机上绘制文本。最后,我们使用 EndDoc 函数结束打印作业,并使用 DeleteDC 函数释放打印机设备上下文。
●StartPage:开始打印作业的一个页面,用于指定页面的设置和准备。
StartPage 函数用于告诉打印机驱动程序开始打印一个页面。
以下是 StartPage 函数的声明:
int StartPage(
HDC hdc //设备上下文(DC)的句柄
);
函数说明:
StartPage 函数用于开始打印作业中的一个页面。
返回值:
如果函数调用成功,返回值为大于零的值。如果函数调用失败,返回值为小于零的错误代码。
●EndPage:结束当前打印作业的页面,完成页面的输出。EndPage 函数用于告诉打印机驱动程序当前页面的打印操作已经完成。
以下是 EndPage 函数的声明:
int EndPage(
HDC hdc //设备上下文(DC)的句柄
);
返回值:
如果函数调用成功,返回值为大于零的值。如果函数调用失败,返回值为小于零的错误代码。
●EndDoc:结束打印文档,标识打印作业的结束。EndDoc 函数用于告诉打印机驱动程序当前打印作业的打印操作已经完成。
以下是 EndDoc 函数的声明:
int EndDoc(
HDC hdc //设备上下文(DC)的句柄
);
函数说明:
EndDoc 函数用于结束当前打印作业的打印操作。
返回值:
如果函数调用成功,返回值为大于零的值。如果函数调用失败,返回值为小于零的错误代码。
●DeleteDC:删除打印机设备环境,释放相关资源。DeleteDC 函数用于释放由 CreateDC 或 CreateCompatibleDC 函数创建的设备上下文的资源。
以下是 DeleteDC 函数的声明:
BOOL DeleteDC(
HDC hdc //要删除的设备上下文的句柄
);
函数说明:
DeleteDC 函数用于删除设备上下文(DC)并释放与之关联的资源。
返回值:
如果函数调用成功,返回值为非零值(TRUE)。如果函数调用失败,返回值为零(FALSE)。
●Escape:用于发送特定的打印机命令和查询打印机信息。通过调用 Escape 函数,可以向设备发送特定的命令或请求,并获得相应的信息或效果。
以下是 Escape 函数的声明:
int Escape(
HDC hdc, //设备上下文(DC)的句柄
int nEscape, //指定要执行的操作的命令代码
int cbInput, //指定输入数据的字节数
LPCSTR lpszInData,// 指向输入数据的指针
LPVOID lpOutData//指向输出数据的缓冲区
);
返回值:
如果函数调用成功,返回值为大于或等于零的值,具体取决于所执行操作的命令代码。如果函数调用失败,返回值为小于零的错误代码。
示例用法:
以下示例展示了如何使用 Escape 函数查询打印机的状态:
#include <windows.h>
void QueryPrinterStatus(const wchar_t* printerName) {
HDC hdcPrinter = CreateDCW(L"WINSPOOL", printerName, NULL, NULL);
if (hdcPrinter != NULL) {
// 查询打印机状态
int status = Escape(hdcPrinter, QUERYESCSUPPORT,
sizeof(DWORD), NULL, NULL);
if (status > 0) {
// 打印机支持查询操作
if (status & PRINTER_STATUS_BUSY) {
// 打印机忙碌
printf("Printer is busy.\n");
} else {
// 打印机空闲
printf("Printer is idle.\n");
}
} else {
// 查询操作不受支持或出错
DWORD error = GetLastError();
printf("Failed to query printer status. Error code: %lu\n", error);
}
// 释放打印机设备上下文
DeleteDC(hdcPrinter);
}
}
int main() {
const wchar_t* printerName = L"Printer Name";
QueryPrinterStatus(printerName);
return 0;
}
在上述示例中,我们使用 CreateDCW 函数创建了一个打印机设备上下文(hdcPrinter),然后使用 Escape 函数执行 QUERYESCSUPPORT 命令来查询打印机的状态。如果返回的状态中包含 PRINTER_STATUS_BUSY 标志,则表示打印机正在忙碌中,否则表示打印机处于空闲状态。如果查询操作不受支持或出错,我们使用 GetLastError 函数获取错误代码并进行相应的处理。最后,我们使用 DeleteDC 函数释放打印机设备上下文。
使用打印机设备环境时,可以调用 GDI 函数(例如 CreatePen、CreateBrush、TextOut 等)来设置绘图对象、绘制文本和图形,然后使用 StartPage 和 EndPage 控制打印页面的输出,最后使用 EndDoc 结束打印作业。
需要注意的是,打印机设备环境与显示器设备环境(屏幕上的绘图环境)在功能和使用方式上有一些区别。打印机设备环境主要用于输出到打印机设备,而显示器设备环境主要用于在屏幕上显示图形。因此,在编写打印机输出代码时,需要考虑和处理一些特定于打印机的设置和操作,如页面设置、纸张大小、分辨率等。
13.1.3 第79练:获取显示器及打印机设备信息
/*------------------------------------------------------------------------
079 WIN32 API 每日一练
第79个例子DEVCAPS2.C:获取显示器及打印机设备信息
WM_SETTINGCHANGE消息
CheckMenuItem 函数
EnumPrinters 函数
AppendMenu 函数
OpenPrinter 函数
PrinterProperties 函数
GetMenuString 函数
CreateIC函数
(c) www.bcdaren.com 编程达人
-----------------------------------------------------------------------*/
#include <windows.h>
#include "resource.h"
#include <VersionHelpers.h>
#pragma warning(disable:4996)
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
void DoBasicInfo (HDC, HDC, int, int) ; //基本信息
void DoOtherInfo (HDC, HDC, int, int) ; //其他信息
void DoBitCodedCaps (HDC, HDC, int, int, int) ; //位图信息
//设备其他信息
typedef struct
{
int iMask;//标识符
TCHAR * szDesc;//描述信息
}
BITS ;
#define IDM_DEVMODE 1000 //预定义值
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("DevCaps2");
…(略)
return msg.wParam;
}
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
static TCHAR szDevice[32], szWindowText[64] ;
static int cxChar, cyChar, nCurrentDevice = IDM_SCREEN,nCurrentInfo = IDM_BASIC ; //基础信息菜单
static DWORD dwNeeded, dwReturned ;
static PRINTER_INFO_4 * pinfo4 ; //NT架构打印机信息数据结构
static PRINTER_INFO_5 * pinfo5 ; //DOS架构打印机信息数据结构
DWORD i ;
HDC hdc, hdcInfo ;
HMENU hMenu ;
HANDLE hPrint ;
PAINTSTRUCT ps ;
TEXTMETRIC tm ;
switch (message)
{
case WM_CREATE :
hdc = GetDC (hwnd) ;
SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT));//选取等宽字体
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ; //字符宽
cyChar = tm.tmHeight + tm.tmExternalLeading ; //字符高
ReleaseDC (hwnd, hdc) ;
// 继续执行
//更改系统参数后,应用程序应将WM_SETTINGCHANGE发送给所有顶级窗口
case WM_SETTINGCHANGE:
//检索由指定菜单项激活的下拉菜单或子菜单的句柄
hMenu = GetSubMenu (GetMenu (hwnd), 0);//获取DEVICE菜单下的子菜单句柄 //从指定菜单中删除项目,指示uPosition给出菜单项的从零开始的相对位置
while (GetMenuItemCount (hMenu) > 1) //确定指定菜单中的项目数并清空
DeleteMenu (hMenu, 1, MF_BYPOSITION) ;
/*获取所有本地和远程打印机的列表,首先确定数组的大小---保存在dwNeeded中,接下来,为info数组分配空间并填充它将打印机名称添加到下拉菜单中获取所有本地和远程打印机*/
//指示当前操作系统版本是否匹配或高于 Windows XP 版本
if (!IsWindowsXPOrGreater())
//if (GetVersion () & 0x80000000) // Windows 98 获取默认打印机
{ //枚举可用的打印机,打印服务器,或打印服务程序。
//第一次调用返回PRINTER_INFO_5结构数量和字节数
EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 5, NULL,
0, &dwNeeded, &dwReturned);
pinfo5 = malloc(dwNeeded);
//获取打印机设备名称
EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 5, (PBYTE)
pinfo5,dwNeeded, &dwNeeded, &dwReturned);
for (i = 0; i < dwReturned; i++)
{
AppendMenu(hMenu, (i + 1) % 16 ? 0 :
MF_MENUBARBREAK, i + 1,
pinfo5[i].pPrinterName);
}
free(pinfo5);
}
else
// Windows NT 获取默认打印机
{
EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 4, NULL,
0, &dwNeeded, &dwReturned);
pinfo4 = malloc(dwNeeded);
EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 4, (PBYTE)
pinfo4,dwNeeded, &dwNeeded, &dwReturned);
for (i = 0; i < dwReturned; i++)
{
AppendMenu(hMenu, (i + 1) % 16 ? 0 : MF_MENUBARBREAK,
i + 1,pinfo4[i].pPrinterName);
}
free(pinfo4);
}
//将新项目追加到指定菜单栏,下拉菜单,子菜单或快捷菜单的末尾
AppendMenu (hMenu, MF_SEPARATOR, 0, NULL) ;
//添加属性菜单,控制选项为自定义IDM_DEVMODE
AppendMenu (hMenu, 0, IDM_DEVMODE, TEXT ("Properties")) ;
wParam = IDM_SCREEN ; //默认菜单选项
// 继续执行
case WM_COMMAND :
hMenu = GetMenu (hwnd) ;
//选择屏幕或打印机
if ( LOWORD (wParam) == IDM_SCREEN || LOWORD (wParam) < IDM_DEVMODE)
{ //将复选标记属性设置为清除状态
CheckMenuItem(hMenu, nCurrentDevice, MF_UNCHECKED);
nCurrentDevice = LOWORD(wParam);//菜单项
//放置选取标记于菜单项旁边
CheckMenuItem(hMenu, nCurrentDevice, MF_CHECKED);
}
else if (LOWORD (wParam) == IDM_DEVMODE) //选择属性菜单项
{
//将打印机名称存入szDevice字符数组
GetMenuString(hMenu, nCurrentDevice,
szDevice,sizeof(szDevice) / sizeof(TCHAR), MF_BYCOMMAND);
//显示打印机属性对话框
//打开指定的打印机,并获取打印机的句柄
if (OpenPrinter(szDevice, &hPrint,NULL))
{//由打印机驱动程序产生间接生成对话框(可能失败)
PrinterProperties(hwnd, hPrint);
ClosePrinter(hPrint);//关闭打印机句柄
}
}
else //Capabilities 下的菜单项设置
{ //将复选标记属性设置为清除状态——下拉菜单只能单选模式
CheckMenuItem(hMenu, nCurrentInfo, MF_UNCHECKED);
nCurrentInfo = LOWORD(wParam);
//放置选取标记于菜单项旁边
CheckMenuItem(hMenu, nCurrentInfo, MF_CHECKED);
}
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_INITMENUPOPUP : //lParam菜单ID
if (lParam == 0) //Device菜单
//启用,禁用或显示指定的菜单项
EnableMenuItem(GetMenu(hwnd),IDM_DEVMODE,
nCurrentDevice == IDM_SCREEN ? MF_GRAYED :
MF_ENABLED);//IDM_SCREEN菜单,属性显灰
return 0;
case WM_PAINT :
lstrcpy(szWindowText, TEXT("Device Capabilities: "));//窗口标题
//SCREEN菜单
if (nCurrentDevice == IDM_SCREEN)
{
lstrcpy(szDevice, TEXT("DISPLAY"));
//信息上下文提供了一种无需创建设备上下文(DC)即可快速获取有关设备信息的方法
hdcInfo = CreateIC(szDevice, NULL, NULL,NULL);
}
else//其他菜单
{
hMenu = GetMenu(hwnd);
//复制菜单名称字符串至缓冲区
GetMenuString(hMenu, nCurrentDevice,
szDevice,sizeof(szDevice), MF_BYCOMMAND);
hdcInfo = CreateIC(NULL, szDevice, NULL,NULL);
}
lstrcat(szWindowText, szDevice);
SetWindowText(hwnd, szWindowText);//更改指定窗口标题栏的文本
hdc = BeginPaint(hwnd, &ps);
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
if (hdcInfo)
{
switch (nCurrentInfo)
{
case IDM_BASIC:
DoBasicInfo(hdc, hdcInfo, cxChar, cyChar);
break;
case IDM_OTHER:
DoOtherInfo(hdc, hdcInfo, cxChar, cyChar);
break;
case IDM_CURVE: //曲线
case IDM_LINE: //直线
case IDM_POLY: //多边形
case IDM_TEXT: //文本
DoBitCodedCaps(hdc, hdcInfo, cxChar, cyChar,
nCurrentInfo - IDM_CURVE);
break;
}
DeleteDC(hdcInfo);
}
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY :
PostQuitMessage(0);
return 0;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
//基础信息
void DoBasicInfo (HDC hdc, HDC hdcInfo, int cxChar, int cyChar)
{
static struct
{
int nIndex;
TCHAR * szDesc;
}
info[] =
{ //显示的宽度(mm)
HORZSIZE, TEXT("HORZSIZE Width in millimeters:"),
//显示的高度(mm)
VERTSIZE, TEXT("VERTSIZE Height in millimeters:"),
//(显示的宽度(像素)
HORZRES, TEXT("HORZRES Width in pixels:"),
//显示的高度(像素)
VERTRES, TEXT("VERTRES Height in raster lines:"),
//每像素的颜色位数(位/像素)
BITSPIXEL, TEXT("BITSPIXEL Color bits per pixel:"),
//调色板的个数
PLANES, TEXT("PLANES Number of color planes:"),
//设备内置画刷的数量
NUMBRUSHES, TEXT("NUMBRUSHES Number of device brushes:"),
//设备内置画笔的数量
NUMPENS, TEXT("NUMPENS Number of device pens:"),
//设备内置标记数
NUMMARKERS, TEXT("NUMMARKERS Number of device markers:"),
//设备内置字体数
NUMFONTS, TEXT("NUMFONTS Number of device fonts:"),
//设备色彩深度(>8位/像素)?
NUMCOLORS, TEXT("NUMCOLORS Number of device colors:"),
//PDEVICE内部结构的大小
PDEVICESIZE, TEXT("PDEVICESIZE Size of device structure:"),
//用于画线的设备像素的相对宽度
ASPECTX, TEXT("ASPECTX Relative width of pixel:"),
//用于画线的设备像素的相对高度
ASPECTY, TEXT("ASPECTY Relative height of pixel:"),
//画线设备对角线长度
ASPECTXY, TEXT("ASPECTXY Relative diagonal of pixel:"),
//每逻辑英寸的水平像素数
LOGPIXELSX, TEXT("LOGPIXELSX Horizontal dots per inch:"),
//每逻辑英寸的垂直像素数
LOGPIXELSY, TEXT("LOGPIXELSY Vertical dots per inch:"),
//调色板中的入口数
SIZEPALETTE, TEXT("SIZEPALETTE Number of palette entries:"),
//系统调色板中保留的入口数目
NUMRESERVED, TEXT("NUMRESERVED Reserved palette entries:"),
//设备的实际颜色分辨率(位/像素)
COLORRES, TEXT("COLORRES Actual color resolution:"),
//页面的物理宽度(dpi)
PHYSICALWIDTH, TEXT("PHYSICALWIDTH Printer page pixel width:"),
//页面的物理高度(dpi)
PHYSICALHEIGHT, TEXT("PHYSICALHEIGHT Printer page pixel height:"),
//可打印区的水平偏移
PHYSICALOFFSETX, TEXT("PHYSICALOFFSETX Printer page x offset:"),
//可打印区的垂直偏移
PHYSICALOFFSETY, TEXT("PHYSICALOFFSETY Printer page y offset:")
};
int i;
TCHAR szBuffer[80];
for (i = 0; i < sizeof(info) / sizeof(info[0]); i++)
TextOut(hdc, cxChar, (i + 1) * cyChar, szBuffer,
wsprintf(szBuffer, TEXT("%-45s%8d"), info[i].szDesc,
GetDeviceCaps(hdcInfo, info[i].nIndex)));
}
//其他信息
void DoOtherInfo(HDC hdc, HDC hdcInfo, int cxChar, int cyChar)
{
static BITS clip[] =
{//设备是否支持剪切为一个矩形
CP_RECTANGLE, TEXT("CP_RECTANGLE Can Clip To Rectangle:")
};
static BITS raster[] = {
//支持BitBlt传送位图函数
RC_BITBLT, TEXT("RC_BITBLT Capable of simple BitBlt:"),
//需要联合支持
RC_BANDING, TEXT("RC_BANDING Requires banding support:"),
//支持缩放
RC_SCALING, TEXT("RC_SCALING Requires scaling support:"),
//支持大于64K位图
RC_BITMAP64, TEXT("RC_BITMAP64 Supports bitmaps >64k:"),
//支持16位Windows 2.0特征
RC_GDI20_OUTPUT, TEXT("RC_GDI20_OUTPUT Has 2.0 output calls:"),
//支持SetDIBits和GetDIBits函数
RC_DI_BITMAP, TEXT("RC_DI_BITMAP Supports DIB to memory:"),
//支持指定一个基于调色板的设备
RC_PALETTE, TEXT("RC_PALETTE Supports a palette:"),
//支持SetDIBitsToDevice函数
RC_DIBTODEV, TEXT("RC_DIBTODEV Supports bitmap conversion:"),
//字体可大于64K
RC_BIGFONT, TEXT("RC_BIGFONT Supports fonts >64k:"),
//支持StrectBlt函数
RC_STRETCHBLT, TEXT("RC_STRECTCHBLT Supports StretchBlt:"),
//支持连续填充
RC_FLOODFILL, TEXT("RC_FLOODFILL Supports FloodFill:"),
//支持StretchDIBits函数
RC_STRETCHDIB, TEXT("RC_STRETCHEDIB Supports StretchDIBits:")
};
static TCHAR * szTech[] = {
TEXT("DT_PLOTTER(Vector plotter)"), //矢量绘图仪
TEXT("DT_RASDISPLAY (Raster display)"), //光栅显示器
TEXT("DT_RASPRINTER (Raster printer)"), //光栅打印机
TEXT("DT_RASCAMERA (Raster camera)"), //光栅照相机
TEXT("DT_CHARSTREAM (Character stream)"), //字符流
TEXT("DT_METAFILE (Metafile)"), //图元文件
TEXT("DT_DISPFILE (Display file)"), //显示器文件
};
TCHAR szBuffer[80];
//在第1行显示驱动程序版本:
TextOut(hdc, cxChar, cyChar, szBuffer,
wsprintf(szBuffer, TEXT("%-24s %04XH"), TEXT("DriverVersion:"),
GetDeviceCaps(hdcInfo, DRIVERVERSION)));
//在第2行设备技术:
TextOut(hdc, cxChar, 2 * cyChar, szBuffer,
wsprintf(szBuffer, TEXT("%-24s %-40s"), TEXT("Technology:"),
szTech[GetDeviceCaps(hdcInfo, TECHNOLOGY)]));
//第4行显示设备支持剪切性能的标志
TextOut(hdc, cxChar, 4 * cyChar, szBuffer,
wsprintf(szBuffer, TEXT("CLIPCAPS(Clipping capabilities)")));
//第6行开始显示是否支持Clipping capabilities
for (int i = 0; i < sizeof(clip) / sizeof(clip[0]); i++)
TextOut(hdc, 9 * cxChar, (i + 6)*cyChar, szBuffer,
wsprintf(szBuffer, TEXT("%-45s %3s"), clip[i].szDesc,
GetDeviceCaps(hdcInfo, CLIPCAPS)&clip[i].iMask ?
TEXT("Yes") : TEXT("No")));
//第8行
TextOut(hdc, cxChar, 8 * cyChar, szBuffer,
wsprintf(szBuffer, TEXT("RASTERCAPS(Raster capabilities)")));
//第10行显示光栅性能
for (int i = 0; i < sizeof(raster) / sizeof(raster[0]); i++)
TextOut(hdc, 9 * cxChar, (i + 10)*cyChar, szBuffer,
wsprintf(szBuffer, TEXT("%-45s %3s"), raster[i].szDesc,
GetDeviceCaps(hdcInfo, RASTERCAPS)&raster[i].iMask ? TEXT("Yes") : TEXT("No")));
}
//位图信息
void DoBitCodedCaps(HDC hdc, HDC hdcInfo, int cxChar, int cyChar, int iType)
{
static BITS curves[] = {
CC_NONE, TEXT("CC_NONE No support to curve:"),//是否支持绘制曲线
CC_CIRCLES, TEXT("CC_CIRCLES Can do circles:"), //支持绘制圆
CC_PIE, TEXT("CC_PIE Can do pie wedges:"), //支持绘制扇形
CC_CHORD, TEXT("CC_CHORD Can do chord arcs:"), //支持绘制弦
CC_ELLIPSES, TEXT("CC_ELLIPSES Can do ellipses:"),//支持绘制椭圆
CC_WIDE, TEXT("CC_WIDE Can do wide borders : "),//支持绘制宽边框
//支持绘制带风格的边界
CC_STYLED, TEXT("CC_STYLED Can do styled borders:"),
//支持绘制宽且带风格的边界
CC_WIDESTYLED, TEXT("CC_WIDESTYLED Can do wide and styled borders:"),
//支持内部填充
CC_INTERIORS, TEXT("CC_INTERIORS Can do interiors:"),
//支持绘制圆角矩形
CC_ROUNDRECT, TEXT("CC_ROUNDRECT Can do round rect:")
};
static BITS lines[] = {
//是否支持绘制折线
LC_POLYLINE, TEXT("LC_POLYLINE Can do polyline:"),
//是否支持绘制标记符
LC_MARKER, TEXT("LC_MARKER Can do markers:"),
//是否支持绘制多种标记符
LC_POLYMARKER, TEXT("LC_POLYMARKER Can do polymarkers:"),
//是否支持绘制宽线
LC_WIDE, TEXT("LC_WIDE Can do wide lines:"),
//是否支持绘制带风格的线段
LC_STYLED, TEXT("LC_STYLED Can do styled lines:"),
//是否支持绘制宽的且带风格的线段
LC_WIDESTYLED, TEXT("LC_WIDESTYLED Can do wide and styled lines:"),
//是否支持内部填充
LC_INTERIORS, TEXT("LC_INTERIORS Can do interiors:")
};
static BITS poly[] =
{ //支持绘制交错填充多边形
PC_POLYGON, TEXT("PC_POLYGON Can do alternate fill polygon:"),
//支持绘制矩形
PC_RECTANGLE, TEXT("PC_RECTANGLE Can do rectangle:"),
//支持绘制折线式填充多边形
PC_WINDPOLYGON, TEXT("PC_WINDPOLYGON Can do winding number fill
polygon:"),
//支持绘制扫描线
PC_SCANLINE, TEXT("PC_SCANLINE Can do scanlines:"),
//支持宽边界
PC_WIDE, TEXT("PC_WIDE Can do wide borders:"),
//支持带风格的边界
PC_STYLED, TEXT("PC_STYLED Can do styled borders:"),
//支持宽的且带风格的边界
PC_WIDESTYLED, TEXT("PC_WIDESTYLED Can do wide and styled borders:"),
//支持内部填充
PC_INTERIORS, TEXT("PC_INTERIORS Can do interiors:")
};
static BITS text[] =
{//支持字符输出精度
TC_OP_CHARACTER, TEXT("TC_OP_CHARACTER Can do character output
precision:"),
//支持笔画输出精度
TC_OP_STROKE, TEXT("TC_OP_STROKE Can do stroke output precision:"),
//支持笔画剪切精度
TC_CP_STROKE, TEXT("TC_CP_STROKE Can do stroke clip precision:"),
//支持字符作90旋转
TC_CR_90, TEXT("TC_CP_90 Can do 90 degree character
rotation:"),
//支持字符任意角度旋转
TC_CR_ANY, TEXT("TC_CR_ANY Can do any character rotation:"),
//支持x和y方向的独立缩放
TC_SF_X_YINDEP, TEXT("TC_SF_X_YINDEP Can do scaling independent of X
and Y:"),
//支持把字符放大一倍
TC_SA_DOUBLE, TEXT("TC_SA_DOUBLE Can do doubled character for
scaling:"),
//支持整数倍缩放
TC_SA_INTEGER, TEXT("TC_SA_INTEGER Can do integer multiples for
scaling:"),
//支持任何倍数的严格缩放
TC_SA_CONTIN, TEXT("TC_SA_CONTIN Can do any multiples for exact
scaling:"),
//支持字符加重
TC_EA_DOUBLE, TEXT("TC_EA_DOUBLE Can do double weight characters:"),
//支持斜字体
TC_IA_ABLE, TEXT("TC_IA_ABLE Can do italicizing:"),
//支持下划线
TC_UA_ABLE, TEXT("TC_UA_ABLE Can do underlining:"),
//支持删除线
TC_SO_ABLE, TEXT("TC_SO_ABLE Can do strikeouts:"),
//支持光栅字体
TC_RA_ABLE, TEXT("TC_RA_ABLE Can do raster fonts:"),
//支持矢量字体
TC_VA_ABLE, TEXT("TC_VA_ABLE Can do vector fonts:")
};
static struct
{
int iIndex;
TCHAR* szTitle;
//pbits为一个数组的指针,元素类型为BTIS,本例中pbits指向
//curves\lines\poly\text等数组。
BITS(*pbits)[];
int iSize;
} bitinfo[] = {
CURVECAPS, TEXT("CURVCAPS(Curve Capabiities)"), (BITS(*)[])curves,
sizeof(curves) / sizeof(curves[0]),
LINECAPS, TEXT("LINECAPS(Line Capabiities)"), (BITS(*)[])lines,
sizeof(lines) / sizeof(lines[0]),
POLYGONALCAPS, TEXT("POLYGONALCAPS(Polygonal Capabiities)"),
(BITS(*)[])poly, sizeof(poly) / sizeof(poly[0]),
TEXTCAPS, TEXT("TEXTCAPS(Curve Capabiities)"), (BITS(*)[])text,
sizeof(text) / sizeof(text[0])
};
static TCHAR szBuffer[80];
BITS(*pbits)[] = bitinfo[iType].pbits;
int iDevCaps = GetDeviceCaps(hdcInfo, bitinfo[iType].iIndex);
TextOut(hdc, cxChar, cyChar, bitinfo[iType].szTitle,
lstrlen(bitinfo[iType].szTitle));
for (int i = 0; i < bitinfo[iType].iSize; i++)
{
TextOut(hdc, cxChar, (i + 3)*cyChar, szBuffer,
wsprintf(szBuffer, TEXT("%-55s %3s"), (*pbits)[i].szDesc,
iDevCaps&(*pbits)[i].iMask ? TEXT("Yes") : TEXT("No")));
}
}
/******************************************************************************
WM_SETTINGCHANGE消息:更改系统参数后,应用程序应将WM_SETTINGCHANGE发送给所有顶级窗口
wPararm参数:要检索或设置的系统范围参数
lParam参数:lParam是指向字符串的指针,该字符串指示包含已更改的系统参数的区域
*******************************************************************************
CheckMenuItem 函数:功能是在弹出菜单中为菜单项增加选中标记或移除选中标记和创建一个水平分隔线等。
DWORD CheckMenuItem(
HMENU hMenu, //含有其菜单项的标志将被提取得的菜单的句柄。
UINT uIDCheckItem,//由uCheck参数确定的要设置其复选标记属性的菜单项。
UINT uCheck//控制uIDCheckItem参数的解释和菜单项的复选标记属性状态的标志。
//此参数可以是MF_BYCOMMAND或MF_BYPOSITION和MF_CHECKED或MF_UNCHECKED的组合。
);
*******************************************************************************
EnumPrinters 函数:枚举可用的打印机,打印服务器,域或印刷服务供应商。
BOOL EnumPrinters(
_In_ DWORD Flags,//函数应枚举的打印对象的类型。该值可以是以下值中的一个或多个。
_In_ LPTSTR Name,//指向缓冲区的指针,该缓冲区接收PRINTER_INFO_1,PRINTER_INFO_2,
//PRINTER_INFO_4或PRINTER_INFO_5结构的数组。每个结构都包含描述可用打印对象的数据。
_In_ DWORD Level,//指定欲枚举的结构的类型,1,2,4或5(4仅适用于NT;5仅适用于Win95和NT 4.0)。
_Out_ LPBYTE pPrinterEnum,//包含PRINTER_ENUM_x结构的缓冲区,其中的x代表级别(Level)
_In_ DWORD cbBuf,//缓冲区中的字符数量
_Out_ LPDWORD pcbNeeded,//指向一个Long型变量的指针,该变量用于保存请求的缓冲区长度,或者实际读入的字节数量
_Out_ LPDWORD pcReturned//载入缓冲区的结构数量(用于那些能返回多个结构的函数)
);
注解
第4和第5级将它们的结构建立在系统注册表的基础上,而且比第2级快得多。后者要求每台打印机都处于打开状态
*******************************************************************************
AppendMenu 函数:在指定的菜单条、下拉式菜单、子菜单或快捷菜单的末尾追加一个新菜单项的函数。
BOOL AppendMenuA(
HMENU hMenu, //将被修改的菜单条、下拉式菜单、子菜单、或快捷菜单的句柄
UINT uFlags, //控制新菜单项的外观和性能的标志。此参数可以是备注里所列值的组合。
UINT_PTR uIDNewItem,//指定新菜单项的标识符,或者当uFlags设置为MF_POPUP时,表示下拉式菜单或子菜单的句柄。
LPCSTR lpNewItem//指定新菜单项的内容。
);
*******************************************************************************
OpenPrinter 函数:打开指定的打印机,并获取打印机的句柄
BOOL OpenPrinter(
_In_ LPTSTR pPrinterName,//要打开的打印机的名字,指针
_Out_ LPHANDLE phPrinter,//打开打印机或打印服务器对象的指针
_In_ LPPRINTER_DEFAULTS pDefault//这个结构保存要载入的打印机信息,可以为NULL。
);
PrinterProperties 函数:显示指定的打印机的打印机的属性的属性表。
BOOL PrinterProperties(
_In_ HWND hWnd, //属性表父窗口的句柄。
_In_ HANDLE hPrinter//打印机对象的句柄。
);
*******************************************************************************
GetMenuString 函数:将指定菜单项的文本字符串复制到指定缓冲区中。
int GetMenuStringA(
HMENU hMenu, //菜单句柄
UINT uIDItem,//要更改的菜单项
LPSTR lpString,//接收以null终止的字符串的缓冲区。
int cchMax,//要复制的字符串的最大长度(以字符为单位)。
UINT flags//指示如何解释uIDItem参数
);
*******************************************************************************
CreateIC函数:创建指定装置的信息的上下文。
信息上下文提供了一种无需创建设备上下文(DC)即可快速获取有关设备信息的方法。
但是,GDI绘图函数不能接受信息上下文的句柄。
HDC CreateICA(
LPCSTR pszDriver,//指向以空字符结尾的字符串的指针,该字符串指定设备驱动程序的名称(例如,Epson)
LPCSTR pszDevice,//指向以空字符结尾的字符串的指针,该字符串指定正在使用的特定输出设备的名称,如打印管理器(例如Epson FX-80)所示。
LPCSTR pszPort,//该参数将被忽略,应设置为NULL。提供它仅是为了与16位Windows兼容。
const DEVMODEA *pdm//指向DEVMODE结构的指针,该结构包含设备驱动程序的设备特定的初始化数据。
);
*/
菜单资源:
/
//
// Menu
//
DevCaps2 MENU
BEGIN
POPUP "&DEVICE"
BEGIN
MENUITEM "&SCREEN", IDM_SCREEN
END
POPUP "&Capabilities"
BEGIN
MENUITEM "&Basic Information", IDM_BASIC
MENUITEM "&Other Information", IDM_OTHER
MENUITEM "&Curve Capabilites", IDM_CURVE
MENUITEM "&Line Capabilites", IDM_LINE
MENUITEM "&Polygonal Capabilites", IDM_POLY
MENUITEM "&Text Capabilites", IDM_TEXT
END
END
运行结果:
图13-3 获取显示器及打印机设备信息
总结
实例DEVCAPS2.C枚举并显示当前显示器和打印机设备信息。显示器设备名默认为“DISPLAY”,而当前打印设备我们并不知道,需要使用EnumPrinters函数枚举出当前所有打印设备,并以子菜单的形式将打印设备名添加到“DEVICE”菜单中。“Capabilities”菜单显示设备性能。性能包括基础信息、其他信息和位图信息(曲线、直线、多边形和文本我们将在第十四章详细讲解位图)。实例使用三个函数分别获取这些信息。
void DoBasicInfo (HDC, HDC, int, int) ; //基本信息
void DoOtherInfo (HDC, HDC, int, int) ; //其他信息
void DoBitCodedCaps (HDC, HDC, int, int, int) ; //位图信息
接下来我们分析一下实例窗口过程的实现。
●WM_CREATE消息:设定输出字符的宽和高。
●WM_SETTINGCHANGE消息:当更改系统配置时,窗口过程接收到该消息。实例调用GetSubMenu函数获取当前窗口子菜单句柄,然后通过一个while循环结构清空子菜单项。接下来需要获取所有打印机设备名称。此处需要判断当前操作系统的版本。如果是DOS架构的Windows系统(win98及之前的版本),则选用PRINTER_INFO_5打印机设备信息结构体。如果是XP及以上Windows版本,则选用PRINTER_INFO_4打印机设备信息结构体。
●操作系统版本的判断方法:
方法1:调用GetVersion函数
if (GetVersion () & 0x80000000) // Windows 98 获取默认打印机
返回一个 DWORD 类型的值,表示当前操作系统的版本信息。
注意事项:
1.在较新的Windows版本中,推荐使用更为强大和灵活的函数 VerifyVersionInfo 或 VerifyVersionInfoEx 来替代 GetVersion 函数。
2.需要屏蔽VS警告信息。
#pragma warning(disable:4996)
方法2:调用IsWindowsXPOrGreater函数。
IsWindowsXPOrGreater 是一个用于判断操作系统版本的宏,属于 Windows SDK 的宏定义之一。它可以用于检查当前操作系统是否是 Windows XP 或更高版本。
以下是 IsWindowsXPOrGreater 宏的定义:
#define IsWindowsXPOrGreater() (LOBYTE(LOWORD(GetVersion())) >= 5)
返回值:
如果当前操作系统是 Windows XP 或更高版本,则返回值为真(非零);否则返回值为假(零)。
注意事项:
IsWindowsXPOrGreater 宏仅用于检查 Windows XP 或更高版本的操作系统。如果需要检查其他特定版本的操作系统,可以使用类似的宏定义,如 IsWindowsVistaOrGreater 或 IsWindows7OrGreater 等。
不论是NT架构还是DOS架构的Windows系统版本,都需要两次调用EnumPrinters函数枚举当前系统中的打印机。第一次调用EnumPrinters函数获打印机设备信息结构数量和存储这些结构所需的字节数。第二次调用才真正获取当前所有打印机设备信息。
最后调用AppendMenu函数将打印机设备名称作为子菜单添加到“DEVICES”菜单栏,并添加一个"Properties"子菜单。
●WM_COMMAND消息:调用CheckMenuItem函数设置当前选中菜单项的复选标记。
如果是选中"Properties"属性菜单,则调用OpenPrinter函数,打开指定打印机设备,返回打印机设备句柄,并调用PrinterProperties函数获取打印机设备信息(是否成功,取决于具体的打印设备驱动程序)。
如果是“Capabilities”菜单下的子菜单,同样调用CheckMenuItem函数添加复选标记。
最后调用InvalidateRect函数重绘窗口。
●WM_INITMENUPOPUP消息:如果是SCREEN设备,则禁用"Properties"属性菜单。否,则启用"Properties"属性菜单。
●WM_PAINT消息:根据设备名称(对应的菜单选项)调用SetWindowText函数设置窗口标题栏。并调用CreateIC函数获取设备上下文句柄,再根据设备上下文句柄,调用GetDeviceCaps函数获取该设备的基础信息、其他信息和位图信息。
【注意】检查BitBlt能力——视频显示器与打印机在使用GDI上的最重要区别!
1.大多数点阵、激光或喷墨打印机具备这种能力,但绘图仪不具备。
2.不具备位块传送能力的设备,也就不支持以下这些GDI函数:CreateCompatibleDC、CreateCompatibleBitmap、PatBlt、BitBlt、StretchBlt、GrayString、DrawIcon、SetPixel、GetPixel、FloodFill、ExtFloodFill、FillRgn、FrameRgn、InvertRgn、PaintRgn、FillRect、FrameRect、InvertRect等。