打印机编程

    本文地址:http://tongxinmao.com/Article/Detail/id/315

    1.打印术语
       cpi(characters Per Inch) 每英寸内所含的字符数,用来表示字符的大小、
       间距。
       cpl(Characters Per Line) 每行中所含的字符个数,用来在横向表示字符
       的宽度和间距。
       cps(Character Per Second):每秒所能打印的字符个数,用来表示打印机的打
       印速度。当然它和打印的字符大小与笔划有关。一般以10cpi的西文字符为基准
       来计算打印速度.
       dpi(Dot Per Inch)每英寸所打印的点数(或线数),用来表示打印机打印分辨.这是
       衡量打印机打印精度的主要参数之一.该值越大表明打印机的打印精度越高.
       lpi(Lines Per Inch): 每英寸所含的行数,用来表示在垂直方向字符的大小
       间距.
       ppm(Papers Per Minute):每分钟打印的页数,这是衡量打印机打印速度的
       重要参数,是指连续打印时的平均速度.
       
       在大多数的Internet及多媒体应用软件中,为使画面下载速度增快,大都是采
       用72dpi或75dpi的低解析度影像,如果仅从屏幕上观看,画质还可以,但是当
       使用解析度较高的打印机打印出来时,就会发生如锯齿状或是模糊不清的问题.
       这主要的原因在于原始影像本身的像素不能呈现高解析度的输出品质.
       因此,无论使用的打印机品质或解析度多高,打印品质仍不理想.使用HP SmartFocus
       智慧聚焦技术的打印机驱动程序,将低品质的影像自动地做解析度提升的处理.
       这样一来,就可以打印出较清晰的影像.
       
       sRGB:(standard Red Green Blue)是一种彩色语言协定.它提供了一个标准方法来定义
       色彩.让计算机的周边设备与应用软件对于色彩有一个共通的语言.sRGB是由HP和微软
       共同推出的开放业界标准.

    2.打印机技术指标
      (1) 打印质量
      衡量图像清晰程度最重要的指标就是分辨率(dpi每平方英寸多少个点).
      300dpi是人眼分辨打印文本与图像的边缘是否有锯齿的临界点.再考虑
      到其他的一些因素,只有360dpi以上的打印效果才能基本令人满意.要将
      基色组成的色点转换成印刷上由四色墨水喷出的各种色彩效果,就需要
      由色彩转换的驱动程序来实现.目前各打印机厂商均有自已的图像调整技术.
      
      (2)打印速度
      打印机的打印速度是每分钟打印多少页纸(PPM),打印机速度与打印的色彩和
      打印分辨有关.
      
      (3)色彩数目
      更多的彩色墨盒数就意味着更丰富的色彩.有红、黄、蓝三色单墨盒。还有
      黑、淡蓝和淡红的六色打印机。
      
      1.1.2 分辨率
      分辨率是一个表示平面图像精细程序的概念,通常它是以横向和纵向点的数量来衡量的,
      表示成:水平点数*垂直点数的形式.在一个固定的平面内,分辨率越高,图像越细致.
      分辨率有多种.在显示器上有表示显示精度的显示分辨率,在打印机上有表示打印精度的
      打印机分辨率.
      
      1、显示分辨率
      显示分辨率是显示器在显示图像时的分辨率。显示分辨率的数值是指整个显示器所有
      可视面积上水平像素和垂直像素的数量.例如:800*600的分辨率是指在整个屏幕上水平
      显示800个像素,垂直显示600个像素.显示分辨率的水平像素和垂直像素的总数总是成一
      定的比例:一般为4:3,5:4,8:5
      
      2、打印机分辨率
      打印机分辨率直接关系到打印机输出图像或文字的质量好坏。打印机分辨率用dpi(dot per inch)
      来表示。喷墨打印机和激光打印机的分辨率通常是相同的.例如:打印机分辨率为600dpi,
      是指打印机在一平方英寸的区域内垂直打印600个点,水平打印600个点.总共可打印360000个点.



    1.2--打印机编程基础(未完) 
    1.2编程基础
      下面将了解打印编程中至关重要的两个概念"设备环境"和"映射模式"
      1.2.1设备环境
      设备环境本身是GDI(Graphics Device interface)对象.每个C++设备对象有一个相关的设备环境.它由
      一个32位HDC类型句柄来标识.GDI是Windows核心DLL中的一组接口函数.这些函数处于硬件的驱动程序
      之上.当应用程序调用这些函数的时候,它们再调用驱动程序提供的接口函数.
      
      1.CDC类
      使用MFC编程,所用的设备环境不是CDC就是从CDC派生的。CDC类中有两个与底层
      GDI对象有关的句柄m_hDC和m_hAttribDC.与m_hDC相关的GDI对象处理绘图函数的所有
      输出流;与m_hAttribDC句柄有关的GDI对象处理所有与绘图属性有关的操作。如颜色属性
      和绘图模式。
      
      每个窗口、控件都拥有一个覆盖窗口或控件的设备环境。我们可以使用任何一个控件的设备环境,
      从而绘制控件或者改善倥件的外观。
      
      获得设备环境对象指针需要调用GetDC函数。在构造一个CDC对象,并且对它处理完之后,务必使用
      ReleaseDC()函数将CDC对象释放。
      
      GetDC()函数和ReleaseDC()函数是CWnd类的成员函数,任何CWnd类及其派生类都可以通过调用它获得
      和释放设备环境对象。
      
      2. CClientDC和CWindowDC
      窗口客户区不包括边框、标题栏和菜单栏,创建CClientDC对象将获得客户区的设备
      环境。构造CClientDC对象,只需要向它传递一个指向窗口的指针,GetDC()函数会被
      自动调用。当ClientDC对象被销毁时,它会自动调用ReleaseDC()函数。
      
      CClientDC派生于CDC,在构造时调用了Windows函数GetDC,在析构时调用了ReleaseDC.
      和CClientDC对象相关的设备上下文是窗口的客户区.
      
      CClientDC 构造一个连接到CWnd上的CClientDC对象.
      CClientDC(CWnd *pWnd);
      throw (CResourceException);
      参数:
      pWnd:设备上下文将要存取的客户区所在的窗口.
      
      CWindowDC能够在整个应用程序窗口上绘图,包括标题栏和窗口边框。一般情况下CWindowDC
      对象很少用,但是当读者不欢喜Windows标准窗口标题栏的样式和按钮,就可以应用它了。
      
      CWindowDC类是从CDC继承的,它在构造的时候调用Windows函数GetWindowDC,在销毁的时候调用ReleaseDC.
      这意味着CWindowDC对象可以访问CWnd的全部屏幕区域(包括客户区和非客户区).
      
      
      AfxGetMainWnd()函数用于获得框架(包括标题栏、菜单栏、状态栏、边框)窗口的指针。
      AfxGetMainWnd()获得主框架窗口指针。
      
      
      
      3、CPaintDC
      CPaintDC类是一个特殊的设备环境封装类。它用来处理来自Windows的WM_PAINT消息。
      当窗口上覆盖的其他窗口移走,或者窗口最小化后又最大化时,窗口就会收一个系统
      发来的WM_PAINT消息,这时应用程序就会重画可见的区域。这个被重画的区域,我们称
      它为无效区域。WM_PAINT消息发出后,Windows会帮助用户判断哪些区域需要重画,哪些
      区域保持不变,从而加快窗口的显示速度。
      
      CPaintDC类有一个成员变量m_ps,m_ps有一个RECT类型的成员变量rcPaint.这个矩形变量
      保存了需要重画的矩形区域,即无效区域。m_ps是一个PAINTSTRUCT结构类型的变量。
      
      PAINTSTRUCT结构的定义如下所示:
      typedef struct tagPAINTSTRUCT
      {
         HDC hDC;
         BOOL fErase;
         RECT rcPaint;
         BOOL fRestore;
         BOOL fIncUpdate;
         byte rgbReserved[32];
      }PAINTSTRUCT;
      
      hdc是底层的GDI设备环境对象的句柄.Ferase标志判断背景是否被清除,如果这个标志
      被设置为TRUE,在重画之前将清除背景.rcPaint 保存了窗口无效区域(即需要重画的
      区域).最后三个变量被声明为保留变量.一般不改动.
      
      4、内存设备环境
      内存设备环境是一个没有设备与它联系的环境。我们一般利用与某个标准设备环境兼容
      的内存设备环境把一个位图复制到屏幕上去。为此可以先创建一个与某个标准设备环境兼容
      的内存设备环境,然后把所要显示的位图复制到内存设备环境中,最后再从内存设备环境复
      制到真正的设备环境,从而把位图显示出来。
      还可以创建内存设备环境对象,使用该对象在内存中绘图来代替在屏幕上绘图,绘制完成后,
      再调用BitBlt()函数把它复制到屏幕上去。这种方法绘图可以克服屏幕闪烁现象。
      
      CGdiObject::GetObject 把描述了将Windows GDI对象附加给CGdiObject对象的数据填充到缓冲区。
      int GetObject(int nCount,LPVOID lpObject)const;
      返回值:获取的字节数。如果发生错误,则返回0
      参数:
      nCount 指定要拷贝到lpObject缓冲区的字节数
      lpObject 指向用户应用缓冲区的指针以接收信息。
      说明:
      用定义指定对象的数据填充缓冲区。函数获得一个类型由图像对象决定
      的数据结构,列表如下所示:
      LoadBitmap  从应用的可执行文件中加载一个命名的位图资源来初始化位图对象。
      
      CDC::BitBlt 从指定设备上下文拷贝位图
      BOOL BitBlt(int x,int y,int nWidth,int nHeight,CDC *pSrcDC,int xSrc,int ySrc,DWORD dwRop);
      返回值:函数成功,返回非零值,否则为0
      
      参数:x 指定目标矩形左上角的逻辑x坐标
            y 指定目标矩形左上角的逻辑y坐标
            nWidth 指定目标矩形矩形和源位图的宽度(逻辑单位)
            nHeight 指定目标矩形和源位图的高度(逻辑单位)
            pSrcDC  指向CDC对象的指针,标识待拷贝位图的设备上下文。
            xSrc    指定源位图左上角的逻辑x坐标
            ySrc    指定源位图左上角的逻辑y坐标
            dwRop   指定要执行的光栅操作。
      CDC::CreateCompatibleDC 创建内存设备上下文,与另一个设备上下文匹配。可以
      用它在内存中准备图像。
      virtual BOOL CreateCompatibleDC(CDC *pDC);
      返回值:如果成功,则返回非零值,否则为0
      参数:
      pDC 设备上下文指针。如果pDC为NULL,函数将产生与系统兼容的内存设备上下文。
      说明:
      产生与pDC指定设备兼容的设备上下文内存,设备上下文内存包含显示表面的信息,
      它用于在向实际的兼容设备表面发送图像之前在内存中作好准备。
      
      void CPrintProjView::OnDrawBitmap()
      {
        CBitmap bitmap;
        bitmap.LoadBitmap(IDB_BITMAP1);
        BITMAP bmpInfo;
        CDC *pDC = GetDC();
        CDC memDC;
        memDC.CreateCompatibleDC();
        CBitmap *pBitmap = memDC.SelectObject(&bitmap);
        bitmap.GetObejct(sizeof(bmpInfo),&bmpInfo);
        pDC->BitBlt(0,0,bmpInfo.bmWidth,bmpInfo.bmHeight,&memDC,0,0,SRCCOPY);
         memDC.DeleteDC();
         ReleaseDC(pDC);
      }
      上面这段程序,先获得视图的设备环境,然后声明了一个内存设备环境memDC.
      memDC调用了CreateCompatibleDC()函数,作用是设置内存设备环境的大小等各种属性,
      从而使内存设备环境与屏幕窗口兼容。Bitmap是位图对象,它调用成员函数LoadBitmap
      载入位图;bmpInfo是BITMAP结构类型的变量,它保存了位图的长、宽等信息。
      
      BitBlt()函数把内存设备环境复制到屏幕设备环境。BitBlt()是一个图像复制函数。
      当向函数传递需要复制区域的宽、高度、开始偏移和复制模式这些参数时,它会将需要复制的
      矩形区域从内存设备环境复制到屏幕上。
      
      注意:
      程序中使用的位图类型是GDI位图。有两种类型的Windows位图:GDI位图和DIB位图。
      GDI位图对象是由MFC库的CBitmap类表示的。GDI位图对象有一个与之相关的Windows
      数据结构,它在Windows GDI模块内进行维护,它是与设备相关的。对于GDI位图来说,
      显示器的“位图”实际上就是显示器表面的映像,打印机设备的“位图”是打印机本身。
      因此,不能将位图选入显示设备环境或打印设备环境,这就是为什么必须使用
      CDC::CreateCompatibleDC函数创建一个特殊的内存设备环境。
      
      5、打印机设备上下文
      CDC::CreateDC 为指定设备创建设备上下文
      virtual BOOL CreateDC(LPCTSTR lpszDriverName,
                            LPCTSTR lpszDeviceName,
                            LPCTSTR lpszOutput,
                            const void *lpInitData)
      返回值:如果成功,则返回非零值,否则为0
      参数:
      lpszDriverName:指向空终止字符串的指针,字符串为设备驱动程序的文件名(不带扩展名,
      例如:"EPSON")也可以为该参数传递CString对象。
      
      lpszDeviceName:指向空终止字符串的指针,字符串为支持特定设备的文件名(例如:
      "EPSON FX-80")
      lpszOutput:指向空终止字符串的指针。字符串为指定了物理输出媒介的文件和设备名
      (文件或输出端口).也可以为该参数传递CString对象。
      
      lpInitData:指向DEVMODE结构的指针,该结构包含有指定设备驱动程序的初始数据,Windows
      的DocumentProperties函数从该结构中获得指定设备的信息。如果设备驱动程序使用用户
      在控制面板的缺省值,lpInitData参数一定要设置为NULL.
      
      说明:
      为指定的设备创建设备上下文。
      如果使用DEVMODE结构,就需要PRINT.H头文件。
      
      或者参数作如下说明:
      lpszDriverName是打印机所用的设备驱动程序。
      lpszDeviceName是进行打印的打印设备名称。设备驱动程序可以支持多种打印设备。
      lpszOutput:指定设备的串口名。
      lpInitData:是设备专用的初始化数据。
      使用打印机设备环境之后,必须调用DeleteDC()成员函数销毁它。但是,如果在堆栈
      中创建CDC类,程序返回时,设备环境自动删除。

      至此Windows提供的五个公共设备环境已经全部介绍完了。


    1.2.2 映射模式 
     /////////////////////////////////////////////////
     1.2.2 映射模式样
     vc++中采用的坐标映射方式使得用户图形坐标和输出设备的像素完全一致,当屏幕的像素大
     小为800*600时,每逻辑英寸包含的屏幕像素数为96,而打印机则需要多出
     好几倍的点数才能到达同样的逻辑尺寸。例如:HP打印机每逻辑英寸包含的打印机点数为600,
     也就是说打印机的清晰度比屏幕要高很多。这样的后果就是在屏幕上显示出来的满屏幕的图像
     在打印出来的纸上却只有一点点,怎么解决这个问题呢?一种简单的方法就是转换坐标映射方式,
     使得打印时采用的坐标比例比显示时采用的坐标比例相应地大若干倍.
     
     映射模式的意思是在屏幕或者打印机上绘图的时候,可以使用英寸或者毫米作为单位,这样更加直观.
     
     这其中要注意设备单位和逻辑单位的区别:逻辑单位是传递给绘图函数的x和y值,它们可以表示英寸和
     毫米,而设备单位是x和y在屏幕上的像素数,或是打印机上的点阵数。单击鼠标以设备单位返回。如果
     想知道鼠标单击位图的什么位置,需要将设备单位转化为逻辑单位。表1-5所示是可用的映射模式。
     
     映射模式        逻辑单位
     MM_TEXT         一个像素
     MM_LOMETRIC     0.1毫米
     MM_HIMETRIC     0.01毫米
     MM_LOENGLISH    0.01英寸
     MM_HIENGLISH    0.001英寸
     MM_TWIPS        1/1440英寸
     MM_ISOTROPIC    用户定义的值,但是x和y方向相等
     MM_ANISOTROPIC  用户定义的值,但是x和yy方向任意
     
     映射模式通过设备环境类的一个成员函数SetMapMode()来设置.只要把上述标志当作参数
     传入函数即可.设置映射模式后,传给任何绘图函数的坐标值都通过GDI内部的映射机制转
     换成设备坐标.如果把映射模式设置为MM_LOENGLISH,然后向绘图函数传递一个100的逻辑
     单位值,映射模式知道用户实际想要的是100*0.1mm,也就是1cm。
     
     映射模式MM_ISOTROPIC和MM_ANISOTROPIC允许改变比例因子和原点,使用这些模式改变窗
     口大小时,绘制的内容也会改变大小。
     
     使用MM_ISOTROPIC方式,x和y始终保持1:1的比例。如果在屏幕上画了一个圆,无论比例因
     子如何改变,始终还是圆,而在MM_ANISOTROPIC方式中,x和y比例因子可分别改变,圆会被
     压成椭圆。
     
     有一点需要注意:当使用MM_TEXT映射模式时候,y轴的正方向是沿屏幕垂直向下的,x轴是沿屏幕
     向右的.然而,其他的映射模式的y轴正方向是沿屏幕向上的,x轴正方向也是向右的.这一点特别容易
     搞混了.
     
     我们定义两个矩形,设置从逻辑环境到设备环境的转换。第一个矩形以逻辑单位表示绘制区域的大小,
     windows术语称之为窗口,第二个矩形以设备单位(即像素)表示第一个矩形大小代表的设备范围大小,
     在Windows术语中称之为视口,两种关系如图:
     1、设置视口到视图的客户区,并可忘记它。SetViewportOrg(0,0);SetViewPortExt(100,50);
     2、把窗口的初始位置设置在想在逻辑环境中看到的地方:SetWindowOrg(100,100);
     3、要缩小图形,只要缩小窗口的范围。要放大图形,只要增大窗口范围。要滚动图形,只要改变
     窗口的初始位置。
     
     SetWindowOrg()用来改变窗口坐标(逻辑坐标)的原点,SetViewPortOrg()用来改变视口坐标(设备坐标)
     的原点。
     void CPrintProjView::OnDraw(CDC *pDC)
     {
        pDC->SetMapMode(MM_TEXT);
        pDC->SetViewPortOrg(50,50);
        pDC->SetWindowPortOrg(200,200);
        CPen myPen(PS_SOLD,1,RGB(0,255,0));
        Cpen *pOldPen = pDC->SelectObject(&myPen);
        pDC->TextOut(200,200,_T("窗口映射模式"));
        pDC->Rectangle(200,200,400,400);
        pDC->SelectObject(pOldPen);
        myPen.DeleteObject();
     }
     这段代码,把视口原点设置为设置坐标(50,50),把窗口原点设置为逻辑坐标(200,200),
     然后通过窗口原点(200,200)绘制一个长宽各为200的方框.逻辑点(200,200)映射到设备
     点(50,50),即把窗口原点映射到视口原点.
     
     SetWindowExt()用来设定逻辑环境的大小,SetViewPortOrg()用来设定设备环境的大小.映射
     模式MM_ISOTROPIC和MM_ANISOTROPIC允许调用SetWindowExt()和SetViewPortExt()函数来设置
     设备坐标和逻辑坐标之间的比例.
     
     可以用下面的公式实现逻辑单位到设备单位的转换:
     x比例因子 = x视口长度/x窗口长度
     y比例因子 = y视口长度/y窗口长度
     设备x = 逻辑x * x比例因子 + x原点偏移量
     设备y = 逻辑y * y比例因子 + y原点偏移量
     设备环境本身提供了两个函数实现逻辑坐标和设备坐标之间的互换.DPtoLP()函数,可以
     接受一个指向CPoint类或者CRect类对象的指针作为参数,然后把它所指向的对象从设备
     坐标转换为逻辑坐标,LPtoDP的作用正好相反.
     
     下面这段程序根据GetClientRect()函数返回窗口客户矩形,函数SetWindowExt和SetViewportExt
     一起设置比例,结果窗口大小恰好是800*800,SetViewPortOrg函数把原点放置在窗口中心.这样椭
     圆恰好充满整个屏幕.
     
     void CPrintProjView::OnDraw(CDC *pDC)
     {
       CRect rcClient;
       GetClientRect(&rcClient);
       pDC->SetMapMode(MM_ANISOTROPIC);
       pDC->SetWindowExt(800,800);
       pDC->SetViewportExt(rcClient.right/2,rcClient.bottom/2);
       CPen myPen(PS_SOLID,1,RGB(255,0,0));
       CPen *pOldPen = pDC->SelectObject(&myPen);
       pDC->Ellipse(-400,-400,400,400);
       pDC->SelectObject(pOldPen);
       myPen.DeleteObject();
     }
     注意:SetWindowExt()和SetViewPortExt()函数只有在MM_ANISOTROPIC和MM_ISOTROPIC模式下才有效,
     在其他模式下,对它们的调用将被忽略。无论彩用哪种映射模式,GetClientRect()和GetWindowRect()
     函数返回的都是设备坐标(即像素)。


    1.2.3 MFC的打印功能分析
     
     1.框架中的打印
     MFC的框架内置了功能强大的打印和打印预览功能。首先,分析MFC应用程序框架打印的内在机制,
     这样将能更有效地使用打印机。
     
     在视图类及其派生类中,通过重载OnDraw(CDC*pDC)函数,利用它提供的pDC(设备上下文)指针,可以在
     屏幕上显示各种图形和数据。CView类的打印是通过OnPrint(CDC *pDC,CPrintInfo*pInfo)这个函数实现
     的,OnPrint()函数对打印的实现就是简单的调用OnDraw(CDC*pDC)这个函数,把打印机的设备上下文
     指针pDC传递给OnDraw(CDC *pDC)函数。可见CView类对输出到屏幕和输出到打印机的处理都是一样的,
     只是换了一个设备上下文而已(输出到屏幕时是通过OnPaint()函数实现的)。
     
     如果重载OnPrint()函数,可以选择根本不调用OnDraw来支持打印逻辑。OnPrint有两个参数,一个是指向
     打印机设备环境的指针,一个是打印信息对象CPrintInfo的指针,该信息包括纸张大小、当前页码和最大
     页码。对每个需要打印的页,应用程序框架都要调用一次OnPrint(),在OnPrintInfo结构中记录着当前页码。
     
     视图类及其派生类在进行显示和打印之前都会调用virtual void OnParepareDC(CDC *pDC,CPrintInfo* pInfo = null)
     这个虚成员函数来准备设备上下文,可以重载这个虚成员函数,进行坐标转换。下面是MFC框架打印的基本流程:
     
        OnPreparePrint---->设置起始和终止页  
           |
        OnBeginPrinting---->创建GDI对象
           |
        OnPrepareDC(每页)---->设置映射模式,并选择检测打印任务的结尾
           |
        OnPrint(每页)----->打印输出
           |
        是否打印完毕
           |
           y
        OnEndPrinting--->删除GDI对象
     
     CDC::GetDeviceCaps 获得指定设备的信息。
     int GetDeviceCaps(int nIndex)const;
     返回值:如果成功,则返回需要的能力值。
     LOGPIXELSX:沿显示宽度方向,每一逻辑单位的像素数。
     LOGPIXELSY:沿显示高度方向,每一逻辑单位的像素数。
     
     
     
     void CPrintProjView::OnDraw(CDC *pDC)
     {
       pDC->TextOut(400,200,_T("Hello word"));
     }
     
     BOOL CPrintProjView::OnPreparePrinting(CPrintInfo*pInfo)
     {
        pInfo->SetMaxPage(2);
        return DoPreparePrinting(pInfo);
     }
     
     void CPrintProjView::OnPrepareDC(CDC *pDC,CPrintInfo *pInfo)
     {
        CView::OnPrepareDC(pDC,pInfo);
        pDC->SetMapMode(MM_ANISOTROPIC); //转换坐标映射方式
        CSize size = CSize(800,560);
        pDC->SetWindowExt(size);//确定窗口的大小
        //得到实际设备每逻辑英寸的像素数量
        int xLogPixelPerInch = pDC->GetDeviceCaps(LOGPIXELSX);
        int yLogPixelPerInch = pDC->GetDeviceCaps(LOGPIXELSY);
        
        //得到设备坐标和逻辑坐标的比例
        long xExt = (long)size.cx * xLogPixelPerInch / 96;
        long yExt = (long)size.cy * yLogPixelPerInch / 96;
        pDC->SetViewportExt((int)xExt,(int)yExt); //设置视口大小
     }
     
     在上面的程序中,首先将坐标映射方式改变为MM_ANISOSTROPIC方式,即各向异性的意思,
     在这种坐标方式下,x轴和y轴的逻辑单位可以进行任意的缩放。改变坐标映射方式后,就要
     确定窗口大小和视口大小,注意窗口大小就是我们在屏幕上所见的尺寸,,而视口大小是实际
     设备,如打印机等,和显示设备每逻辑英寸的像素数量比较所得的比例尺.
     
     通过函数得到显示器和打印机每逻辑英寸的像素数量,然后对视口大小进行相应的缩放,就可以
     使屏幕上的显示和打印机的输出是一致了.
     
     编译、运行程序,使用打印预览功能,会看到在两个视图和预览两个页面中"Hello word"的位置是
     一样的。
     
     下次在看的时候,将这一部分再串一下。注意下面几点。
     1、只有在MM_ANISOTROPIC和MM_ISOTROPIC映射模式下,SetWindowExt和SetViewportExt才起作用。
        MM_ANISOTROPIC和MM_ISOTROPIC的区别是:
        MM_ANISOTROPIC是不锁定纵横比,也就是x和y各向异性,x和y轴的逻辑单位可以任意缩放。
        MM_ISOTROPIC:是锁定纵横比,x和y轴的比值是1:1
        
     2、SetWindowExt()设置窗口的x和y轴范围,也就是窗口的大小
        SetViewportExt()设置视口的x和y轴范围,也就是视口的大小
     
     3、窗口大小是指:我们在屏幕上所见的尺寸
        视口大小是指:实际设备,如打印机等.
        
    2、框架之外的打印
       框架实现了对打印的一些底层支持,直接的打印机制是通过函数StartDoc和EndDoc()来实现的。应用程序
       要使用打印机时,它首先使用CreateDC或PrintDlg来获取指向打印机设备环境的一个句柄,这就使得打印机
       设备驱动程序库模块被加载到内存(如果还没有加载到内存的话),并进行初始化。然后,程序调用StartDoc
       函数,通知一个新文档开始了。StartDoc函数是由GDI模块来处理的。GDI模块调用打印机设备驱动程序中的
       control函数告诉打印机准备打印。
       
       打印一个文档的过程以StartDoc调用开始,以EndDoc调用结束。调用StartPage来开始一页,调用EndPage来
       结束该页。
       
       下面这段代码在对话框中实现了对打印的支持。
       /*
       GetPrinterDC 获取设备环境的句柄。
       HDC GetPrinterDC()const;
       返回值:
       如果成功则返回一个打印机设备环境的句柄;否则返回null.
       说明:
       如果CPrintDialog构造函数的参数bPrintSetupOnly是FALSE(表明显示的是Print对话框,则GetPrinterDC返回一个
       打印机设备环境句柄。当你使用完这个设备环境时,你必须调用Windows DeleteDC函数来删除它。)
       */
       /*
       {
         long cbSize,
         CString lpszDocName,
         CString lpszOutput
       } DOCINFO;
       对文档进行定义的一个结构。
       cbSize:结构的大小
       lpszDocName:文档的名字
       lpszOutput:输出文档的名字
       */
       /*
       StartDoc:开始新的打印作业
       CDC::StartDoc
       int StartDoc(LPDOCINFO lpDocInfo);
       返回值:如果出错,例如存储空间不足或指定端口无效,则返回-1否则返回正值。
       参数:
       lpDocInfo: DOCINFO结构的指针。该结构包含了文档文件和输出文件的名字。
       
       说明:
       通知设备的驱动程序开始一个新的打印作业,其后所有的StartPage和EndPage调用处于假
       脱机状态,直到EndDoc调用出现。这确保了长于一页的文档不被其它作业中断。   
       */
       /*
       EndDoc  结束由StartDoc成员函数启动的打印作业。
       CDC::EndDoc
       int EndDoc();
       返回值:
       如果成功,则返回值大于零或等于零,出错则返回值小于零。下面列出了一般的错误
       类型:
       SP_ERROR:一般错误。
       SP_OUTOFDISK:假脱机所需的磁盘空间不足,没有其它可用的磁盘空间。
       SP_OUTOFMEMORY:假脱机所需的内存不足。
       SP_USERABORT: 用户在打印管理中中止作业。
       
       说明:
       中止由StartDoc成员函数调用的打印作业。在成功完成打印作业后应立即调用。
       如果应用遇到打印错误或取消的打印操作,决不可用EndDoc或AbortDoc去中止
       操作,GDI在返回错误值之前自动中止操作。   
       */
       /*
       StartPage:通知设备的驱动程序开始新页。
       CDC::StartPage
       int StartPage
       说明:
       调用该成员函数使用打印机驱动程序做好准备接收数据。在StartPage和EndPage之间,
       ResetDC成员函数不起作用。
       */
       /*
       EndPage:通知打印机驱动程序打印页结束。
       CDC::EndPage
       int EndPage()
       返回值:如果成功,则返回大于或等于零的值,如果失败则返回如下错误类型:
       SP_ERROR:一般错误
       SP_APPABORT: 作业终止
       SP_USERABORT:用户在打印管理中中止作业。
       SP_OUTOFDISK:假脱机所需的磁盘空间不足。
       SP_OUTOFMEMORY:假脱机所需的内存不足。
       
       说明:
       通知设备已经写完一页。该成员函数通常用在打印机驱动程序开始新的一页。
       */
       /*
       AbortDoc:终止当前打印任务,擦除自上次调用StartDoc成员函数以业写入设备的任何内容。
       CDC::AbortDoc
       int AbortDoc();
       返回值:如果成功,则返回大于或等于零的值,如果出现错误,则为负值。与EndPage和
       EndDoc返回的错误类型值一样。
       说明:
       终止当前打印任务,并擦除自上次StartDoc以后写入设备的任何任务。
       */
       
       /*
       CPrintInfo没有基类。
       CPrintInfo存储有关一次打印或打印预览的信息。每次选择Print或PrintPreview命令,框架
       就会创建一个CPrintInfo对象。并在命令完成时删除此对象。
       
       CPrintInfo包含打印时的一般信息,例如:要打印页的范围,打印机的状态,当前正在打印的页
       这些信息存放在CPintInfo的对象中;此对象还包括在CPrint对话框中输入的值。
       
       在打印期间,一个CPrintInfo对象在框架和视图类之间传递,并且用于两者之间交换信息。例如:
       框架通过对CPrintInfo类的m_nCurPgae成员赋值,来通知视图类要打印文档的哪一页,视图类检索
       此值,并执行指定页的实际打印。
       
       另一个例子就是文档的长度到打印的时候也不知道多少页。视图类每打印一页都要检测是否到了文档的
       末尾。当到达文档的末尾时,视图类将CPrintInfo的m_bContinuePrinting成员设置为FALSE,通知框架
       停止打印循环。
       */
       /*
       Attach:把Windows设备上下文句柄附加在CDC对象上。
       CDC::Attach
       BOOL Attach(HDC hDC);
       返回值:如果成功,返回非零值,否则为0
       参数:
       hDC:Windows设备上下文。
       说明:
       使用这个函数把hDC附加到CDC对象上。
       */
       /*
       Detach:从CDC对象中分离出Windows设备上下文。
       CDC::Detach
       HDC Detach()
       返回值:Windows设备上下文句柄。
       说明:
       调用该函数将m_hDC从CDC对象中分离出来。并将m_hDC与m_bAttribDC设备为NULL。
       */
       void CPrintProj::Print()
       {
        CDC dc;
        CPrintDialog printDlg(FALSE);
           //利用CPrintDialog生成打印机设备环境
        if(printDlg.DoModual() == IDCANCEL) //让用户选择打印纸张等
           return;
        dc.Attach(printDlg.GetPrinterDC());//让Handle连接到dc上.
        dc.m_bPrinting = TRUE;

        CString strTitle;
        strTitle.LoadString(AFX_IDS_APP_TITLE);

        DOCINFO di; //DOCINFO中有相关的打印信息
        ::ZeroMemory(&di,sizeof(DOCINFO));
        di.cbSize = sizeof(DOCINFO);
        di.lpszDocName = strTitle; //设置标题

        BOOL bPrintingOK = dc.StartDoc(&di); //开始打印

        CPrintInfo Info;
        Info.m_rectDraw.SetRect(0,0,dc.GetDeviceCaps(HORZRES),dc.GetDeviceCaps(VERTRES)); //设置范围.

           OnBeginPrinting(&dc,&Info); //调用你自定义的打印功能.
        fo(UINT page = Info.GetMinPage();page < Info.GetMaxPage() && bPrintOK;page++)
        {
         Info.m_nCurPage = page;
         OnPrint(&dc,&Info); //调用你的"Print page"函数
         bPrintOK = dc.EndPage() > 0; //结束页
        }
        OnEndPrinting(&dc,&Info);//结束打印.

        if(bPrintingOK)
         dc.EndDoc();
        else
         dc.AbortDoc();
        dc.Detach();
       }
       
       说明:其实在Windows环境中是设备无关的.只要有了DC,就可以使用各种GDI函数,而不需要理会是在屏幕或是在
       打印机上绘图.


    第2章      打印机基本控制技术

      打印编程之所以比较复杂,在于其需要处理的信息比较复杂.假如编程打印一张报表,我们不但要处理文本信息、
      完成表格绘制、版面编排等,而且可能还要进行相关图像处理。因此编写一个功能强大的打印程序,必须熟练
      掌握打印相关的控制技术。
    2。1 控制打印机
     2。1。1选择当前打印机
      一个系统可以连接多个打印机,甚至可以有其他程序(传真软件)将自已伪装成打印机.不论连接的打印机有多少,
      始终有一个"当前打印机"或者"默认打印机".

      获取默认打印机设备环境的标准方法有:EnumPrinters()函数.该函数填充一个包含每个所连接打印机信息的数组
      结构.根据需要,还可以选择几种结构作为该函数的参数. 如结构PRINTER_INFO_x,x是一个数字,在Windows95,98上
      函数使用PRINTER_INFO_5结构,在Windows2000/xp上,函数使用PRINTER_INFO_4结构.

      获取当前打印机的另一种方法是使用打印对话框CPrintDialog.该类的GetDeviceName()函数用以获得当前选择的打印
      机的名称,GetDriverName()函数用以获得当前选择的打印机驱动程序.

      /*
      EnumPrinters
      Declare function EnumPrinters lib (ByVal flags As long,ByVar name As String,
      Byval Level As long,pPrinterEnum As Byte,ByVal cdBuf As long,pcbNeeded As Long,pcReturned As long ) As Long

      说明:枚举系统中安装的打印机.

      返回值:
      long,非零表示成功,零表示失败.可以通过GetLastError获取错误值.

      参数:
      flags: long,一个或多个下述标志.
       PRINTER_ENUM_LOCAL:检举本地打印机(包括Window95中的网络打印机)
       PRINTER_ENUM_NAME:枚举由name参数指定的打印机.如果name为NULL,则枚举出可用的打印机.
       PRINTER_ENUM_SHARE:枚举出共享打印机(必须同其他常数组合使用)
       PRINTER_ENUM_CONNECTIONS:枚举网络列表中的打印机.(仅适用于NT)
       PRINTER_ENUM_NETWORK:枚举通过网络连接的打印机.级别(level)必须为1,仅适用于NT
      name:String,vbNullString表示枚举同本机连接的打印机.否则由标志和级别决定.
      Level:Long,1,2,4或5(4仅适用于NT,5仅适用于Win95和NT4.0)指定欲枚举的结构的类型.
      如果是1,则name参数由标志设置决定.如果是2或5,那么name就代表要枚举的打印服务器的名字.
      或者为vcbNullString.如果是4,那么只有PRINTER_ENUM_LOCAL和PRINTER_ENUM_CONNECTIONS才有效.名字必须是vbNullString.

      pPrinterEnum:Byte,包含PRINTER_ENUM_x结构的缓冲区,其中x代表级别.
      cbBuf:Long,pPrinterEnum缓冲区中的字符数量.
      pcbNeeded:Long,指向一个Long型变量的指针,该变量用于保存请求的缓冲区长度.或者实际读入的字节数量.
      pcReturned:Long,载入缓冲区的结构数量(用于那些能返回多个结构的函数).

      说明:
      第4级和第5级将它们的结构建立在系统注册表的基础上.而且要比第2级快得多.

      使用EnumPrinter时,要包含以下头文件:#include <winspool.h>
      */
      注意:使用Printing and Print Spooler 函数必须包含"winspool.h"头文件。
      注意:使用Windows NT/2000/xp的读者使用EnumPrinters()函数时请使用PRINTER_INFO_4结构。

      void CPrintProj::OnEnumPrinters()
      {
       DWORD dwSize,dwPtiner;
       ::EnumPrinters(PRINTER_ENUM_LOCAL,NULL,5,NULL,0,&dwSize,&dwPrinter);
       BYTE *pBuffer = new BYTE[dwSize];
       ::EnumPrinters(PRINTER_ENUM_LOCAL,NULL,5,pBuffer,dwSize,&dwSize,&dwPrinter);

       if(dwPrinter != 0)
       {
        PRINTER_INFO_5 *pPrnInfo = (PRINTER_INFO_5*)pBuffer;
        for(UINT i = 0; i < dwPrinter; i++)
        {
         CString strprnInfo = pPrnInfo->pPrinterName;
         AfxMessageBox(strprnInfo);
         pPrnInfo++;
        }
       }
       delete[] pBuffer;
      }


    2。1。2监测当前打印机状态
      Windows标准的打印机监测程序。通过该程序,我们可以了解当前打印机的状态,包括打印机任务队列,
      各项任务状态、所有者、进度和开始时间,并且可以及时暂停、清除打印任务。

      监控打印机就有必要对Windows后台打印机制有所了解。后台程序可以减轻应用程序的打印负担。Windows在启动时就加载后台
      打印程序。因此,当应用程序开始打印时,它已经是活动的了。当程序打印一个文件时,GDI模块创建打印的数据文件。后台
      打印程序的任务是将这些文件发往打印机。GDI模块发出一个消息来通知它开始一个新的打印作业。然后打印机开始读文件,
      并将文件传送到打印机。为了传送打印文件,后台程序为打印机所连接的并口和串口提供各种通信函数。在后台程序向打
      印机发送文件操作结束的命令后,它就删除包含输出数据的临时文件。

                   后台打印程序的组合。
      ----------------------------------------
      打印请求后台程序     | 将数据流传递给打印机
      本地打印提供者       | 为本地打印机创建后台文件
      网络打印提供者       | 为网络打印机创建后台文件。
      打印处理程序         | 将后台的设备无关数据转换为特定打印机的格式
      端口监视程序         | 控制连接打印机的端口
      语言监视程序         | 控制双向通信的打印机,配置并检测打印机的状态

      使用后台打印程序,真正的文件打印操作是后台打印程序的任务,而不是应用程序的任务。
      我们可以暂停打印作业、改变作业的优先级或者取消打印作业。这种管理方式使得应用程序
      可能比下面的这种情况“打印”得更快。即作业以实时方式打印,且必须等到打印完一页后
      才能处理下一页。

      但是如果我们拥有更好的打印机软硬件或者在网络打印机等某些特殊的情况下,可以去掉Windows后台打印程序。
      去掉Windows后以中打印程序可以加快打印速度。因为打印输出不必保存在硬盘上,而可以直接输出到打印机,并被
      外部硬件或软件后台程序所接受。

      实现类似Windows打印监测试程序的功能,需要调用Win32假脱机(SPOOL)枚举API函数。"[使用Win32假脱机枚举函数需要调用
      两次所需的函数。这些API函数通常要填充一组结构。但是,这些结构通常包含指向字符串和其他类型数据的指针。
      这些外来的数据必须存储在返回的内存中,使得字符串和其他数据存储在结构中。所以简单的在堆栈中声明一组结构变量
      不足以为API函数返回的信息设置足够的内存。]"

      因此,正确的函数调用是:首先调用API函数确定需要的内存空间,在随后的调用中传入一个指针,该指针指向了一块动态
      分配的大小合适内存空间。需要进行该类处理的枚举函数包括:
      EnumForms(),EnumJobs(),EnumMonitors(),EnumPorts(),EnumPrinterDrivers(),EnumPrinters()和EnumPrinterProcessors()

      下面这段程序,使用EnumJobs()API函数,枚举出所选打印机当前的打印任务。关于其他打印监控函数的使用大家可以参考
      微软平台SDK中关于打印函数和打印假脱机枚举函数的文档。

      注意:独占设备是指在一个程序(作业、用户)的整个运行其间独占的设备,直到该程序(作业、用户)完成。系统的独占设备是
      有限的(譬如一台计算机只能连接一台打印机),往往不能满足多进程的要求,会引起大量进程由于等待某些独占设备而阻塞,
      成为系统的“瓶颈”。另一方面,申请到独占设备的进程在其整个运行期间虽然占有设备,利用率却常常很低,设备还是经常
      处于空闲状态。为了解决这种矛盾,最常用的方法就是用共享设备来模拟独占设备,从而提高系统地效率和设备利用率.这种
      技术称为虚拟设备技术.实现这一技术的软、硬件系统被称为假脱机(Simultaneous Peripaheral Operation On Line SPOOL)
      系统。打印机是典型的独占设备。引入SPOOL技术后,用户的打印请求传递给SPOOL系统,而不是真正的把打印机分配给用户。
      SPOOL系统的输出进程是先磁盘上申请一个空闲区域,把需要打印的数据传输到里面,再把用户的打印请求挂到打印机队列上。
      如果打印机空闲,就会从打印机队列中取出一个打印请求,再从磁盘上的指定区域取出数据,执行打印操作。由于磁盘是共享
      的,SPOOL系统可以随时响应打印请求并把数据缓存起来,这样独占设备改造成了共享设备,从而提高了设备的利用率和系统
      利用率。

       /*
       OpenPrinter
       Declare Function OpenPrinter Lib "winspool.drv" Alias "OpenPrinterA"(ByVal pPrinterName As String,phPrinter As Long,
       pDefault As PRINTER_DEFAULTS)As Long

       说明:
       打开指定的打印机,并获取打印机的句柄。
       返回值:
       Long,非零表示成功,零表示失败。会设置GetLastError
       参数表:
       pPrinterName  String,要打开的打印机的名字
       phPrinter     Long,用于装载打印机的句柄。
       pDefault      PRINTER_DEFAULT,这个结构保存要载入的打印机信息。
       */
       /*
       Declare Function EnumJobs Lib "winspool.drv"Alias "EnumJobsA"(ByVal hPrinter As Long,
       ByVal First Job As Long,ByVal NoJobs As Long,ByVal Level As Long,pJob As Byte,ByVal cdBuf As Long
       ,pcbNeeded As Long,pcReturned As Long)As Long

       说明:
       枚举打印队列中的作业
       返回值:
       Long,非零表示成功,零表示失败。 可以通过GetLastError获取错误信息。
       hPrinter:  Long,一个已打开的打印机对象的句柄(用OpenPrinter获得)
       FirstJob:  Long,作业列表中要枚举的第一个作业的索引(注意编号从0开始)
       NoJobs     Long,要枚举的作业数量。
       Level      Long,1或2
       pJob       Byte,包含JOB_INFO_1或JOB_INFO_2结构的缓冲区
       cbBuf      Long,pJob缓冲区中的字符数量。
       pcbNeeded  Long,指向一个LONG型变量的指针,该变量用于保存请求的缓冲区长度
                  或者实际读入的字节数量。
       pcReturned Long,载入缓冲区的结构数量(用于那些能返回多个结构的函数)。
       */
       /*
       GetLastError
       Declare Function GetLastError lib "kernel32"Alias "GetLastError"As Long
       说明:
       针对之前调用的api函数,用这个函数取得扩展错误信息
       返回值:Long,由api函数决定.请参考api32.txt文件,其中列出了一系列错误常数;都以ERROR_前
       缀起头.常用的错误代码见下表:
       ERROR_INVALID_HANDLE:无效的句柄作为一个参数传递。
       ERROR_CALL_NOT_IMPLEMENTED  在win95下调用专为win nt设计的win32 api函数
       ERROR_INVALID_PARAMETER   函数中有个参数不正确。
       */
       /*
       EnumJobs
       Declare Function EnumJobs Lib "winspool.drv" alias "EnumJobsA"(ByVal hPrinter As Long,ByVal FirstJob As Long,ByVal NoJobs As Long,By Level As Long
       pJob As Byte,ByVal cdBuf As Long,pcbNeeded As Long,pcReturned As Long) As Long

       说明:
       枚举打印队列中的作业
       返回值:
       Long,非零表示成功,零表示失败。可以通过GetLastError获取错误信息
       参数表:
       hPrinter         Long,一个已打开的打印机对象的句柄(用OpenPrinter获得)
       FirstJob         Long,作业列表中要枚举的第一个作业的索引(注意编号从0开始)
       NoJobs           Long,要枚举的作业数量。
       Level            Long,1或2
       pJob             Byte,包含JOB_INFO_1或JOB_INFO_2结构的缓冲区。
       cbBuf            Long,pJob缓冲区中的字符数量。
       pcbNeeded        Long,指向一个Long型变量的指针,该变量用于保存请求的缓冲区长度,或者实际读入的字节数。
       pcbReturned      Long,载入缓冲区的结构数量。
       */

       BOOL CPrintListDlg::PrintJobList(LPCTSTR szPrintName)
       {
        HANDLE   hPrinter;
        DWORD    dwNeeded,dwReturned,i;
        JOB_INFO_1  *pJobInfo;

        if(!OpenPrinter(szPrinterName,&hPrinter,NULL))
         return FALSE;
        if(EnumJobs(hPrinter,0,0xFFFFFFFF,NULL,1,NULL,0,&dwNeeded,&dwReturned))
        {
         if(GetLastError() != ERROR_INSUFFICIENT_BUFFER)
         {
          ClosePrinter(hPrinter);
          return FALSE;
         }
        }
        if((pJobInfo = (JOB_INFO_1*)malloc(dwNeeded)) == NULL)
        {
         ClosePrinter(hPrinter);
         return FALSE;
        }
        if(!EnumJobs(hPrinter,0,0xFFFFFFFF,1,(LPBYTE)pJobInfo,dwNeeded,&dwNeeded,&dwReturned))
        {
         ClosePrinter(hPrinter);
         free(pJobInfo);
         return FALSE;
        }
        ClosePrinter(hPrinter);
        
        for(UINT i = 0; i < dwReturned; i++)
        {
         m_listBox2.AddString(pJobInfo[i].pDocument);
        }
        free(pJobInfo);
        return TRUE;
       }
       void CPrintListDlg::OnSelchangePrinters()
       {
        int nIndex = m_listBox.GetCurSel();
        CString strPrinterName;
        if(nIndex != CB_ERR)
        {
         m_listBox.GetText(nIndex,strPrinterName);
         if(strPrinterName.IsEmpty())
         {
          PrintJobList((LPCTSTRstrPrinterName));
         }
        }
       }

    上一篇:绕过驱动直接发送原始数据到打印机C代码
    下一篇:添加网络打印机.bat