I am attempting to add drag-and-drop functionality to my application, whereby the originator of the drag event is a JTable. I am currently using the built-in drag support JTable offers by calling setDragEnabled(true).
The problem I'm facing is that in order to commence a drag operation, one has to first click on a row of the table, and then release the mouse; it is only the second mouse press (and all subsequent mouse presses) that generate drag events. This occurs even if the JTable loses focus - i.e. Once the first left-click operation has been performed, drag-and-drop works perfectly until I swap in a new TableModel. When new model has been installed one needs to perform a left-click on the table before drags start working again.
Reading the API documentation for setDragEnabled(boolean) the implication is that this is the L&F's responsibility and hence there may not be anything I can do to solve this. Does anyone have any suggestions? I am using the Alloy L&F but would be reluctant to change it.
I discovered a hacky solution, which was to add a MouseListener to the JTable and hook into the TransferHandler's exportAsDrag method when the mouse is pressed:
final JTable actionTbl = new JTable();
actionTbl.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent evt) {
// Need to explicitly start a drag operation when the mouse is pressed.
// Otherwise drags are only started *after* the user has clicked once
// on the JTable (this could be down to the L&F not doing the right thing).
actionTbl.getTransferHandler().exportAsDrag(actionTbl, evt, TransferHandler.COPY);
}
});
Quote from the javadoc of the setDragEnabled method
When automatic drag handling is enabled, most look and feels (including those that subclass BasicLookAndFeel) begin a drag and drop operation whenever the user presses the mouse button over an item (in single selection mode) or a selection (in other selection modes) and then moves the mouse a few pixels.
If I read this correctly, you should get the desired behavior when you use single selection mode.
If you need that behavior combined with multiple selection, you could opt to manually handle the incoming mouse events on the table, and on mouse_down adjust the selection and then delegating the mouse event to the JTable. So in pseudo-code:
protected void processEvent(AWTEvent e) {
if ( isMouseDownEvent( e ) ){
adjustSelection( e );
}
super.processEvent( e );
}
Note: I haven't tested this. It is solely based on what I read in the javadoc, and might have some unwanted side-effects as the JTable itself will still handle the event and react on it
Adamski's solution works really well, but I had to change TransferHandler.COPY in the last line to TransferHandler.MOVE in order for it to work:
final JTable actionTbl = new JTable();
actionTbl.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent evt) {
actionTbl.getTransferHandler().exportAsDrag(actionTbl, evt, TransferHandler.MOVE);
}
});
Related
I'm using a javax.swing.JTable to show rows in a database table.
I need to fire two different event for two different cases:
when is selected at least a row (or a click on at least a row is performed).
when a double click on a row is performed.
I already looked for an answer on stack overflow, but I didn't find anything satisfying .
Any idea?
when is selected at least a row (or a click on at least a row is performed).
You should monitor changes to the row selection using the JTables ListSelectionModel via a ListSelectionListener. This will notify you when the selection is changed by the user using the mouse or the keyboard or if the selection is changed programmatically for some reason
See How to Write a List Selection Listener for more details
when a double click on a row is performed
The only way you can detect this is through a use of a MouseListener. Normally, users expect that a left mouse button click will do one action and the right mouse button will do something else.
You will want to use SwingUtilities.isLeftMouseButton or SwingUtilities.isRightMouseButton to determine what the user is actually doing.
See How to Write a Mouse Listener for more details
You can add a mouse listener to the table and capture event over there with mouse event like below
table.addMouseListener
(
new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
if (e.getClickCount() == 2)
{
}
if (e.getClickCount() == 1)
{
}
}
}
);
}
and to capture selection event you can use
table.getSelectionModel().addListSelectionListener(...);
Background: I am using custom AWT D&D in my (heavily) customized JTextPane subclass. In other words, I have disabled Swing's D&D with pane.setDragEnabled(false) and I use my own DragSource, DragGestureListener, etc.
The problem: The default selection behavior in JTextPane when Swing's D&D is disabled is as follows:
Select some text using the mouse
Mouse press inside the selection with the intent of starting a drag
Desired: selection is not lost.
Actual: upon mouse press, selection is immediately lost, giving no opportunity for me to start my drag operation since there is now nothing to drag.
I have partially traced this back to BasicTextUI$DragListener, since this is the class that calls the pane's getDragEnabled() method, but BasicTestUI doesn't seems to do much with the text component's selection. So, I'm still not exactly where the selection is being cleared, but I need to find it so I can eliminate the behavior.
I have employed a hack that involves setting a persistent highlight from a carat listener, so even though the selection is lost a highlight will remain that my drag can interact with. I am not happy with this and it has other side effects.
Many thanks for any pointers.
After many more hours of reviewing JDK source, I've determined that the selection behavior is controlled by the Caret and not anything in the text component or UI hierarchy.
A mildly customized Caret seems to do the trick. Note that if you don't override mouseDragged(), the custom drag will still work but the selection will typically be altered in the pane after the drag starts, making the user think they're only dragging part of the text they selected.
textPane.setCaret(new DefaultCaret() {
#Override
public void mousePressed(MouseEvent evt) {
int pos = textPane.viewToModel(evt.getPoint());
if (pos > textPane.getSelectionStart() && pos < textPane.getSelectionEnd()) {
return;
}
super.mousePressed(evt);
}
#Override
public void mouseDragged(MouseEvent e) {
if (dragItem != null) return;
super.mouseDragged(e);
}
});
I have the following code for JList. On click for an item in the list it should highlight the selected item. But if I press too fast it wont actually select the next item on list on the first click. How should I solve this?
MouseListener mouseListener = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 1) {
String selectedItem = (String) jl.getSelectedValue();
if(selectedItem == "Page One"){
System.out.print("Page one");
}
}
}
};
jl.addMouseListener(mouseListener);
A MouseListener is in appropriate for the task, instead use a ListSelectionListener
Take a look at How to write a List Selection Listener and How to use lists for more details
On click for an item in the list it should highlight the selected item
This is the default behaviour, so I'm not sure why you are doing this.
But if I press too fast it wont actually select the next item on list on the first click.
Probably because you aren't generating a mouseClicked event. A mouseClicked event is only generated when a mousePressed/mouseReleased event is generated at the same pixel location. Maybe the mouse is moving slightly. Try just adding your code to mousePressed.
but i only want mouse click, even if the user using the arrow key to change it should not happen
That is a terrible UI. The user should control whether they want to use the mouse or keyboard. Advanced users will use the keyboard and beginners will use the mouse.
I am a new user of Java swing. I need to be able to create a popup with row info when the user clicks on that row. I managed to incorporate the mouseClick event reaction in my table class, and I have the row info available. How can I notify the main window about the event so it can display the dialog box/popup box?
Just call a method on the main window to perform the action
There are several ways to handle this:
1) You can have the custom table class have a custom listener on it (Observer Pattern) which it then calls whenever the click occurs
2) You can have the table call a method on the main window - i.e. pass in the main window as part of the construction of the table
3) You can have the main Window register as a listener to the table (i.e. a mouse listener) and have it handle the events instead.
There are others, I am sure. These are the ones I have seen most often used. Depending on the size, scope and intent of the software being written, each has it's merits and detriments. Is this a project for school, a toy being written to learn about Swing, or is it designed to be a longer term, larger project? If it is the latter, I would recommend looking up Model View Controller (MVC) architecture discussions, as it can make, long term, the maintenance of the code much easier, in my experience.
Good luck.
You can do it like this:
myTable.addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent e) {
if(SwingUtilities.isRightMouseButton(e)) {
int index = myTable.rowAtPoint(e.getPoint());
JPopupMenu popup = new JPopupMenu();
popup.add(myMenuAction);
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
});
And then implement an Action myMenuAction where you use the index from your table.
Here's the situation, I have a jFrame with a tabbed pane and within the tabs I have a couple of jTables and a jTree. I want to be able to chain the selections between the tables and the tree based on whether a user uses a ctrl/shift + click versus a regular click. (If you hold ctrl and click in the first table/tree, it adds to the overall selection, if you use a regular click it clears the selections in the other tables/tree). Currently I'm having an issue with Java's jTree component. I have added a TreeSelectionListener and a MouseListener with a class that implements both interfaces, call it MyBigListener;
i.e.
MyBigListener listener = new MyBigListener();
jTree1.addMouseListener( listener );
jTree1.addTreeSelectionListener( listener );
MyBigListener implements TreeSelectionListener, MouseListener {
private boolean chained = false;
public synchronized setChained(boolean ch){
chained = ch;
}
public synchronized boolean isChained(){
return chained
}
public void valueChanged(TreeSelectionEvent e){
if(isChained()){ blah... }
}
public void mousePressed(MouseEvent e){
setChained(e.isControlDown() || e.isShiftDown());
}
}
My plan was to set a boolean flag if the user uses a ctrl/shift + click that I could check during the valueChanged(TreeSelectionEvent e) implemented by the tree selection listener.
I want to be able to process the mouse events before the valueChanged TreeSelectionEvents, but the problem is that I receive the mouse events after the valueChanged treeSelection event. This seems weird to me that I receive the selection change before the mouse pressed event fires, when the selection change is actually initiated by the mouse pressed. (I've already synchronized the boolean flag setting, which ironically helped to highlight the mis-order of events.)
I've already tried alternatives like adding a keyListener, but this doesn't work when the focus is on a separate frame, which goofs me up when a user holds ctrl and then clicks into the jTree causing it to receive both the focus and fire any valueChanged selection events.
Any help would be appreciated, thanks!
--EDIT-- #akf
I have separate jTables and jTrees in a tabbed Pane that serve as a summary/control panel for data in a nodal-graph. I'm using these components in the tabbed Pane to do coordinated selection to a graph displayed in a separate jFrame. Individually each table works just fine for its selection as does the jTree. It's coordinating between the panes that's tricky. This works fine so far with jTable components because I fire the new selections as the result of a MouseEvent where I can tell if the shift/ctrl button was down, formulate my new selection, and pass it to the parent frame which coordinates selections between all panes and sends the final selection to the graph. However, the parent needs to know if it needs to chain a selection between panes, or squash the others. With the jTables it's fine again, because I fire selection changes as the result of a mouse click. The jTree is more of a problem because I'm doing some forced selections. If you click on a branch, it forces all leaves into the selection. I needed to implement a TreeSelectionListener to do that, but only get a valueChanged(TreeSelectionEvent) to realized changes. I added a mouseListener to listen for ctrl+clicks and shift+clicks, but apparently the events don't always happen in the same order.. at least so far I receive the valueChanged event before the mousePressed event, so checking to if a ctrl+click happened posts after the selection has already been modified.
Right now, I'm posting a pending selection change and then have the MouseListener grab that and send it up the chain, but if these events aren't guaranteed to happen in the same order, at some point it's going to fail. Implementing a delayer also rubs me the wrong way.
Thanks for the help so far.
--EDIT2-- #ykaganovich
I think overriding the fireValueChanged method is closer to the right way to go about things. Depending on my definition of what actions should cause a "chained" selection to the other components, I'd need to gather some context on what's going on before the valuedChanged method fires. This basically means calling it myself in all cases where I can define what it means by who triggers it. I.e. If a mouse event causes it and ctrl is down then set what I need to set (interpret) then fire. If it's going to change due to a keyboard event, again, set what I need to set, then fire. I don't think the TreeSelectionModel is the way to go, because I still won't know what the circumstances were when the event fired. I think this means I'll need to rewrite parts of the jTree to do this but I guess I'll see how it goes. Thanks.
Don't do it that way, override JTree.fireValueChanged instead.
Try something like this (untested):
class ChainedSelectionEvent extends TreeSelectionEvent {
ChainedSelectionEvent(TreeSelectionEvent e) {
super(e.newSource, e.paths, e.areNew, e.oldLeadSelectionPath, e.newLeadSelectionPath);
}
}
protected void fireValueChanged(TreeSelectionEvent e) {
if(chained) { // figure out separately
super.fireValueChanged(new ChainedSelectionEvent(e));
} else {
super.fireValueChanged(e);
}
}
Then check instanceof ChainedSelectionEvent in your listener
EDIT
Actually, I think the right way to do this is to implement your own TreeSelectionModel, and override fireValueChanged there instead. Assuming setSelectionPath(s) methods imply a new selection, and add/removeSelectionPath(s) imply chaining, you could distinguish between the two cleanly. I don't like listening to either keyboard or mouse events explicitly, because there's more than one way to change a selection (e.g. if someone is holding down SHIFT and hitting a down-arrow, you won't get a mouse event).
You may get the mouse event before or after the tree selection event. Best not to rely on such orders. The reason is that the tree selection event is caused in response to the mouse event. Is that mouse event listener called before or after your listener? Could be either.
This sort of thing is closely involved with the implementation of the PL&F.
the key here is to understand that a JTree is delivered with a BasicTreeUI.MouseHandler, see javax.swing.plaf.basic.BasicTreeUI.
to answer the central question, "what fires fireValueChanged", the answer is "this mouse handler"... which turns out to be the only mouse listener returned when you go tree.getMouseListeners().
So you have to replace this default mouse listener with your own version ... which is a little tricky. I use Jython, which everyone needs to discover. This code shouldn't be too difficult to translate into Java however:
from javax.swing.plaf.basic import BasicTreeUI
def makeMouseHandlerClass():
class MouseHandlerClass( BasicTreeUI.MouseHandler ):
def mousePressed( self, mouseEvent ):
genLog.info( "mouse handler MOUSE PRESSED!" )
nTFSelf.mousePressedStatus = True
BasicTreeUI.MouseHandler.mousePressed( self, mouseEvent )
nTFSelf.mousePressedStatus = False
return MouseHandlerClass
suppliedMouseHandler = nTFSelf.taskTree.mouseListeners[ 0 ]
nTFSelf.taskTree.removeMouseListener( suppliedMouseHandler )
nTFSelf.taskTree.addMouseListener( makeMouseHandlerClass()( nTFSelf.taskTree.getUI() ))
... basically the equivalent Java here would be to extend BasicTreeUI.MouseHandler as MyMouseHandler, and then in the last line replace with new MyMouseHandler( tree.getUI() )... "nTFSelf" here is merely a reference to the "this" object of the code where all this is being written...