Mimicking CTRL+Click Multiple Selection in ListView using Javafx - java

I'm trying to find different ways of selecting multiple items in a ListView. The GUI will be running on a touch screen monitor, So I won't be able to CTRL+Click. From researching through various past posts, I have been able to implement Multiple Selection via keeping all the selected items in an Array and then looping through it to get the final selections. The only problem I have with my code is that compared to a CTRL +click , the selection is done smoothly, where as my code leads to a type flickering every time a new item is selected. So basically the listView clears all the selections and then selects the correct ones. Is there a way to make this transition go smoothly? Would it be easier to mimic a touch to have CTRL+click effect?
selectedList = new int[totalTypes];//total number of item properties
for(int x=0; x<selectedList.length;x++){//0 = not selected, 1 = selected
selectedList[x]=0;
}
testView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
testView.setOnMouseClicked(new EventHandler<Event>(){
#Override
public void handle(Event event){
if(selectedList[testView.getSelectionModel().getSelectedIndex()]==0){
selectedList[testView.getSelectionModel().getSelectedIndex()]=1;
}
else{
selectedList[testView.getSelectionModel().getSelectedIndex()]=0;
}
for(int x=0; x<selectedList.length;x++){
if(selectedList[x]==1){
testView.getSelectionModel().select(x);
}
else{
testView.getSelectionModel().clearSelection(x);;
}
}
}
});

