i-net Clear Reports comes with a Java report viewer which can be shown as an applet, embedded into your own massive applications, or simply placed in a simple small standalone application.
This guide is written to show in a down-to-earth, practical way how simple it is to use the Viewer's API to custom-fit the viewer to your specific needs. We will provide a number of short and concise examples extending from building a standard, simple standalone viewer to how to customize the viewer's tool bar, for example by changing the default behavior of the "info" button, to implementing your own bookmark frame to jump to specific pages of the report and to print on the server side using the viewer's API.
Here is a quick look at the various most important classes and interfaces in the viewer, and what their function and role is.
The SwingReportViewer is the topmost level of the viewer. It extends from JPanel, so it can be embedded into your own application components. SwingReportViewer has methods to add, create, and remove individual report views, and provides a hub for the viewer's actions and tool bar. It also is the place to provide various global settings for the viewer, for example the default export location, whether to ever show report group trees or the status bar.
To create a SwingReportViewer, simply use the default constructor:
SwingReportViewer viewer = new SwingReportViewer();
The RenderData interface composes the interface between the viewer and the report data required for displaying a report.
Usually you will not be calling any of these methods, rather the viewer will call them when it needs to request data. There are various implementations of this interface already included with i-net Clear Reports for you to use, or you could implement your own RenderData to fit your specific solution.
The interfaces included with i-net Clear Reports are URLRenderData (for connecting via HTTP), EngineRenderData (for creating engines to render data when necessary), and CacheRenderData (for using i-net Clear Reports Cache object to retrieve report data).
SwingReportViews represent individual reports or subreports. Swing ReprotView extends from JPanel, so a view can be extracted from the Viewer and placed in your own components. SwingReportViews cannot be created with a constructor, you have to use a SwingReportViewer to create report views, passing an instance of a RenderData object. To do this, you can either call
ReportView reportView = viewer.addNewReportView(data);
to create a report view and automatically add it to the Viewer's tabbed pane as a tab, or you could call
ReportView reportView = viewer.createReportView(data);
which creates an individual report view without adding it to the viewer.
SwingReportView offers methods for navigating, printing, exporting, and customizing the status bar and navigation view, as well as retrieving settings and information about the report view.
A ViewerContext can be passed to the SwingReportViewer which will then use it to handle printing, exporting, showing errors, and showing the info dialog. The default implementation of ViewerContext is SwingViewerContext. You may extend from this class if you just wish to change part of the behavior of the Viewer. One simple way to customize the viewer is to create your own implementation of ViewerContext and pass it to the viewer:
viewer.setViewerContext(new SwingViewerContext(viewer) { public void print(ReportView reportView) { // Your own printing implementation comes here... } });
In order for the following examples to work, the following requirements must be met:
We'd like a Viewer to connect to our report server, running on host myhost on port 9000, and display the report called "sample.rpt".
import javax.swing.JFrame; import com.inet.viewer.SwingReportViewer; import com.inet.viewer.URLRenderData; public class ViewerStart { private JFrame frame = new JFrame( "i-net Clear Reports Report Viewer"); private SwingReportViewer viewer; public void initGUI() { frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); URLRenderData myConnection = new URLRenderData( "http://myhost:9000/?report=file:sample.rpt" ); viewer = new SwingReportViewer(); viewer.addNewReportView(myConnection); frame.getContentPane().add(viewer); // Display the window. frame.pack(); frame.setVisible(true); } public static void main(String[] args) { new ViewerStart().initGUI(); } }
We'd like to have a report renderer and viewer in one application. In other words, we'd like our application to render report templates and then show them in a Viewer.
All we have to do is replace our URLRenderData object from the above example with the following lines:
// ... EngineRenderData myConnection = new EngineRenderData( "" ); myConnection.setReportProperty("report","sample.rpt"); // ...
We'd like to display two different reports in two tabs in the same viewer.
// ... URLRenderData myConnection = new URLRenderData( "http://localhost:9000/?report=file:sample.rpt" ); URLRenderData myConnection2 = new URLRenderData( "http://localhost:9000/?report=file:rdc/complex.rpt" ); viewer.addNewReportView(myConnection); viewer.addNewReportView(myConnection2); // ...
In addition to the default behavior of the Viewer in the case of an error, an error dialog with more information about the error should popup. Furthermore you'd also like to log the error with your own log handler from your application.
// code for creating the viewer ... viewer.setViewerContext(new MyViewerContext(viewer)); // further code ... } } class MyViewerContext extends SwingViewerContext { public MyViewerContext( SwingReportViewer viewer ) { super( viewer ); } public void showError( Throwable e, Object source ) { // log the error with my own log handler MyLogger.log(e, source); // shows the dialog super.showError( e, source ); } }
Instead of the default behavior of the Viewer when the info button is pressed, which is showing an info dialog with information about the Java VM, i-net Clear Reports version, etc., you'd like to show your own info dialog.
// code for creating the viewer ... viewer.setViewerContext(new MyViewerContext(viewer)); // further code ... } } class MyViewerContext extends SwingViewerContext { public void showInfo() { JOptionPane.showMessageDialog( null, "MyReportViewer brought to you by <your company>." ); } }
You'd like to take the Viewer's status bar out of the Viewer's panel, and place it in a component of your own application instead.
// ... ReportView reportView = viewer.addNewReportView( myConnection ); StatusBar statusBar = reportView.extractStatusBar(); myApplicationComponent.add(statusBar.getComponent()); // ...
You'd like to completely remove the default status bar and instead display your own status bar component in its place.
// Now we create a new status bar component JPanel ourStatusBar = new JPanel(); ourStatusBar.setBackground( new Color(0.7f,0.4f,0.4f) ); ourStatusBar.add( new JLabel("My very own status bar!") ); // Here we replace the status bar with our own component. reportView.replaceStatusBar( ourStatusBar );
You'd like to access the Viewer's JToolBar object.
ToolBar toolBar = viewer.getToolBar(); Component c = toolBar.getComponent(); // With the Swing Viewer, this is a JToolBar: JToolBar t = (JToolBar)c;
You have a custom action which you'd like represented in the tool bar by a tool bar button.
ToolBar toolbar = viewer.getToolBar(); JToolBar t = (JToolBar)(toolbar.getComponent()); Action a = new AbstractAction("myAction", new ImageIcon("myAction.gif")) { public void actionPerformed( ActionEvent e ) { // Your action goes here... } }; t.add( a );
You'd like to remove the info button from the Tool Bar, also you don't want F1 to open an info dialog either.
// remove info button toolBar.setButtonsVisible( ToolBar.BUTTON_INFO, false ); // disable info action in the ActionPool of the Viewer viewer.getActionPool().getViewerAction( ActionPool.INFO ) .setEnabled( false );
You'd like to actually change the behavior of a tool bar button, for example the refresh button.
// remove the original refresh button toolBar.setButtonsVisible( ToolBar.BUTTON_REFRESH, false ); // disable the original refresh action (F5) viewer.getActionPool().getViewerAction( ActionPool.REFRESH ). setEnabled( false ); // Now create your own refresh action: Action refresh = new AbstractAction("myRefresh", new ImageIcon("refresh.gif")) { public void actionPerformed( ActionEvent e ) { // Your custom action goes here... if (myApp.isReadyForRefresh()) { viewer.getCurrentReportView().refresh(); } else { // Whatever you'd like to do here... } } }; // ...and add your action to the tool bar. t.add( refresh );
You have a report which has groups, but you don't want to display its group tree, which by default would cause the ReportView to become a split pane with the group tree on the left and the actual report on the right.
viewer.setHasGroupTree(false);
reportView.setHasGroupTree(false);
Instead of the Viewer logging to the console as it does by default, you'd like the Viewer to log to a log file.
File f = new File("C:/viewerlog.txt"); // Here we now choose to change the logging output of the viewer: try { SwingReportViewer.setLoggingStream( new PrintStream( new FileOutputStream( f ) ) ); } catch( FileNotFoundException e1 ) { e1.printStackTrace(); }
You'd like to monitor any exporting and printing processes started by the user. If a user has successfully printed the report once, you'd like to disable printing after this so that the user does not print the report more than once. You'd like the same behavior when a user exports successfully.
public class ViewerStart implements PropertyChangeListener { //... public void initGUI(){ //... // We now register ourselves as a propertyChangeListener // with the viewer so we can keep track of any changes in // progresses viewer.addStateChangeListener( (PropertyChangeListener) this ); // ... }
//... public void propertyChange( PropertyChangeEvent evt ) { if (evt.getSource() instanceof Progress) { // If a progress has changed... Progress p = (Progress)evt.getSource(); if (p.getType() == Progress.TYPE_EXPORT || p.getType() == Progress.TYPE_PRINT) { // ...and it is an export or print progress if (evt.getPropertyName().equals( Progress.PROP_PROGRESS_STATUS )) { // ...and its status has changed... int status = ((Integer)evt.getNewValue()) .intValue(); if (status == Progress.STATUS_COMPLETED) { // ... and the progress has successfully // been completed, then disable any // further export or printing, // depending on what just happened. if (p.getType() == Progress.TYPE_EXPORT) { viewer.getActionPool().getViewerAction( ActionPool.EXPORT ).setEnabled(false); } else { viewer.getActionPool().getViewerAction( ActionPool.PRINT ).setEnabled(false); } } } } } } }
You'd like to print a report without showing the report in the ReportViewer. You just want to show a print dialog to the start printing.
// ... ReportView reportView = viewer.createReportView( new URLRenderData("http://localhost:9000/?report=file:sample.rpt"); reportView.print(1,-1,true); // ...
You'd like to print a report without showing the report in the ReportViewer. You just want to print the report without any dialog popping up, including the print dialog.
// ... ReportView reportView = viewer.createReportView( new URLRenderData("http://localhost:9000/?report=file:sample.rpt"); reportView.print( 1, -1, false); // ...
You have a connection to a server with a RenderData instance and would simply like to print without using the class ReportView .
RenderData data = new URLRenderData("http://localhost:9000/?report=file:sample.rpt"); PrinterJob job = PrinterJob.getPrinterJob(); // .... modify PrinterJob if needed. for example the printer HashPrintRequestAttributeSet attributeSet = new HashPrintRequestAttributeSet(); // ... change some attributes if needed, for example paper // size and margins PrinterJobProgress progress = new PrinterJobProgress( null, job, attributeSet, data ); progress.startProgress(); // .. control progress of the process if needed
You'd like to print yourself, but you require information about the report in form of PrintRequestAttributes which include page format, page number, page margin and page orientation information
HashPrintRequestAttributeSet attributeSet = reportView.getDefaultAttributeSet(1, -1);
You want to make use of the print dialog, but would like to customize the initial values displayed in the print dialog.
public class MyViewerContext extends SwingViewerContext { public void print(ReportView reportView) { HashPrintRequestAttributeSet attributeSet = reportView. getDefaultAttributeSet( 1, -1 ); // ... set your customized Attributes here ... // For example to set the Orientation to Landscape attributeSet.add( OrientationRequested.LANDSCAPE ); PrinterJob job = PrinterJob.getPrinterJob(); SwingReportView.showPrintDialog( null, job, attributeSet ); // ... set enforced AttributeSet here, if needed reportView.print( job, attributeSet ); } }
You'd like to print more than one report in a single PrinterJob. For example, your printer might automatically add cover pages to each print job, and you'd like to print 2 reports with only 1 cover page.
public class MyPrintable implements Printable { /** The current Printable */ private Printable currentPrintable; /** List of all sub Printable */ private ArrayList<Printable> progressList = new ArrayList<Printable>(); /** Index of the next Printable */ private int idx; /** Page offset of the current Printable */ private int pageOffset;
/** * {@inheritDoc} */ public int print( Graphics graphics, PageFormat pageFormat, int pageIndex ) throws PrinterException { if( currentPrintable == null ) { // No Printable was added return NO_SUCH_PAGE; } int pageStatus = currentPrintable.print( graphics, pageFormat, pageIndex - pageOffset ); if( pageStatus == NO_SUCH_PAGE ) { // the current Printable has no more pages currentPrintable = nextPrintable(); if( currentPrintable == null ) { // no more Printable return NO_SUCH_PAGE; } // set the page offset and repeat the printing with // the current page // every sub Printable start with page offset 0 pageOffset = pageIndex; return currentPrintable.print( graphics, pageFormat, 0 ); } return PAGE_EXISTS; }
/** * Add a Printable to this super Printable. * @param printable a sub Printable */ public void addPrintable( Printable printable ) { progressList.add( printable ); if( currentPrintable == null ) { currentPrintable = printable; idx++; } } /** * Request the next Printable * @return a Printable or null */ private Printable nextPrintable() { if( idx < progressList.size() ) { return progressList.get( idx++ ); } return null; } }
PrinterJob job = PrinterJob.getPrinterJob(); HashPrintRequestAttributeSet attributeSet = new HashPrintRequestAttributeSet(); MyPrintable myPrint = new MyPrintable(); // Create the needed PrinterJobProgress instances // with the RenderData PrinterJobProgress pjp1 = new PrinterJobProgress(....); pjp1.setStatus( Progress.STATUS_COMPLETED ); myPrint.addPrintable( pjp1 ); job.setPrintable( myPrint ); job.print(attributeSet);
You'd like to customize the way a certain prompt is requested of the user, and have your own custom component to do so.
public class MyCustomPromptEditor implements CustomPromptEditor { // any component will do fine here private JTextField myTextField; public MyCustomPromptEditor() { myTextField = new JTextField(); // customize as needed (e.g. your own Renderer, // your own Model, your own event handling, etc.) } public Object getValue() { return myTextField.getText(); } public void setValue( Object value ) { myTextField.setText( value.toString() ); } public Component getComponent() { return myTextField; } }
// ... MyCustomPromptEditor myEditor = new MyCustomPromptEditor(); viewer.setCustomPromptEditor( "specialPrompt", PromptData.STRING, myEditor ); // ...
You'd like to use one viewer to show multiple reports. However, instead of having a central viewer with a tab pane of reports, you'd like each report opened to have its own frame rather than tab.
(See samples.viewer.Viewer_With_Multiple_Frames.java for full source)
... viewer = new SwingReportViewer() { // Here we will override the default behavior of the Viewer. // For this, we'll keep track of the Views in a HashMap: HashMap myMap = new HashMap(); public void addReportView(ReportView view, boolean isClosable) { // We now override the default behavior of // addReportView which used to open new tabs // for each new view. // Instead, we open a new frame, place the new // report view inside it, and remember it in // combination with the frame in our hash map. JFrame repFrame = new JFrame(view.getReportData(). getReportTitle()); // So that the viewer knows which ReportView is the // current report view (for toolbar actions, etc.), // we'll define a WindowFocusListener so that // whenever a user focuses on a report view window, // it becomes the current report view. WindowFocusListener l = new WindowFocusListener() { public void windowGainedFocus(WindowEvent e) { setCurrentReportView( (ReportView)myMap.get(e.getWindow()) ); } public void windowLostFocus(WindowEvent e) { } }; myMap.put(repFrame,view); // Now we simply add the view to the frame and show it: repFrame.addWindowFocusListener(l); repFrame.getContentPane().add(view.getComponent()); repFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); repFrame.pack(); repFrame.setVisible(true); setCurrentReportView(view); } }; // The next two lines assume we have a report server running on // localhost. Naturally, we could use any report server name // here instead. viewer.addNewReportView( new URLRenderData( "http://localhost:9000/?report=file:rdc/complex.rpt") ); viewer.addNewReportView( new URLRenderData( "http://localhost:9000/?report=file:sample.rpt") ); viewer.setPreferredSize( new Dimension(725,35) ); ...
In the navigation view on the left hand side of the report, you'd like a bookmark view where users can click on a bookmark, causing the report to jump to the bookmarked page in the report. As well, you want a user to be able to add his own bookmarks at any time by pressing Ctrl+D on the page they'd like to bookmark.
(See samples.viewer.bookmark.BookmarkView.java for full source)
public class BookmarkView extends JPanel implements NavigationTab { ... public void addBookmark(String name, int page) { Bookmark bookmark = new Bookmark(name,page); addIfNecessary(bookmark); bookmarkTree.expandRow( 0 ); } }
(See sample.viewer.Viewer_With_Bookmarks.java for full source)
... view = viewer.addNewReportView( data ); bookmarkView = new BookmarkView(view); NavigationView navView = view.getNavigationView(); // Adding the bookmark view navView.addNavigationTab( BookmarkView.TITLE, bookmarkView ); // Making sure the navigation view is shown. navView.setVisible( true ); ...
You have a client application which is communicating with your server application via RPC (such as CORBA, RMI, etc.). You'd like to have a viewer client that communicates with the report server back-end on your server. For this you need an instance of RenderData.
module renderdata { typedef sequence<octet> byteArray; interface CORBARenderData { byteArray getPageData( in long page ); long getPageCount(); byteArray getNextExportChunk(); long getExportChunkCount(); byteArray getGroupTree(); byteArray refreshPageData(in long page); void setReportProperty(in string key, in string value); void setExportProperty(in string key, in string value); string getReportProperty(in string key); void setPromptOnRefresh(in boolean promptOnRefresh); boolean isPromptOnRefresh(); void stop(); byteArray search(in string phrase, in long startPage, in long flags); byteArray getFontData( in long fontID ); }; };
You have local data, for example in a JTable, on your client, and would like to use this data to display a report.