日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

教你怎么使用打印机(api)

發布時間:2024/2/2 综合教程 36 生活家
生活随笔 收集整理的這篇文章主要介紹了 教你怎么使用打印机(api) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

使用打印機

壹佰軟件開發小組整理編譯

為了處理文字和圖形而使用視訊顯示器時,設備無關的概念看來非常完美,但對于打印機,設備無關的概念又怎樣呢?

總的說來,效果也很好。在Windows程序中,用于視訊顯示器的GDI函數一樣可以在印表紙上打印文字和圖形,在以前討論的與設備無關的許多問題(多數都與平面顯示的尺寸、分辨率以及顏色數有關)都可以用相同的方法解決。當然,一臺打印機不像使用陰極射線管的顯示器那么簡單,它們使用的是印表紙。它們之間有一些比較大的差異。例如,我們從來不必考慮視訊顯示器沒有與顯示卡連結好,或者顯示器出現「屏幕空間不夠」的錯誤,但打印機off line和缺紙卻是經常會遇到的問題。

我們也不必擔心顯示卡不能執行某些圖形操作,更不用擔心顯示卡能否處理圖形,因為,如果它不能處理圖形,就根本不能使用Windows。但有些打印機不能打印圖形(盡管它們能在Windows環境中使用)。繪圖機盡管可以打印向量圖形,卻存在位圖塊的傳輸問題。

以下是其它一些需要考慮的問題:

打印機比視訊顯示器慢。盡管我們沒有機會將程序性能調整到最佳狀態,卻不必擔心視訊顯示器更新所需的時間。然而,沒有人想在做其它工作前一直等待打印機完成打印任務。
 

程序可以用新的輸出覆蓋原有的顯示輸出,以重新使用視訊顯示器表面。這對打印機是不可能的,打印機只能用完一整頁紙,然后在新一頁的紙上打印新的內容。
 

在視訊顯示器上,不同的應用程序都被窗口化。而對于打印機,不同應用程序的輸出必須分成不同的文件或打印作業。
 

為了在GDI的其余部分中加入打印機支持功能,Windows提供幾個只用于打印機的函數。這些限用在打印機上的函數(StartDoc、EndDoc、StartPage和EndPage)負責將打印機的輸出組織打印到紙頁上。而一個程序呼叫普通的GDI函數在一張紙上顯示文字和圖形,和在屏幕上顯示的方式一樣。

在第十五、十七和十八章有打印位圖、格式化的文字以及metafile的其它信息。

打印入門

當您在Windows下使用打印機時,實際上啟動了一個包含GDI32動態鏈接庫模塊、打印驅動程序動態連結模塊(帶.DRV擴展名)、Windows后臺打印程序,以及有用到的其它相關模塊。在寫打印機打印程序之前,讓我們先看一看這個程序是如何進行的。

打印和背景處理

當應用程序要使用打印機時,它首先使用CreateDC或PrintDlg來取得指向打印機設備內容的句柄,于是使得打印機設備驅動程序動態鏈接庫模塊被加載到內存(如果還沒有加載內存的話)并自己進行初始化。然后,程序呼叫StartDoc函數,通知說一個新文件開始了。StartDoc函數是由GDI模塊來處理的,GDI模塊呼叫打印機設備驅動程序中的Control函數告訴設備驅動程序準備進行打印。

打印一個文件的程序以StartDoc呼叫開始,以EndDoc呼叫結束。這兩個呼叫對于在文件頁面上書寫文字或者繪制圖形的GDI命令來說,其作用就像分隔頁面的書擋一樣。每頁本身是這樣來劃清界限的:呼叫StartPage來開始一頁,呼叫EndPage來結束該頁。

例如,如果應用程序想在一頁紙上畫出一個橢圓,它首先呼叫StartDoc開始打印任務,然后再呼叫StartPage通知這是新的一頁,接著呼叫Ellipse,正如同在屏幕上畫一個橢圓一樣。GDI模塊通常將程序對打印機設備內容做出的GDI呼叫儲存在磁盤上的metafile中,該文件名以字符串~EMF(代表「增強型metafile」)開始,且以.TMP為擴展名。然而,我在這里應該指出,打印機驅動程序可能會跳過這一步驟。

當繪制第一頁的GDI呼叫結束時,應用程序呼叫EndPage?,F在,真正的工作開始了。打印機驅動程序必須把存放在metafile中的各種繪圖命令翻譯成打印機輸出數據。繪制一頁圖形所需的打印機輸出數據量可能非常大,特別是當打印機沒有高級頁面制作語言時,更是如此。例如,一臺每英寸600點且使用8.5×11英寸印表紙的激光打印機,如果要定義一個圖形頁,可能需要4百萬以上字節的數據。

為此,打印機驅動程序經常使用一種稱作「打印分帶」的技術將一頁分成若干稱為「輸出帶」的矩形。GDI模塊從打印機驅動程序取得每個輸出帶的大小,然后設定一個與目前要處理的輸出帶相等的剪裁區,并為metafile中的每個繪圖函數呼叫打印機設備驅動程序的Output函數,這個程序叫做「將metafile輸出到設備驅動程序」。對設備驅動程序所定義的頁面上的每個輸出帶,GDI模塊必須將整個metafile「輸出到」設備驅動程序。這個程序完成以后,該metafile就可以刪除了。

對每個輸出帶,設備驅動程序將這些繪圖函數轉換為在打印機上打印這些圖形所需要的輸出數據。這種輸出數據的格式是依照打印機的特性而異的。對點陣打印機,它將是包括圖形序列在內的一系列控制命令序列的集合(打印機驅動程序也能呼叫在GDI模塊中的各種「helper」輔助例程,用來協助這種輸出的構造)。對于帶有高階頁面制作語言(如PostScript)的激光打印機,打印機將用這種語言進行輸出。

打印驅動程序將打印輸出的每個輸出帶傳送到GDI模塊。隨后,GDI模塊將該打印輸出存入另一個臨時文件中,該臨時文件名以字符串~SPL開始,帶有.TMP擴展名。當處理好整頁之后,GDI模塊對后臺打印程序進行一個程序間呼叫,通知它一個新的打印頁已經準備好了。然后,應用程序就轉向處理下一頁。當應用程序處理完所有要打印的輸出頁后,它就呼叫EndDoc發出一個信號,表示打印作業已經完成。圖13-1顯示了應用程序、GDI模塊和打印驅動程序的交互作用程序。

 

圖13-1
應用程序、GDI模塊、打印驅動程序和打印隊列程序的交互作用過程

Windows后臺打印程序實際上是幾個組件的一種組合(見表13-1)。

表13-1

打印隊列程序組件

說明

打印請求隊列程序

將數據流傳遞給打印功能提供者

本地打印功能提供者

為本地打印機建立背景文件

網絡打印功能提供者

為網絡打印機建立背景文件

打印處理程序

將打印隊列中與設備無關的數據轉換為針對目的打印機的格式

打印端口監視程序

控件連結打印機的端口

打印語言監視程序

控件可以雙向通訊的打印機,設定設備設定并檢測打印機狀態

打印隊列程序可以減輕應用程序的打印負擔。Windows在啟動時就加載打印隊列程序,因此,當應用程序開始打印時,它已經是活動的了。當程序行印一個文件時,GDI模塊會建立包含打印輸出數據的文件。后臺打印程序的任務是將這些文件發往打印機。GDI模塊發出一個消息來通知它一個新的打印作業開始,然后它開始讀文件并將文件直接傳送到打印機。為了傳送這些文件,打印隊列程序依照打印機所連結的并列端口或串行埠使用各種不同的通信函數。在打印隊列程序向打印機發送文件的操作完成后,它就將包含輸出數據的臨時文件刪除。這個交互作用過程如圖13-2所示。

 

圖13-2 后臺打印程序的操作程序

這個程序的大部分對應用程序來說是透明的。從應用程序的角度來看,「打印」只發生在GDI模塊將所有打印輸出數據儲存到磁盤文件中的時候,在這之后(如果打印是由第二個線程來操作的,甚至可以在這之前)應用程序可以自由地進行其它操作。真正的文件打印操作成了后臺打印程序的任務,而不是應用程序的任務。通過打印機文件夾,使用者可以暫停打印作業、改變作業的優先級或取消打印作業。這種管理方式使應用程序能更快地將打印數據以實時方式打印,況且這樣必須等到打印完一頁后才能處理下一頁。

我們已經描述了一般的打印原理,但還有一些例外情況。其中之一是Windows程序要使用打印機時,并非一定需要后臺打印程序。使用者可以在打印機屬性表格的詳細數據屬性頁中關閉打印機的背景操作。

為什么使用者希望不使用背景操作呢?因為使用者可能使用了比Windows打印隊列程序更快的硬件或軟件后臺打印程序,也可能是打印機在一個自身帶有打印隊列器的網絡上使用。一般的規則是,使用一個打印隊列程序比使用兩個打印隊列程序更快。去掉Windows后臺打印程序可以加快打印速度,因為打印輸出數據不必儲存在硬盤上,而可以直接輸出到打印機,并被外部的硬件打印隊列器或軟件的后臺打印程序所接收。

如果沒有啟用Windows打印隊列程序,GDI模塊就不把來自設備驅動程序的打印輸出數據存入文件中,而是將這些輸出數據直接輸出到打印輸出埠。與打印隊列程序進行的打印不同,GDI進行的打印一定會讓應用程序暫停執行一段時間(特別是進行打印中的程序)直到打印完成。

還有另一個例外。通常,GDI模塊將定義一頁所需的所有函數存入一個增強型metafile中,然后替驅動程序定義的每個打印輸出帶輸出一遍該metafile到打印驅動程序中。然而,如果打印驅動程序不需要打印分帶的話,就不會建立這個metafile;GDI只需簡單地將繪圖函數直接送往驅動程序。進一步的變化是,應用程序也可能得承擔起對打印輸出數據進行打印分帶的責任,這就使得應用程序中的打印程序代碼更加復雜了,但卻免去了GDI模塊建立metafile的麻煩。這樣,GDI只需簡單地為每個輸出帶將函數傳到打印驅動程序。

或許您現在已經發現了從一個Windows應用程序進行打印操作要比使用視訊顯示器的負擔更大,這樣可能出現一些問題-特別是,如果GDI模塊在建立metafile或打印輸出文件時耗盡了磁盤空間。您可以更關切這些問題,并嘗試著處理這些問題并告知使用者,或者您當然也可以置之不理。

對于一個應用程序,打印文件的第一步就是如何取得打印機設備的內容。

打印機設備內容

正如在視訊顯示器上繪圖前需要得到設備內容句柄一樣,在打印之前,使用者必須取得一個打印機設備內容句柄。一旦有了這個句柄(并為建立一個新文件呼叫了StartDoc以及呼叫StartPage開始一頁),就可以用與使用視訊顯示設備內容句柄相同的方法來使用打印機設備內容句柄,該句柄即為各種GDI呼叫的第一個參數。

大多數應用程序經由呼叫PrintDlg函數打開一個標準的打印對話框(本章后面會展示該函數的用法)。這個函數還為使用者提供了一個在打印之前改變打印機或者指定其它特性的機會。然后,它將打印機設備內容句柄交給應用程序。該函數能夠省下應用程序的一些工作。然而,某些應用程序(例如Notepad)僅需要取得打印機設備內容,而不需要那個對話框。要做到這一點,需要呼叫CreateDC函數。

在第五章中,您已知道如何通過如下的呼叫來為整個視訊顯示器取得指向設備內容的句柄:

hdc = CreateDC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
        

您也可以使用該函數來取得打印機設備內容句柄。然而,對打印機設備內容,CreateDC的一般語法為:

hdc = CreateDC (NULL, szDeviceName, NULL, pInitializationData) ;
        

