I'm trying to make a button disable when the mouse enters the node and enable it again when the mouse leaves it. The button responds to MouseEvent.ANY and the handle method gets the event type and handles it accordingly.
The println shows a constant stream of this even though I don't move the cursor:
MOUSE_EXITED
MOUSE_EXITED_TARGET
MOUSE_ENTERED
MOUSE_ENTERED_TARGET
MOUSE_EXITED
MOUSE_EXITED_TARGET
MOUSE_ENTERED
MOUSE_ENTERED_TARGET
MOUSE_EXITED
MOUSE_EXITED_TARG
This makes the button flicker on and off.
public void handle(MouseEvent me) {
System.out.println(me.getEventType());
if(me.getEventType() == MouseEvent.MOUSE_ENTERED_TARGET || me.getEventType() == MouseEvent.MOUSE_MOVED) {
nejButton.setDisable(true);
}
else if((me.getEventType() == MouseEvent.MOUSE_EXITED) || (me.getEventType() == MouseEvent.MOUSE_EXITED_TARGET) )nejButton.setDisable(false);
}
Looks like disabling a Node means the mouse is no longer considered to be within its bounds. It may be a bit of a stretch, but I believe this is documented by the Node#disabled property:
Indicates whether or not this Node is disabled. A Node will become disabled if disable is set to true on either itself or one of its ancestors in the scene graph.
A disabled Node should render itself differently to indicate its disabled state to the user. Such disabled rendering is dependent on the implementation of the Node. The shape classes contained in javafx.scene.shape do not implement such rendering by default, therefore applications using shapes for handling input must implement appropriate disabled rendering themselves. The user-interface controls defined in javafx.scene.control will implement disabled-sensitive rendering, however.
A disabled Node does not receive mouse or key events. [emphasis added]
What happens, I believe, is that when you disable the Node the mouse "exits" because said Node is no longer eligible to receive mouse events. This happens regardless of if you move your mouse or not. Then your code reacts to the mouse "exiting" and you enable the Node. However, since the now enabled Node is again eligible to receive mouse events, and the fact that you kept your mouse in the same place, the mouse "enters" the Node again. Your code then reacts to the mouse "entering" the Node and disables the it again—causing a never-ending loop.
I suspect there's no way to work around this behavior1.
That being said, I find the desired goal strange. What are you trying to tell the user by having the Button disabled only when the mouse hovers over it? It seems that would convey, "Come click this button... just kidding!". That doesn't seem like a pleasant user experience.
1. Since a disabled node does not receive mouse events one could argue this behavior is a bug. However, stopping this loop from occurring won't help your case; moving the mouse out of your button won't fire a mouse-exited event, regardless of if there's a "disabled while hovering" mouse-exited event fired or not. I also am inclined to believe this behavior is not a bug.
Related
I'm working in SWT (no JFace), and I'm attempting to customize the behavior of a Tree that I'm using a sidebar.
The top-level items in the tree shouldn't be selectable; they're basically headers. Only the children of these items should be selectable. As a result, I would like the UI to behave in a way that indicates this; clicking on one of these top-level items should expand or collapse but shouldn't provide any sort of visual feedback outside of the indicator changing its state and the children's visibility changing.
The problem seems to be that on OS X, the expand/collapse indicator is a triangle (don't know if it's an image or a unicode character) that either points right or down, but is also colored based on the "selection" state. I've managed to override all of the relevant behavior for the top-level items except for the arrow changing color.
I've used an SWT.EraseItem listener to hook in to the background drawing so that the background doesn't change color. This works as expected.
I've used an SWT.PaintItem listener to make sure that the text in the top-level item doesn't change color. This works as expected, but doesn't seem to have any influence over the indicator; I've even tried not resetting the GC's foreground color, but the color of the indicator still changes:
Not Selected:
Selected
Some of the things that I've attempted to do, all of which have failed:
Just drawing a rectangle on top of indicator. The indicator seems to always be on top, no matter what I attach the PaintListener to.
Using a Listener on the Selection event type, checking to see if the event.item is one of the top-level items, and then fiddling with the event bits and redraw flags. This doesn't seem to work well at all; the behavior is completely unpredictable. The code looks something like this:
sideBar.addListener(SWT.Selection, new Listener()
{
#Override
public void handleEvent(Event event)
{
TreeItem eventItem = (TreeItem) event.item;
if(sideBarRoots.contains(event.item))
{
event.detail = 0;
event.type = 0;
event.doit = false;
sideBar.setRedraw(false);
}
else
{
sideBar.setRedraw(true);
event.type = SWT.Selection;
event.doit = true;
}
}
});
Since the redraw flag is just a hint, sometimes it gets set properly and others it doesn't. It can have either no effect, or can get locked in to a state where nothing redraws in the sidebar.
I'm aware that a lot of this behavior is highly coupled to the behavior of the underlying native widgets, but I was wondering if there was any way to get the behavior that I'm looking for without roll a custom widget from scratch?
I think in a situation as yours you should ideally not allow selection on a tree header.
You can either cancel the selection event, by not allowing it to be clickable. But that might be a little kludgy implementation, especially for key navigation.
The safer approach, if possible with your data, would be to just move the selection whenever a tree parent node is selected. So it should work like this that if a parent node is selected, either by keyboard or mouse: you expand the node and move the selection to the first child.
This way you can avoid all the paint trickery to hide the selection state.
I recently encountered a bug in java where JList will fire the valueChanged() method twice when changing a value with the mouse, and only once when changing a value with the keyboard. I just found a bug regarding this on Oracle's website (apparently, the bug is more than twelve years old), and I'm wondering if anyone can explain to me why Oracle has decided that this isn't a defect (not to mention that getValueIsAdjusting() returns false when the keyboard is used).
For anyone having this issue, I found that simply checking for when getValueIsAdjusting() is false, then running the rest of my method will get around the issue.
There is a simple explanation.
When you are applying selection with mouse you perform a list of actions:
1. Press left mouse button on some element
- list selects an element under the mouse and fires 1st event
- also here you will get getValueIsAdjusting=true since the mouse is not yet released
2. You might drag mouse without releasing it to change selection
- list will fire an additional event for each selection change made
- getValueIsAdjusting will be also true for each of those events since you are still making changes
3. You release mouse
- list will fire the final event - selection operation is finished
- getValueIsAdjusting=false now, you can do whatever you want with final selection
To summ up - those additional events are fired to let you completely control list behavior on selection changes (on selection change sequence to be exact). You might want to ignore the selection changes when getValueIsAdjusting=true since there always will be a final event with getValueIsAdjusting=false which will inform you that selection changes are finished.
Also, when you change selection with key buttons list wouldn't know if you are going to change it after first key press or not, so getValueIsAdjusting will be always false for such changes.
There is a simple solution:
private void jList1 ValueChanged(javax.swing.event.ListSelectionEvent evt) {
if (!evt.getValueIsAdjusting()) {//This line prevents double events
}
}
In the Java/Swing API's ButtonModel interface, what's the difference between the Rollover and Armed properties? Reading the code and docs, it looks like they're both updated by the same event (mouse entering or exiting the component), but the armed value affects whether or not releasing the mouse button results in an ActionEvent being fired, whereas the Rollover property is just visual. Is there any situation where a ButtonModel would be armed but not rolled over, or vice-versa?
If I understand the docs correctly the Rollover state indicates that the user is over the button and has pressed the mouse button. So usually you will have Pressed and Armed at the same time.
Rollover triggers whenever the user moves the mouse over the button like :hover in css for example.
Rollover means the cursor is above it, but armed means its been clicked but not released or dragged outside the bounds. Subtle difference, but its there.
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...
I'm experiencing some strange behaviour when using a stylus with swing.
I am interpreting pressing the button on the side of the stylus(RIGHT) and pressing the stylus down(LEFT) as a "Grab" event, but occasionally (more often than 0), events are just being dropped.
The JavaDocs for MouseEvent are pretty explicit about how multibutton presses are handled if executed one at a time (left down, right down, right up, left up) but say nothing about simultaneous button presses.
I'm left to wonder, would they be emitted as two mousePressed events, or as one with the button mask set for both buttons, or something else entirely?
Thanks.
I'd interpret the API doc as simultaneous button presses being simply not possible:
When multiple mouse buttons are pressed, each press, release, and click results in a separate event.
So there should be separate events. The problems you observe could be due to errors in your code, the stylus' driver, the hardware, or Swing (this is in decreasing order of likelihood as I see it :)
I'd try to diagnose the problem by logging events at different levels, if possible.
Simultaneous button presses are processed as two separate mousePressed events. Run the Mouse Events Demo to see them processed separately.
As I recall, there's no way to handle simultaneous button presses. What I used to do to ensure that multiple buttons being pressed at once were treated as such was I would have a boolean variable for each button and when it was pressed, I would set it to true and when it was released, I would set the boolean to false. Then when it came time to perform an action, I would check for the boolean variables (sometimes I would have the actionlistener redirect to the method call for determining what action was to happen next after setting the booleans). This doesn't work if the only thing you want to do is them being pressed at the exact same time, but if you're just trying to get combinations to work, then that's how I did it. This was about 4 years ago, before Java 5, so I may be wrong about that.