José, if you're bored...

Started by Paul Squires, August 02, 2018, 09:49:19 PM

Previous topic - Next topic

José Roca

#105
I have added some new methods:


DECLARE SUB GetMarginPixels (BYREF nLeft AS LONG, BYREF nTop AS LONG, BYREF nRight AS LONG, BYREF nBottom AS LONG)
DECLARE SUB GetMarginUnits (BYREF nLeft AS LONG, BYREF nTop AS LONG, BYREF nRight AS LONG, BYREF nBottom AS LONG)
DECLARE FUNCTION PixelsToUnitsX (BYVAL pix AS LONG) AS LONG
DECLARE FUNCTION PixelsToUnitsY (BYVAL pix AS LONG) AS LONG
DECLARE FUNCTION UnitsToPixelsX (BYVAL units AS LONG) AS LONG
DECLARE FUNCTION UnitsToPixelsY (BYVAL units AS LONG) AS LONG


Chris Maher

No success yet changing properties with an HP laser printer but I will keep trying.

The document name seen in the printer queue is a little strange but the print was ok (un-modified A4) see attached.

José Roca

> The document name seen in the printer queue is a little strange

Use

DIM wszDocName AS WSTRING * 260 = "GdiplusPrint"
docInfo.lpszDocName = VARPTR(wszDocName)

José Roca

#108
> No success yet changing properties with an HP laser printer but I will keep trying.

Maybe a problem of access rights? It works with the driver of my broken printer. To be able to change values we have to open the printer with read-write rights. Try to play changing values in pd.DesiredAccess in the SetprinterInfo method. See possible values in https://msdn.microsoft.com/en-us/library/windows/desktop/dd162751(v=vs.85).aspx

