JavaFX loading system icons in a background thread - java

I have a file browser component in my app (TableView), and I load the icons of files via FileSystemView.getFileSystemView().getSystemIcon(). Now, this is quite slow as I also need to convert the icon to BufferedImage, and that to JavaFX Image. So I needed to push this all to background thread(s). I created an IconLoadingTask, that loads a single icon - and during updateItem() of TableView I push these IconLoadingTasks to ExecutorService. It works, but could be improved.
The problem I have is that if the folder has lots of icons, and the user quickly drags the scrollbar, lots of "redundant" icons are loaded, stalling the thread(s) and resulting in often not seeing the icons that matter at the time: the ones that are currently displayed in the TableView.
Anyone have ideas how to solve this issue? I was thinking of accessing the vertical scrollbar of TableView and listening to it (and perhaps only update after scroll has been released), but I'm not sure how to even access the scrollbar... and maybe there's a simpler solution that escapes me.
EDIT:
Well, well. I created a minimal, complete and verifiable example out of my Kotlin code into Java code, and I cannot reproduce the "effect" anymore. It appears to work now as I intended it to in the first place. So it seems I just have to figure what "extra" I am doing in my main application's code. Anyways, here's the apparently working sample code.
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);
}
}

Related

Java: Screen has not been loaded

I created a JavaFX application that is nearly completed. I exported it as a runnable JAR. When opening this JAR I only see a blank window. i followed some other answers from stackoverflow but I did not get it working.
It works only in the Eclipse IDE!
My screens controller:
package gui;
import java.io.File;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
public class ScreensController extends StackPane {
private HashMap<String, Node> screens = new HashMap<>();
public static String sourcePath = "";
private CoreService coreService;
public ScreensController(){
super();
}
public void addScreen(String name, Node screen) {
screens.put(name, screen);
}
public boolean loadScreen(String name, String resource) {
System.out.println("ID: "+name);
System.out.println("Resource: "+resource);
String file = System.getProperty("user.dir")+"\\bin\\"+resource;
// System.out.println(file);
try {
FXMLLoader myLoader = new FXMLLoader();
File f = new File(file);
URL url = f.toURI().toURL();
myLoader.setLocation(url);
// System.out.println("Location: "+myLoader.getLocation());
Parent loadScreen = (Parent) myLoader.load();
ControlledScreen myScreenControler =
((ControlledScreen) myLoader.getController());
myScreenControler.setScreenParent(this);
addScreen(name, loadScreen);
System.out.println("Anzahl Screens: "+screens.size());
return true;
}catch(Exception e) {
System.out.println("Fehler beim Laden von "+file);
System.out.println(e.getMessage());
return false;
}
}
public boolean setScreen(final String name) {
#SuppressWarnings("unused")
Node screenToRemove;
if(screens.get(name) != null){ //screen loaded
if(!getChildren().isEmpty()){ //if there is more than one screen
getChildren().add(0, screens.get(name)); //add the screen
screenToRemove = getChildren().get(1);
getChildren().remove(1); //remove the displayed screen
}else{
getChildren().add(screens.get(name)); //no one else been displayed, then just show
}
return true;
}else {
System.out.println("Screen hasn't been loaded!!! \n");
return false;
}
}
public boolean unloadScreen(String name) {
if(screens.remove(name) == null) {
System.out.println("Screen didn't exist!!!");
return false;
} else {
return true;
}
}
public void print() {
Set<String> keys = screens.keySet();
Iterator<String> it = keys.iterator();
while (it.hasNext()){
System.out.println("Key: "+it.next());
}
}
public CoreService getCoreService(){
return this.coreService;
}
public void setCoreService(CoreService coreService){
this.coreService = coreService;
}
}
And here I use it:
package gui;
import java.util.Optional;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
public class MainMenu extends Application {
private Stage mainStage;
private static CoreService coreService;
public static final String MAIN_SCREEN = "main";
public static final String MAIN_SCREEN_FXML = "gui\\MainMenu.fxml";
#Override
public void start(Stage primaryStage) {
this.mainStage = primaryStage;
ScreensController mainContainer = new ScreensController();
mainContainer.loadScreen(MainMenu.MAIN_SCREEN, MainMenu.MAIN_SCREEN_FXML);
mainContainer.setCoreService(MainMenu.coreService);
mainContainer.setScreen(MainMenu.MAIN_SCREEN);
Group root = new Group();
root.getChildren().addAll(mainContainer);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setOnCloseRequest(confirmCloseEventHandler);
primaryStage.show();
}
private EventHandler<WindowEvent> confirmCloseEventHandler = event -> {
//Source: http://stackoverflow.com/questions/29710492/javafx-internal-close-request
Alert closeConfirmation = new Alert(
Alert.AlertType.CONFIRMATION,
"Are you sure you want to exit?"
);
Button exitButton = (Button) closeConfirmation.getDialogPane().lookupButton(
ButtonType.OK
);
exitButton.setText("Exit");
closeConfirmation.setHeaderText("Confirm Exit");
closeConfirmation.initModality(Modality.APPLICATION_MODAL);
closeConfirmation.initOwner(mainStage);
closeConfirmation.setX(mainStage.getX() + 150);
closeConfirmation.setY(mainStage.getY() - 300 + mainStage.getHeight());
Optional<ButtonType> closeResponse = closeConfirmation.showAndWait();
if (!ButtonType.OK.equals(closeResponse.get())) {
event.consume();
}
};
public static void main(String[] args, CoreService aService) {
// Weitergeben des CoreServices
coreService = aService;
launch(args);
}
}
I do not see where the error is.
When I start the program from command line it says that the MainMenu.fxml file could not been found.
In my application it is in the package gui. -> gui/MainMenu.fxml
Would be nice if someone find my error!
What the error message tells you, that the FXML file cannot be located.
You could try to:
Change this ...
public static final String MAIN_SCREEN_FXML = "gui\\MainMenu.fxml";
... to ...
public static final String MAIN_SCREEN_FXML = "/gui/MainMenu.fxml";
And to change this ...
FXMLLoader myLoader = new FXMLLoader();
File f = new File(file);
URL url = f.toURI().toURL();
myLoader.setLocation(url);
... to (and you don't need the variables file and f)...
FXMLLoader myLoader = new FXMLLoader(getClass().getResource(resource));
Some references:
I had an answer here on how to use getResource.
Also you can check the documentation of getResource()
And you can check this question about loading resources from a JAR

Complete JFXPanel to image

I want to take a snapshot of a website without any video, so it's just plain text with some css and pictures.
I am using a WebView (which is the scene of the JFXPanel) to load the website and then save it via
WritableImage image = scene.snapshot(new WritableImage(1920, 1080));
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(image, null);
ImageIO.write(bufferedImage, "png", file);
(where "scene" is the scene of the JFXPanel)
but the saved image just displays a part of the website, instead of the complete content (see picture).
How do I ensure/enforce that the dimensions of the image matches the dimensions of the JFXPanel content and everthing is visible?
Complete Code:
package renderer;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.embed.swing.JFXPanel;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.WritableImage;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class HtmlRenderer extends Application {
private JFXPanel jfxPanel;
private WebView webView;
public void start(Stage stage) {
jfxPanel = new JFXPanel();
webView = new WebView();
webView.getEngine().getLoadWorker().stateProperty().addListener(
new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue ov, Worker.State oldState, Worker.State newState) {
if (newState == Worker.State.SUCCEEDED) {
HtmlRenderer.this.toImage(jfxPanel.getScene());
try {
Platform.exit();
HtmlRenderer.this.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
jfxPanel.setScene(new Scene(webView));
this.updateView("http://www.stackoverflow.com/");
}
private void toImage(Scene scene) {
WritableImage image = scene.snapshot(new WritableImage(1920, 1080));
// TODO: save in matching dir using proper filename
File file = new File("D:/workspace/SiteChecker/test.png");
try {
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(image, null);
ImageIO.write(bufferedImage, "png", file);
} catch (IOException e) {
// TODO: exception handling
}
}
public void updateView(String url) {
webView.getEngine().load(url);
}
private void reloadView() {
webView.getEngine().reload();
}
}
So I found a solution, but it's far from perfect and not really perfomant.
However, nothing else works for me.
The trick is to load the website once, determine width and height of the site. The second time I set the preferred size of the Webview to the determined values and load the website again with the new size. I think it's because the first time only the visible part is rendered.
The width and height can be determined with javascript, e.g.:
private int getPageWidth(WebView webView) {
String script = "Math.max(" +
"document.body.scrollWidth, document.body.offsetWidth," +
"document.documentElement.clientWidth, document.documentElement.scrollWidth," +
"document.documentElement.offsetWidth );";
WebEngine engine = webView.getEngine();
int maxWidth = (int) engine.executeScript(script);
return maxWidth;
}
For some reason some websites have a funny end/bottom i.e. there is a lot of empty space.

TreeTableView selection contains null values after source change

A list of basic values is filtered by a (changing) predicate. The FilteredList is mapped to TreeItems and this resulting list is then used as the root TreeItems children.
When a selection was made on the TreeTableView and afterwards the predicate changes, accessing the selected items results in a NullPointerException.
It seems to me that items contained in the change are null. Is there a design flaw in this coarse concept?
This does not happen for the classes TreeView and ListView.
I tried to produce a MCVE using https://github.com/TomasMikula/EasyBind for the mapping:
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.fxmisc.easybind.EasyBind;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.Spinner;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application {
// fields protect bound lists from GC
private ObservableList<DataItem> itemizedDataPool;
private FilteredList<Data> filteredDataPool;
private ObservableList<Data> selectedData;
static class Data {
final int value;
public Data(int value) {
this.value = value;
}
}
static class DataItem extends TreeItem<Data> {
final Data data;
public DataItem(Data data) {
this.data = data;
}
}
#Override
public void start(Stage primaryStage) throws IOException {
List<Data> dataPool = new ArrayList<Data>();
for (int i = 1; i < 20; i++) {
dataPool.add(new Data(i));
}
filteredDataPool = new FilteredList<>(FXCollections.observableArrayList(dataPool));
TreeTableView<Data> listView = createTreeTableView();
Spinner<?> lowerBoundSelector = createLowerBoundFilter();
Label sumLabel = createSummarizingLabel(listView.getSelectionModel().getSelectedItems());
Parent root = new VBox(listView, lowerBoundSelector, sumLabel);
Scene scene = new Scene(root, 768, 480);
primaryStage.setScene(scene);
primaryStage.show();
}
private TreeTableView<Data> createTreeTableView() {
itemizedDataPool = EasyBind.map(filteredDataPool, DataItem::new);
TreeItem<Data> itemRoot = new TreeItem<>();
Bindings.bindContent(itemRoot.getChildren(), itemizedDataPool);
TreeTableView<Data> listView = new TreeTableView<>(itemRoot);
listView.setShowRoot(false);
itemRoot.setExpanded(true);
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
listView.getColumns().add(new TreeTableColumn<>("Data"));
return listView;
}
private Label createSummarizingLabel(ObservableList<TreeItem<Data>> selectedItems) {
Label sumLabel = new Label();
selectedData = EasyBind.map(selectedItems, (TreeItem<Data> t) -> ((DataItem) t).data);
selectedData.addListener(new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
int sum = 0;
for (Data d : selectedData) {
sum += d.value;
}
sumLabel.setText("Sum: " + sum);
}
});
return sumLabel;
}
private Spinner<Integer> createLowerBoundFilter() {
Spinner<Integer> lowerBoundSelector = new Spinner<>(0, 20, 0, 1);
lowerBoundSelector.valueProperty().addListener(new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
filteredDataPool.setPredicate(t -> t.value > lowerBoundSelector.getValue());
}
});
return lowerBoundSelector;
}
public static void main(String[] args) {
launch(args);
}
}
Problem
TreeTableView uses TreeTableViewArrayListSelectionModel, which extends MultipleSelectionModelBase, which uses ReadOnlyUnbackedObservableList, which uses (and contains) SelectionListIterator, which has a broken implementation for its method nextIndex.
Thanks to fabian for pointing that out.
He also filed a bug report (http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8145887).
Workaround
Using a buffer in between could provide an effective workaround for the problem above. I tried several approaches. setAll on selection invalidation and Bindings.bindContent do not work. In both cases I received null values in the list. The straightforward "solution" is to simply filter the nulls out. This leads to the inefficient but apparently effective code below.
// [...]
TreeTableView<Data> listView = createTreeTableView();
selectionBuffer = FXCollections.observableArrayList();
listView.getSelectionModel().getSelectedItems().addListener(new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
selectionBuffer.clear();
for (TreeItem<Data> t : listView.getSelectionModel().getSelectedItems()) {
if (t != null) {
selectionBuffer.add(t);
}
}
}
});
// [...]
Using selectionBuffer instead of listView.getSelectionModel().getSelectedItems() should now compensate the implementation problem in nextIndex.

