Selecting only leafs in JTree - java

So I want to be able to only select leafs in JTree. There are some solutions online, but they don't work on multiple selection...
What I would like is to find the part of the code that fires when user clicks on a node and modify that part to suit my needs.
I have found a listener within DefaultTreeCellEditor, but that code seem to apply to when only one node is selected at a time...
The bottom line is, where can I find the code that, when nodes gets clicked, decides if it will select it or not, and will it or not deselect all the other selected nodes?

Fixed it!
public class LeafOnlyTreeSelectionModel extends DefaultTreeSelectionModel
{
private static final long serialVersionUID = 1L;
private TreePath[] augmentPaths(TreePath[] pPaths)
{
ArrayList<TreePath> paths = new ArrayList<TreePath>();
for (int i = 0; i < pPaths.length; i++)
{
if (((DefaultMutableTreeNode) pPaths[i].getLastPathComponent()).isLeaf())
{
paths.add(pPaths[i]);
}
}
return paths.toArray(pPaths);
}
#Override
public void setSelectionPaths(TreePath[] pPaths)
{
super.setSelectionPaths(augmentPaths(pPaths));
}
#Override
public void addSelectionPaths(TreePath[] pPaths)
{
super.addSelectionPaths(augmentPaths(pPaths));
}
}

Related

javafx - .IndexOutOfBoundsException for table view