Using PRINTER_ACCESS_USE or passing a null pointer (that is what I'm doing in the GetDocumentProperties method, that does not need write rights) will always succeed, but then you can't change any value.


   ' // Start by opening the printer
   DIM hPrinter AS HANDLE
   DIM pd AS PRINTER_DEFAULTSW
   pd.DesiredAccess = PRINTER_ALL_ACCESS
   IF OpenPrinterW(m_wszPrinterName, @hPrinter, @pd) = FALSE THEN RETURN FALSE


Lets see if we find somebody else to try.

José Roca

#109
One way to retrieve the paper size and margins set in the Page Setup Dialog is


DIM psd AS PAGESETUPDLGW
psd.lStructSize = SIZEOF(PAGESETUPDLGW)
psd.Flags = PSD_RETURNDEFAULT
IF PageSetupDlgW(@psd) THEN
   print psd.ptPaperSize.x, psd.ptPaperSize.y
   print psd.rtMinMargin.Left, psd.rtMinMargin.Top, psd.rtMinMargin.Right, psd.rtMinMargin.Bottom
   print psd.rtMargin.Left, psd.rtMargin.Top, psd.rtMargin.Right, psd.rtMargin.Bottom
   IF psd.hDevMode THEN GlobalFree(psd.hDevMode)
   IF psd.hDevNames THEN GlobalFree(psd.hDevNames)
END IF


Probably I could provide my own DEVMODE and DEVNAMES structures, retrieve the handle of the OK button of the PageSetup dialog and send a click message to it, but anyway, these values are only meant as a reference to the programmer to know the wishes of the user and adjust the printing according them, so I don't see any purpose in setting them programatically.

BTW I have modified the PageSetup method to free the hDevMode and hDevNames handles.


' ========================================================================================
' Displays a Page Setup dialog box that enables the user to specify the attributes of a
' printed page. These attributes include the paper size and source, the page orientation
' (portrait or landscape), and the width of the page margins.
' ========================================================================================
PRIVATE FUNCTION CPrint.PageSetup (BYVAL hwndOwner AS HWND = NULL) AS BOOLEAN
   DIM psd AS PAGESETUPDLGW
   psd.lStructSize = SIZEOF(PAGESETUPDLGW)
   psd.hwndOwner = hwndOwner
   IF PageSetupDlgW(@psd) THEN
      IF psd.hDevMode THEN GlobalFree(psd.hDevMode)
      IF psd.hDevNames THEN GlobalFree(psd.hDevNames)
      RETURN TRUE
   END IF
   RETURN FALSE
END FUNCTION
' ========================================================================================


José Roca

#110
I have added the following methods:


DECLARE FUNCTION GetDefaultPrinter () AS CWSTR
DECLARE FUNCTION GetDefaultPrinterDriver () AS CWSTR
DECLARE FUNCTION GetDefaultPrinterPort () AS CWSTR
DECLARE FUNCTION ChoosePrinter (BYVAL hwndOwner AS HWND = NULL) AS BOOLEAN
DECLARE FUNCTION EnumPrinterNames () AS CWSTR


and modified the AttachPrinter method to make the wszPrinterName optional. If you don pass a printer name or you pass "", the default printer will be attached.

Chris Maher

The document name in the printer queue is now correct.

I have installed an Epson XP-245 inkjet printer and as you can see from the attached, the properties appear to be changed, but this was not reflected in the printed output.

I did not change any rights before trying this printer. I will now see if I can change them and if that makes a difference - I am using Win10 Pro but I will also check Win7 Pro.

>I don't see any purpose in setting them programatically.
Not for Home or Office but where the 'User' is an 'Operator', for Graphics, In-Store, Mail Merge or Industrial use, then the printer may be set up in a production queue with the Operator being asked to load and confirm substrate before the next job is run. All settings including XY offsets with step and repeat would be programmatic. Perhaps this area may be a little off-topic for now..

All the new methods make this class very comprehensive. I like the default printer option as there may be 150 printer workstations in a warehouse packing area, that all do the same job, but may be of different types and connections, and this makes it simple to code and operate.

José Roca

> [...] but this was not reflected in the printed output.

The output will be what you have drawn. The printer isn't going to change the contents. Your drawing routines must read the setting values and adjust the drawing according them, that is, depending on the paper size, you will have more or less room to draw, etc. CPrint only simplifies the task of attaching a printer and get or set the setting values for reference. It doesn't provide drawing routines. These must be written separately.

Chris Maher

Yes I understand that. My design uses the page print area metrics with an additional line to mark the top left of the design - see attached.

I have had the same results on both Win10 and Win7. What I see with the Microsoft PDF Printer (inkjet jamming), only with the permissions pd.DesiredAccess = PRINTER_ACCESS_USE, is that it updates the user default for orientation and that appears on the next run but not this one.

I was assuming that if I made changes before StartDocW(hdcPrint, @docInfo) that they would have been updated by the DocumentProperties function merging the new settings prior to the page graphic object being created.
I was thinking that the settings would be local to the application as detailed here:
https://msdn.microsoft.com/en-gb/e89a2f6f-2bac-4369-b526-f8e15028698b
but my ability to understand the full code defeated me.

I guess we need someone else to give it a try.

José Roca

#114
Quote
I have had the same results on both Win10 and Win7. What I see with the Microsoft PDF Printer (inkjet jamming), only with the permissions pd.DesiredAccess = PRINTER_ACCESS_USE, is that it updates the user default for orientation and that appears on the next run but not this one.

In fact, the call to SetPrinterW fails with error 5 (Access Denied), although it ends changing the settings. This is a strange an unrealiable behavior.

José Roca

#115
BTW I have added documentation for the regular expressions class.

https://github.com/JoseRoca/WinFBX/blob/master/docs/String%20Management/CRegExp%20Class.md

I have tried hard to make it a bit understandable by adding many examples and an introduction, but it is a difficult subject.

José Roca

Quote
I was assuming that if I made changes before StartDocW(hdcPrint, @docInfo) that they would have been updated by the DocumentProperties function merging the new settings prior to the page graphic object being created.
I was thinking that the settings would be local to the application as detailed here:
https://msdn.microsoft.com/en-gb/e89a2f6f-2bac-4369-b526-f8e15028698b
but my ability to understand the full code defeated me.

I have made some modifications, but I can't see how the technique detailed in your link can work. If I make modifications to the DocumentProperties but I don't call SetPrinterW, when I will call DocumentProperties to retrieve a setting it will return it unchanged. I think that the title is misleading.


José Roca

#117
Somebody as asked in the Free Basic forum if there was an API function to know if the Windows operating system was 32 or 64 bit. It doesn't exist, but as WMI is a great technology to retrieve all kind of hardware information, I have used it to write the following function:


' ========================================================================================
' Returns the address width of the operating system.
' On a 32-bit operating system, the value is 32 and on a 64-bit operating system it is 64.
' ========================================================================================
PRIVATE FUNCTION AfxGetWindowsBitness (BYREF wszServerName AS WSTRING = ".") AS LONG
   DIM pServices AS CWmiServices = $"winmgmts:{impersonationLevel=impersonate}!\\" & wszServerName & $"\root\cimv2"
   IF pServices.ServicesPtr = NULL THEN RETURN 0
   pServices.ExecQuery("SELECT AddressWidth FROM Win32_Processor")
   pServices.GetNamedProperties
   RETURN pServices.PropValue("AddressWidth").ValLong
END FUNCTION
' ========================================================================================


The beauty of this technique is that just changing the WMI class name and the name of the property, we can easily retrieve hardware information for which an API function does not exist.

It uses the CWmiServices class located in CWmiDisp.incm that I have documented today in my GutHub repository:
https://github.com/JoseRoca/WinFBX/blob/master/docs/COM/CWmiDisp%20Class.md

Marc Pons

Quote from: José Roca on August 23, 2018, 01:22:02 AM
Somebody as asked in the Free Basic forum if there was an API function to know if the Windows operating system was 32 or 64 bit.

It is also possible to know via IsWow64Process from kernel32.dll

#include "windows.bi"

Function OsBits() as Long
    #IfDef __FB_64BIT__
        Return 64                                'if compiled under 64, its normal to be 64
    #Else
        Dim handle as HANDLE
        Dim ret1   as Long       
        ' Assume initially that this is not a Wow64 process     
        ' and check  if IsWow64Process function exists
        handle = GetProcAddress(GetModuleHandle( "kernel32") , "IsWow64Process")       
        If handle > 0 Then                       ' IsWow64Process function exists
            ' Now use the function to determine if we are running under Wow64
            IsWow64Process(GetCurrentProcess() , @ret1)
        End If
        If ret1 Then Return 64
        Return 32
    #EndIf
End Function

José Roca

Yes, I also found this hack in the web, but as I was documenting my WMI class I wanted to use it for the task. As I said, the beauty of WMI is that you can use similar code for other tasks. For example, changing "AddressWidth" with "NumberOfCores" in my posted function will return the number of cores for the current instance of the processor.