I'm developing a custom TreeView object.
I'm using a custom cellFactory to provide the TreeCell objects of my TreeView.
This allows me to install custom Context Menu on the various cells, depending on the Item they are displaying.
But I'm not entirely satisfied with the behaviour.
When left-clicking on cell, it gets selected (OK)
But when right-clicking a cell, the context menu is displayed (OK) but the cell is also selected. (NOK)
How can I change this behaviour ?
I tried to implement an eventFilter on the tree view, to consume the event if it is a right-click but this doesn't change anything, the above behaviour still applies.
addEventFilter(MouseEvent.MOUSE_CLICKED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getButton() == MouseButton.SECONDARY) {
event.consume();
}
}
});
setCellFactory(new Callback<TreeView<TreeDisplayable>, TreeCell<TreeDisplayable>>() {
#Override
public TreeCell<TreeDisplayable> call(
final TreeView<TreeDisplayable> treeView) {
return new TreeDisplayableTreeCell(owner, javaModel);
}
});
public class TreeDisplayableTreeCell extends TreeCell<TreeDisplayable> {
...
#Override
public void updateItem(TreeDisplayable item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
setText(getItem().treeViewString());
setGraphic(item.getPic());
if (getTreeItem().getParent() == null) {
// it means it's the root node
setContextMenu(new RootItemContextMenu(javaModel, owner));
} else {
setContextMenu(new TreeItemContextMenu(javaModel, owner,getTreeItem().getValue()));
}
}
}
}
Reacting on Tony's comment
Creating a custom EventDispatcher does the trick.
public class TreeEventDispatcher implements EventDispatcher {
#Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if (event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) event;
if (mouseEvent.getButton() == MouseButton.SECONDARY) {
event.consume();
} else {
event = tail.dispatchEvent(event);
}
} else {
event = tail.dispatchEvent(event);
}
return event;
}
}
The behaviour is identical for all events, except the right click event, which is consumed, thus preventing the right-click selection of any TreeCell.
Luckily enough, the context menu is still displayed on right click (although I don't understand why ...) Does anybody have a clue ?
Previous Facewindu answer is actually working, but there is another way to achieve that behavior and still have context menu appearing on right click:
treeView.addEventFilter(MOUSE_PRESSED, event -> {
if (event.isSecondaryButtonDown()) {
Node text = (Node) event.getTarget();
TreeCell<...> treeCell = (TreeCell<...>) text.getParent();
treeCell.getContextMenu().show(treeCell, 0, 0);
event.consume();
}
});
Related
I am new to JavaFX and, therefore, also to TornadoFX so please bear with me.
I have a simple app in Java but want to move it to Kotlin and am running into problems finding the corresponding mechanisms in TornadoFX. I have a ListView holding implementations of IStoryItem representing Stories and Chapters. I want to be able to move the chapters around and even from one story to another. The TreeView in Java has the following implementation in its setCellFactory call:
tv.setCellFactory(new Callback<TreeView<IStoryItem>, TreeCell<IStoryItem>>() {
#Override
public TreeCell<IStoryItem> call(TreeView<IStoryItem> siTreeView) {
TreeCell<IStoryItem> cell = new TreeCellStoryEditor();
cell.setOnDragDetected((MouseEvent event) -> {
// Don't drag Story nodes.
if (cell.getItem() instanceof Story) return;
Dragboard db = cell.startDragAndDrop(TransferMode.MOVE);
// Put the Part on the dragboard
// From: https://stackoverflow.com/a/30916660/780350
ClipboardContent content = new ClipboardContent();
content.put(objectDataFormat, cell.getItem());
db.setContent(content);
event.consume();
});
cell.setOnDragOver((DragEvent event) -> {
if (event.getGestureSource() != cell && event.getDragboard().hasContent(objectDataFormat)) {
/* allow for moving */
event.acceptTransferModes(TransferMode.MOVE);
}
event.consume();
});
cell.setOnDragEntered((DragEvent event) -> {
IStoryItem item = cell.getItem();
if (item instanceof Story &&
event.getGestureSource() != cell &&
event.getDragboard().hasContent(objectDataFormat)) {
cell.setUnderline(true);
}
event.consume();
});
cell.setOnDragExited((DragEvent event) -> {
cell.setUnderline(false);
event.consume();
});
cell.setOnDragDropped((DragEvent event) -> {
try {
Dragboard db = event.getDragboard();
boolean success = false;
if (db.hasContent(objectDataFormat)) {
Part droppedPart = (Part)db.getContent(objectDataFormat);
IStoryItem targetStoryItem = cell.getItem();
// Question: How to handle drops between leaf items or
// before the initial leaf or after the final leaf.
if (targetStoryItem instanceof Story) {
Story story = (Story) targetStoryItem;
updateStoryWith(droppedPart, story);
addPartTo(cell.getTreeItem(), droppedPart);
success = true;
}
}
event.setDropCompleted(success);
event.consume();
} catch (Exception e) {
System.out.println(e.getMessage());
}
});
cell.setOnDragDone((DragEvent event) -> {
if (event.getTransferMode() == TransferMode.MOVE) {
IStoryItem item = cell.getItem();
TreeItem<IStoryItem> ti = cell.getTreeItem();
TreeItem<IStoryItem> pti = ti.getParent();
pti.getChildren().remove(ti);
IStoryItem psi = pti.getValue();
// Remove the Part/Chapter from its previous Story
boolean removed = removePartFrom(psi, item);
}
event.consume();
});
cell.setEditable(true);
return cell;
};
});
I have looked for something similar in TornadoFX but can't find anything that looks like it would work. I am already using the cellFormat builder but I can't figure out how to add the event handlers inside it. I see from IntelliJ's intellisense that there is also a cellFactory builder but I am not sure how to make use of it or how to add the event handlers to it.
You can use the exact same technique in TornadoFX. Remember, TornadoFX just applies a high level API on top of JavaFX. You can always still access the underlying JavaFX API's without issue.
tv.setCellFactory {
object : TreeCell<IStoryItem>() {
init {
setOnDragOver { event ->
}
setOnDragEntered { event ->
}
setOnDragExited { event ->
}
setOnDragDropped { event ->
}
}
}
}
i have a lot of HBoxes with some different components inside ( RadioButton, Labels, Buttons ...).
The RadioButtons are in a ToggleGroup, so only 1 Radio Button can be selected.
I want to add a OnChange - Event to the Radio Button. If the RadioButton would be unselected there should be a Event-Trigger. How can i add the Event to the Radio-Button?
At the moment i have a code like this, but it doesn't have the function i want to have.
radio.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
if(!radio.isSelected()){
ivTriangleImg.setRotate(iRotateCoord2);
btnTriangle.setGraphic(ivTriangleImg);
}
if(group!=null){
group.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
#Override
public void changed(
ObservableValue<? extends Toggle> arg0,
Toggle arg1, Toggle arg2) {
}
});
}
}
});
I use JavaFX 2.0 and Java 1.7 so i can not use Lambda Functions or the special component functions of JavaFx8 / Java 1.8
The state of JavaFX controls is represented by observable properties. You can access these properties with control.propertyNameProperty() and add ChangeListeners to them:
radioButton.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> obs, Boolean wasPreviouslySelected, Boolean isNowSelected) {
if (isNowSelected) {
// ...
} else {
// ...
}
}
});
radio.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
if(!radio.isSelected()){
ivTriangleImg.setRotate(iRotateCoord2);
btnTriangle.setGraphic(ivTriangleImg);
}
if(group!=null){
group.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
#Override
public void changed(
ObservableValue<? extends Toggle> arg0,
Toggle arg1, Toggle arg2) {
if(!radio.isSelected()&&ivTriangleImg.getRotate()!=iRotateCoord1){
ivTriangleImg.setRotate(iRotateCoord1);
btnTriangle.setGraphic(ivTriangleImg);
}
}
});
}
}
});
This would work for my Question. I have done a little mistake, so i don't check the Radio-Style. It works fine now... Sorry for this Question.
Is there a possibility to check the Event at the Radio Button and not at his group?
In case you have many radio buttons you dont want have a single EventHandlers for each one of them....
Here is an example with 6 radio buttons but just one ChangeListener where the events are all process in a centralized method.
This detects programatic events, keyboard events and mouse events
Note that the listener is added to the ToggleGroup
public class ReportOptionsPane extends BorderPane implements ChangeListener<Toggle> {
RadioButton radioFoldersOnly ;
RadioButton radioAllItems ;
RadioButton radioArtifactsOnly ;
RadioButton radioStatsOrderByOccurrence ;
RadioButton radioStatsOrderByName ;
public ReportOptionsPane(ReportOptions rep) {
super() ;
ToggleGroup tg = new ToggleGroup();
radioFoldersOnly = new RadioButton("Folders only") ;
radioAllItems = new RadioButton("Files & Folders") ;
radioArtifactsOnly = new RadioButton("Artifacts only") ;
radioFoldersOnly.setToggleGroup(tg);
radioAllItems.setToggleGroup(tg);
radioArtifactsOnly.setToggleGroup(tg);
//You add the listener to the toogle group of your radio buttons
tg.selectedToggleProperty().addListener(this);
ToggleGroup tg2 = new ToggleGroup();
radioStatsOrderByOccurrence = new RadioButton("Order stats by occurrence") ;
radioStatsOrderByName = new RadioButton("Order stats by name") ;
radioStatsOrderByOccurrence.setToggleGroup(tg2);
radioStatsOrderByName.setToggleGroup(tg2);
//You can reuse the listener for the second toogle group of your radio buttons
tg2.selectedToggleProperty().addListener(this);
}
//Then you handle all in a centralized place
#Override
public void changed(ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) {
if(newValue.equals(radioFoldersOnly)) {
//react to radio button being selected
} else if(newValue.equals(radioAllItems)) {
//react to radio button being selected
} else if(newValue.equals(radioArtifactsOnly)) {
//react to radio button being selected
} else if(newValue.equals(radioStatsOrderByName)) {
//react to radio button being selected
} else if(newValue.equals(radioStatsOrderByOccurrence) {
//react to radio button being selected
}
}
}
I want to implement a GUI element that works like a button that reacts different on right/left clicks and dragging.
Say this button is an object. Who is in charge to call the right method on mouseevents from the button and how is it designed in usual programming languages like e.g. AWT in Java?
I don't claim that this is 'the professional way' but this is how I do it.
With lazy initialization and anonymous classes.
private JButton btnSpecialbutton;
private JButton getBtnSpecialbutton() {
if (btnSpecialbutton == null) {
btnSpecialbutton = new JButton("SpecialButton");
btnSpecialbutton.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
System.out.println("Dragged");
}
});
btnSpecialbutton.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1)
{
System.out.println("Left Mouse Button");
}
else if(e.getButton() == MouseEvent.BUTTON3)
{
System.out.println("Right Mouse Button");
}
}
});
}
return btnSpecialbutton;
}
I'm working with GWT and want to do an action when the user holds the left mouse button on a GWT button. But I can't find the right event handler or another solution for that problem.
Is there a way in GWT to klick on a button, hold the mouse button and do the same action again and again till the mouse button is released?
Button scrollUpBtn = new Button("Top");
scrollUpBtn.setWidth("66px");
scrollUpBtn.addMouseDownHandler(new MouseDownHandler() {
#Override
public void onMouseDown(MouseDownEvent event) {
//handCards.setVerticalScrollPosition(handCards.getVerticalScrollPosition() - 10);
mouseUp = true;
}
});
scrollUpBtn.addMouseUpHandler(new MouseUpHandler() {
#Override
public void onMouseUp(MouseUpEvent event) {
mouseUp = false;
}
});
scrollUpBtn.addKeyDownHandler(new KeyDownHandler() {
#Override
public void onKeyDown(KeyDownEvent event) {
if (mouseUp == true) {
handCards.setVerticalScrollPosition(handCards.getVerticalScrollPosition() - 10);
}
}
});
Step 3 in Andrei's answer assumes that the KeyDownEvent will keep firing. I'm not sure if that's so..
An alternative would be to use the Down & Up handlers to start/stop a repeating timer which carries out your action. You can then set the repeat interval based on how often you want your action to be carried out. Remember that this runs as single threaded JavaScript, so if you carry out lengthy processing things will slow down and the scheduled intervals will not be on time.
Button btn= new Button("Button");
final Timer actionTimer = new Timer() {
#Override
public void run() {
// Your action here
System.out.println("Doing something!");
}
};
btn.addMouseDownHandler(new MouseDownHandler() {
#Override
public void onMouseDown(MouseDownEvent event) {
// Choose the appropriate delay
actionTimer.scheduleRepeating(1000);
}
});
btn.addMouseUpHandler(new MouseUpHandler() {
#Override
public void onMouseUp(MouseUpEvent event) {
actionTimer.cancel();
}
});
Add MouseDownHandler to your widget. When it fires, it should set a flag to true, e.g. mouseDown = true;
Add MouseUpHandler to your widget. When it fires, it should set a flag to false, e.g. mouseDown = false;
Add KeyDownHandler to your widget. When if fires, check if flag is true - then do something. If flag is false, don't do it.
I have a VBox inside a ScrollPane wich contains a HTMLEditor and other stuff.
When I type text inside the HTMLEditor each time I hit the Space Bar, I get a whitespace inside the editor as expected but also the Scrollpane scrolls down. First I worked around this by adding a EventFilter to the Scrollpane and consume the KEY_PRESSED event. But now I need this event inside the HTMLEditor.
So my question: is there any Flag to tell the Scrollpane not to scroll on KeyCode.SPACE or is there a way to route the input Focus/ Key Events only to the HTMLEditor, bypassing the Scrollpane? Or a way to filter this event only on the Scrollpane?
You can reproduce this also with javafx Scene Builder:
Scrollpane->VBox(larger than Scrollpane so Scrollbars appear)->2*HTMLEditor, Preview in Window, hit the Space Bar.
Solved:
Added an EventFilter to the HTMLEditor, which consumes the KeyCode.SPACE on KEY_PRESSED.
htmlEditor.addEventFilter( KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getEventType() == KeyEvent.KEY_PRESSED){
// Consume Event before Bubbling Phase, -> otherwise Scrollpane scrolls
if ( event.getCode() == KeyCode.SPACE ){
event.consume();
}
}
}
});
I just ran into a similar problem. What I did was to pass the filtered event on to my event handler method directly before consuming it. For your case, it would look something like this (assume you have an KeyEvent handler method that you've named onKeyPressed()):
htmlEditor.setOnKeyPressed(new EventHandler<KeyEvent>() {#Override public void handle(KeyEvent t) { onKeyPressed(t); }});
scrollPane.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
if(t.getCode() == KeyCode.SPACE) {
onKeyPressed(t);
t.consume();
}
}
});
Create your own widget that extends the HTMLEditor and add a listener for the pressed event.
setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.SPACE
|| event.getCode() == KeyCode.TAB ) {
// Consume Event before Bubbling Phase, -> otherwise Scrollpane scrolls
event.consume();
}
});