Problem with setCursor() after closing modal JComponent - swing

When I create a modal JComponent, the mouse (correctly) changes to the default cursor. However, I'm running into a weird issue when I close the component.
If I close the component while my cursor is contained in the bounds
of the component, the cursor will correctly revert.
If I close the component while my cursor is outside the bounds (with esc, for example) the cursor will not revert, even with calls to setCursor().
How can I correctly have my cursor revert in both cases in a non-hacky way?
Example code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
class ModalTest {
public static void main (String[] args) {
// Create a frame.
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setPreferredSize(new Dimension(800, 600));
frame.pack();
frame.setLocationRelativeTo(null);
// Add a button.
JButton button = new JButton();
frame.add(button);
frame.revalidate();
frame.setVisible(true);
frame.setCursor(Cursor.HAND_CURSOR);
// While the mouse is inside the JFrame, the cursor should be Cursor.HAND_CURSOR.
// And when the modal JOptionPane is visible, the cursor should be Cursor.DEFAULT_CURSOR.
// However, when that modal JComponent is closed while the cursor is outside it, it does
// not revert to Cursor.HAND_CURSOR.
button.setAction(new AbstractAction() {
#Override
public void actionPerformed(ActionEvent actionEvent) {
JOptionPane.showConfirmDialog (
frame,
"Move the cursor inside, then outside of this component and press Esc. The cursor will stay default.",
"Cursor Test",
JOptionPane.YES_NO_OPTION
);
// Even if you change the cursor after, it stays default.
frame.setCursor(Cursor.CROSSHAIR_CURSOR);
}
});
}
}

Related

Transparent JFXPanel not forwarding events to components under it

I am trying to create a transparent JFXPanel over a swing based UI. The problem is events are not forwarded through the "transparent" sections the way they are for normal swing components. I included a simple example below. Note that there are 3 layers, swing, swing, then the jfxpanel. If you comment out the
addItemToLayeredPanel(panel, createFXOverlay(), 7);
you can click both buttons even though they have jpanel layered above the bottom one. (Just demonstrating the effect works in swing only doesn't work with JFXPanel)
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame();
JLayeredPane panel = new JLayeredPane();
JButton button = new JButton("Push me");
JPanel overlay = createOverlay();
addItemToLayeredPanel(panel, button, 5);
addItemToLayeredPanel(panel, overlay, 6);
addItemToLayeredPanel(panel, createFXOverlay(), 7);
frame.getContentPane().add(panel);
frame.setSize(500, 500);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private static JPanel createOverlay() {
JPanel panel = new JPanel();
panel.add(new JButton("on top"));
panel.setOpaque(false);
return panel;
}
private static JFXPanel createFXOverlay() {
JFXPanel panel = new JFXPanel();
BorderPane root = new BorderPane();
root.setStyle("-fx-background-color:transparent;");
root.setCenter(new Button("root"));
Scene scene = new Scene(root);
scene.setFill(javafx.scene.paint.Color.TRANSPARENT);
panel.setScene(scene);
panel.setOpaque(false);
return panel;
}
private static void addItemToLayeredPanel(JLayeredPane panel, JComponent item, int layer) {
item.setBounds(0,0,500,500);
panel.add(item);
panel.setLayer(item, layer);
}
}
Ideas? I tried a couple of ways of forwarding the events (generically) to jcomponents underneath, but I was unable to get it to work. I did find a couple of similar questions, but no solutions here and here
When Swings tries to see if a mouse event goes thru, it uses the JComponent method
public boolean contains(int x, int y) {
return (ui != null) ? ui.contains(this, x, y) : super.contains(x, y);
}
The problem is that JFXPanel, unlike other Swing components, does not define a ui ComponentUI. Therefore, the check falls back to the super.contains(x,y), which returns true if a mouse event happens within the bounds of the object.
The solution is to use a class that extends JFXPanel and overrides the contains method:
public class MyJFXPanel extends JFXPanel{
#Override
public boolean contains(int x, int y) { return false; }
}
Where false can be replaced by a more sophisticated statement, such as a pixel transparency condition.
The mouse events will then be automatically forwarded to the element underneat the MyJFXPanel.

Force stop edit cell in JTable on mouse click

