JxBrowser: How to implement 'save link as' within a ContextMenuHandler - jxbrowser

I want to implement a 'save link as' option within a ContextMenuHandler.
But there is no browser function like loadURL(...) to force the Browser component to just start a download for the provided link.

__________________ EDITED__________________________
import com.teamdev.jxbrowser.chromium.Browser;
import com.teamdev.jxbrowser.chromium.ContextMenuHandler;
import com.teamdev.jxbrowser.chromium.ContextMenuParams;
import com.teamdev.jxbrowser.chromium.SavePageType;
import com.teamdev.jxbrowser.chromium.swing.BrowserView;
import java.awt.BorderLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
public class ContextMenuSample {
public static void main(String[] args) {
JPopupMenu.setDefaultLightWeightPopupEnabled(false);
Browser browser = new Browser();
BrowserView view = new BrowserView(browser);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(view, BorderLayout.CENTER);
frame.setSize(700, 500);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
browser.setContextMenuHandler(new MyContextMenuHandler(view));
browser.loadURL("http://www.google.com");
}
private static class MyContextMenuHandler implements ContextMenuHandler {
private final JComponent component;
private MyContextMenuHandler(JComponent parentComponent) {
this.component = parentComponent;
}
private static JMenuItem createMenuItem(String title, final Runnable action) {
JMenuItem reloadMenuItem = new JMenuItem(title);
reloadMenuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
action.run();
}
});
return reloadMenuItem;
}
public void showContextMenu(final ContextMenuParams params) {
final JPopupMenu popupMenu = new JPopupMenu();
final Browser browser = params.getBrowser();
popupMenu.add(createMenuItem("Save As", new Runnable() {
#Override
public void run() {
Browser browser2 = new Browser();
browser2.loadURL(params.getLinkURL());
browser2.saveWebPage(params.getLinkText(), ".", SavePageType.COMPLETE_HTML);
}
}));
final Point location = params.getLocation();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
popupMenu.show(component, location.x, location.y);
}
});
}
}
}

Related

How to Create a Custom Control in JavaFX

Here I created my own control, with a canvas and ScrollBar. I didn't manage to correctly intercept the keyboard shortcuts or key pressed. When adding another component to the scene (here a TextField, commented), no keyboard events are received. Maybe I missed something about the focus?
Also, I had to add some --export to Gradle, to avoid some errors with the module.
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.8'
}
repositories {
mavenCentral()
}
javafx {
version = "15.0.1"
modules = [ 'javafx.controls', 'javafx.graphics' ]
}
run {
jvmArgs = ['--add-exports=javafx.controls/com.sun.javafx.scene.control.behavior=ALL-UNNAMED',
'--add-exports=javafx.controls/com.sun.javafx.scene.control.inputmap=ALL-UNNAMED']
}
mainClassName = 'com.wisecoders.textpane.CustomControlApp'
package com.wisecoders.textpane;
import com.sun.javafx.scene.control.behavior.BehaviorBase;
import com.sun.javafx.scene.control.inputmap.InputMap;
import com.sun.javafx.scene.control.inputmap.KeyBinding;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.*;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class CustomControlApp extends Application {
private final BorderPane root = new BorderPane();
private final Scene scene = new Scene(root, 300, 250);
#Override
public void start(Stage stage) {
stage.setTitle("Sample Canvas");
root.setTop( new TextField());
MyControl control = new MyControl();
root.setCenter(control);
stage.setScene(scene);
stage.sizeToScene();
Platform.runLater( control::requestFocus );
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class MyControl extends Control {
private final StringBuilder buffer = new StringBuilder();
public MyControl(){
setPrefSize(400, 400 );
setFocusTraversable(true);
setOnMouseClicked(ev-> requestFocus());
requestFocus();
setOnKeyTyped(ev-> { if ( !ev.isShortcutDown() ){
addTextToBuffer( ev.getCharacter() );
}});
}
public void addTextToBuffer( String str ){
buffer.append( str );
((MyControlSkin)getSkin()).paintCanvas();
}
public String getText(){ return buffer.toString(); }
#Override protected Skin<?> createDefaultSkin() {
return new MyControlSkin(this);
}
}
class MyControlSkin extends SkinBase<MyControl> {
final BorderPane borderPane = new BorderPane();
final ScrollBar rightScroll = new ScrollBar();
final Canvas canvas = new Canvas();
final MyControlBehavior behavior;
public MyControlSkin(MyControl control) {
super(control);
behavior = new MyControlBehavior( control );
rightScroll.setOrientation(Orientation.VERTICAL);
borderPane.setRight( rightScroll );
borderPane.setCenter(canvas);
canvas.setWidth( 150);
canvas.setHeight( 150 );
getChildren().add( borderPane );
paintCanvas();
}
public void paintCanvas(){
GraphicsContext gr = canvas.getGraphicsContext2D();
gr.clearRect( 0,0, canvas.getWidth(), canvas.getHeight());
gr.setFill( Color.BLACK);
gr.fillText( "Buff:" + getSkinnable().getText(), 30, 20 );
}
#Override
public void dispose() {
super.dispose();
behavior.dispose();
getChildren().removeAll();
}
}
class MyControlBehavior extends BehaviorBase<MyControl> {
final InputMap<MyControl> inputMap;
public MyControlBehavior(MyControl control) {
super(control);
this.inputMap = createInputMap();
addDefaultMapping( inputMap, new InputMap.KeyMapping(new KeyBinding(KeyCode.C).shortcut().ctrl(), e-> copy() ) );
}
public void copy(){
final Clipboard clipboard = Clipboard.getSystemClipboard();
final ClipboardContent content = new ClipboardContent();
content.putString( getNode().getText() );
clipboard.setContent(content);
}
#Override
public InputMap<MyControl> getInputMap() {
return inputMap;
}
}
}

Is JavaFX Animation slow?

I have a simple animation consisting of resizing the stage and fading in/out on scene change.
There are two scenes sample.fxml and sample2.fxml, which are being toggled between.
Both the scenes have a single <Button> centered inside a <BorderPane> and calling the loadScene method.
SampleController:
package sample;
import javafx.animation.*;
import javafx.beans.value.WritableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Window;
import javafx.util.Duration;
import java.io.IOException;
public class SampleController {
public BorderPane rootPane;
double DURATION = 1000;
#FXML
public void initialize() {
rootPane.setStyle("-fx-background-color: teal;");
// Add fade in animation
FadeTransition fadeTransitionIn = new FadeTransition(Duration.millis(DURATION), rootPane);
fadeTransitionIn.setFromValue(0);
fadeTransitionIn.setToValue(1);
fadeTransitionIn.play();
}
public void loadScene(ActionEvent actionEvent) {
Window screen = rootPane.getScene().getWindow();
// Add fade out animation
FadeTransition fadeTransitionOut = new FadeTransition(Duration.millis(DURATION), rootPane);
fadeTransitionOut.setFromValue(1);
fadeTransitionOut.setToValue(0);
WritableValue<Double> writableHeight = new WritableValue<>() {
#Override
public Double getValue() {
return screen.getHeight();
}
#Override
public void setValue(Double value) {
screen.setHeight(value);
}
};
WritableValue<Double> writableWidth = new WritableValue<>() {
#Override
public Double getValue() {
return screen.getWidth();
}
#Override
public void setValue(Double value) {
screen.setWidth(value);
}
};
Timeline timeline = new Timeline();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO,
new KeyValue(writableWidth, screen.getWidth()),
new KeyValue(writableHeight, screen.getHeight())
),
new KeyFrame(Duration.millis(DURATION),
new KeyValue(writableWidth, 1000.0),
new KeyValue(writableHeight, 300.0)
)
);
timeline.setOnFinished(new EventHandler<>() {
#Override
public void handle(ActionEvent event) {
try {
screen.getScene().setRoot(FXMLLoader.load(getClass().getResource("sample2.fxml")));
} catch (IOException e) {
e.printStackTrace();
}
}
});
new ParallelTransition(timeline, fadeTransitionOut).play();
}
}
SampleController2:
package sample;
import javafx.animation.*;
import javafx.beans.value.WritableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.BorderPane;
import javafx.stage.Window;
import javafx.util.Duration;
import java.io.IOException;
public class SampleController2 {
public BorderPane rootPane;
double DURATION = 1000;
#FXML
public void initialize() {
rootPane.setStyle("-fx-background-color: blue;");
// Add fade in animation
FadeTransition fadeTransitionIn = new FadeTransition(Duration.millis(DURATION), rootPane);
fadeTransitionIn.setFromValue(0);
fadeTransitionIn.setToValue(1);
fadeTransitionIn.play();
}
public void loadScene(ActionEvent actionEvent) {
Window screen = rootPane.getScene().getWindow();
// Add fade out animation
FadeTransition fadeTransitionOut = new FadeTransition(Duration.millis(DURATION), rootPane);
fadeTransitionOut.setFromValue(1);
fadeTransitionOut.setToValue(0);
WritableValue<Double> writableHeight = new WritableValue<>() {
#Override
public Double getValue() {
return screen.getHeight();
}
#Override
public void setValue(Double value) {
screen.setHeight(value);
}
};
WritableValue<Double> writableWidth = new WritableValue<>() {
#Override
public Double getValue() {
return screen.getWidth();
}
#Override
public void setValue(Double value) {
screen.setWidth(value);
}
};
Timeline timeline = new Timeline();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO,
new KeyValue(writableWidth, screen.getWidth()),
new KeyValue(writableHeight, screen.getHeight())
),
new KeyFrame(Duration.millis(DURATION),
new KeyValue(writableWidth, 500.0),
new KeyValue(writableHeight, 500.0)
)
);
timeline.setOnFinished(new EventHandler<>() {
#Override
public void handle(ActionEvent event) {
try {
screen.getScene().setRoot(FXMLLoader.load(getClass().getResource("sample.fxml")));
} catch (IOException e) {
e.printStackTrace();
}
}
});
new ParallelTransition(timeline, fadeTransitionOut).play();
}
}
However, the animation is very choppy.
Is JavaFX really that slow?
Or am I doing something wrong?
Edit: I even removed (commented out) the fade in/out animation. It appears even just the resize animation is too slow and choppy.
Any suggestions to improve the animation performance are appreciated.

