I am using scala.swing, but a solution for Java Swing is also applicable to scala.swing.
Therefore I open this question as a Java Swing question.
I have JTable with TransferHandler in JScrollPane.
Usually, the table can scroll with mouse wheel motion.
While dragging, however, the scroll pane does not move with mouse wheel.
The user can scroll by moving cursor to the top or the bottom of the pane(auto scroll),
but auto scroll is slow, so I want to provide usual mouse functionality.
I tried to capture mouse wheel event in JTable.
When not dragging, it receives MouseWheelMove event, but while dragging, it receives nothing.
My guess is that mouse wheel event is delivered to something related to TransferHandler, not to JTable.
How to detect mouse wheel motion while dragging?
Any other method is welcome, as far as my aim is achieved.
It seems (at least on OS X), that the wheel events are buffered and dispatched only after the DnD gesture is complete. Here is my try:
import javax.swing._
val d = Array.fill(1000)(Array[AnyRef]((math.random * 1000).toInt.toString))
val t = new JTable(d, Array[AnyRef]("A")) {
override def processMouseWheelEvent(e: java.awt.event.MouseWheelEvent): Unit = {
println(s"Wheel: ${e.getID}")
super.processMouseWheelEvent(e)
}
def enableEventsP(e: Long): Unit = enableEvents(e)
}
t.setDragEnabled(true)
t.enableEventsP(java.awt.AWTEvent.MOUSE_WHEEL_EVENT_MASK)
val f = new JFrame
f.getContentPane.add(new JScrollPane(t), java.awt.BorderLayout.CENTER)
f.pack()
f.setVisible(true)
When I scroll the wheel while dragging and then release the mouse, the wheel events are delivered.
You may want to override the autoscroll behaviour of the JTable to make it faster, I guess. This is done in DropTarget as far as I can see. I found this example.
Related
I was wondering if there is a way to disable/prevent scrolling in a ScrollPane?
Basically I have a Canvas wrapped in a Group object to enable zooming (which is done by 'Ctrl + scroll'). Though for some reason the ScrollPane consumes the event (if it can be scrolled) before it fires any other scrollEvent (e.g. the ScrollEvent from the Canvas, Group, ScrollPane and even the ScrollPane's Parent!).
So I was wondering what the options are (if there are any) to catch a scrollEvent before it is consumed by the ScrollPane.
Thanks for your time
Thanks to joshpy I got the answer.
I forgat that EventFilters are a thing in javafx. Luckly you can consume the Event in the eventFilter as well, so here is the solution.
scrollPane.addEventFilter(ScrollEvent.SCROLL, event -> {
if(event.isControlDown())
{
zoomCanvas(event) // zoom the canvas instead of scrolling the actual pane.
event.consume();
}
});
Many thanks for the tip!
Although Im still not sure why an usual event wouldn't work.
I have a series of column labels that scrolls independently from the data that is displayed in a matrix below. I can make the whole scrollbar transparent except on hover. The labels are right up against the data, which I like, however, upon hover, unless I shift the vertical scroll (which I'd rather not do), the scrollbar obscures the beginning of all the labels.
I would like to set the background of the scrollbar as transparent so that only the "grabber" (or whatever it's called) is the only thing that is drawn. (It will obscure the beginning of the labels it is over, but would be a lot less so.)
Is there any way to do that? Here is what I tried:
Color bg = new Color(255,255,255,0);
colLabelScroll.setBackground(bg);
This does not seem to make the background of the scrollbar transparent.
What I'm shooting for is like how the iPhone's scrollbar grabber hovers over info in some apps. Is that even possible with JScrollBars?
Transparent JScrollBar can do it, but consider this: if column labels are related to the data and you can scroll them independently, beginner users may not understand what is going on and associate column labels with whatever is visually aligned beneath it. Either you will need some sort of visual indicator that makes it clear that the labels are disconnected from the data, or you should change the way labels are scrolled that never leaves them statically in 1 place.
Here's how I ended up making the relationship between the labels and the data clearer:
Instead of allowing the user to independently and intentionally scroll the labels, I decided to control the label scroll position via mouse hover. This eliminates the need for the obtrusive scrollbar.
I created a scroll-bar-like indicator that shows the portion of the data the labels represent.
I highlighted the currently hovered label that corresponds to the data below it, i.e. the only label that is ever correctly aligned with the data is the one that is under (or directly above) the cursor.
When the mouse is not hovered over (or dragging from) the column labels, do not display any labels. This helps prevent invalid label/data associations by the user.
A few nuanced notes: Implementing your own scrollbar-like indicator is somewhat involved, especially if your labels are painted and then rotated, because the paint position of 0 is at the bottom of the pane, yet the vertical scroll position of the pane is at the top. You will have to track the vertical scroll position to be able to recover it again when the cursor returns since you are blanking the labels on mouse out.
When developing a plugin for IntelliJ, I accomplished it with:
scrollPane.getVerticalScrollBar().setUI(ButtonlessScrollBarUI.createTransparent());
It takes advantage of the the:
ButtonlessScrollBarUI.createTransparent()
method, which is an IntelliJ specific method. However, if you can find a ScrollBarUI which has a transparent background, you can use the same trick.
Since I got a bit lost myself at first after reading #hepcat72's answer I'm posting a little explanation about the BasicScrollBarUI class:
JScrollBar scrollbar = scrollPaneConversation.getVerticalScrollBar();
scrollbar.setUI(new BasicScrollBarUI(){
// This function returns a JButton to be used as the increase button
// You could create your own customized button or return an empty(invisible) button
#Override
protected JButton createIncreaseButton(int orientation){
}
// Same as above for decrease button
#Override
protected JButton createDecreaseButton(int orientation){
}
// This function paints the "track" a.k.a the background of the scrollbar
// If you want no background just return from this function without doing anything
// If you want a custom background you can paint the 'Graphics g' object as you like
#Override
protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds)
{
}
// This function paints the "thumb" a.k.a the thingy that you drag up and down
// You can override this function to paint it as you like
#Override
protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds)
{
}
});
Refer to the Transparent JScrollBar link posted by #hepcat72 for hints about what to do exactly in these functions.
I have the following Swing UI structure.
When I scroll the mouse wheel within the blue JPanel, the JScrollPane doesn't respond to the mouse wheel event. Why?
I read from the java doc that:
JScrollPane provides a scrollable view of a lightweight component. ...
Note that JScrollPane does not support heavyweight components.
So is this because my structure is too heavy? Or any other reasons?
ADD 1
I accidentally switched my window with the mouse middle click. And after that, the mouse wheel suddenly worked for the JScrollPane.
This leads me to think maybe it's related to the focus. Then I found below line:
this.setFocusableWindowState(false);
After I changed it to below, mouse wheel works.
this.setFocusableWindowState(true);
Though javadoc says:
Setting a Window's focusability state to false is the standard
mechanism for an application to identify to the AWT a Window which
will be used as a floating palette or toolbar, and thus should be a
non-focusable Window.
At first, I guess it's because the JDialog is not in focusable window state, so it cannot receive events. But actually, mouse click always works. So I am still not sure about the root cause.
It seems a toolbar or a floating palette cannot be focused but still can receive mouse click event. So I guess maybe only certain events are filtered by setFocusableWindowState(false).
About components being heavy weight: AWT components are meant, that use the operating system widgets. You use swing JComponents, J*. Swing components are called light weight as they do all drawing and event handling emulated, in one large native window.
The JPanel should be larger than the JScrollPane, so a "view port" may be scrolled. Have the preferred sizes set correctly.
In general I would have thought that every JTextPane would be in its own JScrollPane.
Also JScrollPane functions a bit differently.
scrollPane = new JScrollPane(panel);
I'm writing a game in Java and this is the first one that I'm trying to make "pretty". The game is called Bantumi - it's a board game and right now I'm programming the animations for the movements. The problem is that, when the movement animation is running, the board still gets the Mouse Event and if the user selects a new movement, the running one will be discarded.
For the board I'm using a class extending JLayeredPane. This how I have my layers:
Layer 0: The Holes with the seeds, so the users selects one for the movement, each Hole being a JPanel with a MouseListener.
Layer 1: The Highlight that marks the currently selected Hole
Layer 2: The Animation of the Movement.
Layer 10: A custom notification system class that I wrote, it says things like "Your Turn", "Repeat Turn", "You win", etc.
I want to prevent every possible mouse event in any of this layers while the animation is running, how do I do that? I thought that adding a panel covering the whole area in a top player was enough but it didn't work. Any workaround?
For (temporarily) disabling events from MouseListener (KeyListener ....) you can use
public void consume()
Consumes this event so that it will not be processed in the
default manner by the source which originated it.
for example
whatever.addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent e) {
if (somethingIsTrue) {
//do some stuff from mouse listener
} else {
e.consume();
}
}
.
.
.
}
I thought that adding a panel covering the whole area in a top player was enough but it didn't work.
If this is a Swing GUI, you've already got a JPanel that covers the top level window, the glassPane, but the only way to make this work is you have to add a MouseListener (or both MouseListener and MouseMotionListener), and you have to make it visible.
You can get the top window's glasspane by calling getGlassPane() on either a top level window or its root pane, add a MouseListener and MouseMotionListener to it, and then whenever you want to make the GUI unresponsive to mouse events, set the glass pane to visible by calling setVisble(true) on it. You can toggle this effect off by doing the converse, by calling setVisble(false).
I have problem with mouseListener in JPanel.
I add JLabel which contain image(size 600 x 600) on JPanel, and I add mouseListener for the JPanel.
Everytimes I click on the image, the image should change to another image, and this is working fine. However, the problem is that, only if I click on the right side or at the center of the image, then the image will change to another image. The image doesn't change when I click on the top or on the left side. This make me confused. I want the image to change to another image when I click everywhere within the image display.
private final int SECOND= 1;
private final int FIRST = 0;
int imageCounter = 0;
JLabel picture;
JPanel panel;
...
private mainLayout () {
GridBagLayout m = new GridBagLayout();
Container c = (Container)getContentPane();
GridBagConstraints con;
c.setLayout (m);
picture = new JLabel();
picture.setIcon(getImages(myImage.get(imageCounter).get(FIRST))); //first Image
panel = new JPanel();
con = new GridBagConstraints();
con.anchor=GridBagConstraints.CENTER;
con.gridy = 1; con.gridx = 0;
m.setConstraints(panel, con);
c.add(panel);
panel.add(picture); //add the pictures
panel.addMouseListener(l);
....
}
MouseListener l = new MouseAdapter(){
public void mouseClicked (MouseEvent e)
{
Point p = e.getPoint();
if((panel.getBounds().contains(p))
picture.setIcon(getImages(myImage.get(imageCounter).get(SECOND)));
}
};
The problem looks as if it might be in this unnecessary code:
Point p = e.getPoint();
if((panel.getBounds().contains(p))
The mouse listener is on the panel, so the mouse coordinates will be relative to the panel top left. panel.getBounds() gets the bounds of the panel relative to whatever its parent container is.
It's worth noting the mouse event behave very strangely. They "bubble up" until they hit a component with a mouse listener attached. So, adding a mouse listener actually changes the behaviour of a component. Adding the listener to a parent will potentially miss events depending upon the exact way the component is set up (which may change arbitrarily). There are a number of ways around this, none of them good.
The likely problem you have is the same I have encountered, that your event is getting gobbled up in your hierarchy as a previous poster implied. In reference to camickr's reply, it really doesn't matter if you extend JPanel or JLabel as long as you're aware of the consequences. JPanel offers you a lot more capability and has many useful things already built into it. But let's take the simple example where you have the following:
A JFrame that contains a JPanel, call it's JParent. JParent has a set of mouse listeners dedicated to it.
JParent contains a child JPanel that's added to its components, called JChild. JChild also has its own set of mouse listeners.
What happened when you click on an area that is not occupied by JChild? You get the response of JParent's listeners. If you click in an area with JChild visible, only JChild's mouse events will fire.
So be very careful in debugging as Java's swing may have different behavior than you may be used to from other implementations of drawing simple forms/GUIs. Events are basically one-shot and the lowest level component gets first dibs. If you try to fire the parent's events anyway, your x/y coords will be based on the child and not the parent. I'm still trying to come up with a good work around myself because I need access specifically to the parent to move an object elsewhere.
The best I can think of that isn't a mess to debug/think about are these two ideas:
a) have each of your individual components contain their own exclusive set of events and try to prevent the 'bubbling up' effect from coming into play.
b) handle all events on the parent
I'd prefer a as it avoids nasty things such as checking component classes just to figure out what kind of reaction you want (i.e. things like what kind of right-click menu to show).
Edit: So you can handle this either way I've discovered. You can use the dispatchEvent message to forward the events back upward the chain. In my case I have to gobble up the first event (or alter it) and send the x-y positions as if they are relative to the parent (using offsets). I'd say the better way to do it is through forwarding because then all your individual components can be encapsulated.
Are you positive that you are actually clicking? The mouse click event only fires if the mouse is pressed and released in the exact same spot. If your mouse is moving even minimally while you are clicking, the event won't be fired.
Out of curiosity, why are you using a JLabel to display an image? You may be better off writing an ImagePanel class that extends JPanel and overrides the paint() method to do your own drawing. Then you can attach MouseListeners right to the panel.