Link to home
Start Free TrialLog in
Avatar of ctjoumas
ctjoumas

asked on

TableLayout issues

I have a class that is a container for displaying an image.  What I am trying to do is to create multiple contiainers and adding them to a TableLayout object so I have a table of images.  Right now it is working, for the most part, but there are two issues I am facing.

1) I cannot seem to get the images centered.  The entire layout appears to be right aligned - I haven't looked into this too much yet, but hopefully there is an easy solution to that.

2) My biggest problem is an error that is being generated when repainting all my my container objects that display my images.  Here is there error:

Exception occurred during event dispatching:
java.lang.NullPointerException
      at sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:2765)
      at sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:2726)
      at ImageContainer.paintComponent(ImageContainer.java:107)
      at javax.swing.JComponent.paint(JComponent.java:808)
      at javax.swing.JComponent.paintChildren(JComponent.java:647)
      at javax.swing.JComponent.paint(JComponent.java:817)
      at javax.swing.JComponent.paintWithOffscreenBuffer(JComponent.java:4771)
      at javax.swing.JComponent.paintDoubleBuffered(JComponent.java:4724)
      at javax.swing.JComponent._paintImmediately(JComponent.java:4668)
      at javax.swing.JComponent.paintImmediately(JComponent.java:4477)
      at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:410)
      at javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(SystemEventQueueUtilities.java:117)
      at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:178)
      at java.awt.EventQueue.dispatchEvent(EventQueue.java:448)
      at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:197)
      at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:150)
      at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:140)
      at java.awt.Dialog.show(Dialog.java:551)
      at RawImage.doLoadFile(RawImage.java:249)
      at RawImageMenuBar.actionPerformed(RawImageMenuBar.java:127)
      at java.awt.MenuItem.processActionEvent(MenuItem.java:588)
      at java.awt.MenuItem.processEvent(MenuItem.java:548)
      at java.awt.MenuComponent.dispatchEventImpl(MenuComponent.java:285)
      at java.awt.MenuComponent.dispatchEvent(MenuComponent.java:273)
      at java.awt.EventQueue.dispatchEvent(EventQueue.java:452)
      at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:197)
      at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:150)
      at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:144)
      at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:136)
      at java.awt.EventDispatchThread.run(EventDispatchThread.java:99)

I don't quite see what is going on there - I did something to change the layout and the error went away, but I couldn't do much with how that layout was setup (I think I just had it directly on the frame...whereas now I have it on a panel that is added to the frame).

Here is some code extracts of how I am getting this to work:


        // create table layout for the frame
        layout = new TableLayout();

        panel = new JPanel();
        panel.setPreferredSize( new Dimension( 500, 500 ) );
        panel.setLayout( layout );
.
.
.
       // later on when I am loading the images
       int numRows = ( stacks.size() / 5 ) + 1;
       if ( ( stacks.size() % 5 ) > 0 )
           numRows++;

       double [] rows = new double[ numRows ];
       double [] cols = new double[ 6 ];

        // set columns
        for ( int i=0; i<cols.length; i++ ) {
            cols[ i ] = 175;
                                }

        // set rows
        for ( int i=0; i<rows.length; i++ ) {
            rows[ i ] = 175;
        }

        // layout.setConstraints( panel, tlc );
        layout.setVGap( 2 );
        layout.setHGap( 2 );
        layout.setColumn( cols );
        layout.setRow( rows );

        panel.setLayout( layout );

        // then in a for loop I create container objects of the images being loaded and add them to the panel
        int currRow = ( i / 5 ) + 1;
        int currCol = ( i % 5 ) + 1;

        panel.add( container, String.valueOf( currCol ) + ", " + String.valueOf( currRow ) + ", f, c" );


        // after the for loop I finish up with the panel
        panel.doLayout();
        panel.repaint();

Thanks for any help!
Avatar of girionis
girionis
Flag of Greece image

 Which is the line that throws the null pointer exception?
Avatar of ctjoumas
ctjoumas

ASKER

Well, it is kinda strange.  It is in my image container class (which is what I am displaying multiple instances of in my table layout):

    public void paintComponent( Graphics g ) {
        g.drawImage( displayImage, 0, 0, getWidth(), getHeight(), this );
    } // end paintComponent


I'm not sure how the drawImage line is throwing the null pointer exception though..  But, it is usually the first image that isn't fully painted - but I have a component listener in that contianer class and when it resizes, it will callt he same paint component (for all image containers that are in my layout of course) and I never see the error again.
 So does it only happens with the first image? Do the rest work fine?