How to force stop editing a cell in a JTable when the user clicks on any other component than the table itself?
Tried this but it didn't work...
myTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
... or this, which also does not work as expected
Component co = myTable.getEditorComponent();
if (co != null && !(co instanceof JComboBox)) {
co.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusLost(java.awt.event.FocusEvent evt) {
TableCellEditor tce = myTable.getCellEditor();
if (tce != null) {
tce.stopCellEditing(); // should accept partial edit
}
}
});
}
Having a button with an action listener attached to it makes it possible to force stop editing of any cell in the table, however that's not really the solution I am looking for.
It should work by clicking on any component.
SSCCE
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
public class TableExample {
public static void main(String args[]) {
final Object rowData[][] = { { "1", "one", "not empty" }, { "2", "two", "" }, { "3", "three", "" } };
final String columnNames[] = { "#", "Some Column", "Some Other Column" };
final JTable table = new JTable(rowData, columnNames);
JScrollPane scrollPane = new JScrollPane(table);
table.getModel().addTableModelListener(new TableModelListener() {
public void tableChanged(TableModelEvent e) {
System.out.println("column: "+e.getColumn());
if(1==e.getColumn()){
System.out.println(table.getModel().getValueAt(e.getLastRow(), e.getColumn()));
String value = table.getModel().getValueAt(e.getLastRow(), e.getColumn()).toString();
int rowIndex = e.getLastRow();
if(table.getModel().getValueAt(e.getLastRow(), 2).toString().isEmpty())
table.getModel().setValueAt(value,e.getLastRow(), 2);
}
}
});
table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
table.setValueAt("",0,0);
JFrame frame = new JFrame("Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(scrollPane, BorderLayout.CENTER);
frame.setSize(700, 150);
frame.setVisible(true);
}
}
The table does not take up all the space in the viewport of the scrollpane so you are actually clicking on the viewport. By default a viewport doesn't gain focus when you click on it so there is no focusLost method generated on the table.
Maybe you can just use the following so the table fills the viewport:
table.setFillsViewportHeight(true);
Otherwise, you would need to add a MouseListener to the viewport to handle the mouse click event. You would need to do this for all components on the frame that cannot receive focus, like a JPanel.
Or, maybe another approach is to use an AWTEventListener to listen for all mouse clicks. The draw back to this approach is that you can't distinguish between a click on a focusable component and a non-focusable component so you would be attempting to stop the cell editing every time a click is made. See Global Event Listeners for more information.

How to listen for close in a JPanel

I am working with some strange legacy code. They have a custom object which implements a JPanel. This JPanel object is a secondary popup screen within the main application. The issue I'm having is to detect when the secondary popup screen is closed.
I tried to implement a WindowListener for the class, but when I try to add it, there is no JFrame associated with this object. I am assuming this is because they are using a custom object and it is an embedded popup screen.
I tried to retrieve a JFrame using:
JFrame parentFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
which fails on a NullPointerException. I have no idea why it's so difficult to detect the right hand corner "x" close button on this page! I should mention that they were able to add Mouse and Key Listeners to the table which is embedded within the JPanel. But the outside listener for the entire window is causing me troubles.
(Please bear with me, this is my first stackoverflow post and I am new to Swing.)
Thanks so very much!!
Try to call getParent() for that strange panel. It should return the parent GUI component. If this is still not your frame but some intermediate panel instead, call getParent() on it as well. The top level component returns null.
Component p = strangePanel;
while ( p != null && ! (p instanceof Window))
p = p.getParent();
((Window) p ).addWindowListener(..);
Cannot understand why you are getting "NullPointerException" at:
JFrame parentFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
In two cases this can happen:
JFrame parentFrame = (JFrame) SwingUtilities.getWindowAncestor(null);
In your case, this is not possible as you have used this as a parameter.
Second, are you doing some other operations in above code line, like:
JFrame parentFrame = ((JFrame) SwingUtilities.getWindowAncestor(this)).someOperation();
In this case, if your this object represent the top window then you are supposed to get "NullPointerException" because ancestor of top parent is returned as "null". In other cases, I suspect you will get this exception.
Can you post a block of code where you are getting exception.
For this answer I'm making a minor assumption that the Nullpointer is not being thrown at the line that you mentioned, but rather when you attempt to add the WindowListener to the parentFrame. This is most likely because you're calling
JFrame parentFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
before the JPanel has been added to the JFrame hierarchy.
Here's a rought code sample on how you could work around this. The thought it to wait for the panel to be notified that it has been attached to the JFrame somewhere in its hierarchy.
package test;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class HierarchyTest extends JPanel {
protected static void loadApp() {
HierarchyTest test = new HierarchyTest();
JFrame frame = new JFrame();
frame.add(test);
frame.setSize(200, 200);
frame.setVisible(true);
}
/**
* #param args
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
loadApp();
}
});
}
public HierarchyTest() {
this.addHierarchyListener(new HierarchyListener() {
#Override
public void hierarchyChanged(HierarchyEvent e) {
// This can be optimized by checking the right flags, but I leave that up to you to look into
boolean connected = setupListenersWhenConnected();
if (connected) {
HierarchyTest.this.removeHierarchyListener(this);
}
}
});
}
protected boolean setupListenersWhenConnected() {
JFrame parentFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
if (parentFrame == null) {
return false;
}
parentFrame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
// Implementation here
System.out.println("This window is closing!");
}
});
return true;
}
}

Why JScrollPane does not react to mouse wheel events?

I have a JScrollPane containing a panel with a BoxLayout (PAGE AXIS).
My problem is that the JScrollPane does not react to mouse wheel events. To make it scroll using the mouse wheel i need to be on the JScrollBar.
I found this thread and i have no MouseMotionListener or MouseWheelListener, only a MouseListener. I think my problem come from the fact that my JScrollPane act on a JPanel that contains other panels itself. So when the mouse is on a panel within the JScrollPane it seems that the event is consumed by this panel i never seen by the scroll pane.
Is there a correct way to make the events caught by the children of the scroll pane visible to this scroll pane?
SSCCE:
Here a simple test case trying to show when i try to do in my Swing application.
The frame:
public class NewJFrame extends javax.swing.JFrame {
public NewJFrame() {
initComponents();
for (int i = 0; i < 50; i++) {
jPanel1.add(new TestPanel());
}
}
private void initComponents() {
jScrollPane1 = new javax.swing.JScrollPane();
jPanel1 = new javax.swing.JPanel();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
jPanel1.setLayout(new javax.swing.BoxLayout(jPanel1, javax.swing.BoxLayout.PAGE_AXIS));
jScrollPane1.setViewportView(jPanel1);
getContentPane().add(jScrollPane1, java.awt.BorderLayout.CENTER);
pack();
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new NewJFrame().setVisible(true);
}
});
}
}
And the TestPanel definition:
public class TestPanel extends javax.swing.JPanel {
public TestPanel() {
initComponents();
}
private void initComponents() {
jLabel1 = new javax.swing.JLabel();
jLabel2 = new javax.swing.JLabel();
jScrollPane1 = new javax.swing.JScrollPane();
jTextArea1 = new javax.swing.JTextArea();
jLabel1.setText("jLabel1");
setBackground(new java.awt.Color(255, 51, 51));
setLayout(new java.awt.BorderLayout());
jLabel2.setText("TEST LABEL");
jLabel2.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
add(jLabel2, java.awt.BorderLayout.PAGE_START);
jTextArea1.setEditable(false);
jTextArea1.setColumns(20);
jTextArea1.setRows(5);
jTextArea1.setFocusable(false);
jScrollPane1.setViewportView(jTextArea1);
add(jScrollPane1, java.awt.BorderLayout.CENTER);
}
}
The JTextArea seems to consume the event since when the mouse cursor is inside it, the scrolling using wheel does not work. I have to put the mouse cursor outside the text area to make it works again.
Walter beat me to analysing the issue :-)
Adding a bit of detail:
It's correct that a JScrollPane supports mouseWheelHandling. According to the rules of mouseEvent dispatching, the top-most (in z-order) component gets the event, and that's the scrollPane around the textArea. So if wheeling the textarea is not required, a simple solution might be to disable the wheel-support in its scrollPane. And JScrollPane even has api for doing it:
scrollPane.setWheelScrollingEnabled(false);
Unfortunately, that doesn't work. Reason it's not working is that this property has no effect in the event dispatch chain which ultimately calls into eventTypeEnabled:
case MouseEvent.MOUSE_WHEEL:
if ((eventMask & AWTEvent.MOUSE_WHEEL_EVENT_MASK) != 0 ||
mouseWheelListener != null) {
return true;
}
This returns true if a mouseWheelListener is installed - which is done unconditionally by BasicScrollPaneUI, and not removed when the wheelEnabled property is changed (the ui doesn't even listen to that property ...) Plus the listener simply does nothing if the property is false. At least one of those facts is a bug, the ui should
either remove/add the listener depending on wheelEnabled
or: implement the listener such that it dispatches the event up the chain (as Walter does in his example)
The first option can be handled by application code:
scrollPane = new JScrollPane();
scrollPane.removeMouseWheelListener(scrollPane.getMouseWheelListeners()[0]);
it's a bit of a hack (as bug-workarounds always are :-), production code would have to listen to the wheelEnable to re-install if needed plus listen to LAF changes to update/re-remove the listeners installed by the ui.
Implementing the second option in slight modification (as to Walter's dispatching) by subclassing the JScrollPane and dispatch the event to parent if the wheelEnabled is false:
scrollPane = new JScrollPane() {
#Override
protected void processMouseWheelEvent(MouseWheelEvent e) {
if (!isWheelScrollingEnabled()) {
if (getParent() != null)
getParent().dispatchEvent(
SwingUtilities.convertMouseEvent(this, e, getParent()));
return;
}
super.processMouseWheelEvent(e);
}
};
scrollPane.setWheelScrollingEnabled(false);
The mouse wheel event gets consumed by the scroll pane around the text area. You can try to manually pass the event to the parent scroll pane like this:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TestScrollPane2 {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
// might want to use a http://tips4java.wordpress.com/2009/12/20/scrollable-panel/
JPanel panel = new JPanel(new GridLayout(0, 1));
for (int i = 0; i < 10; i++) {
panel.add(new JScrollPane(new JTextArea(3, 40)) {
#Override
protected void processMouseWheelEvent(MouseWheelEvent e) {
Point oldPosition = getViewport().getViewPosition();
super.processMouseWheelEvent(e);
if(getViewport().getViewPosition().y == oldPosition.y) {
delegateToParent(e);
}
}
private void delegateToParent(MouseWheelEvent e) {
// even with scroll bar set to never the event doesn't reach the parent scroll frame
JScrollPane ancestor = (JScrollPane) SwingUtilities.getAncestorOfClass(
JScrollPane.class, this);
if (ancestor != null) {
MouseWheelEvent converted = null;
for (MouseWheelListener listener : ancestor
.getMouseWheelListeners()) {
listener.mouseWheelMoved(converted != null ? converted
: (converted = (MouseWheelEvent) SwingUtilities
.convertMouseEvent(this, e, ancestor)));
}
}
}
});
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(new JScrollPane(panel));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}

I wrote a program using Drag and Drop in java, I need few updation in this. Can anyone help me please?

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class scjp extends TransferHandler
{
JTextField txtField;
JRadioButton lbl=new JRadioButton("Hello");
public static void main(String[] args)
{
scjp sdd = new scjp();
transfer th=new transfer();
}
public scjp()
{
MouseListener ml = new MouseAdapter()
{
public void mousePressed(MouseEvent e)
{
JComponent jc = (JComponent)e.getSource();
TransferHandler th = jc.getTransferHandler();
th.exportAsDrag(jc, e, TransferHandler.COPY);
}
};
MouseMotionListener m2=new MouseAdapter()
{
public void mouseDragged(MouseEvent e)
{
}
};
JFrame frame = new JFrame("SCJP");
txtField = new JTextField(20);
lbl.setTransferHandler(new TransferHandler("text"));
lbl.addMouseListener(ml);
lbl.addMouseMotionListener(m2);
JPanel panel = new JPanel();
panel.add(txtField);
frame.add(lbl, BorderLayout.CENTER);
frame.add(panel, BorderLayout.NORTH);
frame.setSize(400, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EX…
frame.setResizable(false);
}
}
here i m dragging a radio and dropping that into a textBox, when i m dragging the radio button, my mouse pointer looks like (a rectangle and plus sign below the arrow).
What i need to do:
when i pick/drag the radio that time instead of that rectangle and plus sign, i want that string which is of radio??
i mean to say the radio button string/text i want as with my mouse cursor upto i drop that component/radio in text box..?
please help me to figure out this problem as soon as possible, please.
First look here - this explains how to change the cursor while performing drag n drop.
Once you understand the concept, you will have to code a method that creates an in-memory image of text from the radio button in question. That image can be used in populating the cursor.
Pseudo code:
img = //in-memory image created by writing text of radio button to graphics.
Cursor curCircle = Toolkit.getDefaultToolkit().createCustomCursor(img,new Point(5,5),"some text");
Set the new cursor in dragEnter - and then take care of dragExit to restore original cursor.