TestFX clickOn() on particular text on combobox/choicebox - java

I‘m new to TestFX GUI-testing with fxrobot (javafx).
My current task is about clicking on a choice on a drop down menu created with combobox. I didn‘t find any tutorials mentioning this issue.
Is it really possible to implement the clickOn() method selecting a text in a combobox/drop down menu? Is there an example how to do it?
Thanks a million!

This is an example of a way where a user selects the given text in the given combobox.
void user_selects_combo_item(String comboBoxId, String itemToSelect) {
ComboBox<?> actualComboBox = lookupControl(comboBoxId);
// Find and click only on arrow button. This is important for editable combo-boxes.
for (Node child : actualComboBox.getChildrenUnmodifiable()) {
if (child.getStyleClass().contains("arrow-button")) {
Node arrowRegion = ((Pane) child).getChildren().get(0);
robot.clickOn(arrowRegion);
Thread.sleep(100); // try/catch were skipped for shorter code.
robot.clickOn(itemToSelect);
}
}
Assert.fail("Couldn't find an arrow-button.");
}
private <T extends Node> T lookupControl(String controlId) {
T actualControl = robot.lookup(controlId).query();
assertNotNull("Could not find a control by id = " + controlId, actualControl);
return actualControl;
}

Related

JavaFX setGraphic() not displaying icon in MenuItem

I need an icon for my MenuItem's.
This is like a "worker class" to get the ImageView of the icon :
public class IconFactory {
private static ImageView HLP_BOOK_JFX;
public enum ICONS {
BASCET_REMOVE, BASCET_PUT, SAVE, OPEN, ARROW_RIGHT, ARROW_LEFT, ARROW_UP, ARROW_DOWN, CLOCK, ANALOG_SIGNAL, DIGITAL_SIGNAL, REFRESH, GREEN_PLUS, NETWORK, OK, CANCEL, RIGHT_NAV2, LEFT_NAV2, PLAY, PAUSE, LIST_ADD, PAGE_FIND, SET_PARAM, DOWNLOAD, UPLOAD, LOG_FILE, WARNING, INFO, LOG_DIAG, DATA_TRANS, TREE, FILTER, SEARCH, PARAM, ERASE, RESETDEF, RESETDEF2, DEBUG_BUG, INTERNATIONAL, CLOSE, HLP_BOOK
}
public static ImageView getImage(ICONS en) {
switch (en) {
case HLP_BOOK:
if (HLP_BOOK_JFX == null)
HLP_BOOK_JFX = new ImageView(new Image(IconFactory.class.getResourceAsStream("help_book.png")));
return HLP_BOOK_JFX;
}
return null;
}
When I use myMenuItem.setGraphic(IconFactory.getImage(ICONS.HLP_BOOK)) for a single menu item it works perfectly.
But then, when I want to generate two menus in a loop and set the same graphic, one MenuItem has no icon displayed. (the first one in loop in the code below).
My code:
while (keys.hasMoreElements()) {
// that will do 2 loops, do not care about how
MenuItem subMenuHelp = new MenuItem("MenuItem");
subMenuHelp.setGraphic(IconFactory.getImage(ICONS.HLP_BOOK));
subMenuHelp.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
// do not care
openHelpFile(link);
}
});
System.out.println(((ImageView) subMenuHelp.getGraphic()).toString());
myMenu.getItems().add(subMenuHelp);
}
As you can see, I added a System.out.println to see if a graphic was set for the current item.
Result in console : both lines (MenuItem) with the same ImageView:
ImageView#79814766[styleClass=image-view]
ImageView#79814766[styleClass=image-view]
I did exactly the same in Swing (but with Icons and .setIcons() function) and it worked very well. I've also looked for a "repaint" function to force displaying but no way.
Hope you can help me!
This is because the same Node cannot be attached to the scene-graph multiple times and - as you even state - you are adding the same ImageView object.
From the documentation of Node:
If a program adds a child node to a Parent (including Group, Region,
etc) and that node is already a child of a different Parent or the
root of a Scene, the node is automatically (and silently) removed from
its former parent.
The solution is to modify getImage method of IconFactory to return a new ImageView instance on each call or to return Image instances rather than ImageView instances (the second one fits better to the name "IconFactory" I think).
You could store the Image instance instead of storing the ImageView to avoid re-loading the Image itself. You could check this question as reference: Reusing same ImageView multiple times in the same scene on JavaFX
A possible update on IconFactory:
public class IconFactory {
private static HashMap<ICON, Image> images = new HashMap<ICON, Image>();
public enum ICON {
BASCET_REMOVE, BASCET_PUT, SAVE, OPEN, ARROW_RIGHT, ARROW_LEFT, ARROW_UP, ARROW_DOWN, CLOCK, ANALOG_SIGNAL, DIGITAL_SIGNAL, REFRESH, GREEN_PLUS, NETWORK, OK, CANCEL, RIGHT_NAV2, LEFT_NAV2, PLAY, PAUSE, LIST_ADD, PAGE_FIND, SET_PARAM, DOWNLOAD, UPLOAD, LOG_FILE, WARNING, INFO, LOG_DIAG, DATA_TRANS, TREE, FILTER, SEARCH, PARAM, ERASE, RESETDEF, RESETDEF2, DEBUG_BUG, INTERNATIONAL, CLOSE, HLP_BOOK
}
public static Image getImage(ICON en) {
if (!images.containsKey(en)) {
switch (en) {
case HLP_BOOK:
images.put(en, new Image(IconFactory.class.getResourceAsStream("help_book.png"))); break;
default:
return null;
}
}
return images.get(en);
}
}
Usage after the update:
subMenuHelp.setGraphic(new ImageView(IconFactory.getImage(ICONS.HLP_BOOK)));