pInitializationData參數一般被設為NULL。szDeviceName參數指向一個字符串,以告訴Windows打印機設備的名稱。在設定設備名稱之前,您必須知道有哪些打印機可用。

一個系統可能有不只一臺連結著的打印機,甚至可以有其它程序,如傳真軟件,將自己偽裝成打印機。不論連結的打印機有多少臺,都只能有一臺被認為是「目前的打印機」或者「內定打印機」,這是使用者最近一次選擇的打印機。許多小型的Windows程序只使用內定打印機來進行打印。

取得內定打印機設備內容的方式不斷在改變。目前,標準的方法是使用EnumPrinters函數來獲得。該函數填入一個包含每個連結著的打印機信息的數組結構。根據所需的細節層次,您還可以選擇幾種結構之一作為該函數的參數。這些結構的名稱為PRINTER_INFO_x,x是一個數字。

不幸的是,所使用的函數還取決于您的程序是在Windows 98上執行還是在Windows NT上執行。程序13-1展示了GetPrinterDC函數在兩種操作系統上工作的用法。

程序13-1 GETPRNDC

        
GETPRNDC.C
        
/*----------------------------------------------------------------------
        
  GETPRNDC.C -- GetPrinterDC function
        
-----------------------------------------------------------------------*/
        
#include <windows.h>
        
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 = malloc (dwNeeded) ;
        
                         EnumPrinters (PRINTER_ENUM_DEFAULT, NULL, 5, (PBYTE) pinfo5,
        
                   dwNeeded, &dwNeeded, &dwReturned) ;
        
                          hdc = CreateDC (NULL, pinfo5->pPrinterName, NULL, NULL) ;
        
                          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) ;
        
                          hdc = CreateDC (NULL, pinfo4->pPrinterName, NULL, NULL) ;
        
                          free (pinfo4) ;
        
           }
        
           return hdc ; 
        
}
        

這些函數使用GetVersion函數來確定程序是執行在Windows 98上還是Windows NT上。不管是什么操作系統,函數呼叫EnumPrinters兩次:一次取得它所需結構的大小,一次填入結構。在Windows 98上,函數使用PRINTER_INFO_5結構;在Windows NT上,函數使用PRINTER_INFO_4結構。這些結構在EnumPrinters文件(/Platform SDK/Graphics and Multimedia Services/GDI/Printing and Print Spooler/Printing and Print Spooler Reference/Printing and Print Spooler Functions/EnumPrinters,范例小節的前面)中有說明,它們是「容易而快速」的。

修改后的DEVCAPS程序

第五章的DEVCAPS1程序只顯示了從GetDeviceCaps函數獲得的關于視訊顯示的基本信息。程序13-2所示的新版本顯示了關于視訊顯示和連結到系統之所有打印機的更多信息。

程序13-2 DEVCAPS2

        
DEVCAPS2.C
        
/*--------------------------------------------------------------------------
        
  DEVCAPS2.C --         Displays Device Capability Information (Version 2)
        
                                                                (c) Charles Petzold, 1998
        
---------------------------------------------------------------------------*/
        
#include <windows.h>
        
#include "resource.h"
        

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") ;
        
           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                = szAppName ;
        
           wndclass.lpszClassName               = szAppName ;
        
   
        
           if (!RegisterClass (&wndclass))
        
           {
        
                  MessageBox (  NULL, TEXT ("This program requires Windows NT!"),
        
                                                                        szAppName, MB_ICONERROR) ;
        
                  return 0 ;
        
           }
        
   
        
           hwnd = CreateWindow (szAppName, NULL,
        
                         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 ;
        
}
        

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 ;
        
           static PRINTER_INFO_5 * pinfo5 ;
        
           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) ;
        
// fall through
        
           case   WM_SETTINGCHANGE:
        
                          hMenu = GetSubMenu (GetMenu (hwnd), 0) ;
        
        
        
                          while (GetMenuItemCount (hMenu) > 1)
        
                                  DeleteMenu (hMenu, 1, MF_BYPOSITION) ;
        

                                  // Get a list of all local and remote printers
        
                                  //
        
                                 // First, find out how large an array we need; this
        
                                  //   call will fail, leaving the required size in dwNeeded
        
                                  //
        
                                 // Next, allocate space for the info array and fill it
        
                                  //
        
                                 // Put the printer names on the menu
        

            if (GetVersion () & 0x80000000)                                     // Windows 98
        
                  {
        
                                  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) ;
        
                  AppendMenu (hMenu, 0, IDM_DEVMODE, TEXT ("Properties")) ;
        
        
        
                  wParam = IDM_SCREEN ;
        
// fall through
        
           case   WM_COMMAND :
        
                          hMenu = GetMenu (hwnd) ;
        
        
        
                          if (   LOWORD (wParam) == IDM_SCREEN ||     // IDM_SCREEN & Printers
        
                                         LOWORD (wParam) < IDM_DEVMODE)     
        
                          {
        
                          CheckMenuItem (hMenu, nCurrentDevice, MF_UNCHECKED) ;
        
                          nCurrentDevice = LOWORD (wParam) ;
        
                          CheckMenuItem (hMenu, nCurrentDevice, MF_CHECKED) ;
        
                          }
        
                          else if (LOWORD (wParam) == IDM_DEVMODE)     // Properties selection
        
                          {
        
                                                 GetMenuString (hMenu, nCurrentDevice, szDevice,
        
                                  sizeof (szDevice) / sizeof (TCHAR), MF_BYCOMMAND);
        
             
        
                                                 if (OpenPrinter (szDevice, &hPrint, NULL))
        
                                                 {
        
                                                 PrinterProperties (hwnd, hPrint) ;
        
                                                 ClosePrinter (hPrint) ;
        
                                                 }
        
                          }
        
                          else
        
// info menu items
        
         {
        
                                         CheckMenuItem (hMenu, nCurrentInfo, MF_UNCHECKED) ;
        
                                         nCurrentInfo = LOWORD (wParam) ;
        
                                         CheckMenuItem (hMenu, nCurrentInfo, MF_CHECKED) ;
        
                          }
        
                          InvalidateRect (hwnd, NULL, TRUE) ;
        
                          return 0 ;
        
       
        
           case   WM_INITMENUPOPUP :
        
                          if (lParam == 0)
        
                                                EnableMenuItem (GetMenu (hwnd), IDM_DEVMODE,
        
                                                         nCurrentDevice == IDM_SCREEMF_GRAYED : MF_ENABLED) ;
        
                          return 0 ;
        
        
        
           case   WM_PAINT :
        
                          lstrcpy (szWindowText, TEXT ("Device Capabilities: ")) ;
        
        
        
                          if (nCurrentDevice == IDM_SCREEN)
        
                          {
        
                                         lstrcpy (szDevice, TEXT ("DISPLAY")) ;
        
                                         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[] =
        
           {
        
                 HORZSIZE,                     TEXT ("HORZSIZE                   Width in millimeters:"),
        
                  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:"),
        
                  NUMCOLORS,                    TEXT   ("NUMCOLORS                       Number of device colors:"),
        
                  PDEVICESIZE, TEXT("PDEVICESIZESize 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:"),
        
PHYSICALWIDTH, TEXT("PHYSICALWIDTH Printer page pixel "),
        
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[] =
        
    {
        
       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:"),
        
       RC_BITMAP64,  TEXT ("RC_BITMAP64  Supports bitmaps >64K:"),
        
       RC_GDI20_OUTPUT, TEXT ("RC_GDI20_OUTPUT Has 2.0 output calls:"),
        
       RC_DI_BITMAP, TEXT ("RC_DI_BITMAP  Supports DIB to memory:"),
        
           RC_PALETTE,   TEXT ("RC_PALETTE      Supports a palette:"),
        
          RC_DIBTODEV,  TEXT ("RC_DIBTODEV Supports bitmap conversion:"),
        
          RC_BIGFONT,   TEXT ("RC_BIGFONT  Supports fonts >64K:"),
        
           RC_STRETCHBLT,TEXT ("RC_STRETCHBLT Supports StretchBlt:"),
        
           RC_FLOODFILL, TEXT ("RC_FLOODFILL  Supports FloodFill:"),
        
          RC_STRETCHDIB,TEXT ("RC_STRETCHDIB 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)") } ;
        
  int                                  i ;
        
   TCHAR                                szBuffer[80] ;
        
        
        
           TextOut (hdc, cxChar, cyChar, szBuffer,
        
         wsprintf (szBuffer, TEXT ("%-24s%04XH"), TEXT ("DRIVERVERSION:"),
        
              GetDeviceCaps (hdcInfo, DRIVERVERSION))) ;
        
  TextOut (hdc, cxChar, 2 * cyChar, szBuffer,
        
                          wsprintf (szBuffer, TEXT ("%-24s%-40s"), TEXT ("TECHNOLOGY:"),
        
                                         szTech[GetDeviceCaps (hdcInfo, TECHNOLOGY)])) ;
        
   TextOut (hdc, cxChar, 4 * cyChar, szBuffer,
        
                          wsprintf (szBuffer, TEXT ("CLIPCAPS (Clipping capabilities)"))) ;
        
           for (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"))) ;
        
           TextOut (hdc, cxChar, 8 * cyChar, szBuffer,
        
                  wsprintf (szBuffer, TEXT ("RASTERCAPS (Raster capabilities)"))) ;
        
   for (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_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:")
        
           } ;
        
   
        
           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:"),
        
                  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:"),
        
                  TC_SF_X_YINDEP,  TEXT ("TC_SF_X_YINDEP  Can do scaling independent of X and Y:"),
        
                  TC_SA_DOUBLE,    EXT ("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 ;
        
                  BITS                          (*pbits)[] ;
        
                  int                           iSize ;
        
  }
        
           bitinfo[] =
        
           {
        
                  CURVECAPS,    TEXT ("CURVCAPS (Curve Capabilities)"),
        
                                  (BITS (*)[]) curves, sizeof (curves) / sizeof (curves[0]),
        
                  LINECAPS,     TEXT ("LINECAPS (Line Capabilities)"),
        
                                  (BITS (*)[]) lines, sizeof (lines) / sizeof (lines[0]),
        
                  POLYGONALCAPS, TEXT ("POLYGONALCAPS (Polygonal Capabilities)"),
        
                                  (BITS (*)[]) poly, sizeof (poly) / sizeof (poly[0]),
        
                  TEXTCAPS,     TEXT ("TEXTCAPS (Text Capabilities)"),
        
                                  (BITS (*)[]) text, sizeof (text) / sizeof (text[0])
        
   } ;
        
   
        
           static TCHAR szBuffer[80] ;
        
           BITS                  (*pbits)[] = bitinfo[iType].pbits ;
        
           int                  i, iDevCaps = GetDeviceCaps (hdcInfo, bitinfo[iType].iIndex) ;
        
   
        
           TextOut (hdc, cxChar, cyChar, bitinfo[iType].szTitle,
        
                                                 lstrlen (bitinfo[iType].szTitle)) ;
        
           for (i = 0 ; i < bitinfo[iType].iSize ; i++)
        
           extOut (hdc, cxChar, (i + 3) * cyChar, szBuffer,
        
            wsprintf (szBuffer, TEXT ("%-55s %3s"), (*pbits)[i].szDesc,
        
            iDevCaps & (*pbits)[i].iMask ? TEXT ("Yes") : TEXT ("No")));
        
}
        

DEVCAPS2.RC (摘錄)

        
//Microsoft Developer Studio generated resource script.
        
#include "resource.h"
        
#include "afxres.h"
        
/////////////////////////////////////////////////////////////////////////////
        
// Menu
        
DEVCAPS2 MENU DISCARDABLE
        
BEGIN
        
   POPUP "&Device"
        
   BEGIN
        
                          MENUITEM "&Screen",IDM_SCREEN, CHECKED
        
   END
        
   POPUP "&Capabilities"
        
   BEGIN
        
                  MENUITEM "&Basic Information",IDM_BASIC
        
                          MENUITEM "&Other Information",IDM_OTHER
        
                          MENUITEM "&Curve Capabilities",IDM_CURVE
        
                          MENUITEM "&Line Capabilities",IDM_LINE
        
                          MENUITEM "&Polygonal Capabilities",IDM_POLY
        
                          MENUITEM "&Text Capabilities",IDM_TEXT
        
   END
        
END
        

RESOURCE.H (摘錄)

        
// Microsoft Developer Studio generated include file.
        
// Used by DevCaps2.rc
        
#define IDM_SCREEN       40001
        
#define IDM_BASIC         40002
        
#define IDM_OTHER         40003
        
#define IDM_CURVE         40004
        
#define IDM_LINE          40005
        
#define IDM_POLY         40006
        
#define IDM_TEXT          40007
        

因為DEVCAPS2只取得打印機的信息內容,使用者仍然可以從DEVCAPS2的菜單中選擇所需打印機。如果使用者想比較不同打印機的功能,可以先用打印機文件夾增加各種打印驅動程序。

PrinterProperties呼叫

DEVCAPS2的「Device」菜單中上還有一個稱為「Properties」的選項。要使用這個選項,首先得從 Device菜單中選擇一個打印機,然后再選擇Properties,這時彈出一個對話框。對話框從何而來呢?它由打印機驅動程序呼叫,而且至少還讓使用者選擇紙的尺寸。大多數打印機驅動也可以讓使用者在「直印(portrait)」或「橫?。╨andscape)」模式中進行選擇。在直印模式(一般為內定模式)下,紙的短邊是頂部。在橫印模式下,紙的長邊是頂部。如果改變該模式,則所作的改變將在DEVCAPS2程序從GetDeviceCaps函數取得的信息中反應出來:水平尺寸和分辨率將與垂直尺寸和分辨率交換。彩色繪圖機的「Properties」對話框內容十分廣泛,它們要求使用者輸入安裝在繪圖機上之畫筆的顏色和使用之繪圖紙(或透明膠片)的型號。

所有打印機驅動程序都包含一個稱為ExtDeviceMode的輸出函數,它呼叫對話框并儲存使用者輸入的信息。有些打印機驅動程序也將這些信息儲存在系統登錄的自己擁有的部分中,有些則不然。那些儲存信息的打印機驅動程序在下次執行Windows時將存取該信息。

允許使用者選擇打印機的Windows程序通常只呼叫PrintDlg(本章后面我會展示用法)。這個有用的函數在準備打印時負責和使用者之間所有的通訊工作,并負責處理使用者要求的所有改變。當使用者單擊「Properties」按鈕時,PrintDlg還會啟動屬性表格對話框。

程序還可以通過直接呼叫打印機驅動程序的ExtDeviceMode或ExtDeveModePropSheet函數,來顯示打印機的屬性對話框,然而,我不鼓勵您這樣做。像DEVCAPS2那樣,透過呼叫PrinterProperties來啟動對話框會好得多。

PrinterProperties要求打印機對象的句柄,您可以通過OpenPrinter函數來得到。當使用者取消屬性表格對話框時,PrinterProperties傳回,然后使用者通過呼叫ClosePrinter,釋放打印機句柄。DEVCAPS2就是這樣做到這一點的。

程序首先取得剛剛在Device菜單中選擇的打印機名稱,并將其存入一個名為szDevice的字符數組中。

GetMenuString (    hMenu, nCurrentDevice, szDevice,
        
                                                 sizeof (szDevice) / sizeof (TCHAR), MF_BYCOMMAND) ;
        

然后,使用OpenPrinter獲得該設備的句柄。如果呼叫成功,那么程序接著呼叫PrinterProperties啟動對話框,然后呼叫ClosePrinter釋放設備句柄:

if (OpenPrinter (szDevice, &hPrint, NULL))
        
{
        
           PrinterProperties (hwnd, hPrint) ;
        
           ClosePrinter (hPrint) ;
        
}
        

檢查BitBlt支持

您可以用GetDeviceCaps函數來取得頁中可打印區的尺寸和分辨率(通常,該區域不會與整張紙的大小相同)。如果使用者想自己進行縮放操作,也可以獲得相對的圖素寬度和高度。

打印機能力的大多數信息是用于GDI而不是應用程序的。通常,在打印機不能做某件事時,GDI會仿真出那項功能。然而,這是應用程序應該事先檢查的。

以RASTERCAPS(「位映像支持」)參數呼叫GetDeviceCaps,它傳回的RC_BITBLT位包含了另一個重要的打印機特性,該位標示設備是否能進行位塊傳送。大多數點陣打印機、激光打印機和噴墨打印機都能進行位塊傳送,而大多數繪圖機卻不能。不能處理位塊傳送的設備不支持下列GDI函數:CreateCompatibleDC、CreateCompatibleBitmap、PatBlt、BitBlt、StretchBlt、GrayString、DrawIcon、SetPixel、GetPixel、FloodFill、ExtFloodFill、FillRgn、FrameRgn、InvertRgn、PaintRgn、FillRect、FrameRect和InvertRect。這是在視訊顯示器上使用GDI函數與在打印機上使用它們的唯一重要區別。

最簡單的打印程序

現在可以開始打印了,我們盡可能簡單地開始。事實上,我們的第一個程序只是讓打印機走紙而已。程序13-3的FORMFEED程序,展示了打印所需的最小需求。

程序13-3 FORMFEED

        
FORMFEED.C
        
/*-----------------------------------------------------------------------
        
  FORMFEED.C -- Advances printer to next page
        
                                                         (c) Charles Petzold, 1998
        
------------------------------------------------------------------------*/
        
#include <windows.h>
        
HDC GetPrinterDC (void) ;
        
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
        
                                         LPSTR lpszCmdLine, int iCmdShow)
        
