简体   繁体   English

JList重绘单个元素

[英]JList repaint single element

I've got a JList whose elements consist of image files for which I'm creating thumbnails (in a background Thread).我有一个 JList,其元素由我正在为其创建缩略图的图像文件组成(在后台线程中)。 When these thumbnails become available, I'd like to force a repaint of just that item.当这些缩略图可用时,我想强制重新绘制项目。 However, I find that when I use the listModel's fireDataChanged method (see below), all the visible items in the list are repainted (using my custom ListCellRenderer).但是,我发现当我使用 listModel 的 fireDataChanged 方法(见下文)时,列表中的所有可见项都被重新绘制(使用我的自定义 ListCellRenderer)。

public void updateElement(int index) {
    frame.listModel.fireContentsChanged(frame.listModel, index, index);
}

Is there any way to cause ONLY the indexed item to be repainted?有没有办法只重新绘制索引项?

Without some kind of runnable example which demonstrates your issue, it's impossible to make any concrete recommendations.如果没有某种可运行的示例来证明您的问题,就不可能提出任何具体的建议。

The following simple example makes use of a SwingWorker to change the value of the elements within the ListModel .以下简单示例使用SwingWorker更改ListModel中元素的值。 To make it look more realistic, I've shuffled the List of indices and applied a short delay between each.为了让它看起来更真实,我对索引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;
        }

    }
}

The above is a linear progression, meaning it's processing each item in sequence, one at a time.以上是一个线性进程,这意味着它按顺序处理每个项目,一次一个。

Because image loading can take time and is CPU intensive process, you could make use of a ExecutorService and use a pool of threads to help spread the load.由于图像加载可能需要时间并且是 CPU 密集型过程,因此您可以使用ExecutorService并使用线程池来帮助分散负载。

For example:例如:

I find that when I use the listModel's fireDataChanged method (see below), all the visible items in the list are repainted我发现当我使用 listModel 的 fireDataChanged 方法(见下文)时,列表中的所有可见项都被重新绘制

You should NOT invoke that method manually.您不应该手动调用该方法。 The fireXXX(...) methods should only be invoked by the model itself. fireXXX(...) 方法只能由 model 本身调用。

You should be updating the model by using the:您应该使用以下命令更新 model:

model.set(...);

The set(...) method will then invoke the appropriate method to notify the JList to repaint the cell.然后set(...)方法将调用适当的方法来通知 JList 重新绘制单元格。

Here is my attempt at a simple Thumbnail app.这是我对一个简单的缩略图应用程序的尝试。 It attempts to add performance improvements by:它试图通过以下方式增加性能改进:

  1. loading the model with a default Icon so the list doesn't continually need to resize itself使用默认图标加载 model,因此列表不需要不断调整自身大小
  2. Use a ExecutorService to take advantage of multiple processors使用 ExecutorService 来利用多个处理器
  3. Using an ImageReader to read the file.使用 ImageReader 读取文件。 The sub sampling property allows you to use fewer pixels when scaling the image.子采样属性允许您在缩放图像时使用更少的像素。

Just change the class to point to a directory containing some.jpg files and give it a go:只需将 class 更改为指向包含 some.jpg 文件的目录并为其指定 go:

ThumbnailApp:缩略图应用程序:

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();
       }
   }
}

ThumbnailRenderer缩略图渲染器

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;
    }
}

Thumbnail:缩略图:

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();
    }
}

I tested on a directory with 302 images.我在一个包含 302 个图像的目录上进行了测试。 Using the ExecutorService got the load time down from 2:31 to 0:35.使用 ExecutorService 将加载时间从 2:31 缩短到 0:35。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM