When using multiple selection in a TableView, clicking with the secondary button on a selected row to use the ContextMenu of the table will deselect any selected rows except the one that has been clicked. Why does this happen? What is the point of multiple selection if you can't choose an option in the context menu that can apply to that selection?
I'm thinking this might be a bug. This is because in some cases when I select more than one row in my example below, the selection is retained. I couldn't figure out any pattern for when the selection is retained, it seems to happen stochastically.
Update:
The comments have pointed out that this behaviour isn't stochastic. If you click on a part of the row that isn't a real column, the selection is lost. To me, this behaviour doesn't feel intuitive.
The following is an MCVE of a basic TableView that allows multiple selection.
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class TestMultipleSelection extends Application {
#Override
public void start(Stage stage) {
// Table
TableView<ObservableList<String>> table = new TableView<ObservableList<String>>();
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
// Column
TableColumn<ObservableList<String>, String> col = new TableColumn<ObservableList<String>, String>("Column");
col.setCellValueFactory(e -> new SimpleStringProperty(e.getValue().get(0)));
table.getColumns().add(col);
// Data
ObservableList<ObservableList<String>> items = FXCollections.observableArrayList(
FXCollections.observableArrayList("One"), FXCollections.observableArrayList("Two"), FXCollections.observableArrayList("Three"));
table.setItems(items);
// ContextMenu
ContextMenu contextMenu = new ContextMenu();
MenuItem item1 = new MenuItem("Item1");
contextMenu.getItems().add(item1);
table.setContextMenu(contextMenu);
stage.setScene(new Scene(table));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
Tested platforms:
Windows 8.1 Enterprise, 64-bit; Java 8u92.
Windows 7 Enterprise SP1, 64-bit; Java 8u92.
Related
I am writing a program with javafx and tableView feature.
My purpose is when I click on a row of this table,another window opens and shows something but I don't know how to define something like setOnMouseClicked feature for my table.
I searched a lot but I coudn't find a simple way
This is my existing code that define table columns and rows.(rows are defined with observable feature)
package sample;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
TableView tableView = new TableView();
TableColumn<String, Account> column1 = new TableColumn<>("UserName");
column1.setCellValueFactory(new PropertyValueFactory<>("userName"));
column1.setMinWidth(100);
TableColumn<String, Account> column2 = new TableColumn<>("PassWord");
column2.setCellValueFactory(new PropertyValueFactory<>("passWord"));
column2.setMinWidth(100);
tableView.getColumns().add(column1);
tableView.getColumns().add(column2);
tableView.setItems(getAllAccounts());
VBox vbox = new VBox(tableView);
Scene scene = new Scene(vbox,200,200);
Stage window ;
window = primaryStage;
window.setScene(scene);
window.show();
}
private ObservableList<Account> getAllAccounts(){
ObservableList<Account> accounts= FXCollections.observableArrayList(Account.getAccounts());
return accounts;
}
}
You actually have two options:
Method 1:
Implement a click listener on the TableView and retrieve the item that was selected.
// Listen for a mouse click and access the selectedItem property
tblAccounts.setOnMouseClicked(event -> {
// Make sure the user clicked on a populated item
if (tblAccounts.getSelectionModel().getSelectedItem() != null) {
System.out.println("You clicked on " + tblAccounts.getSelectionModel().getSelectedItem().getUsername());
}
});
Method 2:
Create your own RowFactory for the TableView and handle your logic there. (I prefer this method)
// Create a new RowFactory to handle actions
tblAccounts.setRowFactory(tv -> {
// Define our new TableRow
TableRow<Account> row = new TableRow<>();
row.setOnMouseClicked(event -> {
System.out.println("Do your stuff here!");
});
return row;
});
Method #1 is the simplest approach and will work for most needs. You'll want to use method #2 for more complex needs, such as styling the individual rows, or to handle clicks on empty rows.
I am in the process of teaching myself JavaFX. Coming from the Swing world there are a lot of similarities between the 2. Especially event processing. Part of my process is to try and mimic an existing application as closely as possible. One of the things I am doing is creating a dialog that will allow the user to select a font to use. There is a text field for them to type in the font name and a list where they can scroll and select one. When they start typing the list will automatically scroll to through the list to start matching what the user is typing. I am also trying to populate the text field with the currently matched font name and then highlight the portion that the user has not typed yet so they can continue to type until the correct match is found.
For example if the user types the letter 't' on Windows the first font found is Tahoma. So the text field will be set to Tahoma and the carat will be positioned right after the 'T' and the 'ahoma' will be highlighted. What happens instead is that the field is populated with Tahoma and the carat is positioned at the end and nothing is highlighted. So it is like it is ignoring the 2 lines of code for positioning and highlighting or the event processor is causing my calls to JavaFX libraries to be run out of order.
I think this may be a bug with JavaFX but it could also be my misunderstanding of the event system. Please let me know which one and why.
Here is a complete sample code showing the problem. Just start typing in the text field to try it out.
package test;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TestTyping extends Application {
ChangeListener<String> textChange;
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
TextField text = new TextField();
root.setTop(text);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
textChange = (observable, oldValue, newValue) -> {
text.textProperty().removeListener(textChange);
for (String family : Font.getFamilies()) {
if (family.equalsIgnoreCase(newValue) || family.toLowerCase().startsWith(newValue.toLowerCase())) {
text.setText(family);
text.positionCaret(newValue.length());
text.selectEnd();
break;
}
}
text.textProperty().addListener(textChange);
};
text.textProperty().addListener(textChange);
}
public static void main(String... args) {
launch(args);
}
}
Wrap caret position and select end into Platform.runLater. The problem is in events order. I don't know correct details about this issue so I will not provide you a detailed answer, only solution.
Platform.runLater(()-> {
text.positionCaret(newValue.length());
text.selectEnd();
});
Here's an alternative approach entirely, which uses a TextFormatter to modify changes to the text. The advantage here is that it doesn't rely on the "timing" of various property changes with respect to event handling, which is not documented and thus could possibly change in later JavaFX versions. It also avoids the slightly ugly "remove the listener and add it back" idiom.
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class TestTyping extends Application {
ChangeListener<String> textChange;
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
TextField text = new TextField();
root.setTop(text);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
UnaryOperator<Change> filter = c -> {
// for delete, move the caret, or change selection, don't modify anything...
if (c.getText().isEmpty()) {
return c ;
}
for (String family : Font.getFamilies()) {
if (family.toLowerCase().startsWith(c.getControlNewText().toLowerCase())) {
c.setText(family.substring(c.getRangeStart(), family.length()));
c.setAnchor(c.getControlNewText().length());
break ;
}
}
return c ;
};
text.setTextFormatter(new TextFormatter<String>(filter));
}
public static void main(String... args) {
launch(args);
}
}
What's the best way to show a semi-transparent "copy" of a Node next to the mouse icon during a drag/drop?
Basically I have HBoxes with colored backgrounds and text labels in them, and I'd like to give the appearance that they "stick" to the mouse cursor when they're being dragged.
It's nice if users can visually verify WHAT they're dragging, rather than just seeing the mouse cursor change into the various drag icons. Scene Builder tends to do this when you drag some components, like a RadioButton.
The "semi-transparent "copy" of a Node" is accomplished by calling snapshot(null, null) on a node, which returns a WritableImage. Then you set this WritableImage as the drag view of the DragBoard. Here is a small example on how to do this:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class DragAndDrop extends Application {
private static final DataFormat DRAGGABLE_HBOX_TYPE = new DataFormat("draggable-hbox");
#Override
public void start(Stage stage) {
VBox content = new VBox(5);
for (int i = 0; i < 10; i++) {
Label label = new Label("Test drag");
DraggableHBox box = new DraggableHBox();
box.getChildren().add(label);
content.getChildren().add(box);
}
stage.setScene(new Scene(content));
stage.show();
}
class DraggableHBox extends HBox {
public DraggableHBox() {
this.setOnDragDetected(e -> {
Dragboard db = this.startDragAndDrop(TransferMode.MOVE);
// This is where the magic happens, you take a snapshot of the HBox.
db.setDragView(this.snapshot(null, null));
// The DragView wont be displayed unless we set the content of the dragboard as well.
// Here you probably want to do more meaningful stuff than adding an empty String to the content.
ClipboardContent content = new ClipboardContent();
content.put(DRAGGABLE_HBOX_TYPE, "");
db.setContent(content);
e.consume();
});
}
}
public static void main(String[] args) {
launch();
}
}
I have a scene with a choice box. the aim is to get all available system fonts to display in the choice box, I kinda feel I'm on the right path as so far I have managed to get 1 to display in the choice box, but why just the 1?
here is the code -
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.layout.Pane;
import javafx.scene.text.Font;
public class ChoiceBoxFonts extends Application
{
ObservableList<String> fontType;
ChoiceBox<String> fonts;
public static void main(String [] args)
{
Application.launch(args);
}
public void start(Stage primaryStage)
{
Pane root = new Pane();
Font.getFamilies().stream().forEach(i ->{
fontType =
FXCollections.observableArrayList(i
);
});
// New choicebox with observable arraylist fontType
fonts = new ChoiceBox<String> (fontType);
//SingleSelectionModel<String> selMod = fonts.getSelectionModel();
root.getChildren().add(fonts);
Scene scene = new Scene(root,200,200);
primaryStage.setScene(scene);
primaryStage.show();
}
}
The goal of the experiment is to be able to select a font from the choice box and change the font of a text object with that selection.
Also, is there a better UI to be able to do such a thing? If there are a bucket load of fonts, that choice box is going to be very long!
You just need
fontType = FXCollections.observableArrayList(Font.getFamilies());
instead of the iteration you have.
If there are a bucket load of fonts, that choice box is going to be very long!
I would probably consider a ListView.
I'm trying to learn JavaFX. To do so I've been attempting to make a text editor that includes multiple line text box support, as well as the possibility of having syntax highlighting down the road.
Currently, the biggest problem I've been facing is that the ScrollPane I've been encapsulating all my FlowPanes in won't resize according to the size of the Pane it's in. I've been researching this problem for about half a week now and simply cannot get the ScrollPane to just fill the window it's in. The code below displays a JavaFX stage that has working keyboard input and the ScrollPane is always the same size no matter what. Thanks to all in advance!
Here's my Main:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Launcher extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(new DynamicTextBox(),500,500));
primaryStage.show();
}
}
TextBox class:
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.Orientation;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
public class DynamicTextBox extends Pane {
//currentLinePane is made to handle all the direct user inputs
//multiLinePane, while not really used yet will create a new line when the enter key is struck.
private FlowPane currentLinePane, multiLinePane;
private ScrollPane editorScroller;
public DynamicTextBox() {
super();
currentLinePane = new FlowPane(Orientation.HORIZONTAL);
multiLinePane = new FlowPane(Orientation.VERTICAL);
multiLinePane.getChildren().add(currentLinePane);
editorScroller = new ScrollPane(multiLinePane);
editorScroller.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
editorScroller.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
editorScroller.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
configureInput(event);
}
});
super.getChildren().add(editorScroller);
editorScroller.requestFocus();
}
private void configureInput(KeyEvent event) {
currentLinePane.getChildren().add(new Text(event.getText()));
}
}
You're using
ScrollPane.ScrollBarPolicy.AS_NEEDED
which, according to the docs at Oracle, "Indicates that a scroll bar should be shown when required." Instead, use
ScrollPane.ScrollBarPolicy.ALWAYS
alternatively, recall these are constants. you can get the height of the parent using boundsInParent: https://docs.oracle.com/javafx/2/api/javafx/scene/Node.html#boundsInParentProperty
alternatively, you can use getParent() to get the parent and then get its height using computeMinWidth() https://docs.oracle.com/javafx/2/api/javafx/scene/Node.html#getParent()