PieChart (JavaFx) didn't display my Label on Event - JavaFx

I try to create a label on click for my PieChart, but unfortunately my label is never visible.
I found a similar topic on StackOverFlow : Label not showing on mouse event JavaFx
But my application is not as simple. I can't add my Label to the list of children because of my architecture.
(You can found a diagram here : http://i.stack.imgur.com/ZFJaR.png )
Here my code :
PieChartNode.java
package nodeStatsVision.chartFactory;
import java.util.ArrayList;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import nodeStatsVision.beans.ListRepere;
import nodeStatsVision.beans.OptionsChart;
import nodeStatsVision.beans.ValueStat;
/**
*
* #author Zombkey.
*/
public class PieChartNode implements ChartNode {
private ListRepere categories;
private ArrayList<ValueStat> values;
private ObservableList<PieChart.Data> pieChartData;
private Node node;
public PieChartNode(ListRepere categories, ArrayList<ValueStat> values){
this.categories = categories;
this.values = values;
pieChartData = FXCollections.observableArrayList();
node = new PieChart(pieChartData);
Platform.runLater(new Runnable() {
#Override
public void run() {
formatData();
}
});
}
private void formatData() {
final Label caption = new Label("");
caption.setTextFill(Color.DARKORANGE);
caption.setStyle("-fx-font: 24 arial;");
for(ValueStat v : values){
PieChart.Data dataTemp = new PieChart.Data(v.getCategorie().getStringName(),v.getDoubleValue());
pieChartData.add(dataTemp);
dataTemp.getNode().addEventHandler(MouseEvent.MOUSE_CLICKED,
new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent e) {
System.out.println("event : "+v.getCategorie().getStringName()+" : "+v.getDoubleValue());
caption.setTranslateX(e.getSceneX());
caption.setTranslateY(e.getSceneY());
caption.setText(String.valueOf(dataTemp.getPieValue()));
caption.setVisible(true);
System.out.println("label "+caption);
}
});
}
}
#Override
public Node getNodeGraph() {
return node;
}
#Override
public void setOptions(OptionsChart optionsChart) {
//To implemente
}
}
Have you a idea about, how add my Label to the scene ?
Thanks !
(Other question, Why the Node of PieChart.Data is on ReadOnly ?)
Zombkey.
PS : Sorry about my english, I'm a French student, I'm still learning :)
Ps 2 : First time on StackOverflow, if I did mistake, tell me it !
Ok ! I found a solution for my case !
Semantically my Label is only for my PieChart. That's why I don't want had it to my SceneGraph.
My ChartFactory return a Node, then display it. So my node have to contain the PieChart AND the Label.
I create a Group with a StackPane. In the StackPane I add my PieChart and my Label. Then my factory return the Group as a Node.
Drop the code !
package nodeStatsVision.chartFactory;
import java.util.ArrayList;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import nodeStatsVision.beans.ListRepere;
import nodeStatsVision.beans.OptionsChart;
import nodeStatsVision.beans.ValueStat;
/**
*
* #author Zombkey.
*/
public class PieChartNode implements ChartNode {
private ListRepere categories;
private ArrayList<ValueStat> values;
private ObservableList<PieChart.Data> pieChartData;
private Group group;
private Node node;
private final Label caption;
public PieChartNode(ListRepere categories, ArrayList<ValueStat> values){
this.categories = categories;
this.values = values;
group = new Group();
StackPane pane = new StackPane();
group.getChildren().add(pane);
pieChartData = FXCollections.observableArrayList();
node = new PieChart(pieChartData);
pane.getChildren().add(node);
caption = new Label("");
caption.setVisible(false);
caption.getStyleClass().addAll("chart-line-symbol", "chart-series-line");
caption.setStyle("-fx-font-size: 12; -fx-font-weight: bold;");
caption.setMinSize(Label.USE_PREF_SIZE, Label.USE_PREF_SIZE);
pane.getChildren().add(caption);
Platform.runLater(new Runnable() {
#Override
public void run() {
formatData();
}
});
}
private void formatData() {
for(ValueStat v : values){
PieChart.Data dataTemp = new PieChart.Data(v.getCategorie().getStringName(),v.getDoubleValue());
pieChartData.add(dataTemp);
dataTemp.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED,
new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent e) {
caption.setTranslateX(e.getX());
caption.setTranslateY(e.getY());
caption.setText(String.valueOf(dataTemp.getPieValue()));
caption.setVisible(true);
}
});
dataTemp.getNode().addEventHandler(MouseEvent.MOUSE_EXITED,
new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent e) {
caption.setVisible(false);
}
});
}
}
#Override
public Node getNodeGraph() {
return (Node)group;
}
#Override
public void setOptions(OptionsChart optionsChart) {
//To implemente
}
}
Thanks #eckig for your answers !
You create and style your Label named caption but never add it to the SceneGraph.
Somewhere it has to be added to a Parent element, otherwise it will not get displayed.
Your PieChart gets added to a parent element, otherwise it will not be displayed. The same way goes for all other JavaFX Nodes.
As to your second question, read the JavaDocs:
Readonly access to the node that represents the pie slice. You can use this to add mouse event listeners etc.
You could use Tooltip to display a value:
for (final PieChart.Data temp : pieChart.getData()) {
Tooltip tooltip = new Tooltip(String.valueOf(temp.getPieValue()));
Tooltip.install(temp.getNode(), tooltip);
}

