expand TreeView (parent)nodes which contains selected item - recursion

I have a TreeView existing out of User objects. The TreeView represents the hierarchy of the Users:
Master1
Super1
Super2
User1
User2
Super3
User3
Super4
Master2
...
Every TreeItem is Collapsed when the TreeView is initialized. However, it can be that when the FXML is loaded, a TreeItem object is passed through from another FXML file. Eg: User3 has been passed through:
selectedUserTreeItem = (TreeItem<User>) currentNavigation.getPassthroughObject(); //this is the User3 TreeItem
I try to use a recursive function to expand all the parent nodes from the selecterUserTreeItem
if (selectedUserTreeItem != null) {
expandTreeView(selectedUserTreeItem);
}
tvUsers.setRoot(rootItem);
This is what I have so far:
private void expandTreeView(TreeItem<User> selectedItem) {
if (selectedItem != null) {
System.out.println(selectedItem);
if (selectedItem.isLeaf() == false) {
selectedItem.setExpanded(true);
}
TreeItem<User> parent = selectedItem.getParent();
expandTreeView(parent);
} else {
System.out.println("null");
}
}
I think it has to do something with the fact that the function is a void function and it should be returning a TreeItem object I suppose but for some reason I don't succeed in doing it.
Could someone point me in the right direction?

Ok, I notice that in your expandTreeView() method you expand the node and then you recurse to the previous node to expand that. In my own code I expanded from the root to the leaf, so lets try that:
private static <T> void expandTreeView(TreeItem<T> selectedItem) {
if (selectedItem != null) {
expandTreeView(selectedItem.getParent());
if (!selectedItem.isLeaf()) {
selectedItem.setExpanded(true);
}
}
}

No I don't think its because your method is returning void.
Try setting your TreeView root first before expanding:
>>> tvUsers.setRoot(rootItem);
if (selectedUserTreeItem != null) {
expandTreeView(selectedUserTreeItem);
}
If that doesn't work then try wrapping your initial expandTreeView() to run later:
Platform.runlater( new Runnable()
{
public void run() { expandTreeView( selectedUserTreeItem ); }
};

Ok, I had the same problem and found that I had to delay the expansion a bit like this:
Platform.runlater( new Task<Void>()
{
#Override
protected Void call() throws Exception
{
Thread.sleep( 250 );
return null;
}
#Override
protected void succeeded()
{
expandTreeView( selectedUserTreeItem );
}
} );
You may have to try and vary the delay to get it to work in your case.

Related

Headers fragment item listener

I'm trying to print out in console headers fragment item titles/id's on dpad movement using listener. What am I doing wrong and is there maybe an alternative to accomplish my goal?
For instance:
Would print out "Category 3" or "...." id.
public class XFragment extends BrowseFragment {
//onCreate()
setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
#Override
public void onItemSelected(Presenter.ViewHolder itemViewHolder,
Object item, RowPresenter.ViewHolder rowViewHolder, Row row) {
Log.d("TAG", "Here!");
}
});
}
I can't even "catch" the selection between header items.
Found a solution myself. Turns out, it was quite easy.
if (getHeadersFragment() != null){
getHeadersFragment().setOnHeaderViewSelectedListener(new HeadersFragment.OnHeaderViewSelectedListener() {
#Override
public void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
// catch your item by row.getId();
}
});
}

javafx CheckBoxTreeItem update parents programmatically

