(This is only a very brief overview of printing)
Printing is basically the same as drawing on screen: both are handled through the GDI (Graphic Device Interface) who translates your high level drawing commands to low level instructions according to the appropriate device drivers. This driver can be a printer driver, a display driver, fax driver or whatever.
Before you can start to draw anything you must first define on which device you will draw, ie you must get a handle to a device context (HDC). When you want to draw on screen you use the function hdc=GetDC(hwnd) and when you want to draw on a printer you use the function hdc=CreateDC(0,printername,0,lpInitData).
Nearly every GDI function has hdc as its first parameter so it is a very important handle. When you are finished drawing you must use ReleaseDC(hdc) because Windows can only create a limited amount of context handles simultaniously.
In **pseudo-code** your program will have a structure like this:
hdc=CreateDC(...) StartDoc(hdc). StartPage(hdc). TextOut(hdc,10,10,'this is page 1',14). EndPage(hdc). StartPage(hdc). TextOut(hdc,10,10,'this is page 2',14). EndPage(hdc). EndDoc(hdc). DeleteDC(hdc).
Everything between StartDoc and EndDoc is one spooler job, this is important for a multipage document because it will prevent other jobs from printing pages in between.
As I said, this is just pseudo-code. Click here for a working code example.
You will need to know the size of the paper, in pixels. This and other important data can be obtained from GetDeviceCaps and DeviceCapabilities.
GDI has a huge amount of drawing functions. The most commonly used are:
* MoveTo(hdc,x,y) : moves the dc cursor to coordinate x,y
* LineTo(hdc,x,y) : draws a line from current position to x,y
* TextOut(hdc,x,y,text,lenght(text))
There are also several functions to manipulate text placement, see SetTextAlign. To write text in columns it is convenient to use the ExtTextOut function because it also has a rectangle parameter: the text will be clipped to the rectangle when needed and you can also easily ensure that text is centered or right-aligned to the rectangle.
You will need objects like pens, fonts, brushes, bitmaps. For example when you want to draw a red dotted line you will have to create a red dotted pen first using hRedpen=CreatePen(PS_DOT,1,RGB(255,0,0)).
But when you want to create a black solid line, you don't have to create one because it is already 'in stock'. GDI has several stock objects.
After you created one or more objects you must select one of them to make it the current object using SelectObject(hRedpen). Every created object must be manually destroyed too or else you will run out of GDI resources, using DeleteObject(hRedpen). An object can not be destroyed when it is currently selected so you may have to select a stock object first.
By Nenad Orlovic, norlovic@zg.tel.hr
Suppose you have a file that contains printer control codes, and now you want to send this file to a printer. The file may have been created by a report engine when the port was set to "FILE:".
The following procedure copies the file to a printer.
This example will send file to printer even if in printer properties port is FILE:
This is because variable OutFileName = "", but it can be changed to a filename or perhaps even to a UNC-name for a printer.
See the notes near the bottom of this page.
DEFINE INPUT PARAMETER PrinterName AS CHARACTER NO-UNDO. /* As set in Printer properties */ DEFINE INPUT PARAMETER FILENAME AS CHARACTER NO-UNDO. DEFINE VARIABLE X AS INTEGER NO-UNDO. DEFINE VARIABLE hPrinter AS INTEGER NO-UNDO. DEFINE VARIABLE hFile AS INTEGER NO-UNDO. DEFINE VARIABLE pBuf AS MEMPTR NO-UNDO. DEFINE VARIABLE FileSize AS INTEGER NO-UNDO. DEFINE VARIABLE iSize AS INTEGER NO-UNDO. DEFINE VARIABLE xSize AS INTEGER NO-UNDO. DEFINE VARIABLE pFileName AS MEMPTR NO-UNDO. DEFINE VARIABLE OutFileName AS CHARACTER NO-UNDO. DEFINE VARIABLE pOutFileName AS MEMPTR NO-UNDO. DEFINE VARIABLE DataType AS CHARACTER NO-UNDO. DEFINE VARIABLE pDataType AS MEMPTR NO-UNDO. DEFINE VARIABLE pDocInfo AS MEMPTR NO-UNDO. RUN OpenPrinterA (PrinterName,OUTPUT hPrinter,0, OUTPUT X). IF X = 0 THEN MESSAGE "Error opening printer: " PrinterName VIEW-AS ALERT-BOX. ELSE DO: RUN CreateFileA (FILENAME , -2147483648,0,0,3,128,0,OUTPUT hFile). /* -2147483648 = $80000000 */ IF hFile = -1 THEN MESSAGE "Error opening file: " FILENAME VIEW-AS ALERT-BOX. ELSE DO: RUN GetFileSize (hFile,0,OUTPUT FileSize). IF FileSize = -1 THEN MESSAGE "Wrong file size" VIEW-AS ALERT-BOX. ELSE DO: SET-SIZE(pBuf) = FileSize. RUN ReadFile(hFile,pBuf,FileSize,OUTPUT iSize,0, OUTPUT X). IF X = 0 THEN MESSAGE "Error reading file: " FILENAME VIEW-AS ALERT-BOX. ELSE DO: IF iSize = 0 THEN MESSAGE "Attempt to read beyond end of file:" FILENAME VIEW-AS ALERT-BOX. ELSE DO: OutFileName = "". DataType = "RAW". SET-SIZE(pDocInfo) = 12. SET-SIZE(pFileName) = LENGTH(FILENAME) + 1. PUT-STRING(pFileName,1) = FILENAME. SET-SIZE(pOutFileName) = LENGTH(OutFileName) + 1. PUT-STRING(pOutFileName,1) = OutFileName. SET-SIZE(pDataType) = LENGTH(DataType) + 1. PUT-STRING(pDataType,1) = DataType. PUT-LONG(pDocInfo,1) = GET-POINTER-VALUE(pFileName). PUT-LONG(pDocInfo,5) = GET-POINTER-VALUE(pOutFileName). PUT-LONG(pDocInfo,9) = GET-POINTER-VALUE(pDataType). RUN StartDocPrinterA (hPrinter,1,pDocInfo,OUTPUT X). IF X = 0 THEN DO: RUN GetLastError(OUTPUT X). MESSAGE "Error : " X VIEW-AS ALERT-BOX. END. RUN WritePrinter(hPrinter,pBuf,iSize,OUTPUT xSize,OUTPUT X). IF X = 0 THEN DO: RUN GetLastError(OUTPUT X). MESSAGE "Error writing to printer: " PrinterName iSize xsize X VIEW-AS ALERT-BOX. END. RUN EndDocPrinter(hPrinter,OUTPUT X). END. END. END. RUN CloseHandle(hFile,OUTPUT X). IF X = 0 THEN MESSAGE "Error closing file: " FILENAME. END. RUN ClosePrinter(hPrinter,OUTPUT X). IF X = 0 THEN MESSAGE "Error closing printer: " PrinterName VIEW-AS ALERT-BOX. END. SET-SIZE(pBuf) = 0. SET-SIZE(pDocInfo) = 0. SET-SIZE(pFileName) = 0. SET-SIZE(pOutFileName) = 0. SET-SIZE(pDataType) = 0. /******************/ /* DLL Procedures */ /******************/ PROCEDURE GetLastError EXTERNAL "kernel32.dll" : DEFINE RETURN PARAMETER X AS LONG. END PROCEDURE. PROCEDURE StartDocPrinterA EXTERNAL "winspool.drv" : DEFINE INPUT PARAMETER hPrinter AS LONG. DEFINE INPUT PARAMETER Level AS LONG. DEFINE INPUT PARAMETER pDocInfo AS MEMPTR. DEFINE RETURN PARAMETER X AS LONG. END PROCEDURE. PROCEDURE EndDocPrinter EXTERNAL "winspool.drv" : DEFINE INPUT PARAMETER hPrinter AS LONG. DEFINE RETURN PARAMETER X AS LONG. END PROCEDURE. PROCEDURE CreateFileA EXTERNAL "kernel32.dll" : DEFINE INPUT PARAMETER lpFileName AS CHARACTER. DEFINE INPUT PARAMETER dwDesiredAccess AS LONG. DEFINE INPUT PARAMETER dwShareMode AS LONG. DEFINE INPUT PARAMETER lpSecurityAttributes AS LONG. DEFINE INPUT PARAMETER dwCreationDistribution AS LONG. DEFINE INPUT PARAMETER dwFlagsAndAttributes AS LONG. DEFINE INPUT PARAMETER hTemplateFile AS LONG. DEFINE RETURN PARAMETER hFile AS LONG. END PROCEDURE. PROCEDURE ReadFile EXTERNAL "kernel32.dll" : DEFINE INPUT PARAMETER hFile AS LONG. DEFINE INPUT PARAMETER lpBuffer AS MEMPTR. DEFINE INPUT PARAMETER nNumberOfBytesToRead AS LONG. DEFINE OUTPUT PARAMETER lpNumberOfBytesRead AS LONG. DEFINE INPUT PARAMETER lpOverlapped AS LONG. DEFINE RETURN PARAMETER X AS LONG. END PROCEDURE. PROCEDURE WritePrinter EXTERNAL "winspool.drv" : DEFINE INPUT PARAMETER hPrinter AS LONG. DEFINE INPUT PARAMETER pBuf AS MEMPTR. DEFINE INPUT PARAMETER cbBuf AS LONG. DEFINE OUTPUT PARAMETER lpdwWritten AS LONG. DEFINE RETURN PARAMETER X AS LONG. END PROCEDURE. PROCEDURE OpenPrinterA EXTERNAL "winspool.drv" : DEFINE INPUT PARAMETER pPrinterName AS CHARACTER. DEFINE OUTPUT PARAMETER phPrinter AS LONG. DEFINE INPUT PARAMETER pDefault AS LONG. DEFINE RETURN PARAMETER X AS LONG. END PROCEDURE. PROCEDURE ClosePrinter EXTERNAL "winspool.drv" : DEFINE INPUT PARAMETER hPrinter AS LONG. DEFINE RETURN PARAMETER X AS LONG. END PROCEDURE. PROCEDURE GetFileSize EXTERNAL "kernel32.dll" : DEFINE INPUT PARAMETER hFile AS LONG. DEFINE INPUT PARAMETER lpFileSizeHigh AS LONG. DEFINE RETURN PARAMETER FileSize AS LONG. END PROCEDURE. PROCEDURE CloseHandle EXTERNAL "kernel32.dll" : DEFINE INPUT PARAMETER hObject AS LONG. DEFINE RETURN PARAMETER X AS LONG. END PROCEDURE.
Note from Valther Bernardi:
The procedure fails if the printer is a local printer and not a network printer.
For local printers the variable OutFileName need to be initialized to the value of the device associated to printer (i.e. LPT1:). For retrieving the port name associated to the printer you can use EnumPrinters
Note from Brian Maher:
In Progress 4GL you could simply output to the printer and then do a PUT CONTROL NULL which switches us into "windows passthru printing" mode.
EnumPrinter retrieves a list of available printers, that is: local installed printers and also printers made available by the network.
Actually EnumPrinters retrieves an array of PRINTER_INFO_X records (where X stands for level: will be explained later). Each PRINTER_INFO_X record contains information about a single printer, in this example we will only show the Printername (like "HP Laserjet 4L") and Portname (like "LPT1:").
Let's take a look at the source first and explain later.
{windows.i} DEFINE VARIABLE pPrinterEnum AS MEMPTR NO-UNDO. DEFINE VARIABLE pcbNeeded AS INTEGER NO-UNDO. DEFINE VARIABLE pcReturned AS INTEGER NO-UNDO. DEFINE VARIABLE RetValue AS INTEGER NO-UNDO. DEFINE VARIABLE pPrinterInfo AS MEMPTR NO-UNDO. DEFINE VARIABLE StructSize AS INTEGER INITIAL 84. DEFINE VARIABLE i AS INTEGER NO-UNDO. DEFINE VARIABLE lpPrinterName AS MEMPTR NO-UNDO. DEFINE VARIABLE lpPortName AS MEMPTR NO-UNDO. /* The first call to EnumPrinters is only to get the required memory size */ SET-SIZE(pPrinterEnum) = 30. /* A default bobo value */ RUN EnumPrinters{&A} IN hpApi(2, /* = PRINTER_ENUM_LOCAL */ "", 2, GET-POINTER-VALUE(pPrinterEnum), GET-SIZE(pPrinterEnum), OUTPUT pcbNeeded, OUTPUT pcReturned, OUTPUT RetValue). /* RetValue will now be FALSE (=error) because we did not supply enough memory. But at least we know now how much memory was required (pcbNeeded) and also how many printers were found (pcReturned) */ /* no printers installed, then return (rare) */ IF pcbNeeded=0 THEN DO: MESSAGE "No printers found". RUN DeAlloc. RETURN. END. /* Reset the size of pPrinterEnum to the correct size */ SET-SIZE(pPrinterEnum) = 0. SET-SIZE(pPrinterEnum) = pcbNeeded. /* The second call actually fills the pPrinterEnum structure */ RUN EnumPrinters{&A} IN hpApi(2, /* = PRINTER_ENUM_LOCAL */ "", 2, GET-POINTER-VALUE (pPrinterEnum), GET-SIZE(pPrinterEnum), OUTPUT pcbNeeded, OUTPUT pcReturned, OUTPUT RetValue). /* pPrinterEnum holds a couple of PRINTER_INFO_2 records. the number of records is pcReturned. the number of bytes copied to pPrinterEnum is pcbNeeded. size of one PRINTER_INFO_2 record is 84 bytes. */ DO i=0 TO pcReturned - 1 : SET-POINTER-VALUE(pPrinterInfo) = GET-POINTER-VALUE(pPrinterEnum) + (i * StructSize). /* the second LONG field in the PRINTER_INFO_2 structure is a pointer to a string holding the printer name */ SET-POINTER-VALUE(lpPrinterName) = GET-LONG(pPrinterInfo, 5). /* the 4th LONG field in the PRINTER_INFO_2 structure is a pointer to a string holding the port name */ SET-POINTER-VALUE(lpPortName) = GET-LONG(pPrinterInfo,13). MESSAGE "printername=" GET-STRING(lpPrinterName,1) SKIP "portname=" GET-STRING(lpPortName,1) VIEW-AS ALERT-BOX. END. /* Clean Up */ RUN DeAlloc. PROCEDURE DeAlloc: SET-SIZE(pPrinterEnum) = 0. END.
The first parameter (flags) in EnumPrinters is set to PRINTER_ENUM_LOCAL but Windows 95 will also enumerate network printers because these are also handled by the local print provider. Windows NT will strictly enumerate locally installed printers. When you want to enumerate network printers on NT you will have to use other parameters.
The third parameter (level) is set to 2 indicating we expect records of type PRINTER_INFO_2. Windows 95 supports only levels 1,2,5 and NT supports only levels 1,2,4.
structsize=84 is only true for Level=2.
Brad Long enhanced the previous example to support all other PRINTER_INFO types and make it independent of Windows version. The sourcefile is attached.
This piece of source is also interesting for those who struggle with memptr variables (aren't we all?).
pPrinterEnum is a pointer to an array of PRINTER_INFO_2 records. The pointer to the first record is the same as the pointer to the array, the pointer to the second record is (first) + structsize, and so on.
A PRINTER_INFO_2 record does not contain strings but pointers to strings, this indirection is solved by the lpPortName and lpPrinterName MEMPTR variables. Of course you should always use statements like SET-SIZE(memptrvar)=0 for each MEMPTR variable near the end of your procedure, but in this particular example you should not do this for pPrinterInfo because the value of this variable points to memory inside the scope of pPrinterEnum. So if you deallocate pPrinterEnum you automatically deallocate pPrinterInfo. But what about the two string-pointers lpPortName and lpPrinterName, you wonder? Well, these also point to locations within pPrinterEnum as we will see:
There are only 2 printers installed on my PC, so I would expect that pcbNeeded=(2 * 84)=168 but the function returned 948. Almost 700 byte too much: this is where the strings are stored that are pointed to by the several string-pointers in the PRINTER_INFO_2 records.
That is smart thinking by Microsoft: you now have local copies of the strings so the pointers don't have to point to protected memory, and it also solves the important question which process should be responsible for deallocating the string space. The strings are inside Your block of memory so You deallocate them. And it is all done automatically by simple deallocating pPrinterEnum.
Here's a small map to illustrate it all:
wingetprinters.p.zip : example
The attached code returns an array of Bin IDs and Bin Names for the printer/port specified in pcPrinterName and pcPrinterPort.
In 16-bit windows you can find the printer name in WIN.INI, section "windows" key "device".
For example:
[windows] device=HP LaserJet 4L,HPPCL5MS,LPT1:
To read 'WIN.INI' you use the procedure GetProfileString.
To read any other ini-file you use the procedure GetPrivateProfileString.
Example:
{windows.i} DEFINE VARIABLE printername AS CHARACTER NO-UNDO. DEFINE VARIABLE cchRet AS INTEGER NO-UNDO. printername = FILL(" ",100). /* = allocate memory, don't forget! */ RUN GetProfileString{&A} IN hpApi ("windows", "device", "-unknown-,", OUTPUT printername, LENGTH(printername), OUTPUT cchRet). /* split name from driver and port. Note that the default "-unknown-," must have at leat one comma or the ENTRY function may raise an error */ printername = ENTRY(1,printername). /* use the result */ IF printername="-unknown-" THEN MESSAGE "something is wrong with your WIN.INI" VIEW-AS ALERT-BOX. ELSE MESSAGE "your default printer is: " printername VIEW-AS ALERT-BOX.
The above example was used for an in-depth explanation of parameter types on page using a MEMPTR parameter for a CHAR. It also explains why the FILL statement is important.
Of course you can also use the 4GL procedure GET-KEY-VALUE to read this information, as shown in topic "printing: using StartDoc"
Here is the easiest way:
Printername = SESSION:PRINTER-NAME.
Although INI files are obsolete in 32-bits Windows versions, you should still use GetProfileString to find the default printer. The function will not actually read the INI file but will read from Registry.
If you know the key where the printer name is stored in Registry, you could use the following code example but you should not do that. It is always a bad idea to use hardcoded paths because Microsoft does not commit to support those paths forever. For example: Windows 95 and NT 4.0 don't use the same key. Function GetProfileString however is guaranteed to return the right information on all 32-bit Windows versions.
but it's still interesting to have a registry example, so here it goes:
DEFINE VARIABLE Printername AS CHARACTER. LOAD "System" BASE-KEY "HKEY_CURRENT_CONFIG". USE "System". GET-KEY-VALUE SECTION "CurrentControlSet\Control\Print\Printers" KEY "default" VALUE Printername. UNLOAD "System". /* code example by Joseph Richardson */
The example program printfile.p demonstrates how to print an ascii-file using GDI functions.
The program prints a header and a footer on every page using a proportional font. The page header contains the filename (right aligned). The page footer contains the date (left aligned), time (centered) and page number (right aligned).
The contents of the ascii-file is printed in a non-proportional font (Courier New).
The procedure printfile.p has currently only one input parameter: the filename of the ascii-file to print.
The definition section contains some variables who could also have been used as input parameters:
/* ================================================================= file : printfile.p purpose : print a text file usage : RUN printfile.p ("printfile.p"). tested : only on Windows 98 with local printer (HP Laserjet 4) ================================================================= */ DEFINE INPUT PARAMETER p-Filename AS CHARACTER NO-UNDO. /* these variables could also be useful as input parameters: */ DEFINE VARIABLE p-Landscape AS LOGICAL NO-UNDO INITIAL NO. /* Orientation is Landscape or Portrait */ DEFINE VARIABLE p-Silent AS LOGICAL NO-UNDO INITIAL YES. /* show no message boxes (on error) */ DEFINE VARIABLE p-Devicename AS CHARACTER NO-UNDO. /* if "" then default printer */ DEFINE VARIABLE p-docname AS CHARACTER NO-UNDO. /* if "" then p-Filename */ DEFINE VARIABLE p-Leftmargin AS DECIMAL NO-UNDO INITIAL 1.0. /* left margin (inches) from paper edge */ DEFINE VARIABLE p-fontsize AS INTEGER NO-UNDO INITIAL 9. /* pointsize for bodyfont */
API-procedures used in printfile.zip: CreateDC, DeleteDC, DocumentProperties, GetProfileString, StartDoc, EndDoc, StartPage, EndPage, TextOut, SetTextAlign, GetDeviceCaps, MoveToEx, LineTo, SelectObject, DeleteObject, GetTextMetrics, CreateFont, MulDiv.
printfile.zip : example
The attached example shows how to use the WebBrowser control (in shdocvw.dll) to print an HTML page. The example does __not__ show how to change headers/footers at run-time.
printurl.zip : a dialog with WebBrowser control
by Nickolay Borshukov
Download attached file: preview.zip
The example programs in preview.zip are an extended version of printfile.zip (see topic how to print an ASCII file).
The most noticeable extensions are:
* long lines are wrapped to the next line.
* the print layout can be viewed in a Preview window. This window is made with P4GL and Windows API-calls
API-procedures used in preview.zip (in addition to the ones in printfile.p) : CreateCompatibleDC, CreateCompatibleBitmap, SetBkMode, SetTextColor, PatBlt, StretchBlt, GetTextExtentPoint32
preview.zip : example by Nickolay Borshukov
Notice how this source uses the GET-KEY-VALUE function to retrieve information about the default printer. There are a couple of other methods to get the same information, see topic GetDefaultPrinter.
Besides using the default printer you can also use a different printer; those other printers can be picked using the EnumPrinters function.
code example by Roland the Pijper, converted to 32-bit by Jurjen
{windows.i} DEFINE VARIABLE windir AS CHARACTER. DEFINE VARIABLE pdocname AS MEMPTR. DEFINE VARIABLE poutbuf AS MEMPTR. DEFINE VARIABLE lpdocinfo AS MEMPTR. DEFINE VARIABLE pfilename AS MEMPTR. DEFINE VARIABLE outsize AS INTEGER NO-UNDO. DEFINE VARIABLE printerhDC AS INTEGER NO-UNDO. DEFINE VARIABLE apistatus AS INTEGER NO-UNDO. DEFINE VARIABLE docname AS CHARACTER NO-UNDO. DEFINE VARIABLE devicebuf AS CHARACTER NO-UNDO. DEFINE VARIABLE driverbuf AS CHARACTER NO-UNDO. DEFINE VARIABLE initbuf AS CHARACTER NO-UNDO. DEFINE VARIABLE outbuf AS CHARACTER NO-UNDO. DEFINE VARIABLE windowsdir AS CHARACTER NO-UNDO. DEFINE VARIABLE winini AS CHARACTER NO-UNDO. DEFINE VARIABLE printerdev AS CHARACTER NO-UNDO. DEFINE VARIABLE FILENAME AS CHARACTER NO-UNDO. DEFINE VARIABLE tekst AS CHARACTER NO-UNDO. /* Get printer info from WIN.INI */ windir = FILL("x", 260). RUN GetWindowsDirectoryA IN hpApi(OUTPUT windir, LENGTH(windir), OUTPUT outsize). ASSIGN windowsdir = SUBSTRING(windir,1,outsize) winini = windowsdir + "\WIN.INI". LOAD winini. USE winini. GET-KEY-VALUE SECTION "windows" KEY "device" VALUE printerdev. UNLOAD winini. IF printerdev = "" THEN DO: RUN Err ("Could not locate printer device!"). RETURN. END. ASSIGN devicebuf = ENTRY(1,printerdev). driverbuf = ENTRY(2,printerdev). outbuf = ENTRY(3,printerdev). docname = "test". /* Setup pointers to the strings needed in the lpdocinfo STRUCT */ SET-SIZE(pdocname) = LENGTH(docname) + 1. PUT-STRING(pdocname,1) = docname. SET-SIZE(poutbuf) = LENGTH(outbuf) + 1. PUT-STRING(poutbuf,1) = outbuf. /* Load up the lpdocinfo STRUCT */ SET-SIZE(lpdocinfo) = 4 /* INTEGER cbSize */ + 4 /* pointer lpszDocName */ + 4. /* pointer lpszOutput */ PUT-LONG(lpdocinfo,1) = 12. /* size of the STRUCT */ PUT-LONG(lpdocinfo,5) = GET-POINTER-VALUE(pdocname). /* pointer to CHARACTER */ PUT-LONG(lpdocinfo,9) = GET-POINTER-VALUE(poutbuf). /* pointer to CHARACTER */ tekst = "bla bla". /* Print it! */ RUN adecomm/_setcurs.p ("WAIT"). RUN CreateDCA (driverbuf, devicebuf, outbuf, 0, OUTPUT printerhDC). RUN StartDocA IN hpApi(INPUT printerhDC, INPUT GET-POINTER-VALUE(lpdocinfo), OUTPUT apistatus). /* is printjob id */ RUN StartPage IN hpApi(INPUT printerhDC, OUTPUT apistatus). RUN TextOutA IN hpApi(printerhDC, 800, 450, tekst, LENGTH(tekst), OUTPUT apistatus). IF apistatus=0 /* 0=FALSE */ THEN MESSAGE "There was an error during TextOut " VIEW-AS ALERT-BOX ERROR. RUN EndPage IN hpApi(printerhDC, OUTPUT apistatus). RUN EndDoc IN hpApi(printerhDC, OUTPUT apistatus). RUN DeleteDC(printerhDC, OUTPUT apistatus). /* Clean Up */ SET-SIZE(lpdocinfo) = 0. SET-SIZE(pdocname) = 0. SET-SIZE(poutbuf) = 0. RUN adecomm/_setcurs.p ("").
by Jurjen, improved by Ian Keene
/* ============================================================== file : printscreen.p by : Jurjen Dijkstra (Modified by Ian Keene Oct 2003) dd : 05/16/1999 purpose : draw a window to the default printer. usage : RUN printscreen.p ({&WINDOW-NAME}:HWND, YES). parms : hWindow (integer) HWND of the 'widget' to be drawn GetParent (logical) -Use YES if HWND is a Progress window widget so printscreen.p will draw the border/titlebar. -Use NO for all other widgets. ============================================================== */ DEFINE INPUT PARAMETER hWindow AS INTEGER NO-UNDO. /* HWND */ DEFINE INPUT PARAMETER GetParent AS LOGICAL NO-UNDO. /* hWindow=GetParent(hWindow)? */ RUN MakeDocument. /* API definitions used in this proc have been moved to printscreen.i */ {printscreen.i} PROCEDURE MakeDocument : DEFINE VARIABLE hDC AS INTEGER NO-UNDO. DEFINE VARIABLE lpDocInfo AS MEMPTR NO-UNDO. DEFINE VARIABLE lpDocName AS MEMPTR NO-UNDO. DEFINE VARIABLE ReturnValue AS INTEGER NO-UNDO. DEFINE VARIABLE prt-buf AS CHARACTER NO-UNDO. DEFINE VARIABLE prt-out AS CHARACTER NO-UNDO. DEFINE VARIABLE err-check AS INTEGER NO-UNDO. DEFINE VARIABLE drbuf AS CHARACTER NO-UNDO. DEFINE VARIABLE out-buf AS CHARACTER NO-UNDO. DEFINE VARIABLE prt-hwnd AS INTEGER NO-UNDO. ASSIGN prt-out = FILL(" ",127). /* ALLOCATE MEMORY */ RUN GetProfileStringA (INPUT "WINDOWS", INPUT "DEVICE", INPUT "-unknown-,", OUTPUT prt-out, INPUT LENGTH(prt-out), OUTPUT err-check). IF prt-out = "-unknown-," THEN DO: MESSAGE "Aborted, Cannot Determine Default Printer." SKIP VIEW-AS ALERT-BOX INFORMATION. RETURN. END. ASSIGN prt-buf = ENTRY(1,prt-out) drbuf = ENTRY(2,prt-out) out-buf = ENTRY(3,prt-out). /* OPEN THE PRINTER */ RUN OpenPrinterA (INPUT prt-buf, OUTPUT prt-hwnd, INPUT 0). RUN CreateDCA ( "WINSPOOL", prt-buf, 0, 0, OUTPUT hDC) . IF hDC = 0 THEN DO: MESSAGE "Error during CreateDCA in PrintScreen procedure" view-as ALERT-BOX INFORMATION. RETURN. END. SET-SIZE (lpDocName) = LENGTH("PrintScreen") + 1. PUT-STRING(lpDocName,1) = "PrintScreen". SET-SIZE (lpDocInfo) = 12. PUT-LONG (lpDocInfo,1) = 12. PUT-LONG (lpDocInfo,5) = GET-POINTER-VALUE(lpDocName). PUT-LONG (lpDocInfo,9) = 0. RUN StartDocA ( hDC, GET-POINTER-VALUE(lpDocInfo), OUTPUT ReturnValue). IF ReturnValue < 1 THEN MESSAGE "Error during StartDoc in PrintScreen procedure" view-as ALERT-BOX INFORMATION. ELSE DO: RUN StartPage (hDC, OUTPUT ReturnValue). IF ReturnValue < 1 THEN MESSAGE "Error during StartPage in PrintScreen procedure" view-as ALERT-BOX INFORMATION. ELSE DO: RUN PrintWindow (hDC, hWindow). RUN EndPage (hDC, OUTPUT ReturnValue). END. RUN EndDoc (hDC, OUTPUT ReturnValue). END. /* cleanup */ RUN DeleteDC (hDC, OUTPUT ReturnValue). SET-SIZE(lpDocInfo) = 0. SET-SIZE(lpDocName) = 0. END PROCEDURE. /* MakeDocument */ PROCEDURE PrintWindow : /*------------------------------------------------------------------------------ Notes: a couple of extra parameters would be nice, like: input leftmargin, rightmargin, Ytop output Ybottom ------------------------------------------------------------------------------*/ define input parameter hdcDest as INTEGER NO-UNDO. /* Printer */ define input parameter hWin as INTEGER NO-UNDO. /* windows handle to progress window to print */ DEFINE VARIABLE hdcWin as integer no-undo. /* hdc window */ DEFINE VARIABLE PrintHorzRes as integer no-undo. /* printer resolution */ DEFINE VARIABLE PrintVertRes as integer no-undo. DEFINE VARIABLE WinHorzRes as integer no-undo. /* display resolution */ DEFINE VARIABLE WinVertRes as integer no-undo. DEFINE VARIABLE hdcComp as integer no-undo. /* hdc memory */ DEFINE VARIABLE hbmpComp as integer no-undo. /* bitmap in memory */ DEFINE VARIABLE hbmpDest as integer no-undo. /* bitmap on paper */ DEFINE VARIABLE WinWidth as INTEGER NO-UNDO. /* dimensions of window */ DEFINE VARIABLE WinHeight as INTEGER NO-UNDO. DEFINE VARIABLE PictureWidth as integer no-undo. /* dimensions of picture on paper */ DEFINE VARIABLE PictureHeight as integer no-undo. DEFINE VARIABLE Scale as decimal no-undo. DEFINE VARIABLE xMargin as INTEGER NO-UNDO. /* center picture horizontally */ DEFINE VARIABLE numrows as integer no-undo. /* split large pics into rows/cols */ DEFINE VARIABLE numcols as integer no-undo. DEFINE VARIABLE rw as integer no-undo. DEFINE VARIABLE cl as integer no-undo. DEFINE VARIABLE lpRect as MEMPTR NO-UNDO. DEFINE VARIABLE ReturnValue as integer no-undo. DEFINE VARIABLE lpOrigin as MEMPTR NO-UNDO. IF getParent THEN RUN GetParent(hWin, OUTPUT hWin). /* get the window resolution */ run GetDC (hWin, output hdcWin). run GetDeviceCaps(hdcWin, 8 /* = HorzRes */, output WinHorzRes). run GetDeviceCaps(hdcWin, 10 /* = VertRes */, output WinVertRes). /* get the printer resolution */ run GetDeviceCaps(hdcDest, 8 /* = HorzRes */, output PrintHorzRes). run GetDeviceCaps(hdcDest, 10 /* = VertRes */, output PrintVertRes). /* determine dimensions of the window */ /* Also determine the coordinates of the upper-left corner. This is (0,0) for a client window, but will be somewhere around (-4,-20) for a window with titlebar */ SET-SIZE (lpOrigin) = 8. SET-SIZE (lpRect) = 16. RUN GetWindowRect (hWin, GET-POINTER-VALUE(lpRect) , OUTPUT ReturnValue). WinWidth = GET-LONG(lpRect, 9) - GET-LONG(lpRect, 1). WinHeight = GET-LONG(lpRect,13) - GET-LONG(lpRect, 5). PUT-LONG(lpOrigin, 1) = GET-LONG(lpRect, 1). PUT-LONG(lpOrigin, 5) = GET-LONG(lpRect, 5). RUN ScreenToClient (hWin, GET-POINTER-VALUE(lpOrigin), OUTPUT ReturnValue). SET-SIZE (lpRect) = 0. Scale = MINIMUM(PrintHorzRes / WinWidth, PrintVertRes / WinHeight). IF Scale < 1 THEN Scale = 1. run CreateCompatibleDC (hdcWin, output hdcComp). run CreateCompatibleBitmap (hdcComp, integer(winWidth * Scale), integer(winHeight * Scale), output hbmpComp). run CreateCompatibleBitmap (hdcDest, integer(winWidth * Scale), integer(winHeight * Scale), output hbmpDest). run SelectObject (hdcComp, hbmpComp, output ReturnValue). run SelectObject (hdcDest, hbmpDest, output ReturnValue). run StretchBlt (hdcDest, 0, 0, integer(winwidth * Scale) , integer(winHeight * Scale) , hdcWin, get-long(lpOrigin,1), get-long(lpOrigin,5), winwidth, winheight, 13369376, /* = SRCCOPY */ output ReturnValue). /* cleanup */ run ReleaseDC (hWin, hdcWin, output ReturnValue). run DeleteDC (hdcComp, output ReturnValue). run DeleteObject (hbmpComp, output ReturnValue). run DeleteObject (hbmpDest, output ReturnValue). SET-SIZE(lpOrigin)=0. END PROCEDURE. /* PrintWindow */
/* ============================================================== file : printscreen.i by : Jurjen Dijkstra (Modified by Ian Keene Oct 2003) dd : 05/16/1999 purpose : API definitions used in printscreen.p ============================================================== */ &GLOB GDI "gdi32.dll" &GLOB USER "user32.dll" &GLOB BOOL LONG &GLOB COLORREF LONG &GLOB DWORD LONG &GLOB HANDLE LONG &GLOB HDC LONG &GLOB HGDIOBJ LONG &GLOB HWND LONG &GLOB INTEGER LONG &GLOB LONG LONG &GLOB LP LONG &GLOB LPCSTR CHARACTER &GLOB LPCTSTR CHARACTER &GLOB LPSECURITY_ATTRIBUTES LONG &GLOB LPSTRUCT LONG &GLOB LPTSTR CHARACTER &GLOB SHORT SHORT &GLOB UINT LONG &GLOB WORD SHORT PROCEDURE CreateDCA EXTERNAL {&GDI} : DEFINE INPUT PARAMETER lpszDriver AS {&LPCTSTR} NO-UNDO. DEFINE INPUT PARAMETER lpszDevice AS {&LPCTSTR} NO-UNDO. DEFINE INPUT PARAMETER lpszOutput AS {&LP} NO-UNDO. DEFINE INPUT PARAMETER lpInitData AS {&LPSTRUCT} NO-UNDO. DEFINE RETURN PARAMETER hDC AS {&HDC} NO-UNDO. END PROCEDURE. PROCEDURE StartDocA EXTERNAL {&GDI} : DEFINE INPUT PARAMETER hdc AS {&HDC} NO-UNDO. DEFINE INPUT PARAMETER lpdi AS {&LPSTRUCT} NO-UNDO. DEFINE RETURN PARAMETER JobId AS {&int} NO-UNDO. END PROCEDURE. PROCEDURE StartPage EXTERNAL {&GDI} : DEFINE INPUT PARAMETER hdc AS {&HDC} NO-UNDO. DEFINE RETURN PARAMETER ReturnValue AS {&int} NO-UNDO. END PROCEDURE. PROCEDURE EndPage EXTERNAL {&GDI} : DEFINE INPUT PARAMETER hdc AS {&HDC} NO-UNDO. DEFINE RETURN PARAMETER ReturnValue AS {&int} NO-UNDO. END PROCEDURE. PROCEDURE EndDoc EXTERNAL {&GDI} : DEFINE INPUT PARAMETER hdc AS {&HDC} NO-UNDO. DEFINE RETURN PARAMETER ReturnValue AS {&int} NO-UNDO. END PROCEDURE. PROCEDURE GetParent EXTERNAL {&USER} : DEFINE INPUT PARAMETER hWinHdl AS {&HWND} NO-UNDO. DEFINE RETURN PARAMETER hwndParent AS {&HWND} NO-UNDO. END PROCEDURE. PROCEDURE GetDC EXTERNAL {&USER} : DEFINE INPUT PARAMETER hWinHdl AS {&HWND} NO-UNDO. DEFINE RETURN PARAMETER hdc AS {&HDC} NO-UNDO. END PROCEDURE. PROCEDURE GetDeviceCaps EXTERNAL {&GDI} : DEFINE INPUT PARAMETER hdc AS {&HDC} NO-UNDO. DEFINE INPUT PARAMETER nIndex AS {&int} NO-UNDO. DEFINE RETURN PARAMETER dwCaps AS {&int} NO-UNDO. END PROCEDURE. PROCEDURE GetWindowRect EXTERNAL {&USER} : DEFINE INPUT PARAMETER hWinHdl AS {&HWND} NO-UNDO. DEFINE INPUT PARAMETER lpRect AS {&LPSTRUCT} NO-UNDO. DEFINE RETURN PARAMETER ReturnValue AS {&BOOL} NO-UNDO. END PROCEDURE. PROCEDURE ScreenToClient EXTERNAL {&USER} : DEFINE INPUT PARAMETER hWinHdl AS {&HWND} NO-UNDO. DEFINE INPUT PARAMETER lpPoint AS {&LPSTRUCT} NO-UNDO. DEFINE RETURN PARAMETER ReturnValue AS {&BOOL} NO-UNDO. END PROCEDURE. PROCEDURE CreateCompatibleDC EXTERNAL {&GDI} : DEFINE INPUT PARAMETER hdc AS {&HDC} NO-UNDO. DEFINE RETURN PARAMETER hdcComp AS {&HDC} NO-UNDO. END PROCEDURE. PROCEDURE CreateCompatibleBitmap EXTERNAL {&GDI} : DEFINE INPUT PARAMETER hdc AS {&HDC} NO-UNDO. DEFINE INPUT PARAMETER nWidth AS {&int} NO-UNDO. DEFINE INPUT PARAMETER nHeight AS {&int} NO-UNDO. DEFINE RETURN PARAMETER hbmp AS {&HANDLE} NO-UNDO. END PROCEDURE. PROCEDURE SelectObject EXTERNAL {&GDI} : DEFINE INPUT PARAMETER hdc AS {&HDC} NO-UNDO. DEFINE INPUT PARAMETER hgdiobj AS {&HGDIOBJ} NO-UNDO. DEFINE RETURN PARAMETER ReturnValue AS {&HGDIOBJ} NO-UNDO. END PROCEDURE. PROCEDURE StretchBlt EXTERNAL {&GDI} : DEFINE INPUT PARAMETER hdcDest AS {&HDC} NO-UNDO. DEFINE INPUT PARAMETER nXOriginDest AS {&int} NO-UNDO. DEFINE INPUT PARAMETER nYOriginDest AS {&int} NO-UNDO. DEFINE INPUT PARAMETER nWidthDest AS {&int} NO-UNDO. DEFINE INPUT PARAMETER nHeightDest AS {&int} NO-UNDO. DEFINE INPUT PARAMETER hdcSrc AS {&HDC} NO-UNDO. DEFINE INPUT PARAMETER nXOriginSrc AS {&int} NO-UNDO. DEFINE INPUT PARAMETER nYOriginSrc AS {&int} NO-UNDO. DEFINE INPUT PARAMETER nWidthSrc AS {&int} NO-UNDO. DEFINE INPUT PARAMETER nHeightSrc AS {&int} NO-UNDO. DEFINE INPUT PARAMETER dwRop AS {&DWORD} NO-UNDO. DEFINE RETURN PARAMETER ReturnValue AS {&BOOL} NO-UNDO. END PROCEDURE. PROCEDURE BitBlt EXTERNAL {&GDI} : DEFINE INPUT PARAMETER hdcDest AS {&HDC} NO-UNDO. DEFINE INPUT PARAMETER nXOriginDest AS {&int} NO-UNDO. DEFINE INPUT PARAMETER nYOriginDest AS {&int} NO-UNDO. DEFINE INPUT PARAMETER nWidthDest AS {&int} NO-UNDO. DEFINE INPUT PARAMETER nHeightDest AS {&int} NO-UNDO. DEFINE INPUT PARAMETER hdcSrc AS {&HDC} NO-UNDO. DEFINE INPUT PARAMETER nXOriginSrc AS {&int} NO-UNDO. DEFINE INPUT PARAMETER nYOriginSrc AS {&int} NO-UNDO. DEFINE INPUT PARAMETER dwRop AS {&DWORD} NO-UNDO. DEFINE RETURN PARAMETER ReturnValue AS {&BOOL} NO-UNDO. END PROCEDURE. PROCEDURE ReleaseDC EXTERNAL {&USER} : DEFINE INPUT PARAMETER hWinHdl AS {&HWND} NO-UNDO. DEFINE INPUT PARAMETER hdc AS {&HDC} NO-UNDO. DEFINE RETURN PARAMETER ReturnValue AS {&BOOL} NO-UNDO. END PROCEDURE. PROCEDURE DeleteDC EXTERNAL {&GDI} : DEFINE INPUT PARAMETER hdc AS {&HDC} NO-UNDO. DEFINE RETURN PARAMETER ReturnValue AS {&BOOL} NO-UNDO. END PROCEDURE. PROCEDURE DeleteObject EXTERNAL {&GDI} : DEFINE INPUT PARAMETER hObject AS {&HGDIOBJ} NO-UNDO. DEFINE RETURN PARAMETER ReturnValue AS {&BOOL} NO-UNDO. END PROCEDURE. PROCEDURE GetProfileStringA EXTERNAL "kernel32.dll": DEFINE INPUT PARAMETER in-appname AS CHARACTER NO-UNDO. DEFINE INPUT PARAMETER in-keyname AS CHARACTER NO-UNDO. DEFINE INPUT PARAMETER in-default AS CHARACTER NO-UNDO. DEFINE OUTPUT PARAMETER in-ret-str AS CHARACTER NO-UNDO. DEFINE INPUT PARAMETER in-n-size AS LONG NO-UNDO. DEFINE RETURN PARAMETER out-nuchr AS LONG NO-UNDO. END PROCEDURE. PROCEDURE OpenPrinterA EXTERNAL "winspool.drv": DEFINE INPUT PARAMETER in-prtname AS CHARACTER NO-UNDO. DEFINE OUTPUT PARAMETER out-hwnd AS LONG NO-UNDO. DEFINE INPUT PARAMETER in-def AS LONG NO-UNDO. END PROCEDURE.
Based on an idea by Richard Gordon.
The question was: how to get a list of available printers and set a new default printer. Here is a solution that uses the win.ini file.
It is recommended for a 32-bit application to use the EnumPrinters procedure to get a list of available printers.
One might think it's not modern to change the default printer by writing in win.ini but this is still the recommended way according to MS documentation.
{windows.i} DEFINE VARIABLE list-of-printers AS CHARACTER. DEFINE VARIABLE newdefault AS CHARACTER. DEFINE VARIABLE driver-and-port AS CHARACTER. RUN getkey (INPUT "devices", /* The section name */ INPUT "", /* The key name */ INPUT "win.ini", /* Name of ini file */ OUTPUT list-of-printers). /* Returned stuff */ /* you now have a comma separated list of printer names. Check it: */ /* message list-of-printers view-as alert-box. */ /* allow the user to pick one, suppose he picks the third entry: */ newdefault = ENTRY(3, list-of-printers). /* read driver and port for the new default printer */ RUN getkey (INPUT "devices", /* The section name */ INPUT newdefault, /* The key name */ INPUT "win.ini", /* Name of ini file */ OUTPUT driver-and-port). /* Returned stuff */ /* and write it back */ RUN putkey (INPUT "windows", INPUT "device", INPUT "win.ini", INPUT newdefault + "," + driver-and-port). /* check it: message session:printer-name view-as alert-box. */ RETURN. /* ------------- internal procedures ------------ */ PROCEDURE getkey : DEFINE INPUT PARAMETER i-section AS CHARACTER. DEFINE INPUT PARAMETER i-key AS CHARACTER. DEFINE INPUT PARAMETER i-filename AS CHARACTER. DEFINE OUTPUT PARAMETER o-value AS CHARACTER. DEFINE VARIABLE EntryPointer AS INTEGER NO-UNDO. DEFINE VARIABLE mem1 AS MEMPTR NO-UNDO. DEFINE VARIABLE mem2 AS MEMPTR NO-UNDO. DEFINE VARIABLE mem1size AS INTEGER NO-UNDO. DEFINE VARIABLE mem2size AS INTEGER NO-UNDO. DEFINE VARIABLE i AS INTEGER NO-UNDO. DEFINE VARIABLE cbReturnSize AS INTEGER NO-UNDO. ASSIGN SET-SIZE(mem1) = 4000 mem1size = 4000. IF i-key = "" THEN EntryPointer = 0. ELSE DO: /* Must fill memory with desired key name and EntryPointer must point to it */ ASSIGN SET-SIZE(mem2) = 128 mem2size = 128 EntryPointer = GET-POINTER-VALUE(mem2) PUT-STRING(mem2,1) = i-key. END. RUN getprivateprofilestring{&A} IN hpApi (i-section, EntryPointer, "", GET-POINTER-VALUE(mem1), INPUT mem1size, i-filename, OUTPUT cbReturnSize). /* if i-key was "", Windows will return a list of all keys in i-section. This list is not comma-separated but separated by CHR(0). Progress can not handle that easily so we'll now replace every 0 by a comma: */ DO i = 1 TO cbReturnSize: /* If this is a list convert null character into a comma to generate a csv type variable */ o-value = IF (GET-BYTE(mem1, i) = 0 AND i NE cbReturnSize) THEN o-value + "," ELSE o-value + CHR(GET-BYTE(mem1, i)). END. SET-SIZE(mem1) = 0. SET-SIZE(mem2) = 0. END PROCEDURE. PROCEDURE putkey : DEFINE INPUT PARAMETER i-section AS CHARACTER. DEFINE INPUT PARAMETER i-key AS CHARACTER. DEFINE INPUT PARAMETER i-filename AS CHARACTER. DEFINE INPUT PARAMETER i-value AS CHARACTER. DEFINE VARIABLE cbReturnSize AS INTEGER. RUN writeprivateprofilestring{&A} IN hpApi (i-section, i-key, i-value, i-filename, OUTPUT cbReturnSize ). END PROCEDURE.
By Nenad Orlovic
Nenad Orlovic writes: "Here is another example for same purpose that uses SetPrinter API function. I don't know which way is better. Both have the same problem, they can't change SESSION:PRINTER-NAME when it was already changed by SYSTEM-DIALOG PRINTER-SETUP command."
usage:
run SetDefaultPrinter("HP LaserJet 4L").
Additional note from Jurjen: Microsoft knowledgebase article Q140560 says this SetPrinter() call does not work on Windows NT4. Probably because NT4 does not support the PRINTER_INFO_5 structure. The good news is: Windows 2000 finally has the new API function we have all been waiting for: "SetDefaultPrinterA". The usage of this new API function is exactly similiar to the SetDefaultPrinter procedure by Nenad.
I did not try but it may be a good idea to call SendNotifyMessage(HWND_BROADCAST,WM_SETTINGCHANGE,0,"windows") to solve the SESSION:PRINTER-NAME problem.
/* Nenad Orlovic */ /* Change default printer example */ FUNCTION CheckBit RETURNS LOGICAL ( INPUT ip_dword AS INT, INPUT ip_bit AS INTEGER ) : /*------------------------------------------------------------------------------ Purpose: Checks if ip_bit in ip_dword is set Notes: ip_bit = 0,1,...,31 ------------------------------------------------------------------------------*/ DEFINE VARIABLE iBit AS INTEGER EXTENT 32 INIT [ 1 ,2 ,4 ,8 ,16 ,32 ,64 ,128, 256 ,512 ,1024 ,2048 ,4096 ,8192 ,16384 ,32768, 65536 ,131072 ,262144 ,524288 ,1048576 ,2097152 ,4194304 ,8388608, 16777216,33554432,67108864,134217728,268435456,536870912,1073741824,-2147483648 ] NO-UNDO. IF ip_bit > 31 OR ip_bit < 0 then return ?. ip_dword = TRUNC(ip_dword / iBit[ip_bit + 1],0). RETURN (ip_dword MOD 2 = 1). END FUNCTION. PROCEDURE SetDefaultPrinter: /*------------------------------------------------------------------------------ Purpose: Sets ip_printer to be windows default printer Parameters: input - Printer name Notes: ------------------------------------------------------------------------------*/ DEFINE INPUT PARAMETER ip_printer AS CHARACTER NO-UNDO. DEFINE VARIABLE iRet AS INTEGER NO-UNDO. DEFINE VARIABLE pPrinter AS MEMPTR NO-UNDO. DEFINE VARIABLE hPrinter AS INTEGER NO-UNDO. DEFINE VARIABLE iSize AS INTEGER NO-UNDO. DEFINE VARIABLE iAttr AS INTEGER NO-UNDO. DEFINE VARIABLE cMessage AS CHARACTER NO-UNDO. RUN OpenPrinterA (ip_printer,OUTPUT hPrinter,0, OUTPUT iRet). IF iRet = 0 THEN cMessage = "ERROR: OpenPrinterA " + ip_printer. ELSE DO: SET-SIZE(pPrinter) = 1. /* First call is only to get needed size for pPrinter */ RUN GetPrinterA(hPrinter,5,pPrinter,1,OUTPUT iSize,OUTPUT iRet). SET-SIZE(pPrinter) = 0. SET-SIZE(pPrinter) = iSize. RUN GetPrinterA(hPrinter,5,pPrinter,iSize,OUTPUT iSize,OUTPUT iRet). /* pPrinter points to PRINTER_INFO_5 */ IF iRet = 0 THEN cMessage = "ERROR: GetPrinterA " + ip_printer. ELSE DO: iAttr = GET-LONG(pPrinter,9). IF NOT CheckBit(iAttr,2) THEN DO: PUT-LONG(pPrinter,9) = iAttr + 4. /* 4 = PRINTER_ATTRIBUTE_DEFAULT */ RUN SetPrinterA(hPrinter,5,pPrinter,0,OUTPUT iRet). IF iRet = 0 THEN cMessage = "ERROR: SetPrinterA " + ip_printer. END. RUN ClosePrinter(hPrinter,OUTPUT iRet). IF iRet = 0 THEN cMessage = cMessage + "ERROR: ClosePrinter " + ip_printer. END. END. SET-SIZE(pPrinter) = 0. RETURN cMessage. END PROCEDURE. PROCEDURE OpenPrinterA EXTERNAL "WINSPOOL.DLL": DEFINE INPUT PARAMETER pPrinterName AS CHARACTER. DEFINE OUTPUT PARAMETER phPrinter AS LONG. DEFINE INPUT PARAMETER pDefault AS LONG. DEFINE RETURN PARAMETER X AS LONG. END PROCEDURE. PROCEDURE ClosePrinter EXTERNAL "WINSPOOL.DLL": DEFINE INPUT PARAMETER hPrinter AS LONG. DEFINE RETURN PARAMETER X AS LONG. END PROCEDURE. PROCEDURE SetPrinterA EXTERNAL "WINSPOOL.DLL": DEFINE INPUT PARAMETER hPrinter AS LONG. DEFINE INPUT PARAMETER Level AS LONG. DEFINE INPUT PARAMETER pPrinter AS MEMPTR. DEFINE INPUT PARAMETER COMMAND AS LONG. DEFINE RETURN PARAMETER X AS LONG. END PROCEDURE. PROCEDURE GetPrinterA EXTERNAL "WINSPOOL.DLL": DEFINE INPUT PARAMETER hPrinter AS LONG. DEFINE INPUT PARAMETER Level AS LONG. DEFINE INPUT PARAMETER pPrinter AS MEMPTR. DEFINE INPUT PARAMETER cbBuf AS LONG. DEFINE OUTPUT PARAMETER pcbNeeded AS LONG. DEFINE RETURN PARAMETER X AS LONG. END PROCEDURE.
by Johan Bouduin
The next procedure calls the properties dialog for any available printer.
The properties dialog is more or less part of the printer driver, so the appearance will be different for each printer.
It's only tested on Win95.
/******************************************************************** name : prg/de/de_prop.p author : Johan Bouduin date : 13/02/1998 purpose : get printers syntax : run prg/de/de_prop.p parameters : input PC_PRINTER_NAME as character input PH_CALLER as handle internal procedures : PROC_FREEMEM : de-allocate reserved memory internal functions : external functions : OpenPrinter "winspool.drv" ClosePrinter "winspool.drv" PrinterProperties "winspool.drv" modifications : Jurjen moved external procedures to windows.i/p *********************************************************************/ /***** Parameter definitions ****************************************/ DEFINE INPUT PARAMETER PC_PRINTER_NAME AS CHARACTER NO-UNDO. DEFINE INPUT PARAMETER PH_CALLER AS HANDLE NO-UNDO. /***** Variable definitions *****************************************/ DEFINE VARIABLE VM_PRINTER_HANDLE AS MEMPTR NO-UNDO. DEFINE VARIABLE VI_RETURN_VALUE AS INTEGER NO-UNDO. DEFINE VARIABLE VM_PRINTER_NAME AS MEMPTR NO-UNDO. DEFINE VARIABLE VM_DEFAULTS AS MEMPTR NO-UNDO. /***** External procedures ******************************************/ {windows.i} /***** Main-block ***************************************************/ DO: /***** get a printer handle ***************************************/ SET-SIZE(VM_PRINTER_HANDLE) = 4. RUN OpenPrinter{&A} IN hpApi( INPUT PC_PRINTER_NAME, INPUT GET-POINTER-VALUE(VM_PRINTER_HANDLE), INPUT GET-POINTER-VALUE(VM_DEFAULTS), OUTPUT VI_RETURN_VALUE). IF VI_RETURN_VALUE EQ 0 THEN DO: MESSAGE "An error occurred while trying to open the printer" VIEW-AS ALERT-BOX. RETURN "not open". END. /***** call printerproperties *************************************/ RUN PrinterProperties IN hpApi( INPUT PH_CALLER:HWND, INPUT GET-LONG(VM_PRINTER_HANDLE,1), OUTPUT VI_RETURN_VALUE). /***** close the printer ******************************************/ RUN ClosePrinter IN hpApi( INPUT GET-LONG(VM_PRINTER_HANDLE,1), OUTPUT VI_RETURN_VALUE). IF VI_RETURN_VALUE EQ 0 THEN DO: MESSAGE "An error occurred while trying to close the printer" VIEW-AS ALERT-BOX. RETURN "not closed". END. RUN PROC_FREEMEM. RETURN. END. /***** internal procedures ******************************************/ PROCEDURE PROC_FREEMEM: SET-SIZE(VM_PRINTER_HANDLE) = 0. END.
Suppose you have used the EnumPrinters function to populate a selection list widget (SELECT-1) with names of all available printers:
On choose of the button do:
run prg/de/de_prop.p (select-1:screen-value, c-win:handle).
The first parameter is the name of the printer, the second parameter is the Progress widget handle of the current window. This window will be the parent window for the property dialog; you will not be able to activate c-win while the property dialog is opened.