Member level visibility
Hi
Generally I find ReportBuilder easy to work with, however, I have run into difficulties a few times now, when trying to tweak minor things.
This has generally been as a result of a very cautious approach to the visibility of class members.
Although it is possible for me to modify the supplied code, I would only do that as an interim measure or as a last resort.
I was hoping that you might be able to make changes to TppPrinterList along the following lines.
1. Promote some of the member variables and functions from Private to Protected.
2. Change the functions as marked from static to virtual.
Regards
David
{@TppPrinterList }
//*** Change the visibility of some functions to protected and make the following virtual ..
// BuildPrinterList, FreePrinterList, Refresh
// GetPrinterNameForDevice, GetSystemDefaultPrinterName
TppPrinterList = class(TObject)
private
function GetSystemDefaultPrinterNameViaGetProfileString: String;
protected //***
FPrinters: TStrings;
FDefaultPrinterName: String;
FLock: TCriticalSection;
procedure BuildPrinterList; virtual; //***
procedure FreePrinterList; virtual; //***
function GetCount: Integer;
function GetPrinterInfo(aPrinterName: String): TppPrinterInfo;
function GetPrinterName(aIndex: Integer): String;
procedure ParseBuffer(var aBuffer: PChar; aBufSize: Longint; aResultStrings: TStrings);
public
constructor Create;
destructor Destroy; override;
function GetPrinterNameForDevice(aDevice: String): String; virtual; //***
function GetSystemDefaultPrinterName: String; virtual; //***
procedure Refresh;
property Count: Integer read GetCount;
property DefaultPrinterName: String read FDefaultPrinterName;
property PrinterNames: TStrings read FPrinters;
property PrinterName[Index: Integer]: String read GetPrinterName;
property PrinterInfo[aPrinterName: String]: TppPrinterInfo read GetPrinterInfo;
end; {class, TppPrinterList}
Generally I find ReportBuilder easy to work with, however, I have run into difficulties a few times now, when trying to tweak minor things.
This has generally been as a result of a very cautious approach to the visibility of class members.
Although it is possible for me to modify the supplied code, I would only do that as an interim measure or as a last resort.
I was hoping that you might be able to make changes to TppPrinterList along the following lines.
1. Promote some of the member variables and functions from Private to Protected.
2. Change the functions as marked from static to virtual.
Regards
David
{@TppPrinterList }
//*** Change the visibility of some functions to protected and make the following virtual ..
// BuildPrinterList, FreePrinterList, Refresh
// GetPrinterNameForDevice, GetSystemDefaultPrinterName
TppPrinterList = class(TObject)
private
function GetSystemDefaultPrinterNameViaGetProfileString: String;
protected //***
FPrinters: TStrings;
FDefaultPrinterName: String;
FLock: TCriticalSection;
procedure BuildPrinterList; virtual; //***
procedure FreePrinterList; virtual; //***
function GetCount: Integer;
function GetPrinterInfo(aPrinterName: String): TppPrinterInfo;
function GetPrinterName(aIndex: Integer): String;
procedure ParseBuffer(var aBuffer: PChar; aBufSize: Longint; aResultStrings: TStrings);
public
constructor Create;
destructor Destroy; override;
function GetPrinterNameForDevice(aDevice: String): String; virtual; //***
function GetSystemDefaultPrinterName: String; virtual; //***
procedure Refresh;
property Count: Integer read GetCount;
property DefaultPrinterName: String read FDefaultPrinterName;
property PrinterNames: TStrings read FPrinters;
property PrinterName[Index: Integer]: String read GetPrinterName;
property PrinterInfo[aPrinterName: String]: TppPrinterInfo read GetPrinterInfo;
end; {class, TppPrinterList}
This discussion has been closed.
Comments
Woudl probably help influence them if they knew why you needed this :-)
Edward Dressem
Team DM
I would agree.
PrinterList is intended be a 'sealed' class - by that I mean a class that is
not descended from. It is intended to represent the list of printers that
are installed to the machine. The Refresh method can be used to rebuild the
list, for scenarios in which printers are installed/removed while an RB
application is running.
What type of application are you building and what are its special needs?
--
Nard Moseley
Digital Metaphors
www.digital-metaphors.com
Best regards,
Nard Moseley
Digital Metaphors
www.digital-metaphors.com
In terms of our "special needs", we need to distribute reports rather than print them. Printers are simply one of the ways of "distributing" documents, other are (obviously) email and PDF. Unfortunately ReportBuilder really does want to print to its Printers rather than a range of other medium (even if only to determine the characteristics of the output devices).
The reason for wanting this changes was for me to attempt to unhook the Printer-centric behaviour, and add other information to these structures in a unified way.
When we "print" a single report, that report that might include a number of documents of a particular class (for example a bunch of Invoices), I dynamically switch the device upon which the relevant pages are rendered, in this way respect a customers preference for receiving a report by email (via a PDF) or paper (via a printer device). I also collate the document pages based on the target recipient thus grouping multiple documents into one PDF (if the customer wants his invoices via email).
Further, 2 copies in this sense may mean, one for the customer and one for hardcopy, thus I have had to write a bespoke Printer Dialogue, to manage the these options.
Naturally, we also need to support ordinary printing to a printer (or pdf) for other a good number of reports.
The other problem I was trying to investigate and fix was the fact that RB doesn't remember Device specific information each time it creates a PrinterDevice, (you know 1, 2 up, etc), and this gets reset as RB updates the device.
Regards
David
RB prints to lots of ourput devices, but distingishes between printer
drivers and other output devices.
I've created my own printer dialog and have 'Print' and 'Print to File' as
separate options. You can also print to multiple printer drives at the same
time.
I am not sure I know what '1, 2, up, etc' means but as far as remembering
printer driver settings, there are documented ways to implement this--post a
new question and I am sure you will get an answer. (I like to make it so
that it could be found by others in a thread with an appropriate subject
line). I've thought about what RB does and does not do in this regard, and
my best feel is it does what it is suppose to--not overstepping it's
responsiblies (i.e. always remembering the printer driver settings could
frustrate users).
Edward Dressel
ReportBuilder is /not/ printer centric.
Reportbuilder includes built-in support for producing PDF and for sending
reports via email. ReportBuilder can also produce other types of documents:
report text files, archive report files, delimited text files. RB Server
Edition can publish XHTML reports to the web.
Third-party add-ons extend the RB architecture and can output to Excel, RTF,
Word, etc..
ReportBuilder has an open architecture for producing any type of document.
New devices can be created by descending from TppFileDevice and then
registering the new classes with RB.
The abstract ancestor is TppDevice. Here is the class hierarchy...
TppDevice - abstract ancestor
TppScreenDevice - preview
TppPrinterDevice - printer device - uses TppPrinter
TppStreamDevice - abstract ancestor for stream based output
TppFileDevice - abstract ancestor for file based output
TppPDFDevice - pdf documents
TppReportTextFileDevice - report .txt documents
TppArchiveDevice - report archives (.raf files)
TppTextFileDevice - delimited text output
TppXHTMLDevice - html output
The Report.DeviceType property specifies what device is used to generate the
report. You can either use the relevant Report properties to print directly
to a file, with no dialog being shown, or set Report.AllowPrintToFile to
true and the print dialog will include options to print to file. Or you can
replace the built-in printer dialog with a custom version that you create.
Most all dialogs in RB are replaceable by descending from our ancestor and
registering the custom class.
--
Nard Moseley
Digital Metaphors
www.digital-metaphors.com
Best regards,
Nard Moseley
Digital Metaphors
www.digital-metaphors.com
I am afraid I do not agree.
Let's take a scenario where you are printing via a TppPDFDevice ONLY.
When it comes to printing a component, RB obtains the characteristics from the Report's printer or, as it is in this case where there is not printer for the report, the default printer.
E.g. TppCustomMemo.CalcSpaceUsed
TppComponent.GetPrinter
TppBand.GetBand
TppProducer.GetPrinter
ppPrinter which uses the DEFAULT printer.
Surely we shouldn't specify, for instance - the paper size, for a PDF report by setting it on the Default printer. What if one had no printers defined, how would the end user change that?
Yes and in the main part I believe it does it very well. However it is not easily extensible, without a MAJOR copy and paste affair, which is exactly what I am trying to avoid and the reason for my original post.
The Report.PrinterSetup properties are used to define page size, margins,
orientation etc. These properties are saved as part of the report
definition.
A report can be designed on one machine and then deployed to thousands of
different machines - configured with a variety of printers. When
Report.PrinterSetup.PrinterName is set to 'Default' then at run-time, RB can
resolve 'Default' to the machine's default printer, as specified by the
Windows Printer Panel. For the case in which no printers are installed,
'Default' will resolve to 'Screen'. The Screen printer is a virtual printer
created by RB.
TppPrinterList contains the list of printers installed on the machine, plus
two additional entries: Default and Screen.
There are two uses of Printer:
1. RB uses the Printer graphics context to perform graphics calculations.
Again, when no printer is installed, the Printer returns the screen graphics
context.
2. The TppPrinterDevice also use Printer to generate a printed document.
When no printer is installed, the PrinterDevice cannot be used.
Do not confuse the concepts of Printer/PrinterSetup/PrinterList with the
concept of output Device. Even the RB TppPrinterDevice does not need to
extend any of those classes.
--
Nard Moseley
Digital Metaphors
www.digital-metaphors.com
Best regards,
Nard Moseley
Digital Metaphors
www.digital-metaphors.com
Lets go about this another way and tackle these problems for starters ...
1. How does one change the "current" printer for the life of the application, without affecting other applications.
It appears that each time a report is generated, RB reverts to the default printer.
For instance:
User prints Report_1 and selects a non-default printer. User then prints Report_2 and finds that the default printer is selected again.
Yes, it is possible to remember the printer name and set it each time, but that exacerbates problem 2 below.
2. How does one allow the user to make changes to Device properties that are remembered from report to report and for the life of the application.
Obviously some of the properties are specific to the report (i.e. Paper size, Margins, orientation etc.) and these need to be controlled by the report definition, but other device specific properties such as "pages per sheet" should be controlled by the user and be able to be remembered so the user does not have to set them each time. This is the behaviour exhibited by Word, Excel, Thunderbird, Acrobat etc.
It appears that RB creates a fresh DevMode each time a printer is assigned.
By way of an example, lets see how MS Word behaves.
Lets assume we have the following 2 printers:
Lexmark C524, Name='C524', Printer preferences specify "2 pages per sheet".
Brother HL-1450, Name='HL-1450', Printer preferences specify "2 pages per sheet".
Our default printer is 'C524'.
1. Open word and start a new document with some test text.
2. a) Print. (File | Print ) so we get the printer dialog
b) Click Properties to have the properties sheet display for our default printer (C524),
c) Change the "pages per sheet" property from to "2" to "1".
d) OK To the property sheet, then OK on the print dialog.
Observe that it has printed it 1 page per sheet.
3. a) Print. (File | Print ) so we get the printer dialog
b) Select "HL-1450" as the printer.
c) Click Properties to have the properties sheet display for our selected printer (HL-1450),
d) Change the "pages per sheet" property from to "2" to "1".
e) OK To the property sheet, then OK on the print dialog.
Observe that it has printed it 1 page per sheet.
4. a) Print. (File | Print ) so we get the printer dialog
Observe that it has remembered the HL-1450 we selected last time. (In RB this would not happen)
b) Select "C524" as the printer.
c) Click Properties to have the properties sheet display for our selected printer (C524),
d) Observe that it has remembered that we are printing "1 page per sheet". (In RB this would not happen)
e) OK to the property sheet, then OK on the print dialog.
Observe that it has printed it 1 page per sheet.
Note too, that if these were 3 separate documents (as opposed to printing the same document) the same printer selection and property behaviour would be exhibited as detailed above.
Regards
David
Finally, asking a question, rather then attacking RB.
This is something I've done in my app, and it did not take very long.
I created some private variables in a unit that does all my printing:
var
FPrinterDevMode: THandle = 0;
FPrinter: TppPrinter = nil;
(you may need to do it differently).
Then in the constructor of the print dialog, I have the following code:
ppPrinter.SetDevMode(0);
if (FPrinterDevMode = 0) then
begin
ppPrinter.GetDevMode(FPrinterDevMode);
ppPrinter.SetDevMode(FPrinterDevMode);
end;
In the setup printer I use the folowing code:
var
lCursor: TCursor;
begin
lCursor := Screen.Cursor;
Screen.Cursor := crHourGlass;
try
if not assigned(FPrinter) then
begin
FPrinter := TppPrinter.Create;
FPrinter.PrinterSetup := FppReport.PrinterSetup;
end;
ppPrinter.SetDevMode(FPrinterDevMode);
finally
Screen.Cursor := lCursor;
end; // try/finally
if FPrinter.ShowSetupDialog then
FPrinter.GetDevMode(FPrinterDevMode);
end;
And before printing:
if assigned(FPrinter) then
FppReport.PrinterSetup := FPrinter.PrinterSetup;
FppReport.Printer.SetDevMode(FPrinterDevMode);
And in the BeforePrint: (could be changed if I was printing to more then one
device):
if (FppReport.Publisher.DeviceCount = 1) then
begin
CodeSite.Send('FpPReport.Publisher.Devices[0]',
FpPReport.Publisher.Devices[0]);
if (FppReport.Publisher.Devices[0] is TPsRBExportDevice) then
begin
lPsDevice := TPsRBExportDevice(FpPReport.Publisher.Devices[0]);
lPsDevice.ShowSetupDialog := (FPassCount = 1) and
chkShowSetupDialog.Checked;
lPsDevice.ShowProgress := False;
end
else if (FppReport.Publisher.Devices[0] is TppPrinterDevice) then
TppPRinterDevice(FppReport.Publisher.Devices[0]).Printer.SetDevMode(FPrinterDevMode);
end;
and in the finalization:
finalization
if (FPrinterDevMode <> 0) then
GlobalFree(FPrinterDevMode);
if assigned(FPrinter) then
FreeAndNil(FPrinter);
This takes care of your concerns very nicely--and a simple question about
this in the original post would have saved a lot of band width and reading
time.
Edward Dressel
Team DM