{
        
           static DOCINFO di = { sizeof (DOCINFO), TEXT ("FormFeed") } ;
        
           HDC                                         hdcPrint = GetPrinterDC () ;
        
   
        
           if (hdcPrint != NULL)
        
           {
        
          if (StartDoc (hdcPrint, &di) > 0)
        
                                         if (StartPage (hdcPrint) > 0 && EndPage (hdcPrint) > 0)
        
                                                         EndDoc (hdcPrint) ;
        
                  DeleteDC (hdcPrint) ;
        
          }
        
           return 0 ;
        
}
        

這個程序也需要前面程序13-1中的GETPRNDC.C文件。

除了取得打印機設備內容(然后再刪除它)外,程序只呼叫了我們在本章前面討論過的四個打印函數。FORMFEED首先呼叫StartDoc開始一個新的文件,它測試從StartDoc傳回的值,只有傳回值是正數時,才繼續下去:

if (StartDoc (hdcPrint, &di) > 0)
        

StartDoc的第二個參數是指向DOCINFO結構的指針。該結構在第一個字段包含了結構的大小,在第二個字段包含了字符串「FormFeed」。當文件正在被打印或者在等待打印時,這個字符串將出現在打印機任務隊列中的「Document Name」列中。通常,該字符串包含進行打印的應用程序名稱和被打印的文件名稱。

如果StartDoc成功(由一個正的傳回值表示),那么FORMFEED呼叫StartPage,緊接著立即呼叫EndPage。這一程序將打印機推進到新的一頁,再次對傳回值進行測試:

if (StartPage (hdcPrint) > 0 && EndPage (hdcPrint) > 0)
        

最后,如果不出錯,文件就結束:

EndDoc (hdcPrint) ;
        

要注意的是,只有當沒出錯時,才呼叫EndDoc函數。如果其它打印函數中的某一個傳回錯誤代碼,那么GDI實際上已經中斷了文件的打印。如果打印機目前未打印,這種錯誤代碼通常會使打印機重新設定。測試打印函數的傳回值是檢測錯誤的最簡單方法。如果您想向使用者報告錯誤,就必須呼叫GetLastError來確定錯誤。

如果您寫過MS-DOS下的簡單利用打印機走紙的程序,就應該知道,對于大多數打印機,ASCII碼12啟動走紙。為什么不簡單地使用C的鏈接庫函數open,然后用write輸出ASCII碼12呢?當然,您完全可以這么做,但是必須確定打印機連結的是串行端口還是并列埠。然后您還要確定另外的程序(例如,打印隊列程序)是不是正在使用打印機。您并不希望在文件打印到一半時被別的程序把正在打印的那張紙送出打印機,對不對?最后,您還必須確定ASCII碼12是不是所連結打印機的走紙字符,因為并非所有打印機的走紙字符都是12。事實上,在PostScript中的走紙命令便不是12,而是單字showpage。

簡單地說,不要試圖直接繞過Windows;而應該堅持在打印中使用Windows函數。

打印圖形和文字

在一個Windows程序中,打印所需的額外負擔通常比FORMFEED程序高得多,而且還要用GDI函數來實際打印一些東西。我們來寫個打印一頁文字和圖形的程序,采用FORMFEED程序中的方法,并加入一些新的東西。該程序將有三個版本PRINT1、PRINT2和PRINT3。為避免程序代碼重復,每個程序都用前面所示的GETPRNDC.C文件和PRINT.C文件中的函數,如程序13-4所示。

程序13-4 PRINT

        
PRINT.C
        
/*------------------------------------------------------------------------
        
  PRINT.C -- Common routines for Print1, Print2, and Print3
        
--------------------------------------------------------------------------*/
        
#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) ;
        
           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:
        
                          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) ;
        
        
        
                          PageGDICalls (hdc, cxClient, cyClient) ;
        
        
        
                          EndPaint (hwnd, &ps) ;
        
                         return 0 ;
        
        
        
           case   WM_DESTROY :
        
                          PostQuitMessage (0) ;
        
                          return 0 ;
        
           }
        
           return DefWindowProc (hwnd, message, wParam, lParam) ;
        
}
        

PRINT.C包括函數WinMain、WndProc以及一個稱為PageGDICalls的函數。PageGDICalls函數接收打印機設備內容句柄和兩個包含打印頁面寬度及高度的變量。這個函數還負責畫一個包圍整個頁面的矩形,有兩條對角線,頁中間有一個橢圓(其直徑是打印機高度和寬度中較小的那個的一半),文字「Hello, Printer!」位于橢圓的中間。

處理WM_CREATE消息時,WndProc將一個「Print」選項加到系統菜單上。選擇該選項將呼叫PrintMyPage,此函數的功能在程序的三個版本中將不斷增強。當打印成功時,PrintMyPage傳回TRUE值,如果遇到錯誤時則傳回FALSE。如果PrintMyPage傳回FALSE,WndProc就會顯示一個消息框以告知使用者發生了錯誤。

打印的基本程序

打印程序的第一個版本是PRINT1,見程序13-5。經編譯后即可執行此程序,然后從系統菜單中選擇「Print」。接著,GDI將必要的打印機輸出儲存在一個臨時文件中,然后打印隊列程序將它發送給打印機。

程序13-5 PRINT1

        
PRINT1.C
        
/*---------------------------------------------------------------------
        
  PRINT1.C -- Bare Bones Printing
        
                                                         (c) Charles Petzold, 1998
        
----------------------------------------------------------------------*/
        
#include <windows.h>
        
HDC         GetPrinterDC (void) ;                        // in GETPRNDC.C
        
void        PageGDICalls (HDC, int, int) ;               // in PRINT.C
        

HINSTANCE hInst ;
        
TCHAR              szAppName[] = TEXT ("Print1") ;
        
TCHAR              szCaption[] = TEXT ("Print Program 1") ;
        

BOOL PrintMyPage (HWND hwnd)
        
{
        
           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) ;
        
   
        
   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 ;
        
   
        
           DeleteDC (hdcPrn) ;
        
           return bSuccess ;
        
}
        