javafx treeitem file path

i have a question for this sample code.
import java.io.File;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class CopyOfTreeViewSample extends Application {
public static Image folderCollapseImage=new Image(ClassLoader.getSystemResourceAsStream("treeviewsample/folder.png"));
public static Image folderExpandImage=new Image(ClassLoader.getSystemResourceAsStream("treeviewsample/folder-open.png"));
public static Image fileImage=new Image(ClassLoader.getSystemResourceAsStream("treeviewsample/text-x-generic.png"));
public static Image rootImage = new Image(ClassLoader.getSystemResourceAsStream("treeviewsample/computer.png"));
private TreeView treeView;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setTitle("Sample");
stage.setWidth(300);
stage.setHeight(500);
VBox vbox = new VBox();
vbox.setLayoutX(20);
vbox.setLayoutY(20);
TreeItem<String> root = new SimpleFileTreeItem(Paths.get("C:\\Users\\Jake"), true);
treeView = new TreeView<String>(root);
Button b = new Button("Change");
b.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
// TODO Auto-generated method stub
////// How do i this section write code?//////
}
});
vbox.getChildren().addAll(treeView,b);
vbox.setSpacing(10);
((Group) scene.getRoot()).getChildren().add(vbox);
stage.setScene(scene);
stage.show();
}
public class SimpleFileTreeItem extends TreeItem<String> {
private boolean isRoot = false;
private boolean isFirstTimeChildren = true;
private boolean isFirstTimeLeaf = true;
private boolean isLeaf;
private boolean isDirectory;
public boolean isDirectory(){return(this.isDirectory);}
private String fullPath;
public String getFullPath(){return(this.fullPath);}
public SimpleFileTreeItem(Path f, Boolean flag) {
super(f.toString());
fullPath = f.toString();
isRoot = flag;
if(!isRoot)
{
if(Files.isDirectory(f))
{
isDirectory = true;
this.setGraphic(new ImageView(folderCollapseImage));
}
else
{
isDirectory = false;
this.setGraphic(new ImageView(fileImage));
}
//set the value
if(!fullPath.endsWith(File.separator)){
String value=f.toString();
int indexOf=value.lastIndexOf(File.separator);
if(indexOf>0){
this.setValue(value.substring(indexOf+1));
}else{
this.setValue(value);
}
}
this.addEventHandler(TreeItem.branchExpandedEvent(),new EventHandler(){
#Override
public void handle(Event e){
SimpleFileTreeItem source=(SimpleFileTreeItem)e.getSource();
if(source.isDirectory()&&source.isExpanded()){
ImageView iv=(ImageView)source.getGraphic();
iv.setImage(folderExpandImage);
}
}
});
this.addEventHandler(TreeItem.branchCollapsedEvent(),new EventHandler(){
#Override
public void handle(Event e){
SimpleFileTreeItem source=(SimpleFileTreeItem)e.getSource();
if(source.isDirectory()&&!source.isExpanded()){
ImageView iv=(ImageView)source.getGraphic();
iv.setImage(folderCollapseImage);
}
}
});
}
else
{
this.setExpanded(true);
if(Files.isDirectory(f))
{
isDirectory = true;
this.setGraphic(new ImageView(rootImage));
}
else
{
isDirectory = false;
this.setGraphic(new ImageView(fileImage));
}
}
}
#Override
public ObservableList<TreeItem<String>> getChildren() {
if (isFirstTimeChildren) {
isFirstTimeChildren = false;
/*
* First getChildren() call, so we actually go off and determine the
* children of the File contained in this TreeItem.
*/
super.getChildren().setAll(buildChildren(this));
}
return super.getChildren();
}
#Override
public boolean isLeaf() {
if (isFirstTimeLeaf) {
isFirstTimeLeaf = false;
File f = new File(fullPath);
isLeaf = f.isFile();
}
return isLeaf;
}
/**
* Returning a collection of type ObservableList containing TreeItems, which
* represent all children available in handed TreeItem.
*
* #param TreeItem
* the root node from which children a collection of TreeItem
* should be created.
* #return an ObservableList<TreeItem<File>> containing TreeItems, which
* represent all children available in handed TreeItem. If the
* handed TreeItem is a leaf, an empty list is returned.
*/
private ObservableList<TreeItem<String>> buildChildren(TreeItem<String> TreeItem) {
File f = new File(fullPath);
if (f != null && f.isDirectory()) {
File[] files = f.listFiles();
if (files != null) {
ObservableList<TreeItem<String>> children = FXCollections
.observableArrayList();
for (File childFile : files) {
children.add(new SimpleFileTreeItem(childFile.toPath(), false));
}
return children;
}
}
return FXCollections.emptyObservableList();
}
}
I want to select the file that corresponds to the path when the button is pressed .
my eclipse project path is c:\java\samplecode.
I was trying to solve by using Absolutepath the result is c:\java\samplecode\samplefile.txt
i want this path.(c:\Users\jake\samplefile.txt)
Thank you for advice and tips.
Using the setup you have, you should be able to do
b.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
SimpleFileTreeItem<String> selectedItem = (SimpleFileTreeItem)treeView.getSelectionModel().getSelectedItem();
if (selectedItem != null) {
String pathString = selectedItem.getFullPath();
}
}
});
(If you also do
b.disableProperty().bind(
Bindings.isNull(treeView.getSelectionModel().selectedItemProperty()));
then you can safely skip the check for null in the handler.)
I think a better approach would be to make SimpleFileTreeItem a TreeItem<Path>. Then you just keep the Path as the value of the tree item, and you can use a cell factory to just display the file name.
Here is an example using this approach. I took out the images (so it can be executed without relying on external resources) and also a lot of the other unnecessary code.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Collectors;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class CopyOfTreeViewSample extends Application {
private TreeView<Path> treeView;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
stage.setTitle("Sample");
stage.setWidth(300);
stage.setHeight(500);
VBox vbox = new VBox();
vbox.setPadding(new Insets(20));
TreeItem<Path> root = new SimpleFileTreeItem(
Paths.get(System.getProperty("user.home")));
root.setExpanded(true);
treeView = new TreeView<Path>(root);
treeView.setCellFactory(treeView -> new TreeCell<Path>() {
#Override
public void updateItem(Path path, boolean empty) {
super.updateItem(path, empty);
if (empty) {
setText(null);
} else {
setText(path.getFileName().toString());
}
}
});
Button b = new Button("Change");
b.disableProperty().bind(Bindings.isNull(treeView.getSelectionModel().selectedItemProperty()));
b.setOnAction(event -> {
Path selectedPath = treeView.getSelectionModel().getSelectedItem().getValue() ;
// do something with selectedPath...
System.out.println(selectedPath);
});
vbox.getChildren().addAll(treeView, b);
vbox.setSpacing(10);
Scene scene = new Scene(vbox);
stage.setScene(scene);
stage.show();
}
public class SimpleFileTreeItem extends TreeItem<Path> {
private boolean isFirstTimeChildren = true;
private boolean isFirstTimeLeaf = true;
private boolean isLeaf;
public boolean isDirectory() {
return Files.isDirectory(getValue());
}
public SimpleFileTreeItem(Path f) {
super(f);
}
#Override
public ObservableList<TreeItem<Path>> getChildren() {
if (isFirstTimeChildren) {
isFirstTimeChildren = false;
/*
* First getChildren() call, so we actually go off and determine
* the children of the File contained in this TreeItem.
*/
super.getChildren().setAll(buildChildren());
}
return super.getChildren();
}
#Override
public boolean isLeaf() {
if (isFirstTimeLeaf) {
isFirstTimeLeaf = false;
isLeaf = Files.exists(getValue()) && ! Files.isDirectory(getValue());
}
return isLeaf;
}
/**
* Returning a collection of type ObservableList containing TreeItems,
* which represent all children of this TreeITem.
*
*
* #return an ObservableList<TreeItem<File>> containing TreeItems, which
* represent all children available in this TreeItem. If the
* handed TreeItem is a leaf, an empty list is returned.
*/
private ObservableList<TreeItem<Path>> buildChildren() {
if (Files.isDirectory(getValue())) {
try {
return Files.list(getValue())
.map(SimpleFileTreeItem::new)
.collect(Collectors.toCollection(() -> FXCollections.observableArrayList()));
} catch (IOException e) {
e.printStackTrace();
return FXCollections.emptyObservableList();
}
}
return FXCollections.emptyObservableList();
}
}
}