I have to retrieve some data from my database to dynamically create a TreeView and select some CheckBoxTreeItems from this TreeView. This TreeView represents permissions to a menu structure.
My doubt is when I create the TreeView and select specific items from the Tree according to the user's permissions programmatically, the parents items don't have any status change (selected or indeterminate). But when I select any item directly from the interface, the parents get updated.
For example, here I have my screen when I select the items programmatically:
You can see that I have two menu items selected, but the parents aren't.
On this image, I have selected the same menu items using the screen, and the parents were updated with indeterminate status or selected if I select all children inside the submenu.
I have gone through the documentation, google and here on Stack Overflow, but only found examples to update the children.
Is there a way to update the parents programmatically or to call the event executed from the screen when an item is selected?
EDIT:
All items from the Tree have the independent property set to false.
I came with a workaround for this problem.
I had to first create all the TreeView structure, and change the selected property after using this code snippet:
Platform.runLater(new Runnable() {
#Override
public void run() {
selectItems();
}
});
Here is the code to verify the TreeItems:
private void selectItems(){
TreeItem root = tree.getRoot();
if (root != null) {
selectChildren(root);
}
}
private void selectChildren(TreeItem<TesteVO> root){
for(TreeItem<TesteVO> child: root.getChildren()){
// HERE I CHECK IF THE USER HAS PERMISSION FOR THE MENU ITEM
// IF SO, I CHANGE THE SELECTED PROPERTY TO TRUE
if (child.getValue().id == 4) {
((CheckBoxTreeItem) child).setSelected(true);
}
// IF THERE ARE CHILD NODES, KEEP DIGGING RECURSIVELY
if(!child.getChildren().isEmpty()) {
selectChildren(child);
}
}
}
If there is a simpler way, please let me know!
This is not the case. Parent items do get automatically get set to the indeterminate state when you select a child item. I'm not sure if this is something that got corrected from the time that this question was posted, probably not.
My guess is that there's a programming bug in how the node was selected or how the TableView was constructed and initialized.
Here's some code that shows what I'm doing, and it works! In my case, I'm using a CheckBoxTreeItem<File> for the TreeItem.
How the treeview was created
treeView = new TreeView(root);
treeView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observableValue, Object o, Object t1) {
CheckBoxTreeItem<File> node = (CheckBoxTreeItem<File>)t1;
if (node.getValue() != currentFile) {
setFileDetail(node);
showChildren(node);
}
}
});
treeView.setCellFactory(new CallBackWrapper());
treeView.setShowRoot(false);
Below show the CallBackWrapper class.
private class CallBackWrapper implements Callback<TreeView<File>, TreeCell<File>> {
Callback<TreeView<File>, TreeCell<File>> theCallback;
private CallBackWrapper() {
theCallback = CheckBoxTreeCell.<File>forTreeView(getSelectedProperty, converter);
}
#Override
public TreeCell<File> call(TreeView<File> fileTreeView) {
return theCallback.call(fileTreeView);
}
final Callback<TreeItem<File>, ObservableValue<Boolean>> getSelectedProperty = (TreeItem<File> item) -> {
if (item instanceof CheckBoxTreeItem<?>) {
return ((CheckBoxTreeItem<?>) item).selectedProperty();
}
return null;
};
final StringConverter<TreeItem<File>> converter = new StringConverter<TreeItem<File>>() {
#Override
public String toString(TreeItem<File> object) {
File item = object.getValue();
return fileSystemView.getSystemDisplayName(item);
}
#Override
public TreeItem<File> fromString(String string) {
return new TreeItem<File>(new File(string));
}
};
}
And lastly here some code that the selection was made in:
boolean selectNode(CheckBoxTreeItem<File> parentNode, String name) {
Object[] children = parentNode.getChildren().toArray();
for (Object child : children) {
CheckBoxTreeItem<File> childItem = (CheckBoxTreeItem<File>) child;
if (name.equals(childItem.getValue().getName())) {
childItem.setSelected(true);
//treeView.getSelectionModel().select(child); <-- this does not work!
return true;
}
}
return false;
}

ListChangeListener.Change: how to properly handle updated and permutated items