Well, I am not sure if it happens with the first one or not - in each container, I have an image number so I printed that out before the drawImage - and the only number printed before painting is 7...  but this seems to change.  It appears to be random as to which image number is causing hte problem - but that is the only time the painting causes an error...any time the images are repainted, no error occurs.
 Not sure what's going on and I cannot infer anything from the code.

  What did you do here

> I don't quite see what is going on there - I did something to change the layout and the error went away

  and the error went away?
What I did was just create a popup frame to hold the table layout of images....that works fine.  But, the layout is still weird - it appears centered when it is maximized.  Regardless, I can't have a popup for it - i need to get these to load and appear centered on a panel that will be in my main window frame.

I'm not sure what else to post to show what is going wrong - what I have posted is the main process of how everything is being constructed.  I am also stumped as to what is going on with the error when the images are painted for the first time.
the error looks like it is caused by trying to paint a null image.
That's what it looks like, but I don't see how that can happen.  Here is hte basic logic that goes on for setting my container objects, which is what I am adding to the TableLayout - and these are nothing more than a panel used to display an image from the stack of images:

1) read in all image stacks and store them (ImagePlus objects) in a Vector.
2) loop through all stacks in the vector
  - create a new container object and set the image to the first image from the current ImagePlus stack
  - add the container to the next free cell in hte TableLayout
3) the loop has finished, so now let's repaint the panel that has its layout set to the TableLayout.

So, all images in my container object should be valid and not null.
print out the value of displayImage in paintComponent() to check.
 Can you post a small compilable example that demonstrates the problem?
The value of displayImage is null when it generates the error - but I don't see why this would be the case.  displayImage is set each time my container is created - but I must be looking over something.  I'll be looking back into it.  But, I think the other important problem is the alignment issues.

When my program is loaded up, it is set to dimensions of 500x500 and when the images are loaded and layed out in the TableLayout, they seem to be right aligned.

I will look into this problem a little bit, and if it doesn't work I will try and post some code that will simplify my problem.

Chris
Ok, I tried to shorten up the code and only include just the loading and displaying of the images.  This is a very crude mockup of the project - but it shows the error I am getting and should be relatively short.  Hope this helps - there are three classes/files:

Main Class:
--------------------------------------------------------------------------
import ij.ImagePlus;
import info.clearthought.layout.TableLayout;

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.Vector;

public class RawImage extends Frame {

    // TESTING TABLELAYOUT
    private TableLayout layout;
    private JPanel panel;
    private Vector containers = new Vector();

    /**
     * Container object for the image being loaded.
     */
    private ImageContainer container = null;

    /**
     * A vector of images
     */
    private Vector images = new Vector();

    /**
     * Main function.  Creates an object of itself and displays it
     * on the screen.
     * @param args Command line argument array
     */
    public static void main( String [] args ) {

        RawImage ri = new RawImage();

        ri.pack();

        // center on screen
        Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
        Rectangle abounds = ri.getBounds();
        ri.setLocation( ( dim.width - abounds.width ) / 2, ( dim.height - abounds.height ) / 2 );

        ri.show();

    } // end main

    /**
     * Constructor.  Builds the GUI for the program.
     */
    public RawImage() {

        super( "Raw Image Loader" );

        // Allow user to close the window to terminate the program
        addWindowListener(new WindowAdapter() {
            public void windowClosing (WindowEvent e) {
                System.exit( 0 );
            }
        } );

        setLayout( new BoxLayout( this, BoxLayout.Y_AXIS ) );
        setBackground( Color.lightGray );

        // create table layout for the frame
        layout = new TableLayout();

        panel = new JPanel();
        panel.setPreferredSize( new Dimension( 500, 500 ) );
        panel.setLayout( layout );

        // get menu bar from RawImageMenuBar class
        setMenuBar( new RawImageMenuBar( this ) );

        add( panel );

    } // end constructor

