Home General
New Blog Posts: Merging Reports - Part 1 and Part 2

Member level visibility

edited September 2007 in General
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}

Comments

  • edited September 2007
    David:



    Woudl probably help influence them if they knew why you needed this :-)

    Edward Dressem
    Team DM
  • edited September 2007
    > Woudl probably help influence them if they knew why you needed this :-)

    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
  • edited September 2007
    Hi Nard

    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




  • edited September 2007
    > In terms of our "special needs", we need to distribute reports rather than

    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
  • edited September 2007

    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
  • edited September 2007
    Nard Moseley (Digital Metaphors) wrote:
    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.
  • edited September 2007


    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
  • edited September 2007
    Nard,

    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


  • edited September 2007
    > It appears that RB creates a fresh DevMode each time a printer is

    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
This discussion has been closed.