I would to ask why does IndexOutOfBoundsException appear when I try to delete the first row from the table view of supplement which is index 0. I am using a button to delete the row
Update: update the code to have a minimal reproducible example
SupplementTest.java
public class SupplementTest extends Application {
WindowController windowGUI = new WindowController();
Stage stageGUI;
Scene sceneGUI;
#Override
public void start(Stage primaryStage) throws IOException {
FXMLLoader assignment2 = new FXMLLoader(getClass().getResource("SupplementFXML.fxml"));
Parent fxmlFile = assignment2.load();
try {
stageGUI = primaryStage;
windowGUI.initialize();
sceneGUI = new Scene(fxmlFile, 250, 350);
stageGUI.setScene(sceneGUI);
stageGUI.setTitle("Supplement");
stageGUI.show();
} catch (Exception e) {
e.printStackTrace();
}
}
}
WindowController.java
public class WindowController {
Stage newWindow = new Stage();
boolean deleteSupplement;
#FXML
private GridPane primaryGrid = new GridPane();
#FXML
private Label supplementLabel = new Label();
#FXML
private Button deleteBtn = new Button(), addBtn = new Button();
public TableView<Supplement> supplementView = new TableView<>();
int suppIndex;
ArrayList<Supplement> supplementList = new ArrayList<>();
// initialize Method
public void initialize() {
newWindow.initModality(Modality.APPLICATION_MODAL);
newWindow.setOnCloseRequest(e -> e.consume());
initializeWindow();
updateSupplementList();
}
public void initializeWindow() {
deleteSupplement = false;
TableColumn<Supplement, String> suppNameColumn = new TableColumn<>("Name");
suppNameColumn.setCellValueFactory(new PropertyValueFactory<>("supplementName"));
TableColumn<Supplement, Double> suppCostColumn = new TableColumn<>("Weekly Cost");
suppCostColumn.setCellValueFactory(new PropertyValueFactory<>("weeklyCost"));
supplementView.getColumns().addAll(suppNameColumn, suppCostColumn);
supplementView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
suppIndex = supplementView.getSelectionModel().getSelectedIndex();
addBtn.setOnAction(e -> {
supplementList.add(new Supplement("Test1", 10));
supplementList.add(new Supplement("Test2", 20));
supplementList.add(new Supplement("Test3", 15));
updateSupplementList();
});
// remove button
deleteBtn.setOnAction(e -> {
deleteSupplement = true;
deleteSupplement();
});
}
public void updateSupplementList() {
supplementView.getItems().clear();
if (supplementList.size() > 0) {
for(int i = 0; i < supplementList.size(); i++) {
Supplement supplement = new Supplement(supplementList.get(i).getSupplementName(),
supplementList.get(i).getWeeklyCost());
supplementView.getItems().add(supplement);
}
}
}
public void deleteSupplement() {
try {
ObservableList<Supplement> supplementSelected, allSupplement;
allSupplement = supplementView.getItems();
supplementSelected = supplementView.getSelectionModel().getSelectedItems();
supplementSelected.forEach(allSupplement::remove);
supplementList.remove(suppIndex);
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
Supplement.java
public class Supplement implements Serializable {
private String supplementName;
private double weeklyCost;
public Supplement() {
this.supplementName = "";
this.weeklyCost = 0.00;
}
public Supplement(String suppName, double weeklyCost) {
this.supplementName = suppName;
this.weeklyCost = weeklyCost;
}
public String getSupplementName() {
return supplementName;
}
public double getWeeklyCost() {
return weeklyCost;
}
public void setSupplementName(String supplementName) {
this.supplementName = supplementName;
}
public void setWeeklyCost(double weeklyCost) {
this.weeklyCost = weeklyCost;
}
}
How do I fix it so that when I delete any index in the table view the IndexOutOfBoundsException does not appear?
It's difficult to know for certain what is causing the exception, because your code is both incomplete (so no-one here can copy, paste, and run it to reproduce the error), and very confusing (it is full of seemingly-unnecessary code). However:
You seem to be doing two different things to delete the selected item(s) from the table:
supplementSelected = supplementView.getSelectionModel().getSelectedItems();
supplementSelected.forEach(allSupplement::remove);
which is an attempt to delete all selected items (though I don't believe it will work if more than one item is selected)
and
supplementList.remove(suppIndex);
which will delete the selected item, as defined by the selected index property in the selection model. (It is the currently selected item in a single selection model, or the last selected item in a multiple selection model, or -1 if nothing is selected.)
The latter will not work, because you only ever set suppIndex in your initialization code:
public void initializeWindow() {
// ...
suppIndex = supplementView.getSelectionModel().getSelectedIndex();
// ...
}
Of course, when this code is executed, the user has not had a chance to selected anything (the table isn't even displayed at this point), so nothing is selected, and so suppIndex is assigned -1. Since you never change it, it is always -1, and so when you call
supplementList.remove(suppIndex);
you get the obvious exception.
If you are only supporting single selection, and want to delete the currently selected item (or the last selected item in multiple selection), just get the selection at the time. You probably still want to check something is selected:
public void deleteSupplement() {
int selectedIndex = supplementView.getSelectionModel().getSelectedIndex();
if (selectedIndex >= 0) {
supplementView.getItems().remove(selectedIndex);
}
}
A slight variation on this, which I think is preferable, is to work with the actual object instead of its index:
public void deleteSupplement() {
Supplement selection = supplementView.getSelectionModel().getSelectedItem();
if (selection != null) {
supplementView.getItems().remove(selection);
}
}
Now, of course (in a theme that is common to a lot of your code), you can remove suppIndex entirely; it is completely redundant.
If you want to support multiple selection, and delete all selected items, then the code you currently have for that will cause an issue if more than one item is selected. The problem is that if a selected item is removed from the table's items list, it will also be removed from the selection model's selected items list. Thus, the selected items list (supplementSelected) in your code changes while you are iterating over it with forEach(...), which will throw a ConcurrentModificationException.
To avoid this, you should copy the list of selected items into another list, and remove those items:
public void deleteSupplement() {
List<Supplement> selectedItems
= new ArrayList<>(supplementView.getSelectionModel().getSelectedItems());
supplementView.getItems().removeAll(selectedItems);
}
Of course, this code also works with single selection (when the list is always either length 0 or length 1).
To address a couple of other issues: there is really no point in keeping a separate list of Supplement items. The table already keeps that list, and you can reference it at any time with supplementView.getItems(). (If you wanted to reference the list elsewhere, e.g. in a model in a MVC design, you should make sure that there is just a second reference to the existing list; don't create a new list.)
In particular, you should not rebuild the table entirely from scratch every time you add a new item to the list. Get rid of the redundant supplementList entirely from your code. Get rid of updateSupplementList() entirely; it is firstly doing way too much work, and secondly (and more importantly) will replace all the existing items just because you add a new one. This will lost important information (for example it will reset the selection).
To add new items, all you need is
addBtn.setOnAction(e -> {
supplementView.getItems().add(new Supplement("Test1", 10));
supplementView.getItems().add(new Supplement("Test2", 20));
supplementView.getItems().add(new Supplement("Test3", 15));
});
There are various other parts of your code that don't make any sense, such as:
The deleteSupplement variable. This seems to have no purpose.
The try-catch in the deleteSupplement method. The only exceptions that can be thrown here are unchecked exceptions caused by programming logic errors (such as the one you see). There is no point in catching those; you need to fix the errors so the exceptions are not thrown.
The #FXML annotations. You should never initialize fields that are annotated #FXML. This annotation means that the FXMLLoader will initialize these fields. In this case (as far as I can tell) these are not even associated with an FXML file at all, so the annotation should be removed.

Creating a custom tree with javafx

Basically, I wanted to know if I could create a tree and custom it on javaFX...
I tried to do it, but couldn't do anything so far with this code...
public class Main{
......
public Main() throws Exception{
......
// TreeView created
TreeView tv = (TreeView) fxmlLoader.getNamespace().get("treeview");
TreeItem<String> rootItem = new TreeItem<String>("liss");
rootItem.setExpanded(true);
tv.setRoot(rootItem);
/*for (int i = 1; i < 6; i++) {
TreeItem<String> item = new TreeItem<String> ("Message" + i);
rootItem.getChildren().add(item);
}
TreeItem<String> item = new TreeItem<String> ("MessageWoot");
rootItem.getChildren().add(item);
*/
//tv.setEditable(true);
tv.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {
#Override
public TreeCell<String> call(TreeView<String> arg0) {
// custom tree cell that defines a context menu for the root tree item
return new MyTreeCell();
}
});
stage.show();
}
//
private static class MyTreeCell extends TextFieldTreeCell<String> {
private ContextMenu addMenu = new ContextMenu();
public boolean clickedFirstTime = false;
public MyTreeCell() {
// instantiate the root context menu
MenuItem addMenuItem = new MenuItem("Expand");
addMenu.getItems().add(addMenuItem);
addMenuItem.setOnAction(new EventHandler() {
public void handle(Event t) {
TreeItem n0 =
new TreeItem<String>("'program'");
TreeItem n1 =
new TreeItem<String>("<identifier>");
TreeItem n2 =
new TreeItem<String>("body");
getTreeItem().getChildren().add(n0);
getTreeItem().getChildren().add(n1);
getTreeItem().getChildren().add(n2);
}
});
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
// if the item is not empty and is a root...
//if (!empty && getTreeItem().getParent() == null && this.clickedFirstTime) {
System.out.println("UPDATEITEM -> clickedFirstTime : "+this.clickedFirstTime);
if (!this.clickedFirstTime) {
System.out.println("WOOT");
setContextMenu(addMenu);
this.clickedFirstTime = true;
}
}
}
And I'm questioning myself if this is the right "technology" which will solve what I'm trying to do...
What's my objective in this?
Firstly, I'm looking to add or delete a treeItem. I must say that a certain treeItem may be added only once or any N times, like a restriction (for example: treeItem < 6 for a certain level scope and a certain path of the root of tree view).
Secondly, make some treeItem editable and others not editable! When it is Editable, you may pop up something for the user in order to insert some input for example!
Is it possible ?
I saw the tutorial from https://docs.oracle.com/javafx/2/ui_controls/tree-view.htm#BABJGGGF but I'm really confused with this tutorial ... I don't really understand the cell factory mechanism... The fact that he does apply to TreeView when i want only a certain TreeItem... Or how could I control that effect/behaviour ?
I mean, I'm really really lost with TreeView. Probably, TreeView isn't what I'm looking for ...
P.S.: I know that I cannot apply any visual effect or add menus to a tree items and that i use a cell factory mechanism to overcome this obstacle. Just I don't understand the idea and how could I do it !
Sure this is the right "technology", if you want to use JavaFX. You should probably use a more complex type parameter for TreeItem however. You can use your a custom TreeCell to allow the desired user interaction.
This example allows adding children and removing nodes via context menu (unless the content is "nocontext") as well as editing the content (as long as the content is not "noedit"); on the root node the delete option is disabled:
tv.setEditable(true);
tv.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {
private final MyContextMenu contextMenu = new MyContextMenu();
private final StringConverter converter = new DefaultStringConverter();
#Override
public TreeCell<String> call(TreeView<String> param) {
return new CustomTreeCell(contextMenu, converter);
}
});
public class CustomTreeCell extends TextFieldTreeCell<String> {
private final MyContextMenu contextMenu;
public CustomTreeCell(MyContextMenu contextMenu, StringConverter<String> converter) {
super(converter);
if (contextMenu == null) {
throw new NullPointerException();
}
this.contextMenu = contextMenu;
this.setOnContextMenuRequested(evt -> {
prepareContextMenu(getTreeItem());
evt.consume();
});
}
private void prepareContextMenu(TreeItem<String> item) {
MenuItem delete = contextMenu.getDelete();
boolean root = item.getParent() == null;
if (!root) {
delete.setOnAction(evt -> {
item.getParent().getChildren().remove(item);
contextMenu.freeActionListeners();
});
}
delete.setDisable(root);
contextMenu.getAdd().setOnAction(evt -> {
item.getChildren().add(new TreeItem<>("new item"));
contextMenu.freeActionListeners();
});
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setContextMenu("nocontext".equals(item) ? null : contextMenu.getContextMenu());
setEditable(!"noedit".equals(item));
}
}
}
public class MyContextMenu {
private final ContextMenu contextMenu;
private final MenuItem add;
private final MenuItem delete;
public MyContextMenu() {
this.add = new MenuItem("add child");
this.delete = new MenuItem("delete");
this.contextMenu = new ContextMenu(add, delete);
}
public ContextMenu getContextMenu() {
return contextMenu;
}
public MenuItem getAdd() {
return add;
}
public MenuItem getDelete() {
return delete;
}
/**
* This method prevents memory leak by setting all actionListeners to null.
*/
public void freeActionListeners() {
this.add.setOnAction(null);
this.delete.setOnAction(null);
}
}
Of course more complex checks can be done in the updateItem and prepareContextMenu and different user interactions can be supported (TextFieldTreeCell may not be the appropriate superclass for you; You could use a "normal" TreeCell and show a different stage/dialog to edit the item when the user selects a MenuItem in the context menu).
Some clarification about cell factories
Cell factories are used to create the cells in a class that displays data (e.g. TableColumn, TreeView, ListView). When such a class needs to display content, it uses it's cell factory to create the Cells that it uses to display the data. The content displayed in such a cell may be changed (see updateItem method).
Example
(I'm not 100% sure this is exactly the way it's done, but it should be sufficiently close)
A TreeView is created to display a expanded root node with 2 non expanded children.
The TreeView determines that it needs to display 3 items for the root node and it's 2 children. The TreeView therefore uses it's cell factory to creates 3 cells and adds them to it's layout and assigns the displayed items.
Now the user expands the first child, which has 2 children of it's own. The TreeView determines that it needs 2 more cells to display the items. The new cells are added at the end of the layout for efficiency and the items of the cells are updated:
The cell that previously contained the last child is updated and now contains the first child of the first item.
The 2 newly added cells are updated to contain the second child of the first child and the second child of the root respecitvely.
So I decided to eliminate TreeView (because the documentation is so trash...) and, instead, I decided to implement a Webview !
Why ?
Like that, I could create an HTML document and use jstree (jquery plugin - https://www.jstree.com ) in there. It is a plugin which will create the treeview basically.
And the documentation is ten time better than treeview oracle documentation unfortunately.
Also, the possibility in creating/editing the tree with jstree is better.
Which concludes me that it was the best solution that I could figure out for me.
Also, whoever who will read me, I did a bridge with the webview and my javafx application ! It is a connection between my HTML document and the java application (Read more here - https://blogs.oracle.com/javafx/entry/communicating_between_javascript_and_javafx).
Hope It will help more people.

Java Applet java.awt.Choice Add Item not working for second time

I am writing an applet which contains a panel (PanelCondition) with a java.awt.choice dropdown.
http://docs.oracle.com/javase/7/docs/api/java/awt/Choice.html
public class PanelCondition extends Panel
{
Choice choiceCond = new Choice();
public PanelCondition(bool isGood)
{
setCond(isGood);
}
private void setCond(boolean isGood)
{
condCode = getCondItems(isGood);
this.choiceCond.removeAll();
for (int i=0; i < condCode.length; i++)
{
this.choiceCond.addItem(condCode[i]);
}
this.choiceCond.repaint();
}
...
}
The PanelCondition is included in a java.awt.Frame
public class FrameExample extends Frame
{
PanelCondition cond;
private void setCond(bool isGood)
{
cond = new PanelCondition(isGood)
this.add(orderCond,...)
...
}
}
When the FrameExample.setCond() is called first time (isGood=true), the correct items are added to the dropdownlist. However if I call the setCond() the second time (isGood=false), the dropdownlist items don't have any changes.
I have tried to call this.choiceCond.validate() or this.choiceCond.repaint, but it still doesn't work.
Without further evidence,
cond = new PanelCondition(isGood)
this.add(orderCond,...)
Looks suspicious. This is likely just adding another PanelCondition to the UI, but it may be covered by the pre-existing instance.
Generally, it's easy just to update the pre-existing instance...
//cond = new PanelCondition(isGood)
//this.add(orderCond,...)
cond.setCond(isGood);

How do I update a ColourHighlighter (swingx) when the predicate has changed

I have a class called ErrorHighlighter which gets notified anytime a property called errorString is changed. Based on this propertychangeevent I update the HighLighterPredicate to highlight a particular row with a red background.
ErrorHighlighter receives the propertychangeevent, it also changes the HighlighterPredicate, but the table row does not get updated with red background.
I also update the tooltip of the row. That does not get reflected either.
Please see the code below. Could someone please help?
public class ErrorRowHighlighter extends ColorHighlighter implements PropertyChangeListener {
private Map<Integer, String> rowsInError;
private SwingObjTreeTable<ShareholderHistoryTable> treeTable;
public ErrorRowHighlighter(SwingObjTreeTable<ShareholderHistoryTable> treeTable) {
super(CommonConstants.errorColor, null);
this.treeTable = treeTable;
rowsInError=new HashMap<Integer, String>();
setHighlightPredicate(new HighlightPredicate() {
#Override
public boolean isHighlighted(Component renderer, ComponentAdapter adapter) {
if(rowsInError.containsKey(adapter.row)){
return true;
}
return false;
}
});
this.treeTable.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
int row=ErrorRowHighlighter.this.treeTable.rowAtPoint(e.getPoint());
if(rowsInError.containsKey(row)){
ErrorRowHighlighter.this.treeTable.setToolTipText(rowsInError.get(row));
}else{
ErrorRowHighlighter.this.treeTable.setToolTipText(null);
}
}
});
}
public void highlightRowWithModelDataAsError(ShareholderHistoryTable modelData){
int indexForNodeData = treeTable.getIndexForNodeData(modelData);
if(indexForNodeData>-1){
rowsInError.put(indexForNodeData, modelData.getErrorString());
updateHighlighter();
}
}
public void unhighlightRowWithModelDataAsError(ShareholderHistoryTable modelData){
int indexForNodeData = treeTable.getIndexForNodeData(modelData);
if(indexForNodeData>-1){
rowsInError.remove(indexForNodeData);
updateHighlighter();
}
}
public void updateHighlighter(){
treeTable.removeHighlighter(this);
treeTable.addHighlighter(this);
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
ShareholderHistoryTable sourceObject= (ShareholderHistoryTable) evt.getSource();
if(StringUtils.isNotEmpty(sourceObject.getErrorString())){
highlightRowWithModelDataAsError(sourceObject);
}else{
unhighlightRowWithModelDataAsError(sourceObject);
}
}
}
This looks like a mistake on my part. The method treeTable.getIndexForNodeData() actually returns back the index of the row by doing a pre-order traversal of the underlying tree data structure. This includes a root node that is not being displayed on the jxtreetable. Hence I needed to minus 1 from the index
int indexForNodeData = treeTable.getIndexForNodeData(modelData)-1;
This fixed the problem for me. I am leaving the post rather than deleting it if anyone wants to look at an example of a ColorHighlighter and a property change listener.

Java - adding a method to a button in netbeans form

I have a ShortestPath class with a Dijkstra algorithm in it and a method called computeRoutes. I also have a form with a search button - I want to call the computeRoutes method from this button but can't figure out how to do this.
public class ShortestPath {
public static void computeRoutes(Node source){
source.minimumDistance = 0;
PriorityQueue<Node> nodeQueue = new PriorityQueue<Node>();
nodeQueue.add(source);
while(!nodeQueue.isEmpty()){
Node u = nodeQueue.poll();
for(Edge e : u.neighbours){
Node n = e.goal;
int weight = e.weight;
int distThruU = u.minimumDistance + weight;
if(distThruU < n.minimumDistance){
nodeQueue.remove(n);
n.minimumDistance = distThruU;
n.previousNode = u;
nodeQueue.add(n);
}
}
}
}
public static List<Node> getShortestRouteTo(Node goal){
List<Node> route = new ArrayList<Node>();
for(Node node = goal; node != null; node = node.previousNode)
route.add(node);
Collections.reverse(route);
return route;
}
}
public class BPForm extends javax.swing.JFrame {
....
private void btnSearchActionPerformed(java.awt.event.ActionEvent evt) {
(I want to call the computeRoutes method here)
In netbeans designer double click this button. It will open code for ActionListener(If you don't know what this is. you should have a look at event handling for buttons) for this button. Just call computeRoutes() here using an Object(Have you already created an object?) of ShortestPath Class.
i suppose you've implemented ActionListener, and in the code you have to overwrite
public void actionPerformed(ActionEvent e) {
if (e.getSource() == computeRoutes) {
// put the logic here
}
.....
}
You just need to implement OnClick Listener for button click event
and then just call your method
http://www.roseindia.net/java/example/java/awt/MouseClick.shtml
here is an example which will helps you

Categories

Resources