    /**
     * Loads the file containing the raw pixel data of multiple slices, which
     * may or may not make up a movie, and uses a thread to read in the file
     * and create multiple stacks from it.
     */
    public void doLoadFile() {

        File [] f = null;

        JFileChooser chooser = new JFileChooser( "P:/" );
        chooser.setMultiSelectionEnabled( true );
        chooser.setDialogTitle( "Load Images" );

        int returnVal = chooser.showOpenDialog( this );

        if ( returnVal == JFileChooser.APPROVE_OPTION ) {
            f = chooser.getSelectedFiles();
        }

        final File [] files = f;

        if ( files.length > 0 ) {

            try {

                ImagePlus ip = null;

                // clear the stacks vector from any previously loaded stacks
                images.clear();

                for ( int i=0; i<files.length; i++ ) {

                    ip = new ImagePlus( files[ i ].getAbsolutePath() );

                    images.add( ip.getImage() );

                } // end for

                int numRows = ( images.size() / 5 ) + 1;
                if ( ( images.size() % 5 ) > 0 )
                    numRows++;

                double [] rows = new double[ numRows ];
                double [] cols = new double[ 6 ];

                // set columns
                for ( int i=0; i<cols.length; i++ ) {
                    cols[ i ] = 175;
                }

                // set rows
                for ( int i=0; i<rows.length; i++ ) {
                    rows[ i ] = 175;
                }

                // layout.setConstraints( panel, tlc );
                layout.setVGap( 2 );
                layout.setHGap( 2 );
                layout.setColumn( cols );
                layout.setRow( rows );

                panel.setLayout( layout );

                for ( int i=0; i<images.size(); i++ ) {

                    container = new ImageContainer();

                    // set the imageplus object to the first stack
                    displayImage( i );

                    int currRow = ( i / 5 ) + 1;
                    int currCol = ( i % 5 ) + 1;

                    panel.add( container, String.valueOf( currCol ) + ", " + String.valueOf( currRow ) + ", f, c" );

                    // add the image container object to the vector of image containers
                    containers.add( container );

                }

                panel.doLayout();
                panel.repaint();

            } catch ( Exception e ) {
                e.printStackTrace();
            } // end try - catch

        } // end if

    } // end doLoadFile

    /**
     * Displays an image in the ImagePlus stack in the image container.
     *
     * @param imageNumber the image number being displayed
     */
    public void displayImage( int imageNumber ) {

        Image i = (Image) images.get( imageNumber );
        container.setImage( i );

    } // end displayImage

} // end class
--------------------------------------------------------------------------

MenuBar Class:
--------------------------------------------------------------------------
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class RawImageMenuBar extends MenuBar implements ActionListener {

    /**
     * Handle to the parent application
     */
    private RawImage rawImageObj = null;

    /**
     * Handle to the File - Exit option for event handling purposes
     */
    private MenuItem exit = null;

    /**
     * Handle to the File - Open File option for event handling purposes
     */
    private MenuItem openFiles = null;

    /**
     * Constructor.
     *
     * @param ri the parent object
     */
    public RawImageMenuBar( RawImage ri ) {

        rawImageObj = ri;

        // Create File Menu
        Menu menuFile = new Menu( "File" );

        openFiles = new MenuItem( "Open Files" );
        menuFile.add( openFiles );
        openFiles.addActionListener( this );

        menuFile.addSeparator();

        exit = new MenuItem( "Exit" );
        menuFile.add( exit );
        exit.addActionListener( this );

        // add menus to main menu bar
        add( menuFile );

    } // end constructor

    /**
     * Event handler/delegate function.
     *
     * @param e Event information object
     */
    public void actionPerformed( ActionEvent e ) {

        Object src = e.getSource();

        if ( src.equals( exit ) ) {
            System.exit( 0 );
        } else if ( src.equals( openFiles ) ) {
            rawImageObj.doLoadFile();
        } // end if - else

    } // end actionPerformed

} // end class
--------------------------------------------------------------------------

ImageContainer Class:
--------------------------------------------------------------------------
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.event.*;

/**
 * <strong>ImageContainer</strong> - Component that holds an image object created
 * from raw pixel data.  When no image is loaded, this component will display a
 * white background with a "no image message" complaint that will be clickable in
 * order to popup a chooser for loading a file.
 *
 * @author Chris Tjoumas
 */
public class ImageContainer extends JPanel implements ComponentListener {

    /**
       * ActionListener object to activate when the component is clicked on and the component is in an invalid state
       */
      private ActionListener invalidAction;

    /**
     * Image that was loaded.
     */
    private Image image;

    /**
       * Off-screen buffer for drawing things before they are displayed. Especially usefull for its Graphics2D capabilities.
       */
      private BufferedImage displayImage;

    /**
       * Graphics context for drawing stuff off-screen
       */
      private Graphics2D display;