From the JavaDoc:
ObservableList theList = ...;
theList.addListener(new ListChangeListener<Item>() {
public void onChanged(Change<tem> c) {
while (c.next()) {
if (c.wasPermutated()) {
for (int i = c.getFrom(); i < c.getTo(); ++i) {
//permutate
}
} else if (c.wasUpdated()) {
//update item
} else {
for (Item remitem : c.getRemoved()) {
remitem.remove(Outer.this);
}
for (Item additem : c.getAddedSubList()) {
additem.add(Outer.this);
}
}
}
}
});
}
Adding and removing items is straight forward, but what about //update item and // permutate?
How do I know which items have been permutated by which other items?
What does update mean exactly? Is it just adding the same item to the list again?
And what about
for (Item remitem : c.getRemoved()) {
remitem.remove(Outer.this);
}
or (Item additem : c.getAddedSubList()) {
additem.add(Outer.this);
}
What does Outer.this mean?
How do I know which items have been permutated by which other items?
The change has a getPermutation() method that describes how the elements were permuted.
What does update mean exactly?
A list is updated if properties belonging to an element change, though the same elements remain in the list (in the same order). For example, given a class
public class Item {
private final IntegerProperty value = new SimpleIntegerProperty();
public final IntegerProperty valueProperty() {
return value ;
}
public final int getValue() {
return valueProperty().get();
}
public final void setValue(int value) {
valueProperty().set(value);
}
public Item(int value) {
setValue(value);
}
}
calling setValue() on an element of the list may fire an update. Note that the documentation states that updates are "optional" and may not be fired by all lists. Specifically, to obtain a list that fires updates, create it with an extractor:
ObservableList<Item> list = FXCollections.observableArrayList(
item -> new Observable[] {item.valueProperty()});
list.addAll(new Item(1), new Item(2), new Item(3));
list.addListener((Change<? extends Item> c) -> {
while (c.next()) {
if (c.wasUpdated()) {
System.out.println("Items from "+c.getFrom()+" to "+c.getTo()+" changed");
}
}
});
list.get(1).setValue(42);
The last line of code doesn't change which elements are in the list, or which order they are in, but changes a property of one of the elements. So this change will fire an update.
What does Outer.this mean?
It is simply a reference to the current object of the surrounding class (which is assumed to have class name Outer); i.e. not the current object of the anonymous inner class implementation of ListChangeListener. See What is the difference between Class.this and this in Java (and many others). I think the context for the code snippet in the documentation is supposed to be a class that implements ObservableList and maintains its own ObservableList instance (decorator pattern). It observes the list instance and updates itself to keep in sync with it.

Handle multiple JavaFX application launches within a loop

My code currently reads my Gmail inbox via IMAP (imaps) and javamail, and once it finds an email with zip/xap attachment, it displays a stage (window) asking whether to download the file, yes or no.
I want the stage to close once I make a selection, and then return to the place within the loop from which the call came. My problem arises because you cannot launch an application more than once, so I read here that I should write Platform.setImplicitExit(false); in the start method, and then use primartyStage.hide() (?) and then something like Platform.runLater(() -> primaryStage.show()); when I need to display the stage again later.
The problem occuring now is that the flow of command begins in Mail.java's doit() method which loops through my inbox, and launch(args) occurs within a for loop within the method. This means launch(args) then calls start to set the scene, and show the stage. Since there is a Controller.java and fxml associated, the Controller class has an event handler for the stage's buttons which "intercept" the flow once start has shown the stage. Therefore when I click Yes or No it hides the stage but then just hangs there. As if it can't return to the start method to continue the loop from where launch(args) occurred. How do I properly hide/show the stage whenever necessary, allowing the loop to continue whether yes or no was clicked.
Here is the code for Mail.java and Controller.java. Thanks a lot!
Mail.java
[Other variables set here]
public static int launchCount = 0;#FXML public Text subjectHolder;
public static ReceiveMailImap obj = new ReceiveMailImap();
public static void main(String[] args) throws IOException, MessagingException {
ReceiveMailImap.doit();
}
#Override
public void start(Stage primaryStage) throws Exception {
loader = new FXMLLoader(getClass().getResource("prompts.fxml"));
root = loader.load();
controller = loader.getController();
controller.setPrimaryStage(primaryStage);
scene = new Scene(root, 450, 250);
controller.setPrimaryScene(scene);
scene.getStylesheets().add("styleMain.css");
Platform.setImplicitExit(false);
primaryStage.setTitle("Download this file?");
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void doit() throws MessagingException, IOException {
Folder inbox = null;
Store store = null;
try {
Properties props = System.getProperties();
Session session = Session.getDefaultInstance(props, null);
store = session.getStore("imaps");
store.connect("imap.gmail.com", "myAccount#gmail.com", "Password");
inbox = store.getFolder("Inbox");
inbox.open(Folder.READ_WRITE);
Message[] messages = inbox.getMessages();
FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.ENVELOPE);
fp.add(UIDFolder.FetchProfileItem.FLAGS);
fp.add(UIDFolder.FetchProfileItem.CONTENT_INFO);
fp.add("X-mailer");
inbox.fetch(messages, fp);
int doc = 0;
int maxDocs = 400;
for (int i = messages.length - 1; i >= 0; i--) {
Message message = messages[i];
if (doc < maxDocs) {
doc++;
message.getSubject();
if (!hasAttachments(message)) {
continue;
}
String from = "Sender Unknown";
if (message.getReplyTo().length >= 1) {
from = message.getReplyTo()[0].toString();
} else if (message.getFrom().length >= 1) {
from = message.getFrom()[0].toString();
}
subject = message.getSubject();
if (from.contains("myAccount#gmail.com")) {
saveAttachment(message.getContent());
message.setFlag(Flags.Flag.SEEN, true);
}
}
}
} finally {
if (inbox != null) {
inbox.close(true);
}
if (store != null) {
store.close();
}
}
}
public static boolean hasAttachments(Message msg) throws MessagingException, IOException {
if (msg.isMimeType("multipart/mixed")) {
Multipart mp = (Multipart) msg.getContent();
if (mp.getCount() > 1) return true;
}
return false;
}
public static void saveAttachment(Object content)
throws IOException, MessagingException {
out = null; in = null;
try {
if (content instanceof Multipart) {
Multipart multi = ((Multipart) content);
parts = multi.getCount();
for (int j = 0; j < parts; ++j) {
part = (MimeBodyPart) multi.getBodyPart(j);
if (part.getContent() instanceof Multipart) {
// part-within-a-part, do some recursion...
saveAttachment(part.getContent());
} else {
int allow = 0;
if (part.isMimeType("application/x-silverlight-app")) {
extension = "xap";
allow = 1;
} else {
extension = "zip";
allow = 1;
}
if (allow == 1) {
if (launchCount == 0) {
launch(args);
launchCount++;
} else {
Platform.runLater(() -> primaryStage.show());
}
} else {
continue;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if ( in != null) { in .close();
}
if (out != null) {
out.flush();
out.close();
}
}
}
public static File createFolder(String subject) {
JFileChooser fr = new JFileChooser();
FileSystemView myDocs = fr.getFileSystemView();
String myDocuments = myDocs.getDefaultDirectory().toString();
dir = new File(myDocuments + "\\" + subject);
savePathNoExtension = dir.toString();
dir.mkdir();
System.out.println("Just created: " + dir);
return dir;
}
}
Controller.java
public class Controller implements Initializable {
#FXML
private Text subjectHolder;
public Button yesButton, noButton;
public ReceiveMailImap subject;
#Override
public void initialize(URL url, ResourceBundle rb) {
subject= new ReceiveMailImap();
subjectHolder.setText(subject.returnSubject());
}
public Stage primaryStage;
public Scene scene;
#FXML
ComboBox<String> fieldCombo;
public void setPrimaryStage(Stage stage) {
this.primaryStage = stage;
}
public void setPrimaryScene(Scene scene) {
this.scene = scene;
}
public String buttonPressed(ActionEvent e) throws IOException, MessagingException {
Object source = e.getSource();
if(source==yesButton){
System.out.println("How to tell Mail.java that user clicked Yes?");
return "POSITIVE";}
else{subject.dlOrNot("no");
System.out.println("How to tell Mail.java that user clicked No?");
primaryStage.hide();
return "NEGATIVE";}
}
}
There are a lot of issues with the code you have posted, but let me just try to address the ones you ask about.
The reason the code hangs is that Application.launch(...)
does not return until the application has exited
In general, you've kind of misunderstood the entire lifecycle of a JavaFX application here. You should think of the start(...) method as the equivalent of the main(...) method in a "traditional" Java application. The only thing to be aware of is that start(...) is executed on the FX Application Thread, so if you need to execute any blocking code, you need to put it in a background thread.
The start(...) method is passed a Stage instance for convenience, as the most common thing to do is to create a scene graph and display it in a stage. You are under no obligation to use this stage though, you can ignore it and just create your own stages as and when you need.
I think you can basically structure your code as follows (though, to be honest, I have quite a lot of trouble understanding what you're doing):
public class Mail extends Application {
#Override
public void start(Stage ignored) throws Exception {
Platform.setImplicitExit(false);
Message[] messages = /* retrieve messages */ ;
for (Message message : messages) {
if ( /* need to display window */) {
showMessage(message);
}
}
}
private void showMessage(Message message) {
FXMLLoader loader = new FXMLLoader(getClass().getResource("prompts.fxml"));
Parent root = loader.load();
Controller controller = loader.getController();
Scene scene = new Scene(root, 450, 250);
stage.setScene(scene);
stage.initStyle(StageStyle.UNDECORATED);
stage.setTitle(...);
// showAndWait will block execution until the window is hidden, so
// you can query which button was pressed afterwards:
stage.showAndWait();
if (controller.wasYesPressed()) {
// ...
}
}
// for IDEs that don't support directly launching a JavaFX Application:
public static void main(String[] args) {
launch(args);
}
}
Obviously your logic for decided whether to show a window is more complex, but this will give you the basic structure.
To check which button was pressed, use showAndWait as above and then in your controller do
public class Controller {
#FXML
private Button yesButton ;
private boolean yesButtonPressed = false ;
public boolean wasYesPressed() {
return yesButtonPressed ;
}
// use different handlers for different buttons:
#FXML
private void yesButtonPressed() {
yesButtonPressed = true ;
closeWindow();
}
#FXML
private void noButtonPressed() {
yesButtonPressed = false ; // not really needed, but makes things clearer
closeWindow();
}
private void closeWindow() {
// can use any #FXML-injected node here:
yesButton.getScene().getWindow().hide();
}
}

JavaFX - how to set CellFactory for specific TreeItem

I have a TreeView, with many TreeItem. what i want to do is that i enable modification for the selected TreeItem and disable it for others.
To get all the TreeView to be modifiable i use :
syTree.setEditable(true);
syTree.setCellFactory(TextFieldTreeCell.forTreeView());
}
syTree.setOnEditCommit(new EventHandler<TreeView.EditEvent<String>>() {
#Override
public void handle(TreeView.EditEvent<String> t) {
syTree.getRoot().getChildren().set(syTree.getRow(t.getTreeItem()), new TreeItem<String>(t.getNewValue()));
System.out.println("setOnEditCommit");
//}
}
});
syTree.setOnEditCancel(new EventHandler<TreeView.EditEvent<String>>() {
#Override
public void handle(TreeView.EditEvent<String> t) {
System.out.println("setOnEditCancel");
}
});
This line just change all the TreeItems to TextField when trying to modify :
syTree.setCellFactory(TextFieldTreeCell.forTreeView());
How to do it for a specific TreeItem ?
Any help please ?
I think you are saying you want some cells to be editable, and some not to be editable, depending on some condition on the TreeItem they are displaying. If so, it's possible, you just need to do a little more work with your cell factory:
Callback<TreeView<String>, TreeCell<String>> defaultCellFactory = TextFieldTreeCell.forTreeView();
syTree.setCellFactory((TreeView<String> tv) -> {
TreeCell<String> cell = defaultCellFactory.call(tv);
cell.treeItemProperty().addListener((obs, oldTreeItem, newTreeItem) -> {
if (newTreeItem == null) {
cell.setEditable(false);
} else if ( /* newTreeItem should be editable */) {
cell.setEditable(true);
} else {
cell.setEditable(false);
}
});
return cell ;
});

Resources