Board index » cppbuilder » More on Dialogs

More on Dialogs


2007-06-27 09:02:40 PM
cppbuilder46
TPrinterSetupDialog and TPrintDialog will set the TPrinter's
PrinterIndex. Other dialogs such as PageSetupDlg, PrintDlg and
PrintDlgEx do not (duh ... they're win32 APIs) but they do
return a DEVMODE and DEVNAMES struct that one can use to get
the name of the printer but I have a problem with both structs.
The DEVMODE has a dmDeviceName member which is defined as:
BCHAR dmDeviceName[CCHDEVICENAME];
Specifies the the "friendly" name of the printer; for
example, "PCL/HP LaserJet" in the case of PCL/HP
LaserJet? This string is unique among device drivers.
Note that this name may be truncated to fit in the
dmDeviceName array.
and CCHDEVICENAME is 32. Similarly, the DEVNAMES related
member is also 32 bytes long.
I went back and looked at some old BCB5 code to verify that I
was recalling correctly that 32 bytes isn't enough for the
names listed in TPrinter's Printers and I found where I had
noted a change from saving 50 characters for the printer name
to 100 because TPrinter::Printers::IndexOf was failing.
So, How does one get the same names that TPrinter uses?
~ JD
 
 

Re:More on Dialogs

JD wrote:
Quote
TPrinterSetupDialog and TPrintDialog will set the TPrinter's
PrinterIndex. Other dialogs such as PageSetupDlg, PrintDlg and
PrintDlgEx do not (duh ... they're win32 APIs) but they do
So, How does one get the same names that TPrinter uses?
I'm guessing this would be easiest...
// IPrintDialogServices::GetCurrentPrinterName(pPrinterName, pcchSize)
// Returns the printer name for the currently selected printer.
msdn2.microsoft.com/en-us/library/ms646897.aspx
Win2000 required
There is a pName in each of these
PRINTER_INFO_1
PRINTER_INFO_2
PRINTER_INFO_3
PRINTER_INFO_4
PRINTER_INFO_5
You could maybe use EnumPrinters() and strncmp()
 

Re:More on Dialogs

"JD" < XXXX@XXXXX.COM >wrote in message
Quote
TPrinterSetupDialog and TPrintDialog will set the TPrinter's
PrinterIndex. Other dialogs such as PageSetupDlg, PrintDlg
and PrintDlgEx do not (duh ... they're win32 APIs) but they
do return a DEVMODE and DEVNAMES struct that one
can use to get the name of the printer but I have a problem
with both structs.
If you look at the source code for TPrinterSetupDialog and
TPrintDialog, you can see how they both use the DEVMODE and DEVNAMES
structures to set the PrinterIndex. Both dialogs call the
TPrinter::SetPrinter() method. For example (translated from the
dialog source code):
void __fastcall SetPrinter(HANDLE DeviceMode, HANDLE DeviceNames)
{
DEVNAMES *DevNames = (DEVNAMES*) GlobalLock(DeviceNames);
try
{
Printer()->SetPrinter(
((char*)DevNames) + DevNames->wDeviceOffset,
((char*)DevNames) + DevNames->wDriverOffset,
((char*)DevNames) + DevNames->wOutputOffset,
(int)DeviceMode);
}
__finally
{
GlobalUnlock(DeviceNames);
GlobalFree(DeviceNames);
// TPrinter takes ownership of the DeviceMode so do not
free it...
}
}
Gambit
 

{smallsort}

Re:More on Dialogs

"Remy Lebeau \(TeamB\)" < XXXX@XXXXX.COM >wrote:
Quote

[...] Both dialogs call the TPrinter::SetPrinter() method.
For example (translated from the dialog source code):
That is what I'm doing but that doesn't address my original
concern about the length of the printer name. It is my current
understanding that one must first set the PrinterIndex before
one calls SetPrinter.
Simple enough if one uses Printers::IndexOf method but that
will only work if the string is an exact match and the printer
names in Printers are not limited to 32 bytes. Now, the docs
for DEVNAMES says it's 32 bytes and it must match DEVMODE
exactly but I just found this in the docs for PRINTDLGEX:
Note that the dmDeviceName member of the DEVMODE structure
also specifies a printer name. However, dmDeviceName is
limited to 32 characters, and the wDeviceOffset name is
not.
Obviousely there's an error there and I assume that the error
is that dmDeviceName and the string pointed to by wDeviceOffset
need not match exactly so I can use wDeviceOffset with IndexOf.
I just don't have a printer installed that's longer than 32
bytes to test it. Is there a way for me to lengthen a printer's
name?
Quote
// TPrinter takes ownership of the DeviceMode so do not free it...
Don't I need to dispose of the TPrinter's current DEVMODE? For
example:
if( PageSetupDlg(&psd) )
{
unsigned int hDevMode;
char ADevice[MAX_PATH], ADriver[MAX_PATH], APort[MAX_PATH];
Printer()->PrinterIndex = Printer()->Printers->IndexOf(
(char*)pDevNames + pDevNames->wDeviceOffset );
Printer()->GetPrinter( ADevice, ADriver, APort, hDevMode );
DEVNAMES *pDevNames = (DEVNAMES *) GlobalLock( psd.hDevNames );
Printer()->SetPrinter( (char*)pDevNames +
pDevNames->wDeviceOffset,
(char*)pDevNames +
pDevNames->wDriverOffset,
(char*)pDevNames +
pDevNames->wOutputOffset,
(int)pds.hDevMode);
GlobalFree( (void*)hDevMode );
GlobalFree( psd.hDevNames );
}
Thanks.
~ JD
 

Re:More on Dialogs

Bob Gonder < XXXX@XXXXX.COM >wrote:
Quote

I'm guessing this would be easiest...
// IPrintDialogServices::GetCurrentPrinterName(pPrinterName, pcchSize)
// Returns the printer name for the currently selected printer.
I had found that and was considering it when I found some docs
that contradicted the 32 byte limit so I was worried about
nothing. See my reply to Gambit for more.
Thanks.
~ JD
 

Re:More on Dialogs

"JD" < XXXX@XXXXX.COM >wrote in message
Quote
It is my current understanding that one must first set the
PrinterIndex before one calls SetPrinter.
Then your understanding has been misled. TPrinter::SetPrinter()
assigns the PrinterIndex (amongst other things) based on the
structures that are passed to it. The current TPrinter::PrinterIndex
value has no effect whatsoever on TPrinter::SetPrinter().
Quote
Simple enough if one uses Printers::IndexOf method but
that will only work if the string is an exact match
TPrinter::SetPrinter() performs a comparison of the original printer
information that is provided by the OS. It does not perform a
comparison of the string values that are stored (and potentially
reformatted) in the TPrinter::Printers list. On the other hand, the
TPrinter::Printers list is a plain TStringList, and thus its IndexOf()
method does an exact string match on the TStringList values only, not
the original printer information. They are not the same search
operation.
Quote
Now, the docs for DEVNAMES says it's 32 bytes and it
must match DEVMODE exactly but I just found this in the
docs for PRINTDLGEX:
The VCL dialogs do not use PRINTDLGEX.
Quote
Note that the dmDeviceName member of the DEVMODE structure
also specifies a printer name. However, dmDeviceName is
limited to 32 characters, and the wDeviceOffset name is
not.

Obviousely there's an error there
There is not. The dmDeviceName member will contain the truncated copy
of a name if the original is longer than 32 characters. Your original
post in this thread even quoted as much:
"Note that this name may be truncated to fit in the dmDeviceName
array."
The wDeviceOffset name, on the other hand, will be the full name.
Quote
I assume that the error is that dmDeviceName and the string pointed
to by wDeviceOffset need not match exactly
Yes, they do match - but only up to 32 characters. But TPrinter never
uses dmDeviceName to begin with, so it should never be limited to 32
characters.
Quote
so I can use wDeviceOffset with IndexOf.
Only on NT-based systems. On Win9x-based systems, the
TPrinter::Printers list does NOT reflect the wDeviceOffset name by
itself. On those systems, TPrinter::SetPrinter() merges the device
and port names together into a single string, and then stores that
into the TPrinters::Printers list. That entire string is what
IndexOf() will be comparing. You are not taking that into account
when you use IndexOf(). If you really want to know the index of a
given printer name regardless of the OS version, then you would have
to ignore IndexOf() altogether and do your own substring scanning of
the list manually, ie:
#include <StrUtils.hpp>
AnsiString device = ((char*)pDevNames) + pDevNames->wDeviceOffset;
TStrings *list = Printer()->Printers;
int index = -1;
for(int i = 0; i < list->Count; ++i)
{
if( AnsiStartsText(device, list->Strings[i]) )
{
index = i;
break;
}
}
Quote
I just don't have a printer installed that's longer than 32
bytes to test it. Is there a way for me to lengthen a printer's
name?
Have you tried simply renaming it in the Control Panel's Printers
applet? Depending on your OS version, it may or may not allow you to
do that directly.
Quote
Don't I need to dispose of the TPrinter's current DEVMODE?
No. That is handled automatically inside of TPrinter::SetPrinter().
Quote
Printer()->PrinterIndex = Printer()->Printers->IndexOf(
(char*)pDevNames + pDevNames->wDeviceOffset );
Use SetPrinter() instead of assigning PrinterIndex manually. Let
TPrinter do all the work for you.
Quote
GlobalFree( (void*)hDevMode );
DO NOT DO THAT!!!! You are freeing memory that TPrinter owns and uses
internally.
Gambit
 

Re:More on Dialogs

"Remy Lebeau \(TeamB\)" < XXXX@XXXXX.COM >wrote:
Quote

>It is my current understanding that one must first set the
>PrinterIndex before one calls SetPrinter.

Then your understanding has been misled.
From a post by Damon:
if( PageSetupDlg(&psd) )
{
HANDLE HMem;
char device[MAX_PATH], driver[MAX_PATH], port[MAX_PATH];
Printer()->GetPrinter(device, driver, port, (int)HMem);
Printer()->SetPrinter(device, driver, port, (int)psd.hDevMode);
// do some printing...
GlobalFree(HMem);
GlobalFree(psd.hDevNames);
}
and that wasn't working until I added setting the PrinterIndex.
Quote
TPrinter::SetPrinter() assigns the PrinterIndex (amongst
other things) based on the structures that are passed to
it.
Thanks. It's working fine now and the TDialogs respect the
currently selected printer even if an API dialog selected it
but I'm still working on getting the API dialogs to respect
it (they open with the default printer).
Quote
>[...] but I just found this in the docs for PRINTDLGEX:

The VCL dialogs do not use PRINTDLGEX.
True, but they do use DEVNAMES.
Quote
>Obviousely there's an error there

There is not.
The docs for DEVNAMES wDeviceOffset says (in part):
[...] (maximum of 32 bytes including the null) [...]
This string must be identical to the dmDeviceName member
of the DEVMODE structure.
However, not only does PRINTDLGEX contradict that, so does
PAGESETUPDLG:
If both hDevNames and hDevMode have valid handles and the
printer name specified by the wDeviceOffset member of the
DEVNAMES structure is not the same as the name specified
by the dmDeviceName member of the DEVMODE structure, the
system uses the name specified by wDeviceOffset by default.
They can't both be correct (unless there are different rules
for input and output) but that's a moot point now. It just
added to the confusion.
Quote
>Don't I need to dispose of the TPrinter's current DEVMODE?

No. That is handled automatically inside of TPrinter::SetPrinter().
Good to know.
Thanks.
~ JD
 

Re:More on Dialogs

"JD" < XXXX@XXXXX.COM >wrote in message
Quote
Printer()->GetPrinter(device, driver, port, (int)HMem);
That is retreiving the current information for the printer specified
by the TPrinter::PrinterIndex property.
However, the last paramter is wrong. The HMem variable is declared as
a HANDLE. GetPrinter() expects a reference to an int instead. By
casting a HANDLE variable to an int like that, it is actually passing
a temporary variable to GetPrinter(). The compiler even warns as
much. So GetPrinter() is going to assign a value to that temporary
instead of to the original HMem variable. HMem will remain
uninitialized, which will be very bad when that variable is passed to
GlobalFree() later on.
Quote
Printer()->SetPrinter(device, driver, port,
(int)psd.hDevMode);
That is telling TPrinter to use the same printer device that it was
already set to, rather than using the actual printer that was selected
in the dialog. So the code is basically just updating the
TPrinter::Capabilities property without updating the
TPrinter::PrinterIndex property.
Quote
GlobalFree(HMem);
That is undefined behavior since HMem is uninitialized. However, even
if HMem had been filled in properly, that line would still be
problematic. It is freeing the DEVMODE that TPrinter::SetPrinter()
had already freed internally.
Quote
that wasn't working until I added setting the PrinterIndex.
It wasn't working because it was using TPrinter incorrectly to begin
with. The code should look more like the following:
if( ::PageSetupDlg(&psd) )
{
DEVNAMES *pDevNames = (DEVNAMES*) GlobalLock(psd.hDevNames);
char *pDriver = ((char*)pDevNames) + pDevNames->wDriverOffset;
char *pDevice = ((char*)pDevNames) + pDevNames->wDeviceOffset;
char *pPort = ((char*)pDevNames) + pDevNames->wOutputOffset;
Printer()->SetPrinter(pDevice, pDriver, pPort,
(int)psd.hDevMode);
GlobalUnlock(psd.hDevNames);
GlobalFree(psd.hDevNames);
// do some printing...
}
Quote
The docs for DEVNAMES wDeviceOffset says (in part):
You are looking at old documentation There is no size limitation
mentioned in the current documentation.
Gambit