fileChooser.showOpenMultipleDialog() tooo slow and hang when choosing lot of files

I choose about eight-hundred image files, and each has a 5 MB size using the following code:
List<File> flist = fileChooser.showOpenMultipleDialog(label.getScene().getWindow());
When I click open, the filechooser and the main window freeze (and do not respond).
What should I do to ameliorate this problem? (I don't want to use Swing.)
My code is for adding image file names list to a tableView. Not for loading image. After running this code, I am getting output in the terminal:
Total Files added: 800.
But filechooser and the main window freeze (and do not respond)
public void addImage()
{
int i = 0;
List<File> list = fileChooser.showOpenMultipleDialog(label.getScene().getWindow());
if (list != null) {
for (File f : list) {
data.add( new ImgInfo(1 + data.size() + "", f));
i++;
}
System.out.println("Total Files added: " + i);
}
}
I assume that you are working on an image processing project where you don't actually need to view all the input images. I would suggest the following approach if all the files are in a single folder:
public void filesInFolder(File folder) {
for (File file : folder.listFiles()) {
if (fileEntry.isFile()) {
// Open File Here
}
}
}
fileChooser.showOpenMultipleDialog() does not hang and is not too slow when I use it.
Here is a sample application I used. Choosing a few thousand files and displaying their names in a ListView took less than a second. Test environment: JavaFX 8u20, Windows 7, 6 year old PC.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import java.io.File;
import java.util.List;
public class SelectedFileInfoViewer extends Application {
#Override public void start(final Stage stage) throws Exception {
final ListView<File> chosenFilesView = new ListView<>();
final Button chooseFilesButton = new Button("Choose Files");
chooseFilesButton.setOnAction(event -> {
List<File> files = new FileChooser().showOpenMultipleDialog(stage);
if (files != null) {
chosenFilesView.getItems().setAll(files);
}
});
final Label numFilesChosen = new Label();
numFilesChosen.textProperty().bind(
Bindings.concat(
"Number of Files: ",
Bindings.size(
chosenFilesView.getItems()
).asString()
)
);
VBox layout = new VBox(
10,
chooseFilesButton,
chosenFilesView,
numFilesChosen
);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) { launch(args); }
}

Categories