Hey there.

I'm using a JTable for the first time, and I've read through the Sun and other tutorials, but this simple concept is still confusing me. So I seek help!

Basically; I'm creating a multiplayer (AI-controlled) mining game, which will display maps loaded from XML. I've parsed the XML into a DOM tree, and then parsed that into a Object[][] array. I can load one map fine, and have loaded each of the maps, so I'm sure the issue is with my displaying of the JTable instead of the parsers.

Basically; once I load the initial map on startup I can't load any more - the window size changes, but all cell images stay exactly as they were in the previous map and just become squashed/pulled depending on if the new map is smaller/larger. I imagine this is a simple conceptual problem with how I'm using JTable/Table Models/CellRenderers. If you could point out the obvious, I'd most appreciate it!

Here's the GUI class. The table setting method is at the top, followed by the constructor which creates a blank map and has a file chooser that loads other maps.

package gui;

//Imports the modules used for generating the GUI.

import javax.swing.*;
import javax.swing.filechooser.FileFilter;

import mine.*;

import java.awt.*;
import java.awt.event.*;
import java.io.File;

public class MineInterface extends JFrame implements ActionListener {

	Mine mineInstance = new Mine();
	private static final long serialVersionUID = 1L;
	private File mapFile;
	JTable mapTable = null;
	MineTableModel tableModel = null;

	public JTable newMap(Object[][] map) {
		mineInstance.setMap(map); //Sets the mine to use the given map
		tableModel = new MineTableModel(map); //Creates a new table model and assigns it the given map
		mapTable = new JTable(tableModel); //Creates a new JTable assigned to the table model.
		for(int i=0; i<mapTable.getColumnCount(); i++) {
			mapTable.getColumnModel().getColumn(i).setCellRenderer(new IconCellRenderer()); //Updates each cell to display its icon
			mapTable.getColumnModel().getColumn(i).setWidth(20); //Sets the column width to that of the image icon
			mapTable.setRowHeight(20); //Sets the row height to that of the icon
		}
		mapTable.setTableHeader(null); //Hides the header 
		this.setSize(mapTable.getColumnCount() * 20,(mapTable.getRowCount() * 20) + 175); //Resizes the window to suit the new map
		mapTable.revalidate(); //Revalidates the table display
		return mapTable;
	}
	
	MineInterface() {
		super("Diamond Mine Game");
		setSize(625, 400); // initial size
		setLayout(new BorderLayout(10, 10)); // sets layout to border style (which sections out the GUI to N/E/S/W and Center).

		if (mapTable == null) { //If no previous map exists, creates a blank one (e.g. on startup)
			newMap(Mine.createBlankMap());
		}

		JScrollPane mapScrollPane = new JScrollPane(mapTable);

		JPanel optionsMenu = new JPanel();
		JMenuBar menuBar = new JMenuBar();
		JMenu fileMenu = new JMenu("File");
		JMenuItem openChooser = new JMenuItem("Load Map");
		//Adds the action listener for openChooser which activates a file chooser.
		openChooser.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){

				JFileChooser chooser = new JFileChooser();
				//Limits the user choice to text files.
				chooser.addChoosableFileFilter(new FileFilter(){

					//Overrides the accept method to only allow text files to be selected by the user.
					public boolean accept(File f) {
						return f.isDirectory() || f.getName().toLowerCase().endsWith(".xml");
					}

					//Creates a new description for use in the file selection dialog menu.
					public String getDescription() {
						return "Diamond Mine XML Map Files (.xml)";
					}
				});

				//Shows the open file dialog window.
				chooser.showOpenDialog(MineInterface.this);
				mapFile = chooser.getSelectedFile();
				System.out.println("Loading new map called: " + mapFile.getName()); //For testing, shows the filename being loaded.
				System.out.println("Loading new map called: " + mapFile.getAbsolutePath()); //Shows filepath
				mapTable = newMap(mineInstance.getMap(mapFile.getName())); //Makes a new map, using the given map file name, and then loads it.
			}
		});
		//Adds the openChooser menu item to the file menu.
		fileMenu.add(openChooser);
		fileMenu.add(new JMenuItem("Quit")).addActionListener(new ActionListener(  ) {
			public void actionPerformed(ActionEvent e) { System.exit(0); }
		});
		menuBar.add(fileMenu);
		optionsMenu.add(menuBar);

		add(mapScrollPane, BorderLayout.CENTER);
		add(optionsMenu, BorderLayout.NORTH);
		setVisible(true);

	}

	public void actionPerformed(ActionEvent e) {
		// TODO Auto-generated method stub

	}

	public static void main(String[] args) {
		MineInterface display = new MineInterface();
		display.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0); // Ends the program
			}
		});
	}
}

