Image of Navigational Map linked to Home / Contents / Search Keeping a Leash on Crystal

by Ross Mack - GUI Computing
Image of Line Break

With Visual Basic 3.0 Microsoft decided to bundle Crystal Reports with VB to give developers a head start in producing reports on their databases. Crystal is a powerful tool and most every Visual Basic developer has used it for at leas a couple of projects. However, a common complaint is that when creating a report preview Crystal generates a separate window over which the programmer has some control, in the way that it first appears, but is otherwise owned solely by Crystal. Users can often be confused when the Crystal report window appears over their application and they can't get back to the app, or end up with a bunch of errant report windows lying around after they have closed down the application itself. Worse still, these windows consume memory to maintain the report that they are displaying.

A while ago I discovered a way around this, and I have been using it ever since. For anyone who is not aware of this technique, you might just find it useful next time you are including Crystal reports with your application.

Essentially what I do is to instruct Crystal to produce a report window inside one of my windows instead of creating its own window. I then own the window that the report is displayed in and I can dispose of it, move it, minimise or maximise it as I like, all quite simply in code.

The first thing you need is a simple window, like the following. I have a standard one I keep around that I just add to a project when I need it.

As you can see the form is blank, it needs no controls as Crystal will dynamically create the preview and the preview's toolbar dynamically onto the window at runtime. You might also notice that this window is an MDIChild Window. This is because I often like to use an MDI form as a basis for reporting in my applications. It allows the user to create a number of reports and manipulate the windows easily, maintain them in a defined area (as opposed to SDI windows), and view reports side by side with ease just by using the inbuilt functionality of MDI windows.

There is also only a small amount of code within the form.

Sub RedrawChild ()
   Dim hWndChild As Integer

   hWndChild = GetWindow(Me.hWnd, GW_CHILD)
   If (hWndChild <> 0) Then
     MoveWindow hWndChild, Me.ScaleLeft, Me.ScaleTop, Me.ScaleWidth, Me.ScaleHeight, 1
   End If
End Sub
Sub Form_Activate ()
   RedrawChild
End Sub
Sub Form_Resize ()
   RedrawChild
End Sub

The code requires the following API declarations:

Declare Function GetWindow Lib "User" (ByVal hWnd As Integer, ByVal code As Integer) As Integer
Declare Sub MoveWindow Lib "User" (ByVal hWnd As Integer, ByVal l As Integer, ByVal t As Integer, ByVal w As Integer, ByVal h As Integer, ByVal redraw As Integer)
Global Const GW_CHILD = 5

As you can see we have one general function and two calls to it. All the function does is use an API to retrieve the handle of the first child window of the form. When a report is created this will become the Crystal report window (which is created as a child of this form). If it manages to retrieve a valid Window Handle (hWnd) it then moves the child window so that it fills the entire client area of the form. This means that as the form is resized the Crystal preview window will always fill it.

The end result (at runtime) is a window that looks like this:

Again, this example is an MDI child window so I have shown it as it appears within its MDI parent. Note that everything that appears on the form now (excluding the border and title bar) is created dynamically by Crystal just as it would normally create them in it's own windows.

So, the next question is, how do we get Crystal to create a report on our window?

I use a simple bit of code, of which the following is sort of a minimalist version. It assumes that your reports will not need additional formulas set or additional Record Selection Criteria.

Sub DoCrystalPreview (ByVal sTitle_IN As String, ByVal sRPTName_IN As String)
   ' Dimension a new instance of the report preview window 
   Dim frmPrev As New frmChild
   
   Dim sMsg As String
   Dim iResult As Integer

   Busy ' hourglass stuff 

   ' Set the caption of the preview window 
   frmPrev.Caption = sTitle_IN
   ' Tell Crystal what report to print
   Crystal.ReportFileName = sRPTName_IN
   ' WindowParentHandle tells Crystal to print inside the
   ' window we specify by passing it's hWnd   
   Crystal.WindowParentHandle = frmPrev.hWnd
  ' Do the report 
   iResult = Crystal.PrintReport
   UnBusy
   ' Check for and report errors
   If iResult <> 0 Then
      MsgBox "Error Encountered Printing Report: " & sTitle_IN & CRLF(2) & "ERROR " & Crystal.LastErrorNumber & ": " & Crystal.LastErrorString, 48, "Report Error"
   End If
End Sub

The comments should help describe what is going on. What happens first is that we create a new instance of the previewing window (the one I discussed above) so that we can print to it. We do this instead of just referring to the window as normal and loading it as normal because we may want to have more than one instance of this window (with reports on each) active at the same time. The we give Crystal enough information about the report for it to produce it, in this case the RPT filename and a title. Then we set the WindowParentHandle Property. This tells Crystal that instead of creating its own window we want it to create a report preview as a child of the window we have specified. We then check for errors and such and we are finished.

So, that's it. There is not much involved, but it's a trick that can be very useful and save yourself and your users a bit of grief. There is a simple sample application available (VB 3) for download with a couple of simple reports that run off the old favourite BIBLIO.MDB (JET 2.0).



Written by: Ross Mack
January '98

Image of Arrow linked to Previous Article Image of Arrow linked to Next Article
Image of Line Break
[HOME] [TABLE OF CONTENTS] [SEARCH]