    /**
     * Creates a container to display the loaded image.
     */
    public ImageContainer() {

        setPreferredSize( new Dimension( 250, 250 ) );

        setOpaque( true );

        addComponentListener( this );

    } // end constructor

    /**
     * Paint the body of the component.
     *
     * @param g Graphics context on which to draw.
     */
    public void paintComponent( Graphics g ) {
        g.drawImage( displayImage, 0, 0, getWidth(), getHeight(), this );
    } // end paintComponent

       /**
       * Mutator for what to do on a mouse click in an invalid state (not having an image loaded yet).
       */
      public void setInvalidAction( ActionListener a ) {
            invalidAction = a;
      } // end setInvalidAction

    /**
       * Unused mouse callback.
       *
       * @param e Event information object.
       */
      public void mouseClicked( MouseEvent e ) { }

      /**
       * Unused mouse callback.
       *
       * @param e Event information object.
       */
      public void mouseEntered( MouseEvent e ) {      }

      /**
       * Unused mouse callback.
       *
       * @param e Event information object.
       */
      public void mouseExited( MouseEvent e ) { }

    /**
     * Unused mouse callback.
     *
     * @param e Event information object.
     */
    public void mouseReleased( MouseEvent e ) { }

    /**
       * Mouse callback for the mouse getting pressed.
       *
       * @param e Event information object.
       */
      public void mousePressed( MouseEvent e ) {

            if ( image == null && invalidAction != null ) {
                  invalidAction.actionPerformed( new ActionEvent( this, 0, null ) );
                  return;
            } // end if

    } // end mousePressed

         /**
       * Unused component event, when the component is hidden.
       *
       * @param e Event information object.
       */
      public void componentHidden( ComponentEvent e ) { }

      /**
       * Unused component event, when the component is moved.
       *
       * @param e Event information object.
       */
      public void componentMoved( ComponentEvent e ) { }

    /**
       * Unused component event, when the component is shown (unhidden).
       *
       * @param e Event information object.
       */
      public void componentShown( ComponentEvent e ) { }

         /**
       * ComponentListener callback for when the component is resized. This causes a new off-screen display area
       * to be allocated and drawn upon.
       *
       * @param e Event information object.
       */
      public void componentResized( ComponentEvent e ) {

            if ( displayImage == null ||
                   getWidth() != displayImage.getWidth( this ) ||
                   getHeight() != displayImage.getHeight( this ) ) {

                  regenerateOffscreenBuffer( getWidth(), getHeight() );
            } // end if

            drawOntoDisplayImage();

      } // end componentResized

    /**
       * Regenerates the buffered image and the graphics context for the buffered image.
       *
       * @param w width of buffered image
       * @param h height of buffered image
       */
      public void regenerateOffscreenBuffer( int w, int h ) {

        displayImage = new BufferedImage( w, h, BufferedImage.TYPE_INT_RGB );
            display = displayImage.createGraphics();

      } // end regenerateOffscreenBuffer

    /**
     * Draws the image onto the offscreen buffer, which is then painted onto
     * the panel.
     */
    public void drawOntoDisplayImage() {

        if ( displayImage == null )
            return;

        if ( image != null ) {

            display.setColor( Color.BLACK );
            display.fill( new Rectangle( 0, 0, getWidth(), getHeight() ) );

            // display the image in the center of the panel
            int x = ( getWidth() - image.getWidth( this ) ) / 2;
            int y = ( getHeight() - image.getHeight( this ) ) / 2;

            display.drawImage( image, x, y, this );

            display.setColor( Color.blue );

        } // end if - else

        repaint();

    } // end drawOntoDisplayImage

    /**
     * Mutator for the image object.
     *
     * @see #image
     */
    public void setImage( Image i ) {

        image = i;

        drawOntoDisplayImage();

    } // end setImage

} // end class
--------------------------------------------------------------------------

Thanks,
Chris
Sorry, you can ignore all of the mouse stuff and invalidAction stuff in the ImageContainer class.
 Ok I will try it tomorrow since I do not have time right now :)
Appreciate it :)

I'm actually seeing something...  in another project me and another guy did, we had the whole layout (and image containers as well) already setup.  So, the regenerateOffscreenBuffer method was always called.  In this case, it is created on the fly - but I don't know why it still isn't called...I think the component should be resized somehow.  I'm a bit unclear about how this works now so I'm looking into it more.
So the problem is that the componentResized event is never being fired.  Any idea on why?  Also, when the constructor in ImageContainer is called, i set the dimensions to 250x250, yet when I call the setImage, the width and height are 0.  A solution (although it is a hack) is to call regenearateOffscreenBuffer( 250, 250 )...this fixes the problem, but I don't like how it works.