JavaFX: How to show an animated ProgressIndicator while adding columns to a TableView?

Adding the TableColumn objects to my TableView is a lengthy operation in my application---it causes everything to freeze for 3-4 seconds. I would like to keep the UI responsive while this is happening, but this is exactly the kind of thing that must be done on the JavaFX application thread. Can anything be done?
package tableviewpausetest;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.WorkerStateEvent;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
*
* #author drmc
*/
public class TableViewPauseTest extends Application {
public static final int ROW_COUNT = 100;
public static final int COLUMN_COUNT = 80;
public static final ExecutorService executor = Executors.newSingleThreadExecutor();
private TableView<String> tableView = new TableView<>();
private Button button = new Button("Toggle Columns Visibility");
private ProgressIndicator progressIndicator = new ProgressIndicator();
private HBox buttonBox = new HBox(8);
private BorderPane borderPane = new BorderPane();
private Task task = null;
public void start(Stage primaryStage) {
this.tableView.setColumnResizePolicy(
TableView.CONSTRAINED_RESIZE_POLICY);
for (int i = 0; i < ROW_COUNT; ++i) {
this.tableView.getItems().add(":)");
}
this.button.setOnAction(new ToggleHandler(this));
this.buttonBox.getChildren().setAll(this.button);
this.borderPane.setCenter(this.tableView);
this.borderPane.setBottom(this.buttonBox);
Scene scene = new Scene(this.borderPane, 1024, 768);
primaryStage.setTitle("tableviewpausetest");
primaryStage.setScene(scene);
primaryStage.show();
}
private class ToggleHandler implements EventHandler<ActionEvent> {
private TableViewPauseTest app;
public ToggleHandler(TableViewPauseTest app) {
this.app = app;
}
#Override
public void handle(ActionEvent event) {
// Show the progress indicator.
this.app.buttonBox.getChildren().add(this.app.progressIndicator);
this.app.progressIndicator.setPrefHeight(this.app.button.getHeight());
// Ensure the columns exist.
if (this.app.tableView.getColumns().isEmpty()) {
for (int i = 0; i < COLUMN_COUNT; ++i) {
TableColumn<String, String> tableColumn = new TableColumn<>(
String.format("%s", i));
tableColumn.setVisible(false);
this.app.tableView.getColumns().add(tableColumn);
}
}
// Create and submit a concurrent task to toggle column visibility.
this.app.task = new ToggleTask(this.app);
this.app.task.setOnSucceeded(new ToggleSucceededHandler(this.app));
executor.submit(this.app.task);
}
}
private class ToggleSucceededHandler implements EventHandler<WorkerStateEvent> {
private TableViewPauseTest app;
public ToggleSucceededHandler(TableViewPauseTest app) {
this.app = app;
}
#Override
public void handle(WorkerStateEvent event) {
// Hide the progress indicator.
this.app.buttonBox.getChildren().remove(this.app.progressIndicator);
}
}
private class ToggleTask extends Task<String> {
private TableViewPauseTest app;
public ToggleTask(TableViewPauseTest app) {
this.app = app;
}
#Override
public String call() {
boolean newState = false;
String action = "hide";
if (this.app.tableView.getVisibleLeafColumns().isEmpty()) {
newState = true;
action = "show";
}
// This action must be performed on the JavaFX Application Thread,
// and it causes an extremely uncomfortable pause in my application.
Platform.runLater(new ToggleRunnable(this.app.tableView, newState));
return action;
}
}
private class ToggleRunnable implements Runnable {
private TableView<?> tableView;
private boolean newState;
public ToggleRunnable(TableView<?> tableView, boolean newState) {
this.tableView = tableView;
this.newState = newState;
}
#Override
public void run() {
for (TableColumn<?, ?> tableColumn : this.tableView.getColumns()) {
tableColumn.setVisible(this.newState);
}
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
I think this example code useful for you
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package progressbartablecelltest;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;
/**
*
* #author reegan
*/
public class ProgressBarTableCellTest extends Application {
public void start(Stage primaryStage) {
TableView<TestTask> table = new TableView<>();
Random rng = new Random();
for (int i = 0; i < 20; i++) {
table.getItems().add(
new TestTask(rng.nextInt(3000) + 2000, rng.nextInt(30) + 20));
}
TableColumn<TestTask, String> statusCol = new TableColumn("Status");
statusCol.setCellValueFactory(new PropertyValueFactory<TestTask, String>(
"message"));
statusCol.setPrefWidth(75);
TableColumn<TestTask, Double> progressCol = new TableColumn("Progress");
progressCol.setCellValueFactory(new PropertyValueFactory<TestTask, Double>(
"progress"));
progressCol
.setCellFactory(ProgressIndicatorTableCell.<TestTask>forTableColumn());
table.getColumns().addAll(statusCol, progressCol);
BorderPane root = new BorderPane();
root.setCenter(table);
primaryStage.setScene(new Scene(root));
primaryStage.show();
ExecutorService executor = Executors.newFixedThreadPool(table.getItems().size(), new ThreadFactory() {
#Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
});
for (TestTask task : table.getItems()) {
executor.execute(task);
}
}
public static void main(String[] args) { launch(args); }
static class TestTask extends Task<Void> {
private final int waitTime; // milliseconds
private final int pauseTime; // milliseconds
public static final int NUM_ITERATIONS = 100;
TestTask(int waitTime, int pauseTime) {
this.waitTime = waitTime;
this.pauseTime = pauseTime;
}
#Override
protected Void call() throws Exception {
this.updateProgress(ProgressIndicator.INDETERMINATE_PROGRESS, 1);
this.updateMessage("Waiting...");
Thread.sleep(waitTime);
this.updateMessage("Running...");
for (int i = 0; i < NUM_ITERATIONS; i++) {
updateProgress((1.0 * i) / NUM_ITERATIONS, 1);
Thread.sleep(pauseTime);
}
this.updateMessage("Done");
this.updateProgress(1, 1);
return null;
}
}
}
class ProgressIndicatorTableCell<S> extends TableCell<S, Double> {
public static <S> Callback<TableColumn<S, Double>, TableCell<S, Double>> forTableColumn() {
return new Callback<TableColumn<S, Double>, TableCell<S, Double>>() {
#Override
public TableCell<S, Double> call(TableColumn<S, Double> param) {
return new ProgressIndicatorTableCell<>();
}
};
}
private final ProgressIndicator progressIndicator;
private ObservableValue observable;
public ProgressIndicatorTableCell() {
this.getStyleClass().add("progress-indicator-table-cell");
this.progressIndicator = new ProgressIndicator();
setGraphic(progressIndicator);
}
#Override public void updateItem(Double item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
progressIndicator.progressProperty().unbind();
observable = getTableColumn().getCellObservableValue(getIndex());
if (observable != null) {
progressIndicator.progressProperty().bind(observable);
} else {
progressIndicator.setProgress(item);
}
setGraphic(progressIndicator);
}
}
}
Table Column Add with Progress Indicator

Get ListCell via ListView

I have a ListView with my own ListCell<MyObject> implementation. Via a network signal, I receive an index of my ListCell that should be changed.
Over listView.getItems().get(index); there is no problem to access the model, but I want to make a layout change to the listCell with the received index and a layout change to the ListCell with the index+1;
How can I access the ListCell via the ListView?
I search for a method like this:
listView.getListCell(index);
Unfortunately right now there is no API to get List Cell by index or to get All children's(listcells) for ListView. One solution can be, define a new StringProperty specialIndicator in your MyObject class.
class MyObject {
....//u r properties
private StringProperty specialIndicator;
When ever you get index from network signal set this specialIndicator property of object and do forcerefresh of ListView
public void onReceivedNetWorkSignalIndex() {
listView.getItems().get(indexFromService).setSpecialIndicator("selected");
listView.getItems().get(indexFromService+1).setSpecialIndicator("selectedplusone");
//force refresh listview (it will trigger cellFactory again so that you can manipulate layout)
listView.setItems(null);
listView.setItems(allObjects);
}
As you already have custom Object ListView , i am assuming you already have custom cellFactory (if not you have to create one ) ,Modify your custom cell factory to handle this special Indicators
listView.setCellFactory(new Callback<ListView<MyObject>, ListCell<MyObject>>() {
#Override
public ListCell<MyObject> call(ListView<MyObject> myObjectListView) {
ListCell<MyObject> cell = new ListCell<MyObject>(){
#Override
protected void updateItem(MyObject myObject, boolean b) {
super.updateItem(myObject, b);
if(myObject != null) {
setText(myObject.getName());
if("selected".equalsIgnoreCase(myObject.getSpecialIndicator())) {
System.out.println("Setting new CSS/graphics for index retun from service." + myObject.getName());
} else if("selectedplusone".equalsIgnoreCase(myObject.getSpecialIndicator())) {
System.out.println("Setting new CSS/Graphics for index+1 returned from service" + myObject.getName());
}
myObject.setSpecialIndicator(""); // reset it back to empty
}
}
};
return cell;
}
});
Here is the whole sample Application ,you can look into it (in case the above explanation is not clear).
public class ListViewTest extends Application {
#Override
public void start(Stage stage) throws Exception {
VBox root = new VBox();
final ObservableList<MyObject> allObjects = FXCollections.observableArrayList(new MyObject("object0"), new MyObject("object1"),new MyObject("object2"),new MyObject("object3"),new MyObject("object4"));
final ListView<MyObject> listView = new ListView<>(allObjects);
listView.setCellFactory(new Callback<ListView<MyObject>, ListCell<MyObject>>() {
#Override
public ListCell<MyObject> call(ListView<MyObject> myObjectListView) {
ListCell<MyObject> cell = new ListCell<MyObject>(){
#Override
protected void updateItem(MyObject myObject, boolean b) {
super.updateItem(myObject, b);
if(myObject != null) {
setText(myObject.getName());
if("selected".equalsIgnoreCase(myObject.getSpecialIndicator())) {
System.out.println("Setting new CSS/graphics for index retun from service." + myObject.getName());
setText("I am selected Index from Service");
} else if("selectedplusone".equalsIgnoreCase(myObject.getSpecialIndicator())) {
System.out.println("Setting new CSS/Graphics for index+1 returned from service" + myObject.getName());
setText("I am selected Index +1 from Service");
}
myObject.setSpecialIndicator(""); // reset it back to empty
}
}
};
return cell;
}
});
Button serviceIndex2 = new Button("ServiceIndex2");
serviceIndex2.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
int indexFromService =2;
listView.getItems().get(indexFromService).setSpecialIndicator("selected");
listView.getItems().get(indexFromService+1).setSpecialIndicator("selectedplusone");
listView.setItems(null);
listView.setItems(allObjects);
}
});
root.getChildren().addAll(listView,serviceIndex2);
Scene scene = new Scene(root,500,500);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
class MyObject {
private StringProperty name;
private StringProperty specialIndicator;
MyObject(String name) {
this.name = new SimpleStringProperty(name);
this.specialIndicator = new SimpleStringProperty();
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public String getSpecialIndicator() {
return specialIndicator.get();
}
public StringProperty specialIndicatorProperty() {
return specialIndicator;
}
public void setSpecialIndicator(String specialIndicator) {
this.specialIndicator.set(specialIndicator);
}
}
}
Here's a relatively simple approach, where there is just one "selected" index. Here I create a property to hold the index that is selected, and the cell factory just observes it, along with the cell's item property and index property, and sets the text via a binding. You could do something similar to set the graphic, if needed.
import java.util.concurrent.Callable;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListViewStyleAroundSelection extends Application {
#Override
public void start(Stage primaryStage) {
final ListView<String> listView = new ListView<>();
for (int i=1; i<=20; i++) {
listView.getItems().add("Item "+i);
}
final HBox controls = new HBox(5);
final Button button = new Button("Set selection");
final TextField indexField = new TextField();
final IntegerProperty selectionIndex = new SimpleIntegerProperty();
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
try {
selectionIndex.set(Integer.parseInt(indexField.getText()));
} catch (NumberFormatException nfe) {
indexField.setText("");
}
}
});
controls.getChildren().addAll(new Label("Enter selection index:"), indexField, button);
final BorderPane root = new BorderPane();
root.setCenter(listView);
root.setBottom(controls);
listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> lv) {
final ListCell<String> cell = new ListCell<>();
cell.textProperty().bind(Bindings.createStringBinding(new Callable<String>() {
#Override
public String call() throws Exception {
if (cell.getItem() == null) {
return null ;
} else {
switch (cell.getIndex() - selectionIndex.get()) {
case -1: return cell.getItem() + " (selected item below)";
case 0: return cell.getItem() + " (selected)";
case 1: return cell.getItem() + " (selected item above)";
default: return cell.getItem();
}
}
}
}, cell.itemProperty(), cell.indexProperty(), selectionIndex));
return cell;
}
});
final Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
And here's a slightly more complex version. Here I have a custom data type which includes a boolean property. The update sets the boolean property of the specified item to true. The cell factory creates a cell, and observes the selected property both of the current item and of the previous item. Then, as before, it uses a binding to update the text of the cell.
import java.util.concurrent.Callable;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.IntegerBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListViewStyleAroundSelection extends Application {
#Override
public void start(Stage primaryStage) {
final ListView<MyDataType> listView = new ListView<>();
for (int i=0; i<=20; i++) {
listView.getItems().add(new MyDataType("Item "+i, false));
}
final HBox controls = new HBox(5);
controls.setPadding(new Insets(5));
final Button button = new Button("Set selection");
final TextField indexField = new TextField();
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
try {
int index = Integer.parseInt(indexField.getText());
if (index >= 0 && index < listView.getItems().size()) {
final MyDataType item = listView.getItems().get(index);
item.setSelected( ! item.isSelected() );
}
} catch (NumberFormatException nfe) {
indexField.setText("");
}
}
});
controls.getChildren().addAll(new Label("Enter selection index:"), indexField, button);
final BorderPane root = new BorderPane();
root.setCenter(listView);
root.setBottom(controls);
listView.setCellFactory(new Callback<ListView<MyDataType>, ListCell<MyDataType>>() {
#Override
public ListCell<MyDataType> call(ListView<MyDataType> lv) {
final ListCell<MyDataType> cell = new ListCell<>();
final IntegerBinding previousIndex = cell.indexProperty().subtract(1);
final ObjectBinding<MyDataType> previousItem = Bindings.valueAt(listView.getItems(), previousIndex);
final BooleanBinding previousItemSelected = Bindings.selectBoolean(previousItem, "selected");
final StringBinding thisItemName = Bindings.selectString(cell.itemProperty(), "name");
final BooleanBinding thisItemSelected = Bindings.selectBoolean(cell.itemProperty(), "selected");
cell.textProperty().bind(Bindings.createStringBinding(new Callable<String>() {
#Override
public String call() throws Exception {
if (cell.getItem() == null) {
return null ;
} else {
String value = cell.getItem().getName();
if (thisItemSelected.get()) {
value = value + " (selected) " ;
} else if (previousItemSelected.get()) {
value = value + " (selected item is above)";
}
return value ;
}
}
}, thisItemName, thisItemSelected, previousItemSelected));
return cell;
}
});
final Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class MyDataType {
private final BooleanProperty selected ;
private final StringProperty name ;
public MyDataType(String name, boolean selected) {
this.name = new SimpleStringProperty(this, "name", name);
this.selected = new SimpleBooleanProperty(this, "selected", selected);
}
public final String getName() {
return name.get();
}
public final void setName(String name) {
this.name.set(name);
}
public final StringProperty nameProperty() {
return name ;
}
public final boolean isSelected() {
return selected.get();
}
public final void setSelected(boolean selected) {
this.selected.set(selected);
}
public final BooleanProperty selectedProperty() {
return selected;
}
}
public static void main(String[] args) {
launch(args);
}
}
Cell has a style class called ".cell"
public Cell getListCell(ListView list, int index){
Object[]cells = list.lookupAll(".cell").toArray();
return (Cell)cells[index];
}
This is the method I used to solve the same problem. Please note that getting the cell view is considered bad practice, and shouldn't be done in a normal context, updating cells should only be done through the model, my special case was that I wanted to fire an event manually as part of a workaround.
private ListCell<?> getListCell(ListView<?> listView, int cellIndex) {
if (cellIndex == -1) {
return null;
}
//Virtual Flow is the container of all list cells
//Each ListView has exactly one VirtualFlow which we are searching for
Optional<VirtualFlow> virtualFlowOptional = listView.getChildrenUnmodifiable()
.stream()
.filter(node -> node instanceof VirtualFlow)
.map(n -> (VirtualFlow) n)
.findFirst();
if (virtualFlowOptional.isEmpty()) {
return null;
}
VirtualFlow<ListCell<?>> virtualFlow = virtualFlowOptional.get();
return virtualFlow.getCell(cellIndex);
}

Resources