You could handle changing the selection when a user clicks a ListCell yourself instead of using the standard event handling:
#Override
public void start(Stage primaryStage) {
ListView<Integer> listView = new ListView<>();
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
listView.getItems().setAll(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
listView.addEventFilter(MouseEvent.MOUSE_PRESSED, evt -> {
Node node = evt.getPickResult().getIntersectedNode();
// go up from the target node until a list cell is found or it's clear
// it was not a cell that was clicked
while (node != null && node != listView && !(node instanceof ListCell)) {
node = node.getParent();
}
// if is part of a cell or the cell,
// handle event instead of using standard handling
if (node instanceof ListCell) {
// prevent further handling
evt.consume();
ListCell cell = (ListCell) node;
ListView lv = cell.getListView();
// focus the listview
lv.requestFocus();
if (!cell.isEmpty()) {
// handle selection for non-empty cells
int index = cell.getIndex();
if (cell.isSelected()) {
lv.getSelectionModel().clearSelection(index);
} else {
lv.getSelectionModel().select(index);
}
}
}
});
Scene scene = new Scene(listView);
primaryStage.setScene(scene);
primaryStage.show();
}

Related

Is there a good way of managing ListView rendering delays?

I've an app displaying a list of presets items in a ListView.
It's possible to add, modify and remove each of these items, most often by clicking on an item (becoming selected) and then on a button to do the desired operation.
The items are then sorted and the new list is displayed, showing the result of the operation.
Rendering problems come when I keep wanting to see the initial item as selected in the new list (except in case of removing), without having to click on it, and even without operation (in case of switching between apps). So I had to use postDelay at two places in the code.
So here is my code until now, on rebuilding the list:
...
String presetId = presetsHandler.getPresetId(listIndex); // listIndex is the initial position of the selected item
presetsHandler.sortPresets();
listIndex = presetsHandler.getIndex(presetId); // New position
...
lvAdapter.setTextItems(presetsHandler.getConcatenatedDisplayPresetDataList()); // Replace all the items
listView.setAdapter(lvAdapter); // Attach the new preset list to the ListView
...
if (listIndex != LIST_INDEX_DEFAULT_VALUE) { // Need to keep initial item as selected
final long RENDERING_DELAY_MILLIS = 200;
listView.postDelayed(new Runnable() { // Delay to besure that listView is ready (after the setAdapter)
#Override
public void run() {
if (listIndex < listView.getFirstVisiblePosition() || listIndex > listView.getLastVisiblePosition()) { // Invisible
listView.setSelection(listIndex); // To see the item at position listIndex (without selecting it yet)
}
listView.postDelayed(new Runnable() { // Delay to besure that listView is ready (after the possible setSelection)
#Override
public void run() {
View view = listView.getChildAt(listIndex - listView.getFirstVisiblePosition()); // The item at position listIndex was surely visible
view.setSelected(true); // and we can make it appear as selected
}
}, RENDERING_DELAY_MILLIS);
}
}, RENDERING_DELAY_MILLIS);
}
I'd like to know if it's a good way of doing it, particularly as for the postDelayed and Runnable.
Thanks
After a moment, I found this solution, a bit stressful for the OS (10 ms between calls to the runnable), but only during the time needed to finish rendering.
The benefit of this solution is that you don't need to estimate that time lapse and it keeps the things simple.
What do you think ?
Thanks
if (listIndex != LIST_INDEX_DEFAULT_VALUE) { // Need to keep initial item as selected
final long MINIMAL_DELAY_MILLIS = 10;
final Runnable runnable = new Runnable() {
boolean isFirstTime = true;
public void run() {
if (listView.getChildCount() == 0) { // listView is not ready yet
listView.postDelayed(this, MINIMAL_DELAY_MILLIS); // Wait until ready (after setAdapter)
} else { // listView is now ready
if ((listIndex < listView.getFirstVisiblePosition()) || (listIndex > listView.getLastVisiblePosition())) { // The item at position listIndex is not visible
if (isFirstTime) {
isFirstTime = false; // Do setSelection only once !
listView.setSelection(listIndex); // To see the item at position istIndex, without selecting it yet
}
listView.postDelayed(this, MINIMAL_DELAY_MILLIS); // Wait until visible (after setSelection)
} else { // The item at position listIndex is now visible
View view = listView.getChildAt(listIndex - listView.getFirstVisiblePosition());
view.setSelected(true); // and we can make it appear as selected; There's no need to call the runnable anymore
}
}
}
};
listView.postDelayed(runnable, MINIMAL_DELAY_MILLIS); // Run it!
}

Change background color of RecyclerView corresponding to specific item in list when it is not loaded

I have a RecyclerView in my activity that displays a bunch of items in a list. I have a small control panel that changes the background color of the items in that list. I want scroll the absolute first item in the list (not the first visible item in the list) and change its background color as well. When I use scrollToPosition(0) though, it doesn't scroll and when I attempt to change the color of the first item in the list, it's not loaded and so I get null. Here's my code:
#Override
public void onClick(View v) {
int currentRound = currentEncounter.getCurrentRound();
int oldIndex = currentEncounter.getCurrentTurnIndex();
switch (v.getId()) {
case R.id.encounter_activity_next_turn_button:
int idx = currentEncounter.getCurrentTurnIndex();
participantsLayoutManager.scrollToPositionWithOffset(idx, 160);
participantsLayoutManager.findViewByPosition(currentEncounter.getCurrentTurnIndex()).setBackgroundColor(Color.WHITE);
currentEncounter.nextTurn();
if(currentRound != currentEncounter.getCurrentRound()) {
((TextView) findViewById(R.id.encounter_activity_current_round_text_view)).setText(Integer.toString(currentEncounter.getCurrentRound()));
participantsLayoutManager.scrollToPositionWithOffset(1, 160);
participantsLayoutManager.scrollToPositionWithOffset(0, 0);
Log.d(LOG_RECYCLE_VIEW, "Should have scrolled to top");
} else
participantsLayoutManager.scrollToPositionWithOffset(currentEncounter.getCurrentTurnIndex(), 160);
participantsLayoutManager.findViewByPosition(currentEncounter.getCurrentTurnIndex()).setBackgroundColor(Color.RED);
participantAdapter.setCurrentCreatureIndex(currentEncounter.getCurrentTurnIndex());
break;
}
}
Is there a better way to do this?

How to detect a selection on JavaFX TableView when clicking a highlighted item?

I need a way to get the user selection every time a user selects an item on a TableView, even if the item is already selected.
The tableView.getSelectionModel().selectedItemProperty().addListener works when the user selects a different item from the one highlighted, but if the user selects the highlighted item again, it doesn't seem to work.
How would this be fixed?
you can do this:
tableView.setOnMouseClicked((MouseEvent event) -> {
if(event.getButton().equals(MouseButton.PRIMARY)){
System.out.println(tableView.getSelectionModel().getSelectedItem());
}
});
the code above doesn't work if you select the highlighted item again using editable table cell
If you're only interested in the clicks on the rows, use a custom rowFactory:
TableView<Item> table = ...
EventHandler<MouseEvent> clickListener = evt -> {
TableRow<Item> row = (TableRow<Item>) evt.getTarget();
if (!row.isEmpty()) {
// do something for non-empty rows
System.out.println("you clicked " + row.getItem());
}
};
table.setRowFactory(tv -> {
TableRow<Item> row = new TableRow<>();
// add click listener to row
row.setOnMouseClicked(clickListener);
return row;
});
The simplest way what i know:
yourTableView.setOnMousePressed(e ->{
if (e.getClickCount() == 2 && e.isPrimaryButtonDown() ){
int index = yourTableView.getSelectionModel().getSelectedIndex();
System.out.println("" + index);
}
});
put it in to constructor or initialize method in your corntroller class... :)