我們來看看PRINT1.C中的程序代碼。如果PrintMyPage不能取得打印機的設備內容句柄,它就傳回FALSE,并且WndProc顯示消息框指出錯誤。如果函數成功取得了設備內容句柄,它就通過呼叫GetDeviceCaps來確定頁面的水平和垂直大?。ㄒ詧D素為單位)。

xPage = GetDeviceCaps (hdcPrn, HORZRES) ;
        
yPage = GetDeviceCaps (hdcPrn, VERTRES) ;
        

這不是紙的全部大小,只是紙的可打印區域。呼叫后,除了PRINT1在StartPage和EndPage呼叫之間呼叫PageGDICalls,PRINT1的PrintMyPage函數中的程序代碼在結構上與FORMFEED中的程序代碼相同。僅當呼叫StartDoc、StartPage和EndPage都成功時,PRINT1才呼叫EndDoc打印函數。

使用放棄程序來取消打印

對于大型文件,程序應該提供使用者在應用程序行印期間取消打印任務的便利性。也許使用者只要打印文件中的一頁,而不是打印全部的537頁。應該要能在印完全部的537頁之前糾正這個錯誤。

在一個程序內取消一個打印任務需要一種被稱為「放棄程序」的技術。放棄程序在程序中只是個較小的輸出函數,使用者可以使用SetAbortProc函數將該函數的地址傳給Windows。然后GDI在打印時,重復呼叫該程序,不斷地問:「我是否應該繼續打???」

我們看看將放棄程序加到打印處理程序中去需要些什么,然后檢查一些旁枝末節。放棄程序一般命名為AbortProc,其形式為:

BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)
        
{
        
    //其它行程序
        
}
        

打印前,您必須通過呼叫SetAbortProc來登記放棄程序:

SetAbortProc (hdcPrn, AbortProc) ;
        

在呼叫StartDoc前呼叫上面的函數,打印完成后不必清除放棄程序。

在處理EndPage呼叫時(亦即,在將metafile放入設備驅動程序并建立臨時打印文件時),GDI常常呼叫放棄程序。參數hdcPrn是打印機設備內容句柄。如果一切正常,iCode參數是0,如果GDI模塊在生成臨時文件時耗盡了磁盤空間,iCode就是SP_OUTOFDISK。

如果打印作業繼續,那么AbortProc必須傳回TRUE(非零);如果打印作業異常結束,就傳回FALSE(零)。放棄程序可以被簡化為如下所示的形式:

BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)
        
{
        
           MSG   msg ;
        
           while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
        
           {
        
                  TranslateMessage (&msg) ;
        
                  DispatchMessage (&msg) ;
        
           }
        
           return TRUE ;
        
}
        

這個函數看起來有點特殊,其實它看起來像是消息循環。使用者會注意到,這個「消息循環」呼叫PeekMessage而不是GetMessage。我在第五章的RANDRECT程序中討論過PeekMessage。應該還記得,PeekMessage將會控制權返回給程序,而不管程序的消息隊列中是否有消息存在。

只要PeekMessage傳回TRUE,那么AbortProc函數中的消息循環就重復呼叫PeekMessage。TRUE值表示PeekMessage已經找到一個消息,該消息可以通過TranslateMessage和DispatchMessage發送到程序的窗口消息處理程序。若程序的消息隊列中沒有消息,則PeekMessage的傳回值為FALSE,因此AbortProc將控制權返回給Windows。

Windows如何使用AbortProc

當程序進行打印時,大部分工作發生在要呼叫EndPage時。呼叫EndPage前,程序每呼叫一次GDI繪圖函數,GDI模塊只是簡單地將另一個記錄加到磁盤上的metafile中。當GDI得到EndPage后,對打印頁中由設備驅動程序定義的每個輸出帶,GDI都將該metafile送入設備驅動程序中。然后,GDI將打印機驅動程序建立的打印輸出儲存到一個文件中。如果沒有啟用后臺打印,那么GDI模塊必須自動將該打印輸出寫入打印機。

在EndPage呼叫期間,GDI模塊呼叫您設定的放棄程序。通常iCode參數為0,但如果由于存在未打印的其它臨時文件,而造成GDI執行時磁盤空間不夠,iCode參數就為SP_OUTOFDISK(通常您不會檢查這個值,但是如果愿意,您可以進行檢查)。放棄程序隨后進入PeekMessage循環從自己的消息隊列中找尋消息。

如果在程序的消息隊列中沒有消息,PeekMessage會傳回FALSE,然后放棄程序跳出它的消息循環并給GDI模塊傳回一個TRUE值,指示打印應該繼續進行。然后GDI模塊繼續處理EndPage呼叫。

如果有錯誤發生,那么GDI將中止打印程序,這樣,放棄程序的主要目的是允許使用者取消打印。為此,我們還需要一個顯示「Cancel」按鈕的對話框,讓我們采用兩個獨立的步驟。首先,我們在建立PRINT2程序時增加一個放棄程序,然后在PRINT3中增加一個帶有「Cancel」按鈕的對話框,使放棄程序可用。

實作放棄程序

現在快速復習一下放棄程序的機制??梢远x一個如下所示的放棄程序:

BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)
        
{
        
           MSG  msg ;
        
           while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
        
           {
        
                  TranslateMessage (&msg) ;
        
                  DispatchMessage (&msg) ;
        
           }
        
           return TRUE ;
        
}
        

當您想打印什么時,使用下面的呼叫將指向放棄程序的指針傳給Windows:

SetAbortProc (hdcPrn, AbortProc) ;
        

在呼叫StartDoc之前進行這個呼叫就行了。

不過,事情沒有這么簡單。我們忽視了AbortProc程序中PeekMessage循環這個問題,它是個很大的問題。只有在程序處于打印程序時,AbortProc程序才會被呼叫。如果在AbortProc中找到一個消息并把它傳送給窗口消息處理程序,就會發生一些非常令人討厭的事情:使用者可以從菜單中再次選擇「Print」,但程序已經處于打印例程之中。程序在打印前一個文件的同時,使用者也可以把一個新文件加載到程序里。使用者甚至可以退出程序!如果這種情況發生了,所有使用者程序的窗口都將被清除。當打印例程執行結束時,除了退到不再有效的窗口例程之外,您無處可去。

這種東西會把人搞得暈頭轉向,而我們的程序對此并未做任何準備。正是由于這個原因,當設定放棄程序時,首先應禁止程序的窗口接受輸入,使它不能接受鍵盤和鼠標輸入??梢杂靡韵碌暮瘮低瓿蛇@項工作:

EnableWindow (hwnd, FALSE) ;
        

它可以禁止鍵盤和鼠標的輸入進入消息隊列。因此在打印程序中,使用者不能對程序做任何工作。當打印完成時,應重新允許窗口接受輸入:

EnableWindow (hwnd, TRUE) ;
        

您可能要問,既然沒有鍵盤或鼠標消息進入消息隊列,為什么我們還要進行AbortProc中的TranslateMessage和DispatchMessage呼叫呢?實際上并不一定非得需要TranslateMessage,但是,我們必須使用DispatchMessage,處理WM_PAINT消息進入消息隊列中的情況。如果WM_PAINT消息沒有得到窗口消息處理程序中的BeginPaint和EndPaint的適當處理,由于PeekMessage不再傳回FALSE,該消息就會滯留在隊列中并且妨礙工作。

當打印期間阻止窗口處理輸入消息時,您的程序不會進行顯示輸出。但使用者可以切換到其它程序,并在那里進行其它工作,而后臺打印程序則能繼續將輸出文件送到打印機。

程序13-6所示的PRINT2程序在PRINT1中增加了一個放棄程序和必要的支持-呼叫AbortProc函數并呼叫EnableWindow兩次(第一次阻止窗口接受輸入消息,第二次啟用窗口)。

程序13-6 PRINT2

        
PRINT2.C
        
/*---------------------------------------------------------------------
        
  PRINT2.C --   Printing with Abort Procedure
        
                                                         (c) Charles Petzold, 1998
        
----------------------------------------------------------------------*/
        
#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)") ;
        

BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)
        
{
        
                  MSG msg ;
        
                  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 ;
        
}
        

增加打印對話框

PRINT2還不能令人十分滿意。首先,這個程序沒有直接指示出何時開始打印和何時結束打印。只有將鼠標指向程序并且發現它沒有反應時,才能斷定它仍然在處理PrintMyPage例程。PRINT2在進行背景處理時也沒有給使用者提供取消打印作業的機會。

您可能注意到,大多數Windows程序都為使用者提供了一個取消目前正在進行打印操作的機會。一個小的對話框出現在屏幕上,它包括一些文字和「Cancel」按鍵。在GDI將打印輸出儲存到磁盤文件或(如果停用打印隊列程序)打印機正在打印的整個期間,程序都顯示這個對話框。它是一個非系統模態對話框,您必須提供對話程序。

通常稱這個對話框為「放棄對話框」,稱這種對話程序為「放棄對話程序」。為了更清楚地把它和「放棄程序」區別開來,我們稱這種對話程序為「打印對話程序」。放棄程序(名為AbortProc)和打印對話程序(將命名為PrintDlgProc)是兩個不同的輸出函數。如果想以一種專業的Windows式打印方式進行打印工作,就必須擁有這兩個函數。

這兩個函數的交互作用方式如下:AbortProc中的PeekMessage循環得被修改,以便將非系統模態對話框的消息發送給對話框窗口消息處理程序。PrintDlgProc必須處理WM_COMMAND消息,以檢查「Cancel」按鈕的狀態。如果「Cancel」鈕被按下,就將一個叫做bUserAbort的整體變量設為TRUE。AbortProc傳回的值正好和bUserAbort相反。您可能還記得,如果AbortProc傳回TRUE會繼續打印,傳回FALSE則放棄打印。在PRINT2中,我們總是傳回TRUE。現在,使用者在打印對話框中按下「Cancel」按鈕時將傳回FALSE。程序13-7所示的PRINT3程序實作了這個處理方式。

程序13-7 PRINT3

        
PRINT3.C
        
/*-----------------------------------------------------------------
        
  PRINT3.C --   Printing with Dialog Box
        
                                                         (c) Charles Petzold, 1998
        
-------------------------------------------------------------------*/
        
#include <windows.h>
        
HDC GetPrinterDC (void) ;                        // in GETPRNDC.C
        
voidPageGDICalls (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:
        
                          bUserAbort = TRUE ;
        
                          EnableWindow (GetParent (hDlg), TRUE) ;
        
                          DestroyWindow (hDlg) ;
        
                          hDlgPrint = NULL ;
        
                        return TRUE ;
        
           }
        
           return FALSE ;
        
}
        

BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)
        
{
        
           MSG msg ;
        
           while (!bUserAbort && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
        
           {
        
                  if (!hDlgPrint || !IsDialogMessage (hDlgPrint, &msg))
        
                  {
        
                                  TranslateMessage (&msg) ;
        
                                  DispatchMessage (&msg) ;
        
                 }
        
           }
        
  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) ;
        
           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) ;
        
   return bSuccess && !bUserAbort ;
        
}
        
PRINT.RC (摘錄)
        
//Microsoft Developer Studio generated resource script.
        
#include "resource.h"
        
#include "afxres.h"
        
/////////////////////////////////////////////////////////////////////////////
        
// Dialog
        
PRINTDLGBOX DIALOG DISCARDABLE  20, 20, 186, 63
        
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
        
FONT 8, "MS Sans Serif"
        
BEGIN
        
   PUSHBUTTON                           "Cancel",IDCANCEL,67,42,50,14
        
   CTEXT                                                "Cancel Printing",IDC_STATIC,7,21,172,8
        
END
        

