I'm having problems understanding how a TreeViewer's item is highlighted while a user is dragging an item.
Here's what happens: I start dragging the bottom item within the Treeviewer, and the items next to it highlight accordingly. The problem is, I can't get the highlighted item from the DragOver event. But if i drop the item from this position, the event in Drop method will have the "item" field holding the highlighted item. The tree's selection isn't changed when the highlight occurs
What i want to do: I want to change the image of the pointer according to the highlighted item. The problem is I don't know how to understand which one is highlighted. Another mistery to me is that in the Drop method the highlighted item will be the target of the drop (the secont Field from the top, in this case). I do not want to use SWT.FULL_SELECTION
Here's the image:
Source snippets (what i want is the functionality of DragOver in cases when I'm not directly hovering over an item)
final DropTarget valuesTarget = new DropTarget(tree, DND.DROP_MOVE);
valuesTarget.addDropListener(new DropTargetAdapter()
#Override
public void dragOver(DropTargetEvent event)
{
if (transfer.isSupportedType(event.currentDataType))
{
final DropTarget target = (DropTarget)event.widget;
final Tree tree = (Tree)target.getControl();
final Point relativeDropPoint = getRelativeDropPoint(event);
final TreeItem targetItem = tree.getItem(relativeDropPoint);
if (targetItem != null)
{
event.feedback =
DND.FEEDBACK_SELECT | DND.FEEDBACK_SCROLL;
if (event.item.getData() instanceof NotAcceptableClass)
{
event.detail = DND.DROP_NONE;
}
}
}
}
private Point getRelativeDropPoint(DropTargetEvent event)
{
final Tree tree = (Tree)((DropTarget)event.widget).getControl();
final Point tableLocation = tree.toDisplay(0, 0);
return new Point(event.x - tableLocation.x, event.y
- tableLocation.y);
}
Take the TreeItem directly from DropTargetEvent.item.
If you would be using JFace TreeViewer with associated content and label providers then you could use ViewerDropAdapter, which would take care of resolving the item.
Related
I have the following basic GUI for demonstration:
I'm trying to achieve the following functionality but I've exhausted all avenues that I've attempted.
User can left click on any of the ImageView's and it will create an
arrow that follows the user's cursor around until the user let's go of
the mouse button. (arrow start x,y is where he clicked and end x,y is
where his mouse currently is) If the user clicked on the Red
ImageView and dragged it over the Blue ImageView and then let go,
the program would print User just clicked from R to B
If the user clicked on the Red ImageView and let go of the mouse but
was not over a different ImageView, the program would print User
just clicked from R but did not target a different ImageView.
Under all circumstances, the arrow will appear when the user clicks on
the ImageView and will disappear the second he lets go of the mouse.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import java.util.HashMap;
public class Test extends Application
{
public static int HEIGHT = 500, WIDTH = 600;
#Override
public void start(Stage primaryStage) throws Exception
{
ImageView blue = new ImageView(new Image("blue.png")),
red = new ImageView(new Image("red.png")),
dark = new ImageView(new Image("dark.png"));
// Final array as to bypass the `final` requirement of event handler inner classes.
final ImageView[] hoveredOver = new ImageView[1];
final Line[] linePtr = new Line[1];
linePtr[0] = new Line();
linePtr[0].setStrokeWidth(10);
HashMap<ImageView, Character> lookup = new HashMap<ImageView, Character>(3)
{{
put(blue, 'B');
put(red, 'R');
put(dark, 'D');
}};
for (ImageView i : new ImageView[] { blue, red, dark })
{
i.setFitWidth(150);
i.setFitHeight(150);
// Set the anchor points of the click and display the arrow.
i.setOnMousePressed(e -> {
linePtr[0].setStartX(e.getX());
linePtr[0].setStartY(e.getY());
linePtr[0].setVisible(true);
});
// Move the arrow as the mouse moves.
i.setOnMouseDragged(e -> {
linePtr[0].setEndX(e.getX());
linePtr[0].setEndY(e.getY());
});
i.setOnMouseReleased(e -> {
// Not null means that the user WAS actually just now hovering over an imageview.
if (hoveredOver[0] != null)
System.out.printf("The user clicked from %c to %c!\n", lookup.get(i), lookup.get(hoveredOver[0]));
// Null means the user is not over an ImageView.
else
System.out.printf("The user initially clicked %c but did not drag to another Imageview.\n", lookup.get(i));
linePtr[0].setVisible(false);
});
// If the user enters ANY of the ImageViews,
// Set a variable so that the drag release listener
// can know about it!
i.setOnMouseDragOver(e -> hoveredOver[0] = i);
i.setOnMouseDragExited(e -> hoveredOver[0] = null);
}
blue.setX(400);
blue.setY(250);
red.setY(300);
red.setX(50);
/*
In this example I'm using a Pane but in my real program
I might be using a VBOX HBOX etc where I cannot freely move stuff around as I'd like.
This makes things extremely difficult and without using a 'Pane'
I don't know how this can even be done. Suggestions?
*/
Pane pneRoot = new Pane(blue, red, dark, linePtr[0]);
Scene scene = new Scene(pneRoot, WIDTH, HEIGHT);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
This was my best attempt and it's not even close. It moves a line (not an arrow, and ideally I want my arrow to curve as it moves much like this example image from a popular video game) but does not suit my needs. It cannot detect however when I let go while 'dragging over' an ImageView.
Is there a better way to do this? I feel like I can't simply the code I have down any further but there MUST be another way.
Java is an object-oriented language. The basic idea is that you create classes to represent the data you are modeling and then create objects from those classes. If you are tying things together with arbitrary maps to look things up, and arrays kicking around for no apparent reason, you are starting in the wrong place.
JavaFX has a system of observable properties. These wrap objects in a mutable way and can be observed so you can respond to changes.
Make sure you read and understand the documentation on MouseEvents and MouseDragEvents. There are three different modes for handling dragging. For events (mouse drag events) to be sent to nodes other than the one on which the drag was initiated during a mouse drag, you need to be in full "press-drag-release gesture" mode. You can activate this mode by calling startFullDrag() on the node when responding to a dragDetected event.
I would start with something like
public class NamedDragAwareImageView {
private final ObjectProperty<NamedDragAwareImageView> source ;
private final ObjectProperty<NamedDragAwareImageView> destination ;
private final String name ;
private final ImageView imageView ;
public NamedDragAwareImageView(ObjectProperty<NamedDragAwareImageView> source,
ObjectProperty<NamedDragAwareImageView> destination,
String name, String resource) {
this.source = source ;
this.destination = destination ;
this.name = name ;
this.imageView = new ImageView(new Image(resource));
imageView.setOnDragDetected(e -> {
source.set(this);
destination.set(null);
imageView.startFullDrag();
});
imageView.setOnMouseDragReleased(e -> {
if (source.get() != null && source.get() != this) {
destination.set(this);
}
});
// other image view config...
}
public ImageView getView() {
return imageView ;
}
public String getName() {
return name ;
}
}
Then you can do things like:
// observable properties to represent start and end nodes for drag:
ObjectProperty<NamedDragAwareImageView> source = new SimpleObjectProperty<>();
ObjectProperty<NamedDragAwareImageView> destination = new SimpleObjectProperty<>();
Pane root = new Pane();
// create your named image views, referencing the source and destination
// and add their image views to root, e.g.
NamedDragAwareImageView red = new NamedDragAwareImageView(source, destination, "Red", "red.png");
root.getChildren().add(red.getView());
// recommend using SVG paths (i.e. javafx.scene.shape.Path) for the arrow
// easy to draw programmatically, easy to manipulate elements etc:
Path arrowHead = new Path();
MoveTo arrowHeadStart = new MoveTo();
arrowHead.getElements().add(arrowHeadStart);
arrowHead.getElements().addAll(/* draw an arrow head with relative path elements... */);
arrowHead.setVisible(false);
// avoid arrowHead interfering with dragging:
arrowHead.setMouseTransparent(true);
// this will contain a MoveTo and a bunch of LineTo to follow the mouse:
Path arrowLine = new Path();
arrowLine.setMouseTransparent(true);
root.getChildren().addAll(arrowHead, arrowLine);
// change listener for source. source is set when drag starts:
source.addListener((obs, oldSource, newSource) -> {
if (newSource == null) return ;
arrowHeadStart.setX(/* x coord based on newSource */);
arrowHeadStart.setY(/* similarly */);
arrowHead.setVisible(true);
});
// change listener for destination. destination is only set
// when drag complete:
destination.addListener((obs, oldDestination, newDestination) -> {
if (newDestination != null) {
System.out.println("User dragged from "+source.get().getName()+
" to "+destination.get().getName());
}
});
root.setOnMouseDragOver(e -> {
if (source.get()==null && destination.get()!=null) {
// update arrowStart position
// add line element to arrowLine
}
});
root.setOnMouseReleased(e -> {
// clear arrow:
arrowHead.setVisible(false);
arrowLine.getElements().clear();
});
Is there a way to display widget in the expanded area? RowExpander uses AbstractCell which can display only simple html. It is possible to take html from widget and place it into cell but the buttons in the widget won't work after this.
Extend the RowExpander class and/or override the method beforeExpand. Here's what I did and it works great for inserting a button or in my case i needed a grid:
public class RowWidgetExpander extends RowExpander {
private Widget widget;
public void setWidget(Widget widget) {
this.widget = widget;
}
public Widget getWidget() {
return widget;
}
#Override
protected boolean beforeExpand(ModelData model, Element body, El row, int rowIndex) {
RowExpanderEvent e = new RowExpanderEvent(this);
e.setModel(model);
e.setRowIndex(rowIndex);
e.setBodyElement(body);
if(fireEvent(Events.BeforeExpand, e)) {
body.setInnerText("");
body.appendChild(widget.getElement());
ComponentHelper.doAttach(widget);
return true;
}
return false;
}
Instead of inserting the widget to the grid. You can put widget on the grid.
Insert grid in AbsolutPanel. And after expand event you can insert widget into the AbslolutPanel and set position on expanded area.
Set the height of the AbsolutPanel such as total height of grid (all grid lines). And AbsolutPanel put inside a Scroll Panel.
With this solution, You have a widget - not only pure html. And you do not have to worry about Scrolling.
Try this workaround to get events working again in the row expander.
http://www.sencha.com/forum/showthread.php?250316-RowExpander-dosn-t-fire-events-on-ContentCell
I have a JTree with several nodes and each node has a different associated JPanel I want to display to the user. I've made use of a custom TreeCellRenderer and my code currently works as is, but (I think) it's a little too clunky and I have concerns about it
1) If I just leave a tree node selected, my code will keep refreshing the JPanel over and over again. Once the selected node has displayed the JPanel once, I don't want it to again unless another node is selected in between.
2) If the user selects a different node (let's say node2) while my timer is running for node1, I don't want to bother displaying node1's JPanel since the user has navigated away from it.
Here is my code of interest:
Component ret = super.getTreeCellRendererComponent(tree, value,
selected, expanded, leaf, row, hasFocus);
DefaultMutableTreeNode entry = (DefaultMutableTreeNode)value;
// if the node is selected
if(sel)
{
// set the background of the node
setBackgroundNonSelectionColor(new Color(0x91, 0xC5, 0xFF));
// display the JPanel for the node
displayPanel(entry);
}
else
{
// if the node isn't selected then no background selection color
setBackgroundNonSelectionColor(Color.WHITE);
}
return ret;
displayPanel function:
// display the node's JPanel after sleeping for 1s
private void displayPanel(final DefaultMutableTreeNode entry)
{
Thread thr = new Thread(){
public void run(){
// sleep for 1000ms
CF.sleep("1000");
// display the panel for this node
CF.displayPanel(entry);
}
};
thr.start();
}
To be honest, DefaultTreeCellRenderer is still a little confusing to me. I'd appreciate any advice on how to make this run efficiently.
Maybe I'm missing something here but couldn't you just use a Tree Selection listener on the tree?
tree = new JTree(treeModel);
tree.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent arg0) {
//Code here to get selection and display panel
}
});
TreeCellRenderer are meant for "rendering" purposes. What is the representation of a given object? Is it a label? Is it a checkbox? Something more complex? Basically it is used to "print" the representation of each node of the JTree on the display. Rendering may occur many times, at unpredictable moments, and is therefore not a good place to listen for selection.
What you are looking for is a TreeSelectionListener which has a single method to implement and which will be triggered every time the selection of the JTree changes.
Add your listener with javax.swing.JTree.getSelectionModel().addTreeSelectionListener().
final JTree tree = ...;
tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
if (tree.getSelectionPath()!=null)
displayPanel((DefaultMutableTreeNode) tree.getSelectionPath().getLastPathComponent());
}
});
As you can see from the images below the expand and collapse icons are gray, as is the row selection highlight. This causes you to not see the expand or collapse icon (Note: Not the folder icon) when the row is highlighted, I want to have a white expand or collapse icon for the row that is selected. How can that be done?
Something else that would also be cool is, to have the expand and collapse icons completely hidden until the JTree gains focus. like windows 7's tree.
Google says -according to this post: http://www.exampledepot.com/egs/javax.swing.tree/DefIcons.html - :
// Retrieve the three icons
Icon leafIcon = new ImageIcon("leaf.gif");
Icon openIcon = new ImageIcon("open.gif");
Icon closedIcon = new ImageIcon("closed.gif");
// Create tree
JTree tree = new JTree();
// Update only one tree instance
DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer)tree.getCellRenderer();
renderer.setLeafIcon(leafIcon);
renderer.setClosedIcon(closedIcon);
renderer.setOpenIcon(openIcon);
// Remove the icons
renderer.setLeafIcon(null);
renderer.setClosedIcon(null);
renderer.setOpenIcon(null);
// Change defaults so that all new tree components will have new icons
UIManager.put("Tree.leafIcon", leafIcon);
UIManager.put("Tree.openIcon", openIcon);
UIManager.put("Tree.closedIcon", closedIcon);
// Create tree with new icons
tree = new JTree();
// Update row height based on new icons;
Certainly, I'm not sure if you can modify only the color of the images on-the-go. But you can always create new icons, right?
You can try this. You should note however to get this to work, I had to override setUI on the tree to only allow my TreeUI.
private class IconTreeUI extends BasicTreeUI {
private Icon collapseIcon = null;
private Icon expandIcon = null;
#Override
public Icon getCollapsedIcon() {
if (collapseIcon == null) {
collapseIcon = new ImageIcon(yourCollapseImageHere);
}
return collapseIcon;
}
#Override
public Icon getExpandedIcon() {
if (expandIcon == null) {
expandIcon = new ImageIcon(yourExpandImageHere);
}
return expandIcon;
}}
As the question states, I'd like to set a mouse listener to my JTree so that I can change the cursor to a HAND_CURSOR when the user places their mouse over a node.
I already have a MouseAdapter registered on my JTree to handle click events, but I can't seem to get a MouseMoved or MouseEntered/MouseExited to work with what I'm trying to do.
Any suggestions?
You need to add a MouseMotionListener/Adapter:
tree.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
int x = (int) e.getPoint().getX();
int y = (int) e.getPoint().getY();
TreePath path = tree.getPathForLocation(x, y);
if (path == null) {
tree.setCursor(Cursor.getDefaultCursor());
} else {
tree.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
}
});
In a JTree, each of tree node is showed by a label generated by the TreeCellRenderer associated to this tree. The usually used class is DefaultTreeCellRenderer which renders this (the DefaultTreeCellRenderer). As a consequence, you can try adding this DefaultTreeCellRenderer a MouseMotionListener to toggle mouse cursor.
Notice adding MouseMotionListener to the tree will simply toggle mouse rendering when on Tree component, not when mouse is on a label.