问题
I'm having a couple problems with my program. I wanted a jTable to display various data points added to its TableModel, but I can't seem to get the table to update with the new data.
Also, it seems my search for png images within a directory has fallen short of actually searching the directory completely for all png images and stops only at the first folder's contents.
I've placed all data points into appropriate ArrayLists for their data types and converted them to Object arrays before placing them within the TableModel of my jTable. It's not worked, and I've come short of solving these problems with my own knowledge.
Some code:
private void btnPatchSelActionPerformed(java.awt.event.ActionEvent evt) {
fchsFolderChooser.showOpenDialog(null);
File selFile = fchsFolderChooser.getSelectedFile();
fldPatchSel.setText(selFile.getPath());
File pngs[] = selFile.listFiles(IMAGE_FILTER);
for (File png : pngs) {
try {BufferedImage img = ImageIO.read(png);
patchWs.add(img.getWidth()); patchHs.add(img.getHeight());
patchDims.add(img.getWidth() + "x" + img.getHeight());
patchImgs.add(img); patchImgPaths.add(png.getPath());
sels.add(false);
System.out.println("Found " + png.getPath());
} catch (IOException e) {
System.out.println("Bad image: " + png); e.printStackTrace();
}
}
if(!(fldPatchSel.getText().endsWith("...")||fldModSel.getText().endsWith("..."))) {BuildTable();}
}
The mod asset selection's counterpart is analogous but without setting the sels ArrayList.
private void BuildTable(){
DefaultTableModel model = (DefaultTableModel) tblImgList.getModel();
Object[] boolSels = sels.toArray();
Object[] stringPatchPaths = patchImgPaths.toArray();
Object[] stringPatchImgDims = patchDims.toArray();
model.addColumn("", boolSels);
model.addColumn("Patch Image", stringPatchPaths);
model.addColumn("W x H", stringPatchImgDims);
tblImgList.repaint();
}
How would you solve my two problems? The program's not searching subfolders and not able to display new data in the table.
回答1:
First, drop the DefaultTableModel
, you data model is becoming to complex. Start with a POJO (Plain Old Java Object) which wraps the context of what you want to display...
public class ImageProperty {
private File source;
private Dimension size;
private boolean selected;
public ImageProperty(File source, Dimension size, boolean selected) {
this.source = source;
this.size = size;
this.selected = selected;
}
public Dimension getSize() {
return size;
}
public File getSource() {
return source;
}
public boolean isSelected() {
return selected;
}
}
This holds all the information for a single image file and will be used to represent the contents of a row in the table.
Second, create a custom TableModel
. This is kind of a personal thing, but with DefaultTableModel
, you spend way more time hacking around it trying to get it to do what you want which would be better solved by using your own...
public class ImageTableModel extends AbstractTableModel {
private List<ImageProperty> images;
public ImageTableModel() {
images = new ArrayList<ImageProperty>(25);
}
@Override
public int getRowCount() {
return images.size();
}
@Override
public int getColumnCount() {
return 4;
}
@Override
public String getColumnName(int column) {
String name = "";
switch (column) {
case 1:
name = "Path";
break;
case 2:
name = "Name";
break;
case 3:
name = "Size";
break;
}
return name;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return columnIndex == 0 ? Boolean.class : String.class;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
ImageProperty row = images.get(rowIndex);
Object value = null;
switch (columnIndex) {
case 0:
value = row.isSelected();
break;
case 1:
value = row.getSource().getParent();
break;
case 2:
value = row.getSource().getName();
break;
case 3:
value = row.getSize().width + "x" + row.getSize().height;
break;
}
return value;
}
public void addImage(ImageProperty image) {
images.add(image);
fireTableRowsInserted(images.size() - 1, images.size() - 1);
}
public void addImages(List<ImageProperty> newImages) {
int firstRow = images.size();
int lastRow = firstRow + newImages.size() - 1;
images.addAll(newImages);
fireTableRowsInserted(firstRow, lastRow);
}
public void clear() {
int lastRow = images.size() -1;
images.clear();
fireTableRowsDeleted(0, lastRow);
}
}
Third, don't try and do directory scanning in the Event Dispatching Thread, this will cause you UI to "pause" until the scan is complete, and depending on the size of your images and the number of sub directories, could take minutes to complete.
See Concurrency in Swing for more details...
There are any number of ways you might overcome this, but SwingWorker
is probably the simplest, as it provides functionality to provide updates back the EDT safely, so you can update the UI without causing further issues...
public class ScanWorker extends SwingWorker<Object, ImageProperty> {
private File source;
private ImageTableModel model;
public ScanWorker(File source, ImageTableModel model) {
this.source = source;
this.model = model;
}
@Override
protected void process(List<ImageProperty> chunks) {
model.addImages(chunks);
}
@Override
protected Object doInBackground() throws Exception {
scan(source);
return null;
}
protected void scan(File dir) {
firePropertyChange("directory", dir.getParent(), dir);
File pngs[] = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().toLowerCase().endsWith(".png");
}
});
for (File png : pngs) {
try {
BufferedImage img = ImageIO.read(png);
publish(new ImageProperty(png, new Dimension(img.getWidth(), img.getHeight()), false));
} catch (IOException e) {
System.out.println("Bad image: " + png);
e.printStackTrace();
}
}
File dirs[] = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory();
}
});
if (dirs != null && dirs.length > 0) {
for (File subDir : dirs) {
scan(subDir);
}
}
}
}
This worker uses a recursive method call to scan subdirectories...
And, finally the glue...
This snippet disables the "scan" button, clears the current table model, creates the SwingWorker
and registers a PropertyChangeListener
to it and finally starts the worker...
model.clear();
scan.setEnabled(false);
ScanWorker worker = new ScanWorker(new File("..."), model);
worker.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("state".equalsIgnoreCase(evt.getPropertyName())) {
SwingWorker.StateValue state = (SwingWorker.StateValue) evt.getNewValue();
scan.setEnabled(state == SwingWorker.StateValue.DONE);
scan.setText("Scan");
} else if ("directory".equalsIgnoreCase(evt.getPropertyName())) {
scan.setText(text);
}
}
});
worker.execute();
The PropertyChangeListener
monitors for changes to the worker's state and resets the button to enabled when it completes. It also monitors for changes to the scanning directory and updates the text of the button to reflect the current directory been processed...nice
The above code snippet is triggered by the ActionListener
attached to the "scan" button
And a nice runnable example...

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;
public class ShowImages {
public static void main(String[] args) {
new ShowImages();
}
public ShowImages() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
final ImageTableModel model = new ImageTableModel();
JTable table = new JTable(model);
final JButton scan = new JButton("Scan");
scan.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
model.clear();
scan.setEnabled(false);
ScanWorker worker = new ScanWorker(new File("C:\\Users\\shane\\Dropbox\\MegaTokyo"), model);
worker.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("state".equalsIgnoreCase(evt.getPropertyName())) {
SwingWorker.StateValue state = (SwingWorker.StateValue) evt.getNewValue();
scan.setEnabled(state == SwingWorker.StateValue.DONE);
scan.setText("Scan");
} else if ("directory".equalsIgnoreCase(evt.getPropertyName())) {
String text = ((File)evt.getNewValue()).getPath().replaceAll("shane", "...");
text = text.replaceAll("C:\\\\", "...");
text = text.replaceAll("Dropbox", "...");
scan.setText(text);
}
}
});
worker.execute();
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(table));
frame.add(scan, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ImageProperty {
private File source;
private Dimension size;
private boolean selected;
public ImageProperty(File source, Dimension size, boolean selected) {
this.source = source;
this.size = size;
this.selected = selected;
}
public Dimension getSize() {
return size;
}
public File getSource() {
return source;
}
public boolean isSelected() {
return selected;
}
}
public class ImageTableModel extends AbstractTableModel {
private List<ImageProperty> images;
public ImageTableModel() {
images = new ArrayList<ImageProperty>(25);
}
@Override
public int getRowCount() {
return images.size();
}
@Override
public int getColumnCount() {
return 4;
}
@Override
public String getColumnName(int column) {
String name = "";
switch (column) {
case 1:
name = "Path";
break;
case 2:
name = "Name";
break;
case 3:
name = "Size";
break;
}
return name;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return columnIndex == 0 ? Boolean.class : String.class;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
ImageProperty row = images.get(rowIndex);
Object value = null;
switch (columnIndex) {
case 0:
value = row.isSelected();
break;
case 1:
value = row.getSource().getParent();
break;
case 2:
value = row.getSource().getName();
break;
case 3:
value = row.getSize().width + "x" + row.getSize().height;
break;
}
return value;
}
public void addImage(ImageProperty image) {
images.add(image);
fireTableRowsInserted(images.size() - 1, images.size() - 1);
}
public void addImages(List<ImageProperty> newImages) {
int firstRow = images.size();
int lastRow = firstRow + newImages.size() - 1;
images.addAll(newImages);
fireTableRowsInserted(firstRow, lastRow);
}
public void clear() {
int lastRow = images.size() -1;
images.clear();
fireTableRowsDeleted(0, lastRow);
}
}
public class ScanWorker extends SwingWorker<Object, ImageProperty> {
private File source;
private ImageTableModel model;
public ScanWorker(File source, ImageTableModel model) {
this.source = source;
this.model = model;
}
@Override
protected void process(List<ImageProperty> chunks) {
model.addImages(chunks);
}
@Override
protected Object doInBackground() throws Exception {
scan(source);
return null;
}
protected void scan(File dir) {
firePropertyChange("directory", dir.getParent(), dir);
File pngs[] = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().toLowerCase().endsWith(".png");
}
});
for (File png : pngs) {
try {
BufferedImage img = ImageIO.read(png);
publish(new ImageProperty(png, new Dimension(img.getWidth(), img.getHeight()), false));
} catch (IOException e) {
System.out.println("Bad image: " + png);
e.printStackTrace();
}
}
File dirs[] = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory();
}
});
if (dirs != null && dirs.length > 0) {
for (File subDir : dirs) {
scan(subDir);
}
}
}
}
}
Take a look at Worker Threads and SwingWorker for more details
来源:https://stackoverflow.com/questions/26477744/how-to-search-subfolders-and-repaint-a-jtable-with-new-data-in-java