В моем приложении есть компонент файлового браузера (TableView), и я загружаю значки файлов с помощью FileSystemView.getFileSystemView().getSystemIcon()
.Теперь это довольно медленно, так как мне также нужно преобразовать значок в BufferedImage, а в JavaFX Image.Поэтому мне нужно было выдвинуть это все в фоновые потоки.Я создал IconLoadingTask, который загружает один значок - и во время updateItem()
TableView я помещаю эти IconLoadingTasks в ExecutorService.Это работает, но может быть улучшено.
Проблема, с которой я столкнулся, заключается в том, что если в папке много значков, а пользователь быстро перетаскивает полосу прокрутки, загружается множество «лишних» значков, что приводит к остановке потока (-ов).) и приводит к тому, что часто не видны значимые в то время значки: те, которые в настоящее время отображаются в TableView.
У кого-нибудь есть идеи, как решить эту проблему?Я думал о доступе к вертикальной полосе прокрутки TableView и прослушивании ее (и, возможно, обновлении только после того, как прокрутка была выпущена), но я не уверен, как даже получить доступ к полосе прокрутки ... и, возможно, есть более простое решение, которое ускользает от меня.
РЕДАКТИРОВАТЬ:
Ну, хорошо.Я создал минимальный, полный и проверяемый пример из своего кода Kotlin в код Java, и я больше не могу воспроизвести «эффект».Похоже, сейчас работает так, как я и предполагал.Кажется, мне просто нужно выяснить, что «лишнего» я делаю в коде моего основного приложения.В любом случае вот примерный рабочий код.
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.filechooser.FileSystemView;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class IconLoadTest extends Application {
public static class FileEntry {
public Path path;
private Image icon;
public FileEntry(Path path) {
this.path = path;
}
synchronized void setIcon(Image icon) {
this.icon = icon;
}
synchronized Image getIcon() {
return icon;
}
public void setPath(Path path) {
this.path = path;
}
public Path getPath() {
return path;
}
}
class MyTableCell extends TableCell<FileEntry, Path> {
@Override
protected void updateItem(Path item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText("");
setGraphic(null);
return;
}
setText(item.getFileName().toString());
Image icon = dataMap.get(item).getIcon();
if (icon == null) {
setGraphic(null);
executor.execute(new IconLoadingTask(item));
} else {
setGraphic(new ImageView(icon));
}
}
}
class IconLoadingTask extends Task<Void> {
private Path path;
IconLoadingTask(Path path) {
this.path = path;
}
@Override
protected Void call() {
FileEntry entry = dataMap.get(path);
if (entry.icon != null) {
return null;
}
entry.setIcon(getIcon(path.toFile()));
// Refresh currently visible items
Platform.runLater(() -> table.refresh());
return null;
}
}
private TableView<FileEntry> table = new TableView<>();
// Table data
private HashMap<Path, FileEntry> dataMap = new HashMap<>();
private List<FileEntry> data = FXCollections.observableArrayList();
private ExecutorService executor = Executors.newFixedThreadPool(1);
@Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
root.getChildren().add(table);
TableColumn<FileEntry, Path> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("path"));
table.getColumns().add(nameCol);
nameCol.setCellFactory(tableColumn -> new MyTableCell());
// Sort so that dirs come first
nameCol.setComparator((o1, o2) -> {
if (Files.isDirectory(o1) && !Files.isDirectory(o2)) {
return -1;
} else if (!Files.isDirectory(o1) && Files.isDirectory(o2)) {
return 1;
} else {
return o1.toString().toLowerCase().compareTo(o2.toString().toLowerCase());
}
});
// Set to a directory with lots of files (e.g. System32 on Windows)
String directory = "C:\\Windows\\System32\\";
// Load files from directory, and create entries for table
Path dir = Paths.get(directory);
List<Path> files = listContents(dir);
for (Path p : files) {
FileEntry entry = new FileEntry(p);
data.add(entry);
dataMap.put(p, entry);
}
table.setItems((ObservableList<FileEntry>) data);
// Sort
table.getSortOrder().add(table.getColumns().get(0));
table.sort();
// Display the app
Scene scene = new Scene(root, 600, 480);
primaryStage.setTitle("Icon Background Loading");
primaryStage.setScene(scene);
primaryStage.show();
}
// Gets directory contents
private static List<Path> listContents(Path directory) {
ArrayList<Path> paths = new ArrayList<>();
try {
Files.newDirectoryStream(directory).forEach(paths::add);
} catch (IOException ex) {
ex.printStackTrace();
}
return paths;
}
// Gets a system icon for a file
private static Image getIcon(File file) {
Icon ico = FileSystemView.getFileSystemView().getSystemIcon(file);
java.awt.Image awtImage = ((ImageIcon) ico).getImage();
BufferedImage bImg;
if (awtImage instanceof BufferedImage) {
bImg = (BufferedImage) awtImage;
} else {
bImg = new BufferedImage(
awtImage.getWidth(null),
awtImage.getHeight(null),
BufferedImage.TYPE_INT_ARGB
);
Graphics graphics = bImg.createGraphics();
graphics.drawImage(awtImage, 0, 0, null);
graphics.dispose();
}
return SwingFXUtils.toFXImage(bImg, null);
}
public static void main(String[] args) {
Application.launch(IconLoadTest.class, args);
}
}