JList重绘单个元素
JList repaint single element
我有一个 JList,其元素由我正在为其创建缩略图的图像文件组成(在后台线程中)。当这些缩略图可用时,我想强制重绘 just 该项目。但是,我发现当我使用 listModel 的 fireDataChanged 方法(见下文)时,all 列表中的可见项被重新绘制(使用我的自定义 ListCellRenderer)。
public void updateElement(int index) {
frame.listModel.fireContentsChanged(frame.listModel, index, index);
}
有没有办法只重绘索引项?
如果没有某种可运行的示例来说明您的问题,就不可能提出任何具体的建议。
下面的简单示例使用 SwingWorker
来更改 ListModel
中元素的值。为了让它看起来更真实,我打乱了索引的 List
并在每个索引之间应用了一个短暂的延迟。
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private DefaultListModel<String> model = new DefaultListModel<>();
public TestPane() {
setLayout(new BorderLayout());
add(new JScrollPane(new JList(model)));
JButton load = new JButton("Load");
add(load, BorderLayout.SOUTH);
load.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
load.setEnabled(false);
model.removeAllElements();
for (int index = 0; index < 100; index++) {
model.addElement("[" + index + "] Loading...");
}
LoadWorker worker = new LoadWorker(model);
worker.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt.getPropertyName() + " == " + evt.getNewValue());
if ("state".equals(evt.getPropertyName())) {
Object value = evt.getNewValue();
if (value instanceof SwingWorker.StateValue) {
SwingWorker.StateValue stateValue = (SwingWorker.StateValue) value;
if (stateValue == SwingWorker.StateValue.DONE) {
load.setEnabled(true);
}
}
}
}
});
worker.execute();
}
});
}
}
public class LoadResult {
private int index;
private String value;
public LoadResult(int index, String value) {
this.index = index;
this.value = value;
}
public int getIndex() {
return index;
}
public String getValue() {
return value;
}
}
public class LoadWorker extends SwingWorker<Void, LoadResult> {
private DefaultListModel model;
public LoadWorker(DefaultListModel model) {
this.model = model;
}
public DefaultListModel getModel() {
return model;
}
@Override
protected void process(List<LoadResult> chunks) {
for (LoadResult loadResult : chunks) {
model.set(loadResult.index, loadResult.value);
}
}
@Override
protected Void doInBackground() throws Exception {
int count = model.getSize();
List<Integer> indicies = new ArrayList<>(count);
for (int index = 0; index < count; index++) {
indicies.add(index);
}
Collections.shuffle(indicies);
for (int index : indicies) {
Thread.sleep(15);
publish(new LoadResult(index, "[" + index + "] Has been loaded"));
}
return null;
}
}
}
以上是线性进程,这意味着它按顺序处理每个项目,一次一个。
因为图像加载可能需要时间并且是 CPU 密集过程,您可以使用 ExecutorService
并使用线程池来帮助分散负载。
例如:
- Make images fetched from server display in real time
- Extracting images from a text file of 100 image urls using java
I find that when I use the listModel's fireDataChanged method (see below), all the visible items in the list are repainted
您不应手动调用该方法。 fireXXX(...) 方法只能由模型本身调用。
您应该使用以下方法更新模型:
model.set(...);
然后 set(...)
方法将调用适当的方法来通知 JList 重新绘制单元格。
这是我对一个简单的缩略图应用程序的尝试。它尝试通过以下方式增加性能改进:
- 使用默认图标加载模型,这样列表就不需要不断调整自身大小
- 使用 ExecutorService 以利用多个处理器
- 使用 ImageReader 读取文件。子采样 属性 允许您在缩放图像时使用更少的像素。
只需将 class 更改为指向包含一些 .jpg 文件的目录,然后试一试:
缩略图应用程序:
import java.io.*;
import java.util.concurrent.*;
import java.awt.*;
//import java.awt.datatransfer.*;
import java.awt.event.*;
import javax.swing.*;
class ThumbnailApp
{
private DefaultListModel<Thumbnail> model = new DefaultListModel<Thumbnail>();
private JList<Thumbnail> list = new JList<Thumbnail>(model);
public ThumbnailApp()
{
}
public JPanel createContentPane()
{
JPanel cp = new JPanel( new BorderLayout() );
list.setCellRenderer( new ThumbnailRenderer<Thumbnail>() );
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
list.setVisibleRowCount(-1);
Icon empty = new EmptyIcon(160, 160);
Thumbnail prototype = new Thumbnail(new File("PortugalSpain-000.JPG"), empty);
list.setPrototypeCellValue( prototype );
cp.add(new JScrollPane( list ), BorderLayout.CENTER);
return cp;
}
public void loadImages(File directory)
{
new Thread( () -> createThumbnails(directory) ).start();
}
private void createThumbnails(File directory)
{
try
{
File[] files = directory.listFiles((d, f) -> {return f.endsWith(".JPG");});
int processors = Runtime.getRuntime().availableProcessors();
ExecutorService service = Executors.newFixedThreadPool( processors - 2 );
long start = System.currentTimeMillis();
for (File file: files)
{
Thumbnail thumbnail = new Thumbnail(file, null);
model.addElement( thumbnail );
// new ThumbnailWorker(file, model, model.size() - 1).execute();
service.submit( new ThumbnailWorker(file, model, model.size() - 1) );
}
long duration = System.currentTimeMillis() - start;
System.out.println(duration);
service.shutdown();
}
catch(Exception e) { e.printStackTrace(); }
}
private static void createAndShowGUI()
{
ThumbnailApp app = new ThumbnailApp();
JFrame frame = new JFrame("ListDrop");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane( app.createContentPane() );
frame.setSize(1600, 900);
frame.setVisible(true);
// File directory = new File("C:/Users/netro/Pictures/TravelSun/2019_01_Cuba");
File directory = new File("C:/Users/netro/Pictures/TravelAdventures/2018_PortugalSpain");
app.loadImages( directory );
}
public static void main(String[] args)
{
javax.swing.SwingUtilities.invokeLater(() -> createAndShowGUI());
}
}
ThumbnailWorker:
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.Iterator;
//import java.util.concurrent.*;
import javax.imageio.*;
import javax.imageio.stream.*;
import javax.swing.*;
class ThumbnailWorker extends SwingWorker<Image, Void>
{
private File file;
private DefaultListModel<Thumbnail> model;
private int index;
public ThumbnailWorker(File file, DefaultListModel<Thumbnail> model, int index)
{
this.file = file;
this.model = model;
this.index = index;
}
@Override
protected Image doInBackground() throws IOException
{
// Image image = ImageIO.read( file );
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("jpg");
ImageReader reader = readers.next();
ImageReadParam irp = reader.getDefaultReadParam();
// irp.setSourceSubsampling(10, 10, 0, 0);
irp.setSourceSubsampling(5, 5, 0, 0);
ImageInputStream stream = new FileImageInputStream( file );
reader.setInput(stream);
Image image = reader.read(0, irp);
int width = 160;
int height = 90;
if (image.getHeight(null) > image.getWidth(null))
{
width = 90;
height = 160;
}
BufferedImage scaled = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = scaled.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.drawImage(image, 0, 0, width, height, null);
g2d.dispose();
image = null;
return scaled;
}
@Override
protected void done()
{
try
{
ImageIcon icon = new ImageIcon( get() );
Thumbnail thumbnail = model.get( index );
thumbnail.setIcon( icon );
model.set(index, thumbnail);
System.out.println("finished: " + file);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
缩略图渲染器
import java.awt.Component;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class ThumbnailRenderer<E> extends JLabel implements ListCellRenderer<E>
{
public ThumbnailRenderer()
{
setOpaque(true);
setHorizontalAlignment(CENTER);
setVerticalAlignment(CENTER);
setHorizontalTextPosition( JLabel.CENTER );
setVerticalTextPosition( JLabel.BOTTOM );
setBorder( new EmptyBorder(4, 4, 4, 4) );
}
/*
* Display the Thumbnail Icon and file name.
*/
public Component getListCellRendererComponent(JList<? extends E> list, E value, int index, boolean isSelected, boolean cellHasFocus)
{
if (isSelected)
{
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
else
{
setBackground(list.getBackground());
setForeground(list.getForeground());
}
//Set the icon and filename
Thumbnail thumbnail = (Thumbnail)value;
setIcon( thumbnail.getIcon() );
setText( thumbnail.getFileName() );
return this;
}
}
缩略图:
import java.io.File;
import javax.swing.Icon;
public class Thumbnail
{
private File file;
private Icon icon;
public Thumbnail(File file, Icon icon)
{
this.file = file;
this.icon = icon;
}
public Icon getIcon()
{
return icon;
}
public void setIcon(Icon icon)
{
this.icon = icon;
}
public String getFileName()
{
return file.getName();
}
}
我在一个包含 302 个图像的目录上进行了测试。使用 ExecutorService 将加载时间从 2:31 减少到 0:35。
我有一个 JList,其元素由我正在为其创建缩略图的图像文件组成(在后台线程中)。当这些缩略图可用时,我想强制重绘 just 该项目。但是,我发现当我使用 listModel 的 fireDataChanged 方法(见下文)时,all 列表中的可见项被重新绘制(使用我的自定义 ListCellRenderer)。
public void updateElement(int index) {
frame.listModel.fireContentsChanged(frame.listModel, index, index);
}
有没有办法只重绘索引项?
如果没有某种可运行的示例来说明您的问题,就不可能提出任何具体的建议。
下面的简单示例使用 SwingWorker
来更改 ListModel
中元素的值。为了让它看起来更真实,我打乱了索引的 List
并在每个索引之间应用了一个短暂的延迟。
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private DefaultListModel<String> model = new DefaultListModel<>();
public TestPane() {
setLayout(new BorderLayout());
add(new JScrollPane(new JList(model)));
JButton load = new JButton("Load");
add(load, BorderLayout.SOUTH);
load.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
load.setEnabled(false);
model.removeAllElements();
for (int index = 0; index < 100; index++) {
model.addElement("[" + index + "] Loading...");
}
LoadWorker worker = new LoadWorker(model);
worker.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt.getPropertyName() + " == " + evt.getNewValue());
if ("state".equals(evt.getPropertyName())) {
Object value = evt.getNewValue();
if (value instanceof SwingWorker.StateValue) {
SwingWorker.StateValue stateValue = (SwingWorker.StateValue) value;
if (stateValue == SwingWorker.StateValue.DONE) {
load.setEnabled(true);
}
}
}
}
});
worker.execute();
}
});
}
}
public class LoadResult {
private int index;
private String value;
public LoadResult(int index, String value) {
this.index = index;
this.value = value;
}
public int getIndex() {
return index;
}
public String getValue() {
return value;
}
}
public class LoadWorker extends SwingWorker<Void, LoadResult> {
private DefaultListModel model;
public LoadWorker(DefaultListModel model) {
this.model = model;
}
public DefaultListModel getModel() {
return model;
}
@Override
protected void process(List<LoadResult> chunks) {
for (LoadResult loadResult : chunks) {
model.set(loadResult.index, loadResult.value);
}
}
@Override
protected Void doInBackground() throws Exception {
int count = model.getSize();
List<Integer> indicies = new ArrayList<>(count);
for (int index = 0; index < count; index++) {
indicies.add(index);
}
Collections.shuffle(indicies);
for (int index : indicies) {
Thread.sleep(15);
publish(new LoadResult(index, "[" + index + "] Has been loaded"));
}
return null;
}
}
}
以上是线性进程,这意味着它按顺序处理每个项目,一次一个。
因为图像加载可能需要时间并且是 CPU 密集过程,您可以使用 ExecutorService
并使用线程池来帮助分散负载。
例如:
- Make images fetched from server display in real time
- Extracting images from a text file of 100 image urls using java
I find that when I use the listModel's fireDataChanged method (see below), all the visible items in the list are repainted
您不应手动调用该方法。 fireXXX(...) 方法只能由模型本身调用。
您应该使用以下方法更新模型:
model.set(...);
然后 set(...)
方法将调用适当的方法来通知 JList 重新绘制单元格。
这是我对一个简单的缩略图应用程序的尝试。它尝试通过以下方式增加性能改进:
- 使用默认图标加载模型,这样列表就不需要不断调整自身大小
- 使用 ExecutorService 以利用多个处理器
- 使用 ImageReader 读取文件。子采样 属性 允许您在缩放图像时使用更少的像素。
只需将 class 更改为指向包含一些 .jpg 文件的目录,然后试一试:
缩略图应用程序:
import java.io.*;
import java.util.concurrent.*;
import java.awt.*;
//import java.awt.datatransfer.*;
import java.awt.event.*;
import javax.swing.*;
class ThumbnailApp
{
private DefaultListModel<Thumbnail> model = new DefaultListModel<Thumbnail>();
private JList<Thumbnail> list = new JList<Thumbnail>(model);
public ThumbnailApp()
{
}
public JPanel createContentPane()
{
JPanel cp = new JPanel( new BorderLayout() );
list.setCellRenderer( new ThumbnailRenderer<Thumbnail>() );
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
list.setVisibleRowCount(-1);
Icon empty = new EmptyIcon(160, 160);
Thumbnail prototype = new Thumbnail(new File("PortugalSpain-000.JPG"), empty);
list.setPrototypeCellValue( prototype );
cp.add(new JScrollPane( list ), BorderLayout.CENTER);
return cp;
}
public void loadImages(File directory)
{
new Thread( () -> createThumbnails(directory) ).start();
}
private void createThumbnails(File directory)
{
try
{
File[] files = directory.listFiles((d, f) -> {return f.endsWith(".JPG");});
int processors = Runtime.getRuntime().availableProcessors();
ExecutorService service = Executors.newFixedThreadPool( processors - 2 );
long start = System.currentTimeMillis();
for (File file: files)
{
Thumbnail thumbnail = new Thumbnail(file, null);
model.addElement( thumbnail );
// new ThumbnailWorker(file, model, model.size() - 1).execute();
service.submit( new ThumbnailWorker(file, model, model.size() - 1) );
}
long duration = System.currentTimeMillis() - start;
System.out.println(duration);
service.shutdown();
}
catch(Exception e) { e.printStackTrace(); }
}
private static void createAndShowGUI()
{
ThumbnailApp app = new ThumbnailApp();
JFrame frame = new JFrame("ListDrop");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane( app.createContentPane() );
frame.setSize(1600, 900);
frame.setVisible(true);
// File directory = new File("C:/Users/netro/Pictures/TravelSun/2019_01_Cuba");
File directory = new File("C:/Users/netro/Pictures/TravelAdventures/2018_PortugalSpain");
app.loadImages( directory );
}
public static void main(String[] args)
{
javax.swing.SwingUtilities.invokeLater(() -> createAndShowGUI());
}
}
ThumbnailWorker:
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.Iterator;
//import java.util.concurrent.*;
import javax.imageio.*;
import javax.imageio.stream.*;
import javax.swing.*;
class ThumbnailWorker extends SwingWorker<Image, Void>
{
private File file;
private DefaultListModel<Thumbnail> model;
private int index;
public ThumbnailWorker(File file, DefaultListModel<Thumbnail> model, int index)
{
this.file = file;
this.model = model;
this.index = index;
}
@Override
protected Image doInBackground() throws IOException
{
// Image image = ImageIO.read( file );
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("jpg");
ImageReader reader = readers.next();
ImageReadParam irp = reader.getDefaultReadParam();
// irp.setSourceSubsampling(10, 10, 0, 0);
irp.setSourceSubsampling(5, 5, 0, 0);
ImageInputStream stream = new FileImageInputStream( file );
reader.setInput(stream);
Image image = reader.read(0, irp);
int width = 160;
int height = 90;
if (image.getHeight(null) > image.getWidth(null))
{
width = 90;
height = 160;
}
BufferedImage scaled = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = scaled.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.drawImage(image, 0, 0, width, height, null);
g2d.dispose();
image = null;
return scaled;
}
@Override
protected void done()
{
try
{
ImageIcon icon = new ImageIcon( get() );
Thumbnail thumbnail = model.get( index );
thumbnail.setIcon( icon );
model.set(index, thumbnail);
System.out.println("finished: " + file);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
缩略图渲染器
import java.awt.Component;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class ThumbnailRenderer<E> extends JLabel implements ListCellRenderer<E>
{
public ThumbnailRenderer()
{
setOpaque(true);
setHorizontalAlignment(CENTER);
setVerticalAlignment(CENTER);
setHorizontalTextPosition( JLabel.CENTER );
setVerticalTextPosition( JLabel.BOTTOM );
setBorder( new EmptyBorder(4, 4, 4, 4) );
}
/*
* Display the Thumbnail Icon and file name.
*/
public Component getListCellRendererComponent(JList<? extends E> list, E value, int index, boolean isSelected, boolean cellHasFocus)
{
if (isSelected)
{
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
else
{
setBackground(list.getBackground());
setForeground(list.getForeground());
}
//Set the icon and filename
Thumbnail thumbnail = (Thumbnail)value;
setIcon( thumbnail.getIcon() );
setText( thumbnail.getFileName() );
return this;
}
}
缩略图:
import java.io.File;
import javax.swing.Icon;
public class Thumbnail
{
private File file;
private Icon icon;
public Thumbnail(File file, Icon icon)
{
this.file = file;
this.icon = icon;
}
public Icon getIcon()
{
return icon;
}
public void setIcon(Icon icon)
{
this.icon = icon;
}
public String getFileName()
{
return file.getName();
}
}
我在一个包含 302 个图像的目录上进行了测试。使用 ExecutorService 将加载时间从 2:31 减少到 0:35。