How can I get the componentResized even to fire or, if that cna't happen, how can I get the width and height to remain 250x250 after it is set in the constructor?

Thanks,
Chris
When adding the containers to the panels layout, if I add a "pack()" there (oh, and I also changed the Frame to a JFrame...) it will allow the container to have a width and height, so when I set the image, I can call the regenerateOffscreenBuffer method with getWidth() and getHeight() as parameters.

So, in the main class, the for loop is:

                for ( int i=0; i<images.size(); i++ ) {

                    container = new ImageContainer();

                    int currRow = ( i / 5 ) + 1;
                    int currCol = ( i % 5 ) + 1;

                    panel.add( container, String.valueOf( currCol ) + ", " + String.valueOf( currRow ) + ", f, c" );

                    pack();

                    // set the imageplus object to the first stack
                    displayImage( i );

                    // add the image container object to the vector of image containers
                    containers.add( container );

                }


and in the container classes setImage method, I have

    public void setImage( Image i ) {

        image = i;

        regenerateOffscreenBuffer( getWidth(), getHeight() );

        drawOntoDisplayImage();

    } // end setImage



If there is anyhting wrong with this approach, please let me know!!!

The other problem is still there though... getting the images in the TableLayout to be displayed correctly - I can't tell if they are centered or not... when the window is maximized, it appears to be centered.  I'll try adding a JScrollPane and see what happens.
Arrrggg...  nevermind.  The pack forces the component to repaint - so the paintComponent is once again called and tries to paint a null displayImage.  However, if I take that out, the width and height of the ImageContainer panel is 0.  


!!!!!!
ASKER CERTIFIED SOLUTION
Avatar of Mick Barry
Mick Barry
Flag of Australia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I was thinking of doing that too...I'm sure that it would work fine, but I am wondering why the image is null?  I guess that maybe when the frame has a size the componentResized event will be fired, which will in turn create my displayImage?

Well, I'll give it a shot tomorrow.

Thanks!

Chris
it's null becuase paintComponent is getting called before you create it :)
Ok..that makes sense now.  It is null because it tries painting it before the component is resized...and when the component is resized, the regenerateOffscreenBuffer is called to create the displayImage.

When I load a second file, the component is resized, but the width and height are 0 - so my regenerateOffscreenBuffer is passed 0x0 for width and height, which causes an error.  Should I just add the condition for getWidth() and getHeight() to the already existing conditions to check and make sure it is not 0?  Currently I have:

if ( displayImage == null ||
   getWidth() != displayImage.getWidth( this ) ||
   getHeight() != displayImage.getHeight( this ) ) {

  if ( getWidth() != 0 )
    regenerateOffscreenBuffer( getWidth(), getHeight() );

} // end if

Does it seem ok to add the getWidth() != 0 && getHeight() != 0 there?

If that's it, that should do it for the errors..thanks!  The last thing is the alignment - If i have say 18 images loaded, all I can see is the top left 2 images or so...  if anyone has any suggestions on this, please lemme know!

Thanks again,
Chris
> Does it seem ok to add the getWidth() != 0 && getHeight() != 0 there?

y
> The last thing is the alignment - If i have say 18 images loaded,
> all I can see is the top left 2 images or so...

Sounds like its related to your layout manager.
Ya, I know it is related to my layout manager.  I am using TableLayout from import info.clearthought.layout.TableLayout;

Do you have any idea on how to get this centered?  And also attache it to a scrollpane?  I have been trying that for a bit (it's been a while and I'm gonna start taking another shot at it now), but no luck.  Any idea?  If not, just let me know and I will just close this question up and give out the points.

Thanks,
Chris
i've never used it sorry.
To use with scroll pane, just add your component to a scroll pane
JScrollPane pane = new JScrollPane(mycomponent);
Thanks for the help.  Also, I already tried the JScrollPane and assumed it just wasn't working - but it only scrolls if the frame is set at a certain width/height...but the even when the images are not all visible, it won't scroll.  I dunno why that is - this layout manager is just a bit strange.

Thanks,
Chris
Let me know if you have any more questions :)

http://www.objects.com.au/staff/mick