javaFX filling/updating Tableview

I have some trouble using javaFX to fill a tableColumn with some data according to a selected index from an other table.
the Table starts empty.
and then I want to fill it when the user press a button. (so far, so good)
here's what the button controller looks like :
#FXML
private void handleNextRequest() {
int selectedIndex = headingTable.getSelectionModel().getSelectedIndex();
if (selectedIndex >= 0)
mainApp.updateEntity(headingColumn.getCellData(selectedIndex));
entityTable.setItems(mainApp.getEntity());
}
So this calls a function from the main class which update my observable list.
The selectedIndex parameter is used to determine which data I have to load in the list (those data are located on a database which I can access via a web service, hence the "api" (which works fine)).
So here's what this function looks like :
public void updateEntity(String header){
try {
this.entity.clear();
int i = 0;
while(header != heading.get(i).getName()){
i++;
}
api.getEntity(new URL(heading.get(i).getURL()), this.entity, primaryStage);
} catch(MalformedURLException e){}
}
And up to this point everything is functional. when pressing the button the function is called properly and the observable list (entity) is updated correctly. (checked and re-checked)
and then... boom.
the "setItems" function (back to the button controller) doesn't seems to like whatever I've done and throw a NullPointerException.
If someone could help me understand what the problem might be here I would be delighted !
EDIT :
here's the initialize code that I have :
I have this in the initialize method :
#FXML
private void initialize() {
headingColumn.setCellValueFactory(CellData -> CellData.getValue().nameProperty());
entityColumn.setCellValueFactory(CellData -> CellData.getValue().nameProperty());
showTableDetails(null);
headingTable.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> showTableDetails(newValue));
}
and both entityTable and entityColumn declared properly.
Okay I figured it out.
Pretty dumb mistake but the fx:id="entityTable" was missing in the fxml file.
thank you for helping me realizing that !

How to extend JavaFX Pagination navigation to display additional controls?

I would like to use Pagination to show a table page wise. This works in principle but I would like to add additional controls that are in the same line as the default pagination navigation:
button for going to first page ("<<")
text field for jumping to specified page index
button for going to last page (">>")
text field for number of entries per page
search text field for jumping to a page that contains the row entry with a given ID.
I am able to customize the page above the pagination control with the method setPageFactory() but I am not able to customize the navigation control itself. How to do that? If I add my additional controls above or below the default navigation I waste some space:
Related article:
JavaFX Pagination, adding << and >>> options
Filed an enhancement request
Custom navigation controls are not supported. While waiting for the enhancement request to be fixed, we could apply a hack (if QA guidelines allow) as outlined below. It's a hack because everything related to the navigation control is package private in PaginationSkin which itself is not (yet) public api.
The basic idea is to insert additional nodes into core navigation control, which obviously implies relying on implementation details (dont, dont, dont :-). We do so on-the-fly at instantiation and whenever the next button is inserted again - core clears out all its children quite often during layout and state changes on the pagination. This involves:
lookup the pane that contains the buttons, it's selector is control-box
keep a reference to its last child, which is the next button
add a listener to the pane's children to be able to insert custom controls
Code example for a custom skin, here simply two button for first/last:
public static class CustomPaginationSkin extends PaginationSkin {
private HBox controlBox;
private Button prev;
private Button next;
private Button first;
private Button last;
private void patchNavigation() {
Pagination pagination = getSkinnable();
Node control = pagination.lookup(".control-box");
if (!(control instanceof HBox))
return;
controlBox = (HBox) control;
prev = (Button) controlBox.getChildren().get(0);
next = (Button) controlBox.getChildren().get(controlBox.getChildren().size() - 1);
first = new Button("A");
first.setOnAction(e -> {
pagination.setCurrentPageIndex(0);
});
first.disableProperty().bind(
pagination.currentPageIndexProperty().isEqualTo(0));
last = new Button("Z");
last.setOnAction(e -> {
pagination.setCurrentPageIndex(pagination.getPageCount());
});
last.disableProperty().bind(
pagination.currentPageIndexProperty().isEqualTo(
pagination.getPageCount() - 1));
ListChangeListener childrenListener = c -> {
while (c.next()) {
// implementation detail: when nextButton is added, the setup is complete
if (c.wasAdded() && !c.wasRemoved() // real addition
&& c.getAddedSize() == 1 // single addition
&& c.getAddedSubList().get(0) == next) {
addCustomNodes();
}
}
};
controlBox.getChildren().addListener(childrenListener);
addCustomNodes();
}
protected void addCustomNodes() {
// guarding against duplicate child exception
// (some weird internals that I don't fully understand...)
if (first.getParent() == controlBox) return;
controlBox.getChildren().add(0, first);
controlBox.getChildren().add(last);
}
/**
* #param pagination
*/
public CustomPaginationSkin(Pagination pagination) {
super(pagination);
patchNavigation();
}
}