如果您使用PRINT3,那么最好臨時暫停使用后臺打??;否則,只有在打印隊列程序從PRINT3中接收數據時才可見到的「Cancel」按鈕可能會很快消失,讓您根本沒有機會去按它。如果您按「Cancel」按鈕時打印并不立即終止(特別是在一個慢速打印機上),不要驚訝。打印機有一個內部緩沖區,在打印機停止之前其中的數據必須全部送出,按「Cancel」只是告訴GDI不要向打印機的緩沖區發送更多的數據而已。

PRINT3增加了兩個整體變量:一個是叫做bUserAbort的布爾變量,另一個是叫做hDlgPrint的對話框窗口句柄。PrintMyPage函數將bUserAbort初始化為FALSE。與PRINT2一樣,程序的主窗口是不接收輸入消息的。指向AbortProc的指標用于SetAbortProc呼叫中,而指向PrintDlgProc的指標用于CreateDialog呼叫中。CreateDialog傳回的窗口句柄儲存在hDlgPrint中。

現在,AbortProc中的消息循環如下:

while (!bUserAbort && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
        
{
        
           if (!hDlgPrint || !IsDialogMessage (hDlgPrint, &msg))
        
          {
        
                  TranslateMessage (&msg) ;
        
                  DispatchMessage (&msg) ;
        
           }
        
}
        
return !bUserAbort ;
        

只有在bUserAbort為FALSE,也就是使用者還沒有終止打印工作時,這段程序代碼才會呼叫PeekMessage。IsDialogMessage函數用來將消息發送給非系統模態對話框。和普通的非系統模態對話框一樣,對話框窗口的句柄在這個呼叫之前受到檢查。AbortProc的傳回值正好與bUserAbort相反。開始時,bUserAbort為FALSE,因此AbortProc傳回TRUE,表示繼續進行打?。坏莃UserAbort可能在打印對話程序中被設定為TRUE。

PrintDlgProc函數是相當簡單的。處理WM_INITDIALOG時,該函數將窗口標題設定為程序名稱,并且停用系統菜單上的「Close」選項。如果使用者按下了「Cancel」鈕,PrintDlgProc將收到WM_COMMAND消息:

case        WM_COMMAND :
        
           bUserAbort = TRUE ;
        
           EnableWindow (GetParent (hDlg), TRUE) ;
        
           DestroyWindow (hDlg) ;
        
           hDlgPrint = NULL ;
        
           return TRUE ;
        

將bUserAbort設定為TRUE,則說明使用者已經決定取消打印操作,主窗口被啟動,而對話框被清除(按順序完成這兩項活動是很重要的,否則,在Windows中執行其它程序之一將變成活動程序,而您的程序將消失到背景中)。與通常的情況一樣,將hDlgPrint設定為NULL,防止在消息循環中呼叫IsDialogMessage。

只有在AbortProc用PeekMessage找到消息,并用IsDialogMessage將它們傳送給對話框窗口消息處理程序時,這個對話框才接收消息。只有在GDI模塊處理EndPage函數時,才呼叫AbortProc。如果GDI發現AbortProc的傳回值是FALSE,它將控制權從EndPage傳回到PrintMyPage。它不傳回錯誤碼。至此,PrintMyPage認為打印頁已經發完了,并呼叫EndDoc函數。但是,由于GDI模塊還沒有完成對EndPage呼叫的處理,所以不會打印出什么東西來。

有些清除工作尚待完成。如果使用者沒在對話框中取消打印作業,那么對話框仍然會顯示著。PrintMyPage重新啟用它的主窗口并清除對話框:

if (!bUserAbort)
        
{
        
    EnableWindow (hwnd, TRUE) ;
        
    DestroyWindow (hDlgPrint) ;
        
}
        

兩個變量會通知您發生了什么事:bUserAbort可以告訴您使用者是否終止了打印作業,bSuccess會告訴您是否出了故障,您可以用這些變量來完成想做的工作。PrintMyPage只簡單地對它們進行邏輯上的AND運算,然后把值傳回給WndProc:

return bSuccess && !bUserAbort ;
        

為POPPAD增加打印功能

現在準備在POPPAD程序中增加打印功能,并且宣布POPPAD己告完畢。這需要第十一章中的各個POPPAD文件,此外,還需要程序13-8中的POPPRNT.C文件。

程序13-8 POPPRNT

        
POPPRNT.C
        
/*---------------------------------------------------------------------
        
  POPPRNT.C -- Popup Editor Printing Functions
        
-----------------------------------------------------------------------*/
        
#include <windows.h>
        
#include <commdlg.h>
        
#include "resource.h"
        

BOOL bUserAbort ;
        
HWND hDlgPrint ;
        

BOOL CALLBACK PrintDlgProc (       HWND hDlg, UINT msg, WPARAM wParam,LPARAM lParam)
        
{
        
           switch (msg)
        
           {
        
           case   WM_INITDIALOG :
        
                          EnableMenuItem (GetSystemMenu (hDlg, FALSE), SC_CLOSE, MF_GRAYED) ;
        
                          return TRUE ;
        
        
        
           case   WM_COMMAND :
        
                          bUserAbort = TRUE ;
        
                          EnableWindow (GetParent (hDlg), TRUE) ;
        
                          DestroyWindow (hDlg) ;
        
                          hDlgPrint = NULL ;
        
                          return TRUE ;
        
           }
        
           return FALSE ;
        
}       
        

BOOL CALLBACK AbortProc (HDC hPrinterDC, int iCode)
        
{
        
           MSG msg ;
        
           while (!bUserAbort && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
        
           {
        
                  if (!hDlgPrint || !IsDialogMessage (hDlgPrint, &msg))
        
                  {
        
                                  TranslateMessage (&msg) ;
        
                                  DispatchMessage (&msg) ;
        
                  }
        
           }
        
           return !bUserAbort ;
        
}
        

BOOL PopPrntPrintFile (HINSTANCE hInst, HWND hwnd, HWND hwndEdit,
        
                                                                                PTSTR szTitleName)
        
{
        
           static DOCINFO        di = { sizeof (DOCINFO) } ;
        
           static PRINTDLG       pd ;
        
           BOOL                  bSuccess ;
        
           int                   yChar, iCharsPerLine, iLinesPerPage, iTotalLines,
        
                                  iTotalPages, iPage, iLine, iLineNum ;
        
           PTSTR                 pstrBuffer ;
        
           TCHAR                 szJobName [64 + MAX_PATH] ;
        
           TEXTMETRIC            tm ;
        
           WORD                  iColCopy, iNoiColCopy ;
        

                                  // Invoke Print common dialog box
        
   
        
           pd.lStructSize                =      sizeof (PRINTDLG) ;
        
           pd.hwndOwner                  =      hwnd ;
        
           pd.hDevMode                   =      NULL ;
        
           pd.hDevNames                  =      NULL ;
        
           pd.hDC                        =      NULL ;
        
           pd.Flags                      =      PD_ALLPAGES | PD_COLLATE |
        
                                   PD_RETURNDC | PD_NOSELECTION ;
        
           pd.nFromPage                  =      0 ;
        
           pd.nToPage                    =      0 ;
        
           pd.nMinPage                   =      0 ;
        
           pd.nMaxPage                   =      0 ;
        
           pd.nCopies                    =      1 ;
        
           pd.hInstance                  =      NULL ;
        
           pd.lCustData                  =      0L ;
        
           pd.lpfnPrintHook              =      NULL ;
        
           pd.lpfnSetupHook              =      NULL ;
        
           pd.lpPrintTemplateName        =      NULL ;
        
           pd.lpSetupTemplateName        =      NULL ;
        
           pd.hPrintTemplate             =      NULL ;
        
           pd.hSetupTemplate             =      NULL ;
        
   
        
  if     (!PrintDlg (&pd))
        
                          return TRUE ;
        
   
        
  if     (0 == (iTotalLines = SendMessage (hwndEdit, EM_GETLINECOUNT, 0, 0)))
        
                          return TRUE ;
        

                                  // Calculate necessary metrics for file
        
   
        
           GetTextMetrics (pd.hDC, &tm) ;
        
           yChar = tm.tmHeight + tm.tmExternalLeading ;
        
   
        
           iCharsPerLine = GetDeviceCaps (pd.hDC, HORZRES) / tm.tmAveCharWidth ;
        
           iLinesPerPage = GetDeviceCaps (pd.hDC, VERTRES) / yChar ;
        
    iTotalPages   = (iTotalLines + iLinesPerPage - 1) / iLinesPerPage ;
        

                                  // Allocate a buffer for each line of text
        
   
        
           pstrBuffer    = malloc (sizeof (TCHAR) * (iCharsPerLine + 1)) ;
        

                                  // Display the printing dialog box
        
   
        
           EnableWindow (hwnd, FALSE) ;
        
           bSuccess                      = TRUE ;
        
           bUserAbort            = FALSE ;
        

           hDlgPrint             = CreateDialog (hInst, TEXT ("PrintDlgBox"),
        
                                                                                hwnd, PrintDlgProc) ;
        

           SetDlgItemText (hDlgPrint, IDC_FILENAME, szTitleName) ;
        
          SetAbortProc (pd.hDC, AbortProc) ;
        

                                  // Start the document
        
           GetWindowText (hwnd, szJobName, sizeof (szJobName)) ;
        
           di.lpszDocName = szJobName ;
        
           if (StartDoc (pd.hDC, &di) > 0)
        
           {
        
                                         // Collation requires this loop and iNoiColCopy
        
                  for (iColCopy = 0 ;
        
                                         iColCopy < ((WORD) pd.Flags & PD_COLLATE ? pd.nCopies : 1) ;
        
                                         iColCopy++)
        
                  {
        
                                  for (iPage = 0 ; iPage < iTotalPages ; iPage++)
        
                                  {
        
               for (iNoiColCopy = 0 ;
        
                    iNoiColCopy < (pd.Flags & PD_COLLATE ? 1 : pd.nCopies);
        
                                                                iNoiColCopy++)
        
                                         {
        
                                                                      // Start the page
        
                                                         if (StartPage (pd.hDC) < 0)
        
                                                         {
        
                                                                                bSuccess = FALSE ;
        
                                                                                break ;
        
                                                         }
        

              // For each page, print the lines
        
              for (iLine = 0 ; iLine < iLinesPerPage ; iLine++)
        
                                                         {
        
               iLineNum = iLinesPerPage * iPage + iLine ;
        
               if (iLineNum > iTotalLines)
        
                                                              break ;
        
               *(int *) pstrBuffer = iCharsPerLine ;
        
               TextOut    (pd.hDC, 0, yChar * iLine, pstrBuffer,
        
               (int) SendMessage (hwndEdit, EM_GETLINE,
        
                   (WPARAM) iLineNum, (LPARAM) pstrBuffer));
        
                                                               }
        
                           
        
                if (EndPage (pd.hDC) < 0)
        
                                                                {
        
                 bSuccess = FALSE ;
        
                 break ;
        
                                                                }
        
                      
        
                 if (bUserAbort)
        
                break ;
        
                                                         }
        
                  
        
                 if (!bSuccess || bUserAbort)
        
                 break ;
        
                                         }
        
             
        
                  if (!bSuccess || bUserAbort)
        
                 break ;
        
                          }
        
   }
        
           else
        
                          bSuccess = FALSE ;
        
          if     (bSuccess)
        
                          EndDoc (pd.hDC) ;
        
          
        
           if     (!bUserAbort)
        
    {
        
                          EnableWindow (hwnd, TRUE) ;
        
                          DestroyWindow (hDlgPrint) ;
        
           }
        
   
        
           free (pstrBuffer) ;
        
           DeleteDC (pd.hDC) ;
        
   
        
           return bSuccess && !bUserAbort ;
        
}
        

與POPPAD盡量利用Windows高階功能來簡化程序的方針一致,POPPRNT.C文件展示了使用PrintDlg函數的方法。這個函數包含在通用對話框鏈接庫(common dialog box library)中,使用一個PRINTDLG型態的結構。

通常,程序的「File」菜單中有個「Print」選項。當使用者選中「Print」選項時,程序可以初始化PRINTDLG結構的字段,并呼叫PrintDlg。

PrintDlg顯示一個對話框,它允許使用者選擇打印頁的范圍。因此,這個對話框特別適用于像POPPAD這樣能打印多頁文件的程序。這種對話框同時也給出了一個確定副本份數的編輯區和名為「Collate(逐份打印)」的復選框。「逐份打印」影響著多個副本頁的順序。例如,如果文件是3頁,使用者要求打印三份副本,則這個程序能以兩種順序之一打印它們。選擇逐份打印后的副本的頁碼順序為1、2、3、1、2、3、1、2、3,未選擇逐份打印的副本的頁碼順序是1、1、1、2、2、2、3、3、3。程序在這里應負起的責任就是以正確的順序打印副本。

這個對話框也允許使用者選擇非內定打印機,它包括一個標記為「Properties」的按鈕,可以啟動設備模式對話框。這樣,至少允許使用者選擇直印或橫印。

從PrintDlg函數傳回后,PRINTDLG結構的字段指明打印頁的范圍和是否對多個副本進行逐份打印。這個結構同時也給出了準備使用的打印機設備內容句柄。

在POPPRNT.C中,PopPrntPrintFile函數(當使用者在「File」菜單里選中「Print」選項時,它由POPPAD呼叫)呼叫PrintDlg,然后開始打印文件。PopPrntPrintFile完成某些計算,以確定一行能容納多少字符和一頁能容納多少行。這個程序涉及到呼叫GetDeviceCaps來確定頁的分辨率,呼叫GetTextMetrics來確定字符的大小。

這個程序通過發送一條EM_GETLINECOUNT消息給編輯控件來取得文件中的總行數(在變量iTotalLines中)。儲存各行內容的緩沖區配置在局部內存中。對每一行,緩沖區的第一個字被設定為該行中字符的數目。把EM_GETLINE消息發送給編輯控件可以把一行復制到緩沖區中,然后用TextOut把這一行送到打印機設備內容中(POPPRNT.C還沒有聰明到對超出打印寬度的文字換到下一行去處理。在 第十七章我們會討論這種文字繞行的技術)。

為了確定副本份數,應注意打印文字的處理方式包括兩個for循環。第一個for循環使用了一個叫作iColCopy的變量,當使用者指定將副本逐份打印時,它將會起作用。第二個for循環使用了一個叫作iNonColCopy的變量,當不對副本進行逐份打印時,它將起作用。

如果StartPage或EndPage傳回一個錯誤,或者如果bUserAbort為TRUE,那么這個程序退出增加頁號的那個for循環。如果放棄程序的傳回值是FALSE,則EndPage不傳回錯誤。正是由于這個原因,在下一頁開始之前,要直接測試bUserAbort。如果沒有報告錯誤,則進行EndDoc呼叫:

if (!bError)
        
           EndDoc (hdcPrn) ;
        

您可能想通過打印多頁文件來測試POPPAD。您可以從打印任務窗口中監視打印進展情況。在GDI處理完第一個EndPage呼叫之后,首先打印的文件將顯示在打印任務窗口中。此時,后臺打印程序開始把文件發送到打印機。然后,如果在POPPAD中取消打印作業,那么后臺打印程序將終止打印,這也就是放棄程序傳回FALSE的結果。當文件出現在打印任務窗口中,您也可以透過從「Document」菜單中選擇「Cancel Printing」來取消打印作業,在這種情況下, POPPAD中的EndPage呼叫會傳回一個錯誤。

Windows的程序設計的新手經常會抱住AbortDoc函數不放,但實際上這個函數幾乎不在打印中使用。像在POPPAD中看到的那樣,使用者幾乎隨時可以取消打印作業,或者通過POPPAD的打印對話框及通過打印任務窗口。這兩種方法都不需要程序使用AbortDoc函數。 POPPAD中允許AbortDoc的唯一時刻是在對StartDoc的呼叫和對EndPage的第一個呼叫之間,但是程序很快就會執行過去,以至不再需要AbortDoc。

圖13-3顯示出正確打印多頁文件之打印函數的呼叫順序。檢查bUserAbort的值是否為TRUE的最佳位置是在每個EndPage函數之后。只有當對先前的打印函數的呼叫沒有產生錯誤時,才使用EndDoc函數。實際上,如果任何一個打印函數的呼叫出現錯誤,那么表演就結束了,同時您也可以回家了。

 

圖13-3 打印一個文件時的函數呼叫順序

 

總結

以上是生活随笔為你收集整理的教你怎么使用打印机(api)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

亚洲黑丝少妇 | 天天操天天射天天操 | 精品国产一区二区三区男人吃奶 | 女人高潮一级片 | 免费黄色av电影 | 97精品超碰一区二区三区 | 日本精油按摩3 | 亚州精品在线视频 | 国产黑丝袜在线 | 丁香六月av| 丁香花在线视频观看免费 | 综合天堂av久久久久久久 | 字幕网av| 91精品影视| 亚洲精品国产成人 | 丁香久久 | 亚洲在线日韩 | 97手机电影网 | 免费一级特黄录像 | 五月天伊人 | 射射色 | 亚洲一区欧美精品 | 国产91精品一区二区麻豆网站 | 99r在线视频| 国产a国产a国产a | 天天操综合| 精品在线免费视频 | 激情欧美xxxx | 久久精品欧美一 | 国产成人精品av | 91精品国自产在线观看 | 91c网站色版视频 | 91视频 - v11av | 国产一区视频在线 | 在线国产精品视频 | av天天干| 亚洲免费公开视频 | 91视频91色 | 奇米7777狠狠狠琪琪视频 | 日韩精品一区二区三区丰满 | 西西www4444大胆视频 | 久草在线视频中文 | 91av电影在线观看 | 婷婷精品在线 | 日韩一区二区免费视频 | 久久综合狠狠综合久久综合88 | 久久99国产综合精品 | 久草综合视频 | 永久av免费在线观看 | 亚洲免费视频在线观看 | 久久亚洲福利视频 | 久热免费在线观看 | 懂色av一区二区三区蜜臀 | 精品国内 | 色综合狠狠干 | 91亚洲精品乱码久久久久久蜜桃 | 久久女教师| 国产激情小视频在线观看 | 黄色大片免费播放 | 亚洲国产操| 中文字幕久久网 | 在线成人一区 | 久久久久久久国产精品视频 | 天天干天天操人体 | 国产精品久久久久久婷婷天堂 | 久久99精品波多结衣一区 | 一区二区三区电影大全 | 五月婷网站 | 爱av在线网 | 久久久2o19精品 | 五月激情久久久 | 午夜99 | 国产成人精品综合久久久久99 | 最近av在线 | 五月天久久| 美女福利视频 | 在线免费黄色片 | 国产伦精品一区二区三区四区视频 | 久久国产精品免费一区二区三区 | 亚洲艳情 | 亚洲性少妇性猛交wwww乱大交 | av三级av| 久久国产影视 | 五月天六月色 | 国产高清视频在线免费观看 | 欧美专区日韩专区 | 婷婷六月天天 | 九九九热精品免费视频观看 | 久久免费视频网站 | 一区二区三区观看 | 国产日产精品久久久久快鸭 | 欧美在线一二区 | av成人免费 | 国产色婷婷精品综合在线手机播放 | 日韩欧美黄色网址 | 亚洲妇女av | 中文字幕欧美三区 | 欧美日韩一区二区三区免费视频 | 91桃色在线免费观看 | a久久免费视频 | 日韩网站一区二区 | 精产嫩模国品一二三区 | 高清有码中文字幕 | 青春草免费在线视频 | 亚洲精品国产综合99久久夜夜嗨 | 人人盈棋牌 | 亚洲日本一区二区在线 | 欧美一区二区三区免费看 | 久久这里 | 一级黄色毛片 | 色综合久久久久综合体桃花网 | 免费在线播放 | 人人干网站 | 久久成人欧美 | 日本中文字幕视频 | 少妇超碰在线 | 92中文资源在线 | 91亚洲夫妻 | 91麻豆精品国产午夜天堂 | 欧美一区二区三区四区夜夜大片 | 国产精品视频在线观看 | 免费在线观看成人小视频 | 国产性天天综合网 | 久久免费片 | 欧美 亚洲 另类 激情 另类 | 中文字幕一二 | 黄色美女免费网站 | 麻豆视频在线观看免费 | 五月激情视频 | 久久资源总站 | av成人免费在线观看 | 深爱激情五月网 | 亚洲精品成人av在线 | 婷婷av在线| 欧美激情视频在线观看免费 | av网站播放 | 91人网站| 国产精品乱码一区二区视频 | 国产日本亚洲高清 | 久久影视一区 | 最新中文字幕在线播放 | 久久免费视频播放 | 欧美,日韩 | 日韩大片在线 | 色偷偷88欧美精品久久久 | 在线免费观看国产黄色 | 日韩在线视频观看免费 | 中文日韩在线视频 | 黄色av影院| 欧美91精品久久久久国产性生爱 | 国产成人区 | 久久久av免费 | 国产精品久久久久久久久软件 | 天天爱天天干天天爽 | 天天干干 | 国产精品美女在线 | 欧美性做爰猛烈叫床潮 | 国产精品色在线 | 国产成人福利片 | 久久在线一区 | 日韩免费看的电影 | www五月天婷婷 | 97成人啪啪网 | 日日麻批40分钟视频免费观看 | 国产视频一区二区在线 | 涩涩网站在线 | 丁香婷婷激情网 | 干亚洲少妇 | 精品美女久久久久久免费 | 色婷婷电影网 | 亚洲精品午夜久久久 | 国产1区2区 | 欧美 国产 视频 | 久久久久久久久福利 | 日韩二区在线观看 | 成人在线观看日韩 | 在线黄频 | 久久福利综合 | 亚洲精品国产综合99久久夜夜嗨 | 精品亚洲一区二区三区 | 在线观看成人小视频 | 日韩在线观看免费 | 中文字幕一区二区三区久久蜜桃 | a视频在线| 国产精品色婷婷视频 | 久久精品看 | 成人久久久久 | 亚洲视频www | 精品久久久久久久久久岛国gif | 国际av在线 | 天天av在线播放 | 国产成在线观看免费视频 | 精品久久国产一区 | 久久伦理| 黄色三级视频片 | 麻花豆传媒mv在线观看 | 国产精品久久久久久69 | 三级黄色片子 | 特级毛片在线免费观看 | 丝袜制服综合网 | 色综合久久中文字幕综合网 | 国产精品久久久久影院日本 | 国产高清av免费在线观看 | 色天堂在线视频 | 亚洲国产精品电影 | 人人玩人人添人人澡97 | 日韩电影在线一区二区 | 欧美日韩在线免费观看视频 | 天堂视频中文在线 | 久久精品综合 | 久久久999免费视频 日韩网站在线 | 亚洲精品成人 | 欧洲精品亚洲精品 | 久久草精品| 中文字幕在线视频精品 | 国产专区精品 | 最近2019好看的中文字幕免费 | 顶级bbw搡bbbb搡bbbb | 99精品国产一区二区 | 免费看三级 | 韩国av一区二区 | 伊甸园永久入口www 99热 精品在线 | 国产一级在线观看 | 国产精品 日韩 欧美 | 久久久私人影院 | 久久国产欧美日韩精品 | 黄色国产高清 | 国产精品免费一区二区三区在线观看 | 丁香婷婷射 | 国产日产亚洲精华av | 又黄又爽又色无遮挡免费 | 玖玖色在线观看 | 少妇超碰在线 | 天天干天天操天天拍 | 啪啪资源 | 国产在线色站 | 干干干操操操 | 精品久久久久一区二区国产 | 天天干天天干天天干天天干天天干天天干 | 最近2019中文免费高清视频观看www99 | 97人人模人人爽人人喊中文字 | 亚洲欧美日韩中文在线 | 伊人久久在线观看 | 国产精品毛片一区二区在线 | 欧洲激情在线 | 欧美大片第1页 | 四虎影视成人 | www.97色.com| 在线黄色免费 | 天天干天天操天天 | 干干日日 | 免费观看xxxx9999片 | 精品国产一区二区三区蜜臀 | 成人中文字幕在线观看 | 国产精品大全 | 一区二区三区电影在线播 | 狠狠干狠狠插 | 丁香九月激情综合 | 国产视频在 | 中文av字幕在线观看 | 国产视频网站在线观看 | 久操视频在线播放 | 国产一级一片免费播放放a 一区二区三区国产欧美 | 丰满少妇高潮在线观看 | 在线免费黄色av | 国产在线播放一区 | 国产精品九九久久久久久久 | 最新黄色av网址 | 久久免费高清 | 在线看的av网站 | 九九精品视频在线观看 | 色综合在 | 国产精品一区二区三区在线看 | 天天干天天操天天入 | 亚洲一级片免费观看 | 久久艹综合 | 亚洲综合在线观看视频 | 国产成人av在线影院 | 麻豆手机在线 | 又湿又紧又大又爽a视频国产 | www.狠狠| 久久av影视 | 国产精品久久久久久影院 | 综合网中文字幕 | 国产麻豆剧传媒免费观看 | 激情欧美xxxx | 毛片区| 日日夜夜网站 | 91女人18片女毛片60分钟 | 黄色特级片 | 国产精品久久久电影 | 亚洲精品动漫成人3d无尽在线 | 精品一二三四视频 | 亚洲综合一区二区精品导航 | 欧美日韩亚洲一 | а天堂中文最新一区二区三区 | 国产xxxxx在线观看 | 天天爽夜夜爽人人爽曰av | 成人黄色在线 | 欧美日韩久久不卡 | 欧美日韩后 | 久草视频在线免费播放 | 国产一区二区在线免费播放 | 蜜桃视频精品 | 亚州精品在线视频 | 91麻豆免费看 | 亚洲成人资源在线观看 | 国产色久 | 欧美性护士 | 狠狠色噜噜狠狠狠 | 日韩视频免费在线观看 | 一区二区视频在线观看免费 | 亚洲精品久久在线 | 精品影院一区二区久久久 | av短片在线观看 | 亚洲成人国产精品 | 在线精品播放 | 日本高清中文字幕有码在线 | 99色人 | 免费在线播放 | 91精品国产92久久久久 | 中文字幕免费观看 | 国产 日韩 在线 亚洲 字幕 中文 | 欧美色图狠狠干 | 日韩一区二区免费视频 | 亚洲视频电影在线 | 成人黄色在线视频 | 91av福利视频 | 中文字幕一区二区三区久久 | 91中文字幕在线视频 | 成人性生交大片免费看中文网站 | 亚洲成人黄色 | 91丨九色丨国产在线 | 国产精品久久久影视 | 久久久久久久久久久免费av | 麻豆视频观看 | 最新影院 | 亚洲精品久久激情国产片 | 国产区高清在线 | 亚洲午夜精品福利 | 国模一区二区三区四区 | 99免在线观看免费视频高清 | 三级动态视频在线观看 | 91一区二区三区久久久久国产乱 | 深夜免费福利 | 国产一级片直播 | 欧美性粗大hdvideo | 五月婷婷丁香 | 国产香蕉视频在线播放 | 天天五月天色 | 国产无吗一区二区三区在线欢 | 国产午夜精品视频 | 国产xxxx做受性欧美88 | 特级西西www44高清大胆图片 | 一区二区三区日韩在线 | 亚洲精品一区二区三区高潮 | 国产小视频在线免费观看 | bbbbb女女女女女bbbbb国产 | 97色在线观看免费视频 | 91九色蝌蚪在线 | 不卡av在线 | 亚洲一区 影院 | 狠狠躁夜夜躁人人爽超碰91 | 免费一级毛毛片 | 久热免费在线观看 | 国产精品久久久久毛片大屁完整版 | 国产视频手机在线 | 香蕉久草 | 亚洲国产免费看 | 久久精品一区二区国产 | 2021久久 | 中文在线中文a | 玖玖在线免费视频 | 国偷自产中文字幕亚洲手机在线 | 国内小视频在线观看 | 久99久在线 | 在线看欧美 | 99久久国产免费免费 | 韩日色视频| 亚洲区视频在线 | 狠狠色2019综合网 | 中文字幕在线观看网 | 成人久久18免费网站图片 | 亚洲在线视频网站 | 国产九色在线播放九色 | 国产精品九九久久99视频 | 日韩视频在线一区 | 中文字幕你懂的 | 日本精品久久久久 | 日本一区二区免费在线观看 | 日韩av免费一区二区 | 中文字幕制服丝袜av久久 | 午夜色站 | 日日干夜夜草 | 中文字幕乱码日本亚洲一区二区 | 精品日韩在线一区 | 亚洲三级在线免费观看 | www.香蕉视频| 9ⅰ精品久久久久久久久中文字幕 | 天天做天天爱天天爽综合网 | 成人黄色片免费看 | 国产剧情一区二区 | 五月天亚洲精品 | 91黄色在线观看 | 超碰在线人人97 | 粉嫩av一区二区三区免费 | 欧美日韩国产一区二区三区在线观看 | 一区二区三区免费在线观看视频 | 草久中文字幕 | 久草电影免费在线观看 | 99久久er热在这里只有精品15 | 肉色欧美久久久久久久免费看 | www国产亚洲精品久久网站 | 国产精品久久久久影视 | 日本三级在线观看中文字 | 亚洲一区精品人人爽人人躁 | 亚洲影院天堂 | 国内丰满少妇猛烈精品播放 | 色诱亚洲精品久久久久久 | 99riav1国产精品视频 | 18女毛片| 亚洲精品久久久久999中文字幕 | 欧美日韩不卡一区二区三区 | 中文字幕文字幕一区二区 | 亚洲成av人片在线观看 | 99久久这里有精品 | 日批视频在线观看免费 | 中文在线免费看视频 | 亚洲欧美视频在线 | 开心丁香婷婷深爱五月 | 亚洲综合色视频在线观看 | 中文字幕乱码电影 | 午夜精品久久久久久久久久久 | 久久公开免费视频 | 超碰在线最新网址 | 亚洲国产精品一区二区尤物区 | 久久精品视频一 | 国产一区高清在线观看 | 久久精品视 | 狠狠激情中文字幕 | 伊人干综合 | 午夜精品久久久久久 | 片网址 | 最近免费中文字幕mv在线视频3 | 日韩在线观看精品 | 精品国产片| 五月婷婷丁香综合 | 最新av网站在线观看 | 亚洲美女免费精品视频在线观看 | 欧美va日韩va | 欧美在线91 | 在线亚洲天堂网 | 欧美精品久久久久久 | 激情五月综合 | 国产伦理一区 | 国产一区欧美在线 | 亚洲精品久久久久999中文字幕 | av再线观看 | 欧美韩国日本在线 | 久久免费视频7 | 国产精品欧美久久久久无广告 | 日韩视频中文字幕 | 日韩精品久久久久久 | 免费看一级特黄a大片 | www.亚洲黄色| 久久久久久久影视 | 亚洲三级黄色 | 午夜精品福利一区二区三区蜜桃 | 日本少妇高清做爰视频 | 久久草av| 中文字幕在线一区观看 | 国产成人91| 伊人永久在线 | 在线免费视频a | 免费亚洲电影 | 粉嫩av一区二区三区四区五区 | 久草视频99 | 日本久久不卡视频 | 日日久视频 | 日本精品一区二区三区在线播放视频 | 亚洲精品午夜久久久 | www..com黄色片 | 国产精品欧美 | 免费亚洲精品视频 | 国产视频综合在线 | 亚洲国产精品久久久久久 | 色天天天 | 超碰在线人人草 | 97国产精品一区二区 | 国产日韩欧美在线观看 | 精品国产一区二区三区久久久蜜月 | 91九色视频在线播放 | 亚洲欧美日韩在线一区二区 | 黄色免费看片网站 | 日韩av午夜在线观看 | 日本成人中文字幕在线观看 | 丝袜一区在线 | 婷婷伊人网 | 福利一区二区 | 亚洲成人午夜在线 | 国产91精品一区二区 | 四虎在线观看 | 国产精品k频道 | 日日夜夜综合 | 久草在线资源网 | 国产免费中文字幕 | 午夜视频在线观看一区 | 久草观看 | 天天透天天插 | 亚洲日韩中文字幕在线播放 | 免费激情在线电影 | www黄色av| 久久国产片 | 日韩毛片一区 | 成人午夜性影院 | 国产一区二区网址 | 天天天天天天天操 | 麻豆视频网址 | 丁香午夜| 日韩色在线 | 日韩电影在线看 | 免费在线播放黄色 | 69精品久久 | 日韩爱爱网站 | 午夜精品福利一区二区三区蜜桃 | 亚洲精品在线视频播放 | 日韩99热| 成人免费视频在线观看 | 日韩成人在线免费观看 | 精品亚洲视频在线观看 | 成人免费在线视频 | 久久爱导航 | 96精品高清视频在线观看软件特色 | 久久婷婷一区 | av在线播放免费 | 日韩欧美在线视频一区二区三区 | 中文字幕一区二区三区久久蜜桃 | 一区中文字幕电影 | 91麻豆文化传媒在线观看 | 97精品一区二区三区 | 久久久蜜桃 | 99久久婷婷国产精品综合 | 亚洲免费公开视频 | 成人av手机在线 | 国产一级片久久 | www视频在线免费观看 | 国产福利小视频在线 | 中文字幕精品久久 | 精品国内自产拍在线观看视频 | 91亚洲精品国偷拍自产在线观看 | 日韩在线不卡视频 | 国产高潮久久 | 天天干天天操天天拍 | 久久精品观看 | 亚洲,国产成人av | 国产高清日韩欧美 | 国语自产偷拍精品视频偷 | 91av在线视频免费观看 | 欧美日韩久| 日韩欧美视频免费在线观看 | 久久免费激情视频 | 日日操操 | 久久www免费视频 | 欧美人牲 | 在线亚州 | 亚洲福利精品 | 免费久久99精品国产婷婷六月 | 国产玖玖视频 | 黄色a一级片 | 久久爱www. | 天天爱天天射 | 在线观看久 | 国产又粗又猛又黄 | 国产色网| 久久久免费播放 | 色永久免费视频 | 亚洲精品福利视频 | 在线免费黄 | 91成人短视频在线观看 | 中文字幕在线免费观看 | 久久久午夜精品理论片中文字幕 | 久香蕉| 黄色精品免费 | 人人超碰在线 | 天天做天天爱天天综合网 | 国产中文 | 18性欧美xxxⅹ性满足 | 国产高清久久久久 | 福利网址在线观看 | 五月婷婷色丁香 | 国偷自产中文字幕亚洲手机在线 | 久久成年人 | 亚洲精品视频在 | 人人爽久久涩噜噜噜网站 | 亚洲日韩中文字幕 | 久久免费福利视频 | av电影在线免费观看 | 成人黄色在线视频 | 99热 精品在线 | 日韩在线观看高清 | 久久久高清免费视频 | 国产在线污 | 色a资源在线 | 久久国产精品99精国产 | 81精品国产乱码久久久久久 | 天天爽天天爽夜夜爽 | 97在线超碰 | 亚洲国产激情 | 国产黄色免费看 | 精品产品国产在线不卡 | 激情综合网在线观看 | 亚洲国产日韩精品 | 91精品爽啪蜜夜国产在线播放 | 日韩视频一区二区在线观看 | 欧美亚洲国产精品久久高清浪潮 | 香蕉视频在线视频 | 中文字幕精品一区二区三区电影 | 国产日本高清 | 9热精品 | 日韩精品短视频 | 91看片看淫黄大片 | 粉嫩高清一区二区三区 | 国产在线观看你懂的 | 色综合在 | 在线精品观看国产 | 狠狠色丁香| 日韩免费播放 | 亚洲视频每日更新 | 丁香五月网久久综合 | 麻豆国产精品视频 | 日韩av成人 | 国产视频久| 丁香5月婷婷久久 | a视频免费看 | 久久日韩精品 | 国产精品久久久久婷婷 | 久久激情电影 | 天天骚夜夜操 | 99久久精品国产一区二区三区 | 国产午夜精品av一区二区 | 一区二区av | 99久久精品免费看国产免费软件 | 激情在线网站 | 欧美在线一二 | 国产精品系列在线观看 | 中文字幕a∨在线乱码免费看 | 亚洲国产精久久久久久久 | 久久久久免费精品国产 | 国产理论在线 | 国产精品第10页 | 人人插人人艹 | 一本一本久久a久久精品综合妖精 | 三上悠亚在线免费 | 日韩小视频网站 | 亚洲国产片 | 亚洲精品免费在线播放 | 91精品在线免费观看 | 免费黄色a级毛片 | 安徽妇搡bbbb搡bbbb | 丝袜制服天堂 | 国产精品系列在线 | 91日韩精品视频 | 欧美日bb | 欧美日韩在线精品一区二区 | 成人免费视频网站 | 国产又黄又硬又爽 | 国产精品电影一区 | 91理论片午午伦夜理片久久 | 色天天综合久久久久综合片 | 成人免费网站视频 | 国产精品porn | 国产精品一区二区久久久久 | 视频一区在线播放 | 欧美激情精品久久久久久 | 中文字幕在线国产精品 | 亚洲一区二区三区四区精品 | 亚洲最大av | 99精品免费观看 | 久久免费影院 | 天天操比| 色射爱 | 久草电影免费在线观看 | 天天干干 | 欧洲精品码一区二区三区免费看 | 久99久久 | 99久久er热在这里只有精品66 | 韩国精品视频在线观看 | www.香蕉视频 | 丁香视频全集免费观看 | 欧美日韩视频精品 | 精品免费| 久久视讯| 日韩在线第一区 | 成人国产精品免费观看 | 国产精品久久久久9999 | 久久免费av电影 | 成人av网页 | 日韩电影在线观看一区二区三区 | 精品视频不卡 | 九七在线视频 | 久草在线国产 | 亚洲精品久久久蜜臀下载官网 | 一级黄色片在线免费观看 | 一区 在线 影院 | www.国产在线观看 | 日本精油按摩3 | 国产精品欧美一区二区三区不卡 | 亚洲精品国产品国语在线 | 亚洲午夜精品久久久久久久久久久久 | 成年人免费在线观看网站 | 中文字幕在线播放一区二区 | 91精品小视频 | 人人爽人人爽人人片av免 | 深爱激情婷婷网 | 欧美在线一二区 | 超碰激情在线 | 天天操综合 | 91在线视频观看 | 日韩精品久久久免费观看夜色 | 精品国产一区二区三区久久久 | www.69xx| 亚洲视频在线观看 | 蜜臀av性久久久久蜜臀aⅴ涩爱 | 97精品国产97久久久久久免费 | 亚洲精品91天天久久人人 | 99这里只有久久精品视频 | 久久看片网站 | 国产精品乱码一区二区视频 | 国产精品久久久久久久久久白浆 | 福利视频导航网址 | 欧美做受高潮电影o | av中文字幕免费在线观看 | 天天天天天天天操 | 久久艹人人 | 国产免费嫩草影院 | 免费三级黄色片 | 91中文在线 | 国产又粗又硬又爽视频 | 97超碰中文字幕 | 激情在线网站 | 日b视频国产 | 久久艹久久 | 久久国产精品免费一区二区三区 | 久久精品国产亚洲 | 久久美女免费视频 | 欧美激情综合色综合啪啪五月 | 中文字幕在线观看完整版 | 欧美一级在线看 | 色夜影院 | 探花系列在线 | 日本午夜在线亚洲.国产 | 伊人婷婷网 | 国产亚洲欧美一区 | 久久影院午夜论 | 久久99精品国产麻豆宅宅 | 久久久久久久久久久成人 | 超碰在线网 | 99精品视频中文字幕 | 色噜噜噜| 91av在线不卡 | 91av电影在线观看 | 在线激情网 | 伊人天堂av | 久久av中文字幕片 | 成人在线视频在线观看 | 欧美午夜久久久 | www.亚洲视频.com | 五月天激情综合网 | 精品毛片一区二区免费看 | 草在线视频 | 婷婷婷国产在线视频 | 西西www4444大胆在线 | 97品白浆高清久久久久久 | 久久影院中文字幕 | mm1313亚洲精品国产 | 久草成人在线 | 天天射综合网视频 | 国产91精品一区二区 | 日韩欧美精品免费 | 国产成人专区 | 西西444www | 欧美男同视频网站 | 国产伦理一区 | 国产又粗又猛又爽又黄的视频免费 | 久久99久久99精品免视看婷婷 | 激情视频亚洲 | av成人亚洲 | 婷婷六月激情 | 天天综合网在线 | 九九久久视频 | 天天爱天天草 | 综合网五月天 | 91精品对白一区国产伦 | 91视频在线 | 久久精品第一页 | 亚洲精品乱码白浆高清久久久久久 | 国产区精品| 国产一区二区久久久 | 激情av综合 | 日韩欧美在线观看一区二区 | 欧美色就是色 | 免费91麻豆精品国产自产在线观看 | 精品国产欧美一区二区 | 四虎影视成人精品国库在线观看 | 成人国产精品 | 日本成人a | 欧美韩国日本在线 | 国产麻豆精品久久一二三 | 国产区免费 | 18岁免费看片 | 日韩视频免费观看高清完整版在线 | 国产一级黄色av | 黄色片软件网站 | 久久久精品 一区二区三区 国产99视频在线观看 | 麻花豆传媒mv在线观看 | 91精品国自产拍天天拍 | 操操爽| 欧美性极品xxxx做受 | 美女久久一区 | 久久成人免费 | 国产精品一级在线 | 久草在线资源免费 | 国产永久免费观看 | 免费一级特黄录像 | 91av电影在线观看 | 欧美极品久久 | 一级片免费观看视频 | 日本中文字幕在线电影 | 国产一区免费视频 | 色综合天天色综合 | 国产91精品一区二区麻豆亚洲 | 久久在线免费观看 | 免费99精品国产自在在线 | 国产不卡免费av | 免费观看91视频 | 91精品啪在线观看国产81旧版 | 久久在线免费视频 | 黄色大片日本免费大片 | 99精品美女| 色天天综合久久久久综合片 | 91久久精品一区 | av免费线看 | 日本夜夜草视频网站 | 中文字幕麻豆 | 国产美女精品视频免费观看 | 久久好看| 亚洲精品永久免费视频 | 精品国产乱码久久久久久浪潮 | 日韩三级视频在线观看 | 久草精品视频在线播放 | 久久美女免费视频 | 国产精品欧美久久久久三级 | 五月天婷亚洲天综合网鲁鲁鲁 | 国产资源av | 久久不卡电影 | 国产精品资源在线 | 高清av免费一区中文字幕 | 亚洲色图美腿丝袜 | 中文字幕乱码电影 | 成人h在线 | 国产精品色婷婷 | 午夜性盈盈 | 天天做天天爱天天爽综合网 | 免费看片网站91 | 丝袜美女在线 | 色综合天天在线 | 久久精品一二区 | www.色午夜 | 伊人狠狠色丁香婷婷综合 | 成人午夜剧场在线观看 | 成人免费网站在线观看 | 在线 国产 亚洲 欧美 | 日韩午夜剧场 | 激情五月婷婷综合 | 国产一区欧美一区 | 欧洲亚洲激情 | 久久99热国产 | 亚洲精品免费在线观看 | www.亚洲激情.com | 亚洲免费在线播放视频 | 九九激情视频 | 日韩av快播电影网 | 亚洲无吗视频在线 | wwwwww色 | 五月激情视频 | 99久久99久久精品 | 日韩二级毛片 | 97在线看| 欧美日韩中文视频 | 操操日 | 麻花豆传媒mv在线观看 | 成人中文字幕在线观看 | 国产99久久久国产 | 丁香婷婷久久久综合精品国产 | 精品国产不卡 | 亚洲另类人人澡 | 色播激情五月 | 波多野结衣网址 | 999久久久欧美日韩黑人 | 欧美91精品久久久久国产性生爱 | 国产乱码精品一区二区蜜臀 | 久草在线观看资源 | 国产高清视频网 | 日韩丝袜在线观看 | a级国产乱理论片在线观看 伊人宗合网 | 色综合色综合久久综合频道88 | 久久永久免费 | 91在线超碰| 在线婷婷 | 欧美成人影音 | 激情综合五月网 | 久久神马影院 | 超碰在线97观看 | 久久国产精品99国产精 | 韩日电影在线观看 | 国产日韩视频在线播放 | 色综合久久天天 | 狠狠操狠狠插 | 国产精品99久久久久人中文网介绍 | 看全黄大色黄大片 | 在线国产专区 | 免费看国产精品 | 亚洲视频播放 | 精品一区二区三区四区在线 | 亚洲mv大片欧洲mv大片免费 | 免费在线播放黄色 | 就要色综合 | a午夜电影 | 中文av影院 | 国产做爰视频 | 日韩欧美高清一区二区三区 | 在线观看视频中文字幕 | 在线观看成人网 | 麻豆视频大全 | 日日爱视频 | 97精品久久 | 激情综合婷婷 | 日韩av在线小说 | 国产精品 日本 | 四虎影视成人精品 | 日韩三级免费观看 | 欧洲视频一区 | 夜夜躁天天躁很躁波 | 婷婷激情在线观看 | 三级黄色大片在线观看 | 五月激情六月丁香 | 97在线观看视频免费 | 91av99| 欧美福利网站 | 在线精品一区二区 | 国产小视频你懂的 | 91mv.cool在线观看 | 综合色在线观看 | 麻豆网站免费观看 | 国产在线色 | 成人精品亚洲 | 青草草在线 | 国产精品美女视频网站 | 国产一区二区精品久久 | 午夜aaaa| 日韩久久久久 | 色婷五月天 | 国产免费视频一区二区裸体 | 国产 日韩 在线 亚洲 字幕 中文 | 亚洲欧美国产视频 | 精品久久久久久一区二区里番 | 免费无遮挡动漫网站 | 欧美男同视频网站 | 午夜成人免费影院 | 波多野结衣最新 | 国产伦精品一区二区三区… | 久久久网站 | 亚洲毛片一区二区三区 | 久久久免费播放 | 亚洲欧美乱综合图片区小说区 | 国产精品毛片一区视频 | 久草在线视频看看 | 天天操天天射天天爽 | 亚洲国产手机在线 | 久 久久影院 | 久久福利 | 2023av| 91精品在线视频观看 | 日日躁夜夜躁aaaaxxxx | 色午夜| 久久久久久久久久免费视频 | 啪一啪在线| 国产私拍在线 | 国产三级国产精品国产专区50 | 91av在线电影 | 91超碰免费在线 | 欧美久久久一区二区三区 | 久久久综合色 | 黄色视屏免费在线观看 | 综合网av| 国产在线观看不卡 | 美女国内精品自产拍在线播放 | 欧美韩日在线 |