views:

932

answers:

3

I have a very VBA intensive report. When I preview it everything is great but when I print it after previewing things go wacky. I have spent many hours narrowing down the possibilities and I have conclude with a certain level of confidence that it is a bug in MS Access.

Up to this point my method for printing reports was to open the report using docmd.openreport "report". I then use the docmd.printout command so that I can set the page range, collation etc.

Is there a way to print a report directly and still be able to set options like page rage, collate etc without doing a preview first?

Thanks, Jeff

+1  A: 

long ago, i had a very difficult case. i had to do some field creations, and moving and formatting and this could only be done one way. i took a bold approach and it turned to be the only way: i opened the report hidden and in design mode, had vba do it's stuff, and when done, the report was changed to normal and visible for display and printing.

dde
Ugh. That just sounds ugly. On the other hand, it fits in with the normal Access paradigm!
MJB
BZZZZT!!!! It absolutely does NOT fit any Access paradigm. It was not until Access 2002 that a proper printer object was introduced that allowed full control of printing. That was because of legacy issues with VB and the Windows printing system. Before the introduction of the MDE, it wasn't such an issue, as you could make changes in design view, but with the increasing use of MDEs (first introduced behind-the-scenes in A95, and first introduced to end users in A97), it became a real problem. Why it took them so long to fix it, I can't say.
David-W-Fenton
Believe it or not, this actually fixed my problem. I know it is UGLY to have to open it in design mode but hey, if it works what can you say?
Icode4food
It's a terrible solution, as it will break if you decide to distribute your front end as an ACCDE/MDE, since objects are not editable in those formats (which is one of the reason the format exists).
David-W-Fenton
A: 

One solution is to set the printer options in the design of the report, save those changes and the print it. The downside is that this will tie the report to a specific printer unless you go into the design and change it.

DoCmd.OpenReport "ReportName", acViewDesign, Null, Null, acHidden

Dim oRpt As Report
Set oRpt = Reports(0)
oRpt.UseDefaultPrinter = False
oRpt.Printer = Application.Printers("printer name")

With oRpt.Printer
    .PaperBin = acPRBNAuto
    .PaperSize = acPRPSLetter
    .Copies = 1
    .PrintQuality = acPRPQMedium
End With

DoCmd.Close acReport, "ReportName", acSaveYes
DoCmd.OpenReport "ReportName", acViewNormal

Set oRpt = Nothing
Thomas
+1  A: 

There is unfortunately no way to do it entirely neatly in code, but it can still be done since the introduction of the WindowMode parameter of the DoCmd.OpenReport method. This makes it possible to open a report in print preview mode and have it be hidden. You can then set properties of the report's Printer object (such as the output printer and orientation), and then use DoCmd.PrintOut to print a page range.

One thing to note:

You can't do this in the report's OnOpen event, because changing anything that has an effect on the layout will not give you correct results. For instance, if in the OnOpen event, you changed from Portrait to Landscape orientation, you won't have an accurate count of how many pages there are in the report, because the report hasn't been formated at the time the OnOpen event fires. For everything but pages, though, it's OK.

The way I would implement this is with a public function and a dialog form. The function would look something like this:

  Public Function PrintReport(strReport As String) As Boolean
    ' open report in PREVIEW mode but HIDDEN
    DoCmd.OpenReport strReport, acViewPreview, , , acHidden
    ' open the dialog form to let the user choose printing options
    DoCmd.OpenForm "dlgPrinter", , , , , acDialog, strReport
    With Forms!dlgPrinter
      If .Tag <> "Cancel" Then
         Set Reports(strReport).Printer = Application.Printers((!cmbPrinter))
         Reports(strReport).Printer.Orientation = !optLayout
         Application.Echo False
         DoCmd.SelectObject acReport, strReport
         DoCmd.PrintOut acPages, !txtPageFrom, !txtPageTo
         PrintReport = True
      End If
    End With
    DoCmd.Close acForm, "dlgPrinter"
    DoCmd.Close acReport, strReport
    Application.Echo True
  End Function

The dialog form would look something like this:

alt text

As you can see above, I open this dialog with an OpenArg parameter, which is the name of the report. In the dialog's OnLoad event, I initialize the controls on the form:

  Dim varPrinter As Printer
  Dim strRowsource As String
  Dim strReport As String

  If Len(Me.OpenArgs) > 0 Then
     strReport = Me.OpenArgs
     Me.Tag = strReport
     For Each varPrinter In Application.Printers
       strRowsource = strRowsource & "; " & varPrinter.DeviceName
     Next varPrinter
     Me!cmbPrinter.RowSource = Mid(strRowsource, 3)
     ' first check to see that the report is still open
     If (1 = SysCmd(acSysCmdGetObjectState, acReport, strReport)) Then
        With Reports(strReport).Printer
          Me!cmbPrinter = .DeviceName
          Me!optLayout = .Orientation
        End With
        Me!txtPageTo = Reports(strReport).Pages
     End If
  End If

I use the form's .Tag property for the report name, and then do everything based on that, including making changes to report properties on the fly, which is possible because the report is open in preview mode, but not visible.

For instance, I have this AfterUpdate event behind the Layout option group:

  With Reports(Me.Tag)
    .Printer.Orientation = Me!optLayout
    Me!txtPageTo = .Pages
  End With

The reason I change the page range numbers is because changing the orientation will most likely change the number of pages. Unlike in the OnOpen event, changes to a the format properties of a report open invisibly in Print Preview mode happen immediately.

I use my standard methods for dialog forms, which is to have the Cancel and Continue buttons set the form's .Visible property to False, which allows the calling code to continue. For the Cancel button, I set the form's .Tag property to "Cancel" and check the .Tag property when the code continues in the calling context (see above).

So, this isn't as great as it would be to be able to set the page range on the Printer object directly, but it gets the job done.

One thing that would need to be changed in production code is making sure there was an error handler in the PrintReport function so that if something went wrong, Application.Echo can be turned back on (otherwise, the user might be stuck with a blank screen and unable to work). The alternative would be to just let the report appear onscreen when the DoCmd.SelectObject method is invoked. But if I'm hiding the report preview from the user, I would want to go all the way.

For more information on this, you should investigate the .Printer object in the Object Browser (F2 in the VBE), and MS Knowledge Base article 290293 is helpful in explaining the interactions between the Application.Printers collection and Application.Printer object and the ones associated with a particular report. I also found a little tutorial on the Office site that clarified a few things.

David-W-Fenton