Adding double-click expansion to the Tree Viewer in SWT

Double-Clicking tree items works completely fine, but when I press CTRL + M on the keyboard then the tree items expand\collapse, can someone please tell me the reason behind this? Is this a bug in Eclipse or why does this double-click functionality get triggered when I press CTRL+M.
Thanks.
Use TreeViewer.addDoubleClickListener to listen for tree double clicks not a mouse listener. You could use something like this:
private class DoubleClickListener implements IDoubleClickListener
{
#Override
public void doubleClick(final DoubleClickEvent event)
{
final IStructuredSelection selection = (IStructuredSelection)event.getSelection();
if (selection == null || selection.isEmpty())
return;
final Object sel = selection.getFirstElement();
final ITreeContentProvider provider = (ITreeContentProvider)treeViewer.getContentProvider();
if (!provider.hasChildren(sel))
return;
if (treeViewer.getExpandedState(sel))
treeViewer.collapseToLevel(sel, AbstractTreeViewer.ALL_LEVELS);
else
treeViewer.expandToLevel(sel, 1);
}
}
Update:
Using TreeViewer.addDoubleClickListener is the preferred way to do double click handling for all classes derived from StructuredViewer.
Each double click listener is run using SafeRunnable which deals with any exceptions that the listener may throw, this safeguards the rest of the code for errors in the listeners.
The DoubleClickEvent provides direct access to the model object data so it is not necessary to deal with Tree or TreeItem objects to work out selections.
The double click code in the TreeViewer interfaces correctly with the OpenStrategy single / double click to open code.
I think the following code will be better , cause it will not cause the tree item to reload children and will keep the original state of other tree items.
_treeViewer.addDoubleClickListener( new IDoubleClickListener()
{
#Override
public void doubleClick( DoubleClickEvent event )
{
ISelection selection = event.getSelection();
if( selection instanceof ITreeSelection )
{
TreePath[] paths= ((ITreeSelection)selection).getPathsFor(selectedItem);
for (int i= 0; i < paths.length; i++)
{
_treeViewer.setExpandedState(paths[i], !_treeViewer.getExpandedState(paths[i]));
}
}
}
}
} );

Unable to know deselected items when using "Move Selection" short-cut deselected something on a Tree

I'm using ZK 6.5.1.
Sometimes a tree may contains some logic, like do things when user select/deselect an item, this can easily done by listening event like this.
#Listen("onSelect = #tree")
public void onSelect(SelectEvent<Treeitem, String> event) {
Treeitem ref = event.getReference();
if (ref.isSelected()) {
// do things when item is selected
} else {
// do things when item is deselected
}
}
On the other hand, ZK provide function that user can using up, down, Page Up, Page Down, Home, End on a Tree to "Move Selection". This action will also send out a select event about new selected item, but not send events for deselected items. Therefore, previous code snippets dose not works when user use these keys.
My questions are :
How should I do so I can know exactly which items are deselected when user "Move Selection"?
If not, can I disable these keys ?
Here's a SSCCE example if someone want to know. Or in ZKFiddle
Composer :
public class TestComposer extends SelectorComposer<Div> {
#Listen("onSelect = #tree")
public void onSelect(SelectEvent<Treeitem, String> event) {
Treeitem ref = event.getReference();
if (ref.isSelected()) {
Clients.log("Selected " + ref.getLabel());
} else {
Clients.log("Deselected " + ref.getLabel());
}
}
}
ZUL :
<div apply="mytest.TestComposer">
<tree id="tree" multiple="true" checkmark="true">
<treechildren>
<treeitem label="A" />
<treeitem label="B" />
<treeitem label="C" />
</treechildren>
</tree>
</div>
This is logical, cos in event-driven programming it is good practice
to send only one event per user action. But all the information you need
is inside the event.
See here on zk fiddle how I improved you example.
I just add a few lines to the event method.
#Listen("onSelect = #tree")
public void onSelect(SelectEvent<Treeitem, String> event) {
Treeitem ref = event.getReference();
Set<Treeitem> newSelection = new HashSet<Treeitem>(event.getSelectedItems());
if (ref.isSelected()) {
if (selected != null) {
Set<Treeitem> deselected = new HashSet<Treeitem>(selected);
deselected.removeAll(newSelection);
for (Treeitem t : deselected) {
Clients.log("Deselected " + t.getLabel());
}
}
Clients.log("Selected " + ref.getLabel());
} else {
Clients.log("Deselected " + ref.getLabel());
}
selected = newSelection;
}
I use java Set and set-theoretic logic to compute a Set of deselected Items.

Categories