JavaFX: How to change the focus traversal policy?

Is it possible in JavaFX to change the focus traversal policy, like in AWT?
Because the traversal order for two of my HBoxes is wrong.
The simplest solution is to edit the FXML file and reorder the containers appropriately. As an example, my current application has a registration dialog in which a serial number can be entered. There are 5 text fields for this purpose. For the focus to pass from one text field to the other correctly, I had to list them in this way:
<TextField fx:id="tfSerial1" layoutX="180.0" layoutY="166.0" prefWidth="55.0" />
<TextField fx:id="tfSerial2" layoutX="257.0" layoutY="166.0" prefWidth="55.0" />
<TextField fx:id="tfSerial3" layoutX="335.0" layoutY="166.0" prefWidth="55.0" />
<TextField fx:id="tfSerial4" layoutX="412.0" layoutY="166.0" prefWidth="55.0" />
<TextField fx:id="tfSerial5" layoutX="488.0" layoutY="166.0" prefWidth="55.0" />
Bluehair's answer is right, but you can do this even in JavaFX Scene Builder.
You have Hierarchy panel in left column. There are all your components from scene. Their order represents focus traversal order and it responds to their order in FXML file.
I found this tip on this webpage:www.wobblycogs.co.uk
In common case the navigation is done in a container order, in order of children, or according to arrow keys pressing. You can change order of nodes - it will be the optimal solution for you in this situation.
There is a back door in JFX about traversal engine strategy substitution :
you can subclass the internal class com.sun.javafx.scene.traversal.TraversalEngine
engine = new TraversalEngine(this, false) {
#Override public void trav(Node owner, Direction dir) {
// do whatever you want
}
};
And use
setImpl_traversalEngine(engine);
call to apply that engine.
You can observe the code of OpenJFX, to understand, how it works, and what you can do.
Be very careful : it is an internal API, and it is likely to change, possibly, in the nearest future. So don't rely on this (you cannot rely on this officialy, anyway).
Sample implementation :
public void start(Stage stage) throws Exception {
final VBox vb = new VBox();
final Button button1 = new Button("Button 1");
final Button button2 = new Button("Button 2");
final Button button3 = new Button("Button 3");
TraversalEngine engine = new TraversalEngine(vb, false) {
#Override
public void trav(Node node, Direction drctn) {
int index = vb.getChildren().indexOf(node);
switch (drctn) {
case DOWN:
case RIGHT:
case NEXT:
index++;
break;
case LEFT:
case PREVIOUS:
case UP:
index--;
}
if (index < 0) {
index = vb.getChildren().size() - 1;
}
index %= vb.getChildren().size();
System.out.println("Select <" + index + ">");
vb.getChildren().get(index).requestFocus();
}
};
vb.setImpl_traversalEngine(engine);
vb.getChildren().addAll(button1, button2, button3);
Scene scene = new Scene(vb);
stage.setScene(scene);
stage.show();
}
It will require strong analitical skills for common case ;)
We're using JavaFX event filters for this, e.g.:
cancelButton.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.TAB && event.isShiftDown()) {
event.consume();
getDetailsPane().requestFocus();
}
}
});
The event.consume() suppresses the default focus traversal, which otherwise causes trouble when calling requestFocus().
This is the accepted answer adapted to change of internal api (happened at some point of fx-8, my current version is 8u60b5). Obviously the original disclaimer still applies: it's internal api, open to change without notice at any time!
The changes (compared to the accepted answer)
Parent needs a TraversalEngine of type ParentTraversalEngine
nav is no longer a method of TraversalEngine (nor ParentTE) but only of TopLevelTraversalEngine
the navigation implementation is delegated to strategy called Algorithm
actual focus transfer is (seems to be?) handled by TopLevelTE, Algorithm only finds and returns the new target
The plain translation of the example code:
/**
* Requirement: configure focus traversal
* old question with old hack (using internal api):
* http://stackoverflow.com/q/15238928/203657
*
* New question (closed as duplicate by ... me ..)
* http://stackoverflow.com/q/30094080/203657
* Old hack doesn't work, change of internal api
* rewritten to new internal (sic!) api
*
*/
public class FocusTraversal extends Application {
private Parent getContent() {
final VBox vb = new VBox();
final Button button1 = new Button("Button 1");
final Button button2 = new Button("Button 2");
final Button button3 = new Button("Button 3");
Algorithm algo = new Algorithm() {
#Override
public Node select(Node node, Direction dir,
TraversalContext context) {
Node next = trav(node, dir);
return next;
}
/**
* Just for fun: implemented to invers reaction
*/
private Node trav(Node node, Direction drctn) {
int index = vb.getChildren().indexOf(node);
switch (drctn) {
case DOWN:
case RIGHT:
case NEXT:
case NEXT_IN_LINE:
index--;
break;
case LEFT:
case PREVIOUS:
case UP:
index++;
}
if (index < 0) {
index = vb.getChildren().size() - 1;
}
index %= vb.getChildren().size();
System.out.println("Select <" + index + ">");
return vb.getChildren().get(index);
}
#Override
public Node selectFirst(TraversalContext context) {
return vb.getChildren().get(0);
}
#Override
public Node selectLast(TraversalContext context) {
return vb.getChildren().get(vb.getChildren().size() - 1);
}
};
ParentTraversalEngine engine = new ParentTraversalEngine(vb, algo);
// internal api in fx8
// vb.setImpl_traversalEngine(engine);
// internal api since fx9
ParentHelper.setTraversalEngine(vb, engine);
vb.getChildren().addAll(button1, button2, button3);
return vb;
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(getContent()));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
In scene builder go to the view menu and select show document. on the left will be all objects in your current fxml document. drag controls up or down in the list to reorder for tab index. Select hide document to use the other tools since the document pane hogs the space.
I used Eventfilter as solution in combination with referencing the field ID's, so all you have to do is name the fields like (field1,field2,field3,field4) so you can place the fields where u want:
mainScene.addEventFilter(KeyEvent.KEY_PRESSED, (event) -> {
if(event.getCode().equals(KeyCode.TAB)){
event.consume();
final Node node = mainScene.lookup("#field"+focusNumber);
if(node!=null){
node.requestFocus();
}
focusNumber ++;
if(focusNumber>11){
focusNumber=1;
}
}
});
You can do this easily with SceneBuilder. Open fxml file with scenebuilder and go to hierarchy under Document tab. Place input controls into a order you want by dragging input controls. Remember to check Focus Traversal in properties. Then focus traversal policy will work nicely when you press tab bar.
You can use NodeName.requestFocus() as said above; furthermore, make sure you request this focus after instantiating and adding all of your nodes for the root layout, because as you do so, the focus will be changing.
I had a problem in which a JavaFX Slider was capturing right and left arrow keystrokes that I wanted to be handled by my method keyEventHandler (which handled key events for the Scene). What worked for me was to add the following line to the code that initialized the Slider:
slider.setOnKeyPressed(keyEventHandler);
and to add
keyEvent.consume();
at the end of keyEventHandler.
General solution inspired by Patrick Eckert's answer.
When I am creating the UI, say for example adding a TextField, I set things up like so:
List<String> displayOrder;
Map<String, Node> cycle;
TextField tf = new TextField();
tf.setId("meTF");
cycle.put("meTF", tf);
displayOrder.add("meTF");
getChildren().add(tf);
Then on the UI element (layout usually) you add this:
ui.addEventFilter(KeyEvent.KEY_PRESSED, (ke) ->
{
if(!ke.getCode().equals(KeyCode.TAB) || !(ke.getTarget() instanceof Node))
return;
int i = displayOrder.indexOf(((Node)ke.getTarget()).getId());
if(i < 0) // can't find it
return;
if(ke.isShiftDown())
i = (i == 0 ? displayOrder.size() - 1 : i - 1);
else
i = ++i % displayOrder.size();
cycle.get(displayOrder.get(i)).requestFocus();
ke.consume();
});
FYI I think it is important to only consume the event if you are actually going to call request focus on something. Less likely to break something unintentionally that way...
If anyone can think of ways to optimize this further I'd appreciate knowing :)

Multiple selection and JPopupMenu on right click

I am having problems with a popup menu on a JTable and the fact that this JTable allows for Multiple Interval Selection.
I'm going to explain in detail my situation, making it as clear as possible, hopefully.
I have a basic data class, lets call it Item, with a string id (name) and two boolean fields, online and active (with relative getters).
The idea behind the JTable is that, for each item in the dataset, it will show its name in the first colum and its status in the second column, where by 'status' I mean that, it will show "ACTIVE/NOT ACTIVE" if the Item is Online, otherwise it will show "OFFLINE".
I have implemented a TableModel that does the job and it works.
I also want, when the user right clicks on a row, a popup to appear (if the selected Item is ONLINE) allowing to Activate/Deactivate the item, depending on its status.
This worked perfectly as long as the Selection Model was SINGLE SELECTION, but when I changed it to MULTIPLE INTERVALS SELECTION, I could not make it work properly anymore.
The behaviour that I want is that, on right-click, a popup appears where the click is performed, the row is added to the selection and highlighted and all the previously selected rows stay selected! This I cannot manage to do!
Here is the code I have in the MouseListener:
tblModel.addMouseListener(new MouseAdapter() {
void showPopup(MouseEvent e){
int r = tblModel.rowAtPoint(e.getPoint());
if (r >= 0 && r < tblModel.getRowCount()) {
//tblModel.setRowSelectionInterval(r, r);
} else {
tblModel.clearSelection();
}
int[] viewRowIndexes = tblModel.getSelectedRows();
int rowViewIndex = tblModel.getSelectedRow();
if (rowViewIndex < 0)
return;
int rowModelIndex = tblModel.convertRowIndexToModel(rowViewIndex);
if (e.isPopupTrigger() && e.getComponent() instanceof JTable) {
Action changeActiveAction;
Action changeInactiveAction;
List<String> actives = new ArrayList<String>();
List<String> inactives = new ArrayList<String>();
DefaultListSelectionModel selectionModel = (DefaultListSelectionModel) tblModel.getSelectionModel();
for (int viewRowIndex : viewRowIndexes) {
int modelRowIndex = tblModel.convertRowIndexToModel(viewRowIndex);
if (selectionModel.isSelectedIndex(viewRowIndex)) {
boolean online = ((MyTableModel) tblModel.getModel()).isItemOnline(modelRowIndex);
if (!online)
continue;
boolean active = ((MyTableModel) tblModel.getModel()).isItemActive(modelRowIndex);
String idItem = (String) ((MyTableModel) tblModel.getModel()).getValueAt(modelRowIndex,0);
if (active) {
actives.add(idItem);
} else {
inactives.add(idItem);
}
}
}
if (actives.size() > 0 || inactives.size() > 0) {
popup = new JPopupMenu();
if (actives.size() > 0) {
changeActiveAction = new ChangeAction("Deactivate ACTIVE Items","This will deactivate all the selected ACTIVE items",actives, false);
popup.add(new JMenuItem(changeActiveAction));
}
if (inactives.size() > 0) {
changeInactiveAction = new ChangeAction("Activate INACTIVE Items","This will activate all the selected INACTIVE items",inactives, true);
popup.add(new JMenuItem(changeInactiveAction));
}
popup.show(e.getComponent(), e.getX(),e.getY());
}
}
}
#Override
public void mousePressed(MouseEvent e) {
showPopup(e);
}
#Override
public void mouseReleased(MouseEvent e) {
showPopup(e);
}
};
The behaviour is functionally correct, but the selection of rows is not working.
Having commented the line
//tblModel.setRowSelectionInterval(r, r);
when I right-click on a row, a popup appears, but it ignores the row on which I clicked.
On the other hand, if uncommented, that line will select only the clicked row, losing all the rest of the selection....
I am sorry for the long post, but I didn't know how to explain my problem without giving all the details of my situation....
Hopefully this is a trivial thing and you can tell me how I can fix/change it.
Thank you in advance.
One part of the answer is:
if (tblModel.isSelectedIndex(r)) {
tblModel.removeSelectionInterval(r, r);
} else {
tblModel.addSelectionInterval(r, r);
}

Categories

Resources