Here's the table model I'm using:

package gui;

import javax.swing.table.AbstractTableModel;

class MineTableModel extends AbstractTableModel {
    
	private static final long serialVersionUID = 2L;
	private String[] columnNames;
    private Object[][] mapData;
    
    MineTableModel(Object[][] map) {
    	mapData = map; //Assigns the map to the mapData
    	columnNames = new String[map[0].length]; //Creates an array of the appropriate size
    	for (int i=0; i<map[0].length; i++) {
			columnNames[i] = Integer.toString(i+1); //Column array is then filled with numbers as placeholders.
    	}
    }

    public int getColumnCount() {
        return columnNames.length;
    }

    public int getRowCount() {
        return mapData.length;
    }

    public String getColumnName(int col) {
        return columnNames[col];
    }
    
    public boolean isCellEditable(int row, int col)
    { return false; }


    public Object getValueAt(int row, int col) {
        return mapData[row][col];
    }

    public void setValueAt(Object value, int row, int col) {
		mapData[row][col] = value;
        fireTableCellUpdated(row, col);
    }
    
}

And finally here's the cell renderer:

package gui;

import java.awt.Component;

import javax.swing.ImageIcon;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;

public class IconCellRenderer extends DefaultTableCellRenderer {

	private static final long serialVersionUID = 1L;

  public Component getTableCellRendererComponent(JTable table, Object value,
                                                 boolean isSelected, boolean hasFocus, 
                                                 int row, int column) {

  	ImageIcon img = null;
  	
  	if (value.getClass() == mine.Rock.class) {
  		img = new ImageIcon(getClass().getResource("rock.jpg")); //If the cell contains a rock, loads the rock image icon.
  	}
  	else if (value.getClass() == mine.Diamond.class) {
  		img = new ImageIcon(getClass().getResource("diamond.jpg")); //As rock, but for diamond.
  	}
  	else if (value.getClass() == mine.Exit.class) {
  		img = new ImageIcon(getClass().getResource("exit.jpg")); //As rock, but for exit.
  	}
  	else if (value.getClass() == mine.ToolStore.class) {
  		img = new ImageIcon(getClass().getResource("toolstore.jpg")); //As rock, but for the toolstore.
  		System.out.println("TOOLSTORE is located at: " + "(" + row + "," + column + ")"); //Test purposes, to show if the toolstore is at a different position on the two maps.
  	}
  	else {
  		img = new ImageIcon(getClass().getResource("space.jpg")); //If none of the above, must be a space.
  	}
    setIcon(img); //Sets the icon for the cell to the image specified above.
    return this;
  }
}

Thanks! :)

I think it's probably just a validation issue with the container since you're changing the reference to the table. I would try to validate the JFrame and call repaint after you're swapped out that table as a start.

Something to consider though: you only need to change the model when you load a new map. You can build a new model for that map file and call table.setModel() with the new model, without having to create a new table every time.

I think it's probably just a validation issue with the container since you're changing the reference to the table. I would try to validate the JFrame and call repaint after you're swapped out that table as a start.

Hmm, I've tried:

mapTable.revalidate(); //Revalidates the table display mapTable.repaint();[/java]

After (or before) the cell rendering, after the table/model changes, and it stays exactly the same; same images as the original loading map, only the window dimensions change.

In regards to the not needing a new JTable; that's what I thought, but when I try just having the mapTable.setMap(tableModel), I get a NullPointerException on startup.

Thanks for the help so far![CODE=java]
mapTable.revalidate(); //Revalidates the table display
mapTable.repaint();[/java]

After (or before) the cell rendering, after the table/model changes, and it stays exactly the same; same images as the original loading map, only the window dimensions change.

In regards to the not needing a new JTable; that's what I thought, but when I try just having the mapTable.setMap(tableModel), I get a NullPointerException on startup.

Thanks for the help so far!

Annnnnyone at all?

I've checked (just using print statements) that the new map is being passed to the method correctly (right new size, right new contents). The mineInstance map and the mineMap both correctly update. However in the cellRenderer it always says the Toolstore icon is in the same place, even on different maps. So it must be something to do with the repaint part, is my cellrenderer correct?

You may need to set the viewport view of your scrollpane to the new table, since you have re-assigned that reference to a new instance. Try calling JScrollPane.setViewportView(java.awt.Component) on your scroll pane with the new table component.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.