Windows程序设计核心总结(打印机-2018.5.5)

本人大三学生,自学Windows程序设计有两三个月了,我是看鱼C工作室发布的Windows程序设计视频入门的,这视频集数虽然不是特别多,目前只有前面九章的视频内容,但小甲鱼老师讲解书本内容十分详细、入微,能让我们学习到不少知识。我开发Win32的环境是VS2013。

一、打印机工作机理

在Windows中使用打印机时,实际上启动了一系列模块之间复杂的交互过程,包括GDI32模块、打印机设备驱动程序模块、Windows后台打印处理程序和其他模块。
应用程序想要开始使用打印机,首先调用CreateDC函数获取打印机设备环境句柄,而该参数必须需要知道打印机设备名,所以还需要先待用EnumPrinters函数获取打印机设备名。注意,当调用了CreateDC函数后,参数相应的打印机设备驱动程序被载入内存。应用程序再调用StartDoc函数开始新文档,该函数由GDI模块处理,GDI模块调用刚刚被调入内存中的打印机设备驱动程序中的Control函数,告诉设备驱动程序做好打印准备。然后调用StartPage函数开始新的一页,以EndPage函数结束这一页,注意,在StartPage和EndPage函数之间调用GDI函数开始在页面绘制,这时GDI模块就会先将这些GDI绘制函数存储到硬盘上的图元文件上。好了,调用完EndPage函数结束了这一页,那么真正的打印工作开始了,打印机设备驱动程序就会将硬盘上的图元文件转化成适用于打印机的输出,具体怎么转换我们不关心。接着,这些转化后的打印机输出会被GDI模块存储到另一个临时文件中,到这为止,这一页的所有工作都完成了,那就要进行下一页的打印了,怎么告诉后台打印处理程序需要打印新的一页?GDI模块会采用进程间调用告诉后台打印处理程序新的作业已就绪,应用程序应该处理下一个页面了,循环反复...将所有页面都打印完后,就可以调用EndDoc函数表示打印作业完成。

二、获取打印机设备环境句柄

我们知道,要使用打印机,必须首先获取打印机设备环境句柄,一般地,通过调用CreateDC函数获取打印机设备环境句柄,但是要注意的问题是,该函数的参数2需要指定打印机设备的名称。打印机设备的名称怎么来?我们都知道,一台计算机可以同时连接多台打印机,而不管连接多少台打印机,默认打印机只有一台,默认打印机就是用户最近一次选用的打印机。所以,我们可以获取默认打印机设备的名称,通过调用EnumPrinters函数获取默认打印机的名称,再将该名称作为CreateDC函数的参数。下面是完整的获取打印机设备环境句柄的代码例子:

HDC GetPrinterDC(void)
{
    DWORD            dwNeeded, dwReturned;
    HDC              hdc;
    PRINTER_INFO_4 * pinfo4;
    PRINTER_INFO_5 * pinfo5;
    if (GetVersion() & 0x80000000)         // Windows 98
    {
    //第一次调用该函数是为了得到所需的结构大小
        EnumPrinters(PRINTER_ENUM_DEFAULT, NULL, 5, NULL,
            0, &dwNeeded, &dwReturned);
        pinfo5 = (PRINTER_INFO_5 *)malloc(dwNeeded);
//第二次调用该函数才是真正填充该结构
        EnumPrinters(PRINTER_ENUM_DEFAULT, NULL, 5, (PBYTE)pinfo5,
            dwNeeded, &dwNeeded, &dwReturned);
//将获取的结构里的pPrinterName成员作为CreateDC函数的参数
        hdc = CreateDC(NULL, pinfo5->pPrinterName, NULL, NULL);
        free(pinfo5);
    }
    else                                    // Windows NT
    {
    //下面同理
        EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 4, NULL,
            0, &dwNeeded, &dwReturned);
        pinfo4 = (PRINTER_INFO_4 *)malloc(dwNeeded);
        EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 4, (PBYTE)pinfo4,
            dwNeeded, &dwNeeded, &dwReturned);
        hdc = CreateDC(NULL, pinfo4->pPrinterName, NULL, NULL);
        free(pinfo4);
    }
    //返回打印机设备环境句柄
    return hdc;
}

三、打印图形和文字

我们在上面的代码中辣么辛苦获取了打印机设备环境句柄,那么我们该怎么使用它呢?不急,我们先放放。我们先创建一个应用程序窗口,在窗口的客户区显示我们将要打印的内容(GDI绘制函数调用),还有在系统菜单中添加打印功能的菜单项,当用户点击打印菜单项,就会执行打印功能。我们先放上应用程序窗口的代码例子:

