Accessing properties of custom object from JavaFX DragAndDrop Clipboard - javafx

How can I get my actual object out of the Dragboard with getContent()? Is there any possibility to specify the type? Todo and Doing are FlowPanes - MyRectangle is an exammple of a custom component.
What I want to have:
Put an Object eg. an Rectangle with height, filled color etc. on the clipboard and get that object back from the board with height, color etc....
private static final DataFormat itemFormat = new DataFormat("custom.item");
MyRectangle myRectangle = generateRectangle();
myRectangle.setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
Dragboard db = myRectangle
.startDragAndDrop(TransferMode.MOVE);
ClipboardContent content = new ClipboardContent();
content.put(taskFormat, myRectangle);
// Rectangle has height
System.out.println(myRectangle.getHeight());
TaskItem task = new TaskItem();
task.setTime(6);
content.put(itemFormat, task);
db.setContent(content);
event.consume();
}
});
myRectangle.setOnDragDone(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
event.consume();
}
});
doing.setOnDragOver(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
if (event.getGestureSource() != doing) {
event.acceptTransferModes(TransferMode.MOVE);
}
event.consume();
}
});
doing.setOnDragEntered(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
event.consume();
}
});
doing.setOnDragExited(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
event.consume();
}
});
doing.setOnDragDropped(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
final Dragboard db = event.getDragboard();
boolean success = false;
if (db.hasContent(taskFormat)) {
MyRectangle rect2 = (MyRectangle) db.getContent(taskFormat);
System.out.println(rect2.getHeight());
todo.getChildren().remove(rect2);
doing.getChildren().add(rect2);
success = true;
// doing.getChildren().add(rectangle);
}
event.setDropCompleted(success);
event.consume();
}
});
private MyRectangle generateRectangle() {
final MyRectangle rect2 = new MyRectangle(0, 0, 10, 10);
rect2.setId("app");
rect2.setArcHeight(8);
rect2.setWidth(80);
rect2.setArcWidth(8);
rect2.setStrokeWidth(1);
rect2.setStroke(Color.WHITE);
rect2.setHeight(60);
return rect2;
}

Well, you really should be thinking about having a representation of the data (not the view of the data) which is the object that is dragged and dropped. You register a handler to detect the drag with a node (view of the data), set the data into the dragboard, and then on a successful drop create another view of the same data where you dropped it. Remove the previous Node for a move, don't remove it for a copy.
Unfortunately, it doesn't work.
See this
and vote for this bug/feature, which would allow you to do this directly.
Currently you can only place Java objects on the drag board if they implement Serializable. Since the JavaFX Properties do not implement Serializable, any class that uses those for data representation (which, imho, is any class you'd want to use to represent data you want to drag and drop around an application). And even if your class is Serializable, as I understand it, the object is serialized on placing it in the dragboard and deserialized when you remove it, which means you'd get a new object when you dragged and dropped, not a reference to the same object: that's probably not what you want. (If you copy something by drag and drop, then edit it, you probably want both copies to respect the edit.)
So, for now, I think the solution is to create a local repository of some kind and store the dragged object in it. This might be as simple as just an ObjectProperty<?> currentlyDraggedObject, or something more complex like the LocalDragboard I implemented at the bottom of the discussion referenced earlier. (This is nothing more than copying the code you'll find if you google "standard example of a typesafe heterogeneous container".)
I have to say, I find the way drag and drop is done a bit weird. Almost everything in JavaFX 2 and later was written in a very modern style of Java, with (almost) everything using generics very comfortably, some very nice concurrency APIs that were designed for the newer high-level concurrency APIs, all the event handling designed with an eye to more recent language developments such as lambda expressions and streams. The Bindings API even seems to tip its hat slightly towards the whole reactive programming movement. But drag and drop seems to have been designed as though the only data we would ever want to transfer by drag gestures were Strings, Images, and Files. It's as though the designers of the DnD API hadn't really got their heads around the idea that programmers would want to, you know, develop their own data representation classes.
So in the midst of this very modern-looking GUI framework, you have a DnD API that looks like it was designed in the late 90s (if that). Very strange.

Related

Confused on ActionListeners

Hello fellow coders of the night,
I am stuck with a moral dilemma (well not moral, but mostly i don't know what to do).
Suppose I have one button that can do several actions, depending on the menu item which is chosen.
Basically, I've imagined this
private void menuButtonActionPerformed(ActionEvent b)
ActionEvent a
if(a.getSource()==menuItem)
if(b.getSource()==button)
do this and that
Is this the correct way to do this? because if it is I'd have to add ActionListeners on the menuItem but I get stuck with some stupid error code somewhere!
Thanks in advance for helping me!
Post Scriptum : #David, I've tried this, however the initial condition isn't verified.
private void buttonValidateActionPerformed(java.awt.event.ActionEvent evt)
ActionListener l = (ActionEvent e) -> {
if(e.getSource()==menuItemAdd)
{
System.out.println("eureka!");
buttonSearch.setEnabled(false);
if (evt.getSource()==buttonValidate)
{
DataTransac dt = new DataTransac();
dt.addCoders("...");
}
}
if(e.getSource()==itemDelete)
{
DataTransac dt = new DataTransac();
dt.deleteCoders("...");
}
};
menuItemAdd.addActionListener(l);
itemDelete.addActionListener(l);
That won't work; your listener will get a different invocation for each time the listener is used -- so the event source will be either a button or a menu item for a single invocation.
You'll need to respond to the menu item with one ActionListener that stores state, and then separately handle the button action. You could do this with one listener, but I wouldn't; I'd do this:
private MenuItem selected;
private class MenuItemListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
// if you really want to have one listener for multiple menu items,
// continue with the .getSource() strategy above, but store some
// state outside the listener
selected = (MenuItem)event.getSource();
// you could alternatively have a different listener for each item
// that manipulates some state
}
}
private class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
// take conditional action based on selected menu item, as you describe
// in the question
}
}
void setup() {
JMenuItem first = /* ... */;
JMenuItem second = /* ... */;
MenuItemListener listener = new MenuItemListener();
first.addActionListener(listener);
second.addActionListener(listener);
JButton button = /* ... */;
button.addActionListener(buttonListener);
}
Generally speaking this is the preferred approach -- use a different listener for each semantic action, rather than one that introspects the source. Your code will be cleaner, simpler, and easier to understand.
For the same reasons, some people prefer to use anonymous classes for Java event listeners. Here's a Gist that shows several syntaxes: https://gist.github.com/sfcgeorge/83027af0338c7c34adf8. I personally prefer, if you are on Java 8 or higher:
button.addActionListener( event -> {
// handle the button event
} );

