Saturday, March 15, 2008

Getting PaperSize ID programmatically

In my previous tips, I've shown you how to add a custom paper size programmatically. Then you can modify the report and use the Page Setup to set your report to the custom paper size you have just created. VFP will automatically save the PaperSize ID according the ID when it is created in your PC. Now, when you add a custom paper in some PC, sometimes you can get a different PaperSize ID, because maybe it already have another custom paper size. So you have to hack the FRX to reflect the PaperSize ID in that computer. Here is how to get the PaperSize ID programmatically.

- As usual, cut & paste the code below into PRG, then run "beautify" to make the code readable
CToBin()  function shown in the code is an Enhancements function in VFP9. It doesn't work on earliear version. For VFP8 and lower, use Str2Num UDF to replace the function. You can find the UDF on Universal Thread or many other VFP forums.

** Updated: July 07, 2008
** Bug fixed by: Julio Veloz

#Define DC_PAPERS 2
#Define DC_PAPERS_Size 2
#Define DC_PAPERNAMES_Size 64
Declare Long DeviceCapabilities in WinSpool.drv ;
String cPrinterName, String cPort, Short nCapFlags, ;
String @O_cBuffer, Long pDevMode

Local array la_Printer[1]
Local ln_Row, ln_Result, ln_I, ln_Index
Local lc_PrinterName, lc_Buffer
Local lc_FindPaperName, lc_PaperName, lc_PaperSizeID
lc_PrinterName = set( 'Printer', 2 ) && Get default windows printer
= APrinters( la_Printer )
ln_Row = AScan( la_Printer, lc_PrinterName, 1, 0, 0, 9 )
ln_Result = DeviceCapabilities( la_Printer[ ln_Row, 1 ], ;
la_Printer[ ln_Row, 2 ], DC_PAPERNAMES, 0, 0 )
If (ln_Result > 0)
ln_Index = -1
lc_FindPaperName = upper( 'MyCustom - Half A4' )
lc_Buffer = replicate( chr(0), ln_Result * DC_PAPERNAMES_Size )
ln_Result = DeviceCapabilities( la_Printer[ ln_Row, 1 ], ;
la_Printer[ ln_Row, 2 ], DC_PAPERNAMES, @lc_Buffer, 0 )
For ln_I = 0 to ln_Result-1
lc_PaperName = upper( substr( lc_Buffer, (ln_I * DC_PAPERNAMES_Size )+1, ;
If (lc_FindPaperName $ lc_PaperName)
ln_Index = ln_I
If (ln_Index != -1)
** Paper Name found, Get The PaperSize ID

ln_Result = DeviceCapabilities( la_Printer[ ln_Row, 1 ], ;
la_Printer[ ln_Row, 2 ], DC_PAPERS, 0, 0 )
If (ln_Result > 0)
lc_Buffer = replicate( chr(0), ln_Result * DC_PAPERS_Size )
ln_Result = DeviceCapabilities( la_Printer[ ln_Row, 1 ], ;
la_Printer[ ln_Row, 2 ], DC_PAPERS, @lc_Buffer, 0 )
lc_PaperSizeID = substr( lc_Buffer, (ln_Index * DC_PAPERS_Size )+1, DC_PAPERS_Size )
? 'PaperSize ID for "' + lc_FindPaperName + '" is', CToBin( lc_PaperSizeID, '2rs' )


Happy coding!


Anonymous said...

Thank you very much Herman, very usefull for me this code a the previous one, but I have another question, can I create a form in a network printer? the previous code doesn't work with a network printer.

Herman Tan said...

I never try for the network printer, and I don't have the network environment here to try it. But, maybe you can try to capture the network printer, redirect it into LPT1

Anonymous said...

Herman, It look's like the code is not always returning the apropiate id for the printer, in certain machines works fine and in others not, I'm been having this problem with my customers inclusive with users who has only one matrix printer connected directly to their machines, I traced the code and the line lc_PaperSizeID = upper( substr( lc_Buffer, (ln_Index * DC_PAPERS_Size )+1, ;
DC_PAPERS_Size )) not returns the code that vfp assigns when you modi fy with modi report directly.

Herman Tan said...

Hi, thanks for your feedback!
I'll try to look into it, if you could provide me more details. Such as what OS the PC is using? What is the return value from the code "asc( lc_PaperSizeID )", and what is the id that VFP assigned in the report?

Anonymous said...

Hi Herman:

After some time of facing this problem, finally I found that, in the machines where the code doesn't work, if I take off the upper function from the line
lc_PaperSizeID = upper( substr( lc_Buffer, (ln_Index * DC_PAPERS_Size )+1, DC_PAPERS_Size )) the code works correctly, this is perfect! but now I have the problem of when to apply this, because I don't find any difference in OS or in service pack between machines where the code works with the upper function and machines where the code works without the upper function.

Herman Tan said...


You're right! It should not use UPPER() function at all. Also the ASC() function should be CTOBIN(). I updated the code, please take a look.
BTW, please leave your name so I can put it in my updated code.

Julio Veloz said...

Ok Herman, thanks you very much! this would be very usefull for me.

My name is Julio Veloz.


Herman Tan said...

You are welcome Julio and thank you for being so patient to fixing the bugs!

Romoon said...

VB.NET (version)

Declare Auto Function DeviceCapabilities Lib "winspool.drv" _
(ByVal pDeviceName As String, ByVal pPort As String, ByVal iIndex As Short, _
ByVal pOutput As IntPtr, ByVal pDevMode As Integer) As Integer

Dim output As String
Dim nRes As Integer
Dim pAddr As IntPtr
Dim PrintDoc As System.Drawing.Printing.PrintDocument = New System.Drawing.Printing.PrintDocument
Dim PrinterName As String = PrintDoc.PrinterSettings.PrinterName

nRes = DeviceCapabilities(PrinterName, "LPT1", DCFlags.DC_PAPERS, Nothing, 0)
pAddr = Marshal.AllocHGlobal(nRes * 20)
nRes = DeviceCapabilities(PrinterName, "LPT1", DCFlags.DC_PAPERS, pAddr, 0)

Dim pid(nRes) As Short
Dim offset As Integer
offset = pAddr.ToInt32()

Dim i As Integer
For i = 0 To (nRes - 1)
pid(i) = Marshal.ReadInt16(New IntPtr(offset + i * 2))
output += (pid(i).ToString & _
vbTab & PrintDoc.PrinterSettings.PaperSizes(i).PaperName & _
vbTab & PrintDoc.PrinterSettings.PaperSizes(i).Kind.ToString & vbCrLf)