#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
BOOL PrintMyPage (HWND) ;
extern HINSTANCE hInst ;//这里是声明另一文件的全局变量
extern TCHAR     szAppName[] ;//这里是声明另一文件的全局变量
extern TCHAR     szCaption[] ;//这里是声明另一文件的全局变量

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szAppName ;

     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }

     hInst = hInstance ;
     hwnd = CreateWindow (szAppName, szCaption,
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}
//在打印页或客户区(为什么说在客户区也有绘制?后面你就知道了)绘制图形和文字
void PageGDICalls (HDC hdcPrn, int cxPage, int cyPage)
{
     static TCHAR szTextStr[] = TEXT ("Hello, Printer!") ;
     Rectangle (hdcPrn, 0, 0, cxPage, cyPage) ;//沿cxPage宽度,cyPage高度的打印页来绘制矩形
         //在打印页绘制对角线
     MoveToEx (hdcPrn, 0, 0, NULL) ;
     LineTo   (hdcPrn, cxPage, cyPage) ;
     MoveToEx (hdcPrn, cxPage, 0, NULL) ;
     LineTo   (hdcPrn, 0, cyPage) ;
         //保存当前设备环境,因为等等需要改变映射模式,绘制椭圆和在中心显示文本
     SaveDC (hdcPrn) ;
     SetMapMode       (hdcPrn, MM_ISOTROPIC) ;
     SetWindowExtEx   (hdcPrn, 1000, 1000, NULL) ;
     SetViewportExtEx (hdcPrn, cxPage / 2, -cyPage / 2, NULL) ;
     SetViewportOrgEx (hdcPrn, cxPage / 2,  cyPage / 2, NULL) ;
     Ellipse (hdcPrn, -500, 500, 500, -500) ;
     SetTextAlign (hdcPrn, TA_BASELINE | TA_CENTER) ;
     TextOut (hdcPrn, 0, 0, szTextStr, lstrlen (szTextStr)) ;
         //恢复到原来的设备环境,那么刚刚设置的映射模式等都没效了
     RestoreDC (hdcPrn, -1) ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static int   cxClient, cyClient ;
     HDC          hdc ;
     HMENU        hMenu ;
     PAINTSTRUCT  ps ;
     switch (message)
     {
     case WM_CREATE:
         //获取系统菜单句柄
          hMenu = GetSystemMenu (hwnd, FALSE) ;
                    //在系统菜单添加打印菜单项
          AppendMenu (hMenu, MF_SEPARATOR, 0, NULL) ;
          AppendMenu (hMenu, 0, 1, TEXT ("&Print")) ;
          return 0 ;
     case WM_SIZE:
          cxClient = LOWORD (lParam) ;
          cyClient = HIWORD (lParam) ;
          return 0 ;
     case WM_SYSCOMMAND:
         //当用户点击打印菜单项,那么就会执行PrintMyPage函数来进行打印,PrintMyPage函数返回值是判断打印是否成功,若失败则弹出一个错误对话框
          if (wParam == 1)
          {
               if (!PrintMyPage (hwnd))
                    MessageBox (hwnd, TEXT ("Could not print page!"),
                                szAppName, MB_OK | MB_ICONEXCLAMATION) ;
               return 0 ;
          }
          break ;
     case WM_PAINT :
          hdc = BeginPaint (hwnd, &ps) ;
                    //Look,我们都知道当生成窗口时,整个客户区都是无效的,那么就会发射一条WM_PAINT消息,接着就调用PageGDICalls函数,在客户区绘制了需要打印的内容
          PageGDICalls (hdc, cxClient, cyClient) ;
          EndPaint (hwnd, &ps) ;
          return 0 ;
     case WM_CLOSE:
        if (IDOK == MessageBox(hwnd, TEXT("是否退出?"), TEXT("对话框"), MB_OKCANCEL | MB_DEFBUTTON1 | MB_ICONQUESTION))
        {
            DestroyWindow(hwnd);
        }
        else
        {
            return 0;
        }
     case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

四、打印功能的实现(即PrintMyPage函数的实现)

到了这里,我们已经完成了大部分功能,就差最后一个打印功能的函数了,即PrintMyPage函数。
先放代码上来吧,再进行分析:

#include <windows.h>

HDC  GetPrinterDC (void) ;
void PageGDICalls (HDC, int, int) ;
HINSTANCE hInst ;
TCHAR     szAppName[] = TEXT ("Print1") ;//定义全局变量,在上一个文件中有引用
TCHAR     szCaption[] = TEXT ("Print Program 1") ;//定义全局变量,在上一个文件中有引用

BOOL PrintMyPage (HWND hwnd)
{
//DOCINFO结构,第一个字段表明了该结构的大小,第二个字段则是一个值为TEXT ("Print1: Printing")的字符串
     static DOCINFO di = { sizeof (DOCINFO), TEXT ("Print1: Printing")的字符串 } ;
     BOOL           bSuccess = TRUE ;
     HDC            hdcPrn ;
     int            xPage, yPage ;//打印纸的长度和宽度
     if (NULL == (hdcPrn = GetPrinterDC ()))//获取打印机设备环境
          return FALSE;
     xPage = GetDeviceCaps (hdcPrn, HORZRES) ;
     yPage = GetDeviceCaps (hdcPrn, VERTRES) ;
         /*
         只有StartDoc、StartPage、EndPage函数都成功时,即返回值都大于0时,才能够调用EndDoc结束文档
         */
     if (StartDoc (hdcPrn, &di) > 0)//开始新文档
     {
          if (StartPage (hdcPrn) > 0)//开始新的一页
          {
                    //GDI绘制命令,GDI模块将GDI绘制命令存储在硬盘上的图元文件
               PageGDICalls (hdcPrn, xPage, yPage) ;

               if (EndPage (hdcPrn) > 0)//在调用EndPage函数后,打印机设备程序将图元文件转化为打印输出,最后将打印输出存储为另一个临时文件
                    EndDoc (hdcPrn) ;//打印结束
               else
                    bSuccess = FALSE ;
          }
     }
     else
          bSuccess = FALSE ;
     DeleteDC (hdcPrn) ;
     return bSuccess ;
}

五、用异常终止过程取消打印

好啦,到目前为止,全部功能基本实现了。可出现了一个问题,如果一个文档非常大,用户想打印一页,但不小心按错了,变成打印几百页了,那怎么终止打印呢?所以,当应用程序仍在打印时,程序应为用户提供一个可取消打印作业的便利方法。所以,我们需要修改一下打印功能文件的代码。如果需要取消一个打印作业,那么就要调用一个“异常终止过程”,它是一个函数哦。程序员可以把这个函数的地址作为参数传给SetAbortProc函数(其实这个流程就是注册一个“异常终止过程”),每当打印时,调用EndPage函数时,就会调用“异常终止过程”来提前判断是否继续打印。好的,这里先上代码吧。

#include <windows.h>

HDC  GetPrinterDC (void) ;              // in GETPRNDC.C
void PageGDICalls (HDC, int, int) ;     // in PRINT.C
HINSTANCE hInst ;
TCHAR     szAppName[] = TEXT ("Print2") ;
TCHAR     szCaption[] = TEXT ("Print Program 2 (Abort Procedure)") ;
//添加的内容,异常终止过程函数的定义,即在调用EndPage函数时执行的函数
BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)//参数1是打印机设备环境句柄,如果一切正常,参数2为0,如果由于GDI模块生成临时打印输出文件导致磁盘空间不足,参数2为SP_OUTOFDISK
{
     MSG msg ;
         //看,好像消息循环。没错,这里就是消息循环,不过获取消息的函数是PeedMessage函数,我们都知道若消息队列有等待处理的消息,那么就返回TRUE,若没有消息,则返回FALSE。我们能注意到,无论该函数怎么处理,最后始终是返回TRUE,说明打印作业可以继续,那么貌似不能达到我们预期的效果(根据用户的操作,手动取消打印),后面我们会继续完善,添加打印对话框实现用户与程序交互。
     while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return TRUE ;
}

BOOL PrintMyPage (HWND hwnd)
{
     static DOCINFO di = { sizeof (DOCINFO), TEXT ("Print2: Printing") } ;
     BOOL           bSuccess = TRUE ;
     HDC            hdcPrn ;
     short          xPage, yPage ;
     if (NULL == (hdcPrn = GetPrinterDC ()))
          return FALSE ;
     xPage = GetDeviceCaps (hdcPrn, HORZRES) ;
     yPage = GetDeviceCaps (hdcPrn, VERTRES) ;
     // 禁止窗口接收鼠标和键盘消息,避免重复打印
     EnableWindow (hwnd, FALSE) ;
     SetAbortProc (hdcPrn, AbortProc) ;
     if (StartDoc (hdcPrn, &di) > 0)
     {
          if (StartPage (hdcPrn) > 0)
          {
               PageGDICalls (hdcPrn, xPage, yPage) ;

               if (EndPage (hdcPrn) > 0)
                    EndDoc (hdcPrn) ;
               else
                    bSuccess = FALSE ;
          }
     }
     else
          bSuccess = FALSE ;
     // 启用窗口接收鼠标键盘消息
     EnableWindow (hwnd, TRUE) ;
     DeleteDC (hdcPrn) ;
     return bSuccess ;
}

六、增加一个打印对话框(实现用户与程序交互)

我们知道上一个代码的改进存在问题,首先它不直接显示它是否在打印以及打印何时结束,只有当你用鼠标在程序上移动并发现程序没有反应时,你才确定它还在处理PrintMyPage例程,即还在打印过程中。我们可以提供一个非模态对话框,还有维护对话框过程。当用户点击对话框的Cancel按钮时,代表用户想要取消打印,所以程序就终止了打印操作。这个对话框经常被称为“终止对话框”,该对话框过程经常被称为“终止对话框过程”。现在,放上改进代码:

#include <windows.h>

HDC  GetPrinterDC (void) ;              // in GETPRNDC.C
void PageGDICalls (HDC, int, int) ;     // in PRINT.C

HINSTANCE hInst ;
TCHAR     szAppName[] = TEXT ("Print3") ;
TCHAR     szCaption[] = TEXT ("Print Program 3 (Dialog Box)") ;

BOOL bUserAbort ;
HWND hDlgPrint ;

// 打印对话框处理程序
BOOL CALLBACK PrintDlgProc (HWND hDlg, UINT message,
                            WPARAM wParam, LPARAM lParam)
{
     switch (message)
     {
     case WM_INITDIALOG:
         // 设置窗口标题
          SetWindowText (hDlg, szAppName) ;
          // 停用系统菜单的关闭选项
          EnableMenuItem (GetSystemMenu (hDlg, FALSE), SC_CLOSE, MF_GRAYED) ;
          return TRUE ;

     case WM_COMMAND:  // 按下取消按钮之后
         // 全局变量,TRUE标识取消按钮按下
          bUserAbort = TRUE ;
          EnableWindow (GetParent (hDlg), TRUE) ;  // 启动主窗口
          DestroyWindow (hDlg) ;  // 关闭对话框
          hDlgPrint = NULL ;  // 设定为NULL,防止在消息循环中呼叫IsDialogMessage
          return TRUE ;
     }
     return FALSE ;
}

// 放弃处理程序,用来停止打印
BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)
{
     MSG msg ;

     while (!bUserAbort && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
     {
         // IsDialogMessage函数用来将消息发送给非系统模态对话框
          if (!hDlgPrint || !IsDialogMessage (hDlgPrint, &msg))
          {
               TranslateMessage (&msg) ;
               DispatchMessage (&msg) ;
          }
     }
     // 返回TRUE标识继续打印
     return !bUserAbort ;
}

BOOL PrintMyPage (HWND hwnd)
{
     static DOCINFO di = { sizeof (DOCINFO), TEXT ("Print3: Printing") } ;
     BOOL           bSuccess = TRUE ;
     HDC            hdcPrn ;
     int            xPage, yPage ;

     if (NULL == (hdcPrn = GetPrinterDC ()))
          return FALSE ;
     xPage = GetDeviceCaps (hdcPrn, HORZRES) ;
     yPage = GetDeviceCaps (hdcPrn, VERTRES) ;

     EnableWindow (hwnd, FALSE) ;

     // 先设置用户取消状态为False
     bUserAbort = FALSE ;
     // 设置弹窗回调函数
     hDlgPrint = CreateDialog (hInst, TEXT ("PrintDlgBox"),
                               hwnd, PrintDlgProc) ;
     // 设置放弃处理程序回调函数
     SetAbortProc (hdcPrn, AbortProc) ;

     if (StartDoc (hdcPrn, &di) > 0)
     {
          if (StartPage (hdcPrn) > 0)
          {
               PageGDICalls (hdcPrn, xPage, yPage) ;

               if (EndPage (hdcPrn) > 0)
                    EndDoc (hdcPrn) ;
               else
                    bSuccess = FALSE ;
          }
     }
     else
          bSuccess = FALSE ;
     if (!bUserAbort)
     {
         // 如果用户没有取消打印,就重新启用主窗口,并清除打印对话框
          EnableWindow (hwnd, TRUE) ;
          DestroyWindow (hDlgPrint) ;
     }
     DeleteDC (hdcPrn) ;
     // bUserAbort可以告诉您使用者是否终止了打印作业
     // bSuccess会告诉您是否出了故障
     return bSuccess && !bUserAbort ;
}

原文地址:http://blog.51cto.com/12731497/2113115

时间: 2024-10-28 07:36:59

Windows程序设计核心总结(打印机-2018.5.5)的相关文章

《windows程序设计》第一章,建议想学API的每天看一章

开始 壹佰软件开发小组  整理编译   本书介绍了在Microsoft Windows 98.Microsoft Windows NT 4.0和Windows NT 5.0下程序写作的方法.这些程序用C语言编写并使用原始的Windows Application Programming Interface(API).如在本章稍后所讨论的,这不是写作Windows程序的唯一方法.然而,无论最终您使用什么方式写作程序,了解Windows API都是非常重要的. 正如您可能知道的,Windows 98已

Windows 程序设计 复习笔记(共 77 问)

Windows 程序设计 复习笔记(共 77 问) (个人整理,仅做复习用 :D,转载注明出处:http://blog.csdn.net/hcbbt/article/details/42706501) 知识点 双字节字符集和Unicode字符集有何区别?采用双字节字符集有何问题 双字节字符集(DBCS)编码是0-255,DBCS含有1字节代码与2字节代码,而Unicode是统一的16位系统,这样就允许表示 65536个字符.Unicode中的每个字符都是16位宽而不是8位宽.在Unicode中,

关于《Windows程序设计(第五版)》中一个实例程序的疑问

最近一直在看Charlse Petzold的<Windows程序设计>,作为一个新得不能再新的新手,只能先照着书的抄抄源码了,之前的例子一直都很正常,但昨天遇到一个很诡异的BUG. 先看实例源码吧: 1 /*----------------------------------------------------------------- 2 ENVIRON.C -- Environment List Box 3 (c) Charles Petzold,1998 4 Copy by XXXX,2

windows程序设计笔记

2014.05.06 新建一个visual C++ -- 常规 -- 空白 的项目,用.c后缀名指定这是一个用C语言来写的windows项目.和C语言的hellworld程序做了一个比较,按照windows程序设计规定的入口函数名称.函数参数.参数传递方式等写个入口函数,并弹出一个MessageBox. windows程序设计笔记,布布扣,bubuko.com

Windows程序设计画图实现哆啦A梦

在看雪论坛上看到的一个帖子,很喜欢,转载一下.原文地址:http://bbs.pediy.com/showthread.php?t=138630哆啦A梦是画出来的,不知道作者算这些坐标位置算了多久,真的很犀利.记得原来看<Windows程序设计>的时候,每次看到GDI都不是很理解,也没有仔细去研究.现在编程也很少与GDI打交道,还是等以后有空了在深入了解一下吧.把这个代码转载一份到博客,以后再回头研究一下.顺便贴一下程序运行后的截图:(很酷吧~~~)字数补丁~代码疯子~程序人生~字数补丁~代码

Windows程序设计笔记4:第10章:TCP/IP和网络通信

WinSock接口:Windows处理网络的API 套接字socket 流套接字:SOCKET_STREAM           可靠连接   TCP HTTP POP3 数据报套接字:SOCKET_DGRAM     不可靠连接 UDP 寻址方式: 1:sockaddr的第1个版本 struct sockaddr{ u_short  sa_family;       //地址家族 char      sa_data[14];   //数据 } 2.sockaddr的TCP/IP版本的 sock

葛军视频教程《windows程序设计》配套文章_1.1 从最经典的“hello world”C语言程序说起

第一章 编写第一个Windows应用程序 1.1 从最经典的"hello world"C语言程序说起 本文视频教程地址: 优酷网(超清):http://v.youku.com/v_show/id_XNjkwMjgwNzE2.html 土豆网(超清.原画):http://www.tudou.com/programs/view/NV1scXZCbJ8/ 备受全世界程序员青睐的一本书是<The C Programming Language>(中文名<C语言程序设计>)

Windows 无法打开“添加打印机”。本地打印后台处理程序服务没有运行。请重新启动打印机后台处理程序或重新启动计算机。

安装的win7系统,连接打印机出现 Windows 无法打开"添加打印机".本地打印后台处理程序服务没有运行.请重新启动打印机后台处理程序或重新启动计算机 解决方案: 1.首先先检查一下电脑print spooler服务是否被禁用,经常喜欢用软件优化电脑开机启动,有可能会禁用,如果被禁用,设置为自动启动 2.如果电脑没有print Spooler服务,推荐方法,下载*.reg,解压后直接运行Print Spooler.reg,运行后,重启计算机即可.如果运行失败,注意是否被使用的安全软

《Windows程序设计》复习题

<Windows程序设计>模拟题 一.单项选择题 1. 在VC++中,项目文件的扩展名是(B). A)exe                                             B)dsp C) dsw                                             D)cpp 2. 在MFC中,利用ClassWizard不能(D ). A)建立新类                                    B)进行消息映射 C) 增加类