How do I stop TextArea from listening to Shortcut KeyCombinations as KeyEvents?

Just as the title says, how do I stop shortcut keys (accelerators) being picked up as key events in TextArea? I have tried the method suggested here with different modifications: TextArea ignore KeyEvent in JavaFX with no luck.
If you want to stop specific accelerators from working when the TextArea has focus simply add an event filter for KEY_PRESSED events.
public class AcceleratorFilter implements EventHandler<KeyEvent> {
// blacklist of KeyCombinations
private final Set<KeyCombination> combinations;
public AcceleratorFilter(KeyCombination... combinations) {
this.combinations = Set.of(combinations);
}
#Override
public void handle(Event event) {
if (combinations.stream().anyMatch(combo -> combo.match(event)) {
event.consume();
}
}
}
TextArea area = new TextArea();
area.addEventFilter(KeyEvent.KEY_PRESSED, new AcceleratorFilter(
KeyCombination.valueOf("shortcut+o"),
KeyCombination.valueOf("shortcut+s") // etc...
));
If you want to indiscriminately block all accelerators registered with the Scene then you can query the Scenes accelerators and consume the KeyEvent if appropriate.
TextArea area = new TextArea();
area.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
var scene = ((Node) event.getSource()).getScene();
// #getAccelerators() = ObservableMap<KeyCombination, Runnable>
var combos = scene.getAccelerators().keySet();
if (combos.stream().anyMatch(combo -> combo.match(event)) {
event.consume();
}
});
This latter option may cause issues if you're not careful. For instance, if you have a default Button in the Scene then the above event filter may interfere with the ENTER key. Also, this option won't necessarily stop things like shortcut+c, shortcut+v, etc. because those shortcuts are registered with the TextInputControl, not the Scene.

Javafx : ComboBoxTableCell - how to select a value in one click?

I have a TableView with a ComboBoxTableCell, when using the default implementation the user have to click three times to select a value from of the ComboBox's list.
I want when the user clicks on the cell to show the combo box list. I based my solution on this one:
JavaFX editable ComboBox in a table view
The cell does get into edit mode (startEdit() is called) but it takes another click to show the list of values, what am I missing?
table.addEventHandler(MouseEvent.MOUSE_CLICKED, (e) ->
{
if (table.getEditingCell() == null)
{
TablePosition focusedCellPos = table.getFocusModel().getFocusedCell();
table.edit(focusedCellPos.getRow(), focusedCellPos.getTableColumn());
}
});
Thanks.
Interesting problem - bubbling up again after quite a while :)
Looks like the approach of the OP is indeed working (as of fx11, some bugs around its editing seem to be fixed) - with a little help from the combo cell:
start editing in a single click handler on the tableView (from OP)
extend ComboBoxTableCell and override its startEdit to open the dropDown
Code snippet:
// set editable to see the combo
table.setEditable(true);
// keep approach by OP
table.addEventHandler(MouseEvent.MOUSE_CLICKED, (e) -> {
TablePosition<Person, ?> focusedCellPos = table.getFocusModel()
.getFocusedCell();
if (table.getEditingCell() == null) {
table.edit(focusedCellPos.getRow(),
focusedCellPos.getTableColumn());
}
});
// use modified standard combo cell shows its popup on startEdit
firstName.setCellFactory(cb -> new ComboBoxTableCell<>(firstNames) {
#Override
public void startEdit() {
super.startEdit();
if (isEditing() && getGraphic() instanceof ComboBox) {
// needs focus for proper working of esc/enter
getGraphic().requestFocus();
((ComboBox<?>) getGraphic()).show();
}
}
});
Maybe not the cleanest solution to this problem, but I found a workaround to make the ComboBoxTableCells drop down its menu in just 1 click:
column.setCellFactory(new Callback<TableColumn<Person, String>, TableCell<Person, String>>() {
#Override
public TableCell<Person, String> call(TableColumn<Person, String> column) {
ComboBoxTableCell cbtCell = new ComboBoxTableCell<>(cbValues);
cbtCell.setOnMouseEntered(new EventHandler<Event>() {
#Override
public void handle(Event event) {
// Without a Person object, a combobox shouldn't open in that row
if (((Person)((TableRow)cbtCell.getParent()).getItem()) != null) {
Robot r = new Robot();
r.mouseClick(MouseButton.PRIMARY);
r.mouseClick(MouseButton.PRIMARY);
}
}
});
return cbtCell;
}
});
PS: I know that this topic is a bit old, but I also stumbled upon this problem recently and could not find any working solution to it online. As I sad, it's not the cleanest workaround, but at least it does its job. ;)

Layout of Layer in GlassPane not working as expected

I installed a Layer in the GlassPane like that:
MobileApplication.getInstance().getGlassPane().getLayers().add(myLayer);
Now I would expect the content of myLayer to be alligned in the center of the glassPane like it is when I add a layer to View:
view.getLayers().add(myLayer)
Although alignment is set to center I get the following result:
I noticed the layoutBounds of the layer added to glassPane being all "0", while the layoutBounds of the layer in view are identical to the view layoutBounds.
Furthermore I don't need to call layer.show() as stated in the gluon documentation ("Showing a Layer is then achieved either by calling show(), ..."), because the layer is immediately shown after it is added to a layer.
Am I missing something?
I suggest you have a look at the detailed documentation here, and not only to the JavaDoc.
There you will find a more detailed explanation on how layers can be added and created.
The preferred way is by using addLayerFactory(), like:
#Override
public void init() {
addViewFactory(BASIC_VIEW, () -> new BasicView(BASIC_VIEW));
addLayerFactory("My Layer", () -> new SidePopupView(new StackPane(new Button("Side"))));
}
The layer will be hidden unless you show it with MobileApplication.getInstance().showLayer("My Layer").
You can create your own Layer implementation, like:
private class MyLayer extends Layer {
private final Node root;
private final double size = 150;
public MyLayer() {
root = new StackPane(new Button("A custom layer"));
root.setStyle("-fx-background-color: white;");
getChildren().add(root);
getGlassPane().getLayers().add(this);
}
#Override
public void layoutChildren() {
root.setVisible(isShowing());
if (!isShowing()) {
return;
}
root.resize(size, size);
resizeRelocate((getGlassPane().getWidth() - size)/2, (getGlassPane().getHeight()- size)/2, size, size);
}
}
and add it as well to the factory:
addLayerFactory("My Layer", () -> new MyLayer());
But notice you will have to resize and relocate it, otherwise you will get 0,0 location as in your picture, and take care of its visibility.
Or you can use built-in layers, like SidePopupView, and you won't need to worry about these more low level details.

TranslateTransition in JavaFX does nothing

I'm trying to use a TranslateTransition object in JavaFX to move an onscreen object in a LOGO program I am building. I have an onscreen TurtleDisplay object, which extends ImageView, and this is what I'm trying to move. The code to move it is here:
public void drawTurtle(TurtleData currentData) {
TurtleImage inList = getTurtleImage(currentData);
if (inList==null) {
TurtleImage temp = new TurtleImage(currentData.getX(),
currentData.getY(), currentData.getHeading(), turtleImage);
myTurtlesGroup.getChildren().add(temp);
myTurtlesList.add(temp);
}
else {
TranslateTransition tt = new TranslateTransition(Duration.seconds(3),inList);
tt.setFromX(inList.getX());
tt.setFromY(inList.getY());
tt.setToX(inList.getX()+currentData.getX());
tt.setToY(inList.getY()+currentData.getY());
tt.setCycleCount(Timeline.INDEFINITE);
tt.play();
}
}
This code, which is part of the front end, is called from the back end via a Listener on an ObservableList. The backend contains this ObservableList of TurtleData objects that contain the information necessary to move a turtle on screen -- which turtle to move, the coordinate to move to, and the rotation of the turtle. The code to call this is here:
ObservableList<TurtleData> myTurtles = FXCollections
.observableArrayList();
myTurtles.addListener(new ListChangeListener<TurtleData>() {
#Override
public void onChanged(Change<? extends TurtleData> c) {
myDisplay.getSelectedWorkspace().getTV().clearTurtles();
while (c.next()) {
for (TurtleData addItem : c.getAddedSubList()) {
myDisplay.getSelectedWorkspace().getTV().drawTurtle(addItem);
}
}
}
});
I have stepped through with a debugger and ensured that this code is called -- specifically, the tt.play() line is run. Nothing moves on screen. Does anyone have any idea what is wrong? Do I need to setup an Animation Timeline? Thank you for any help!

Resources