TreeModelListener not responding to changes in TreeModel which it subscribes to - swing

I'm having some bother understanding why I cannot get a TreeModelChanged listener to respond to changes in the model which it subscribes to.
I have managed to reproduce the problem in a small example.
The SysOut message does not print to the console whenever a new node is added to the tree.
I intend to replace the SysOut message with some commands to redraw the tree. At the moment I am using a SysOut message just to prove that the listener is not being fired.
Am I missing something fundamental?
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
public class TreeTest {
private JTree t;
private DefaultTreeModel m ;
public static void main(String[] args) {
new TreeTest();
}
public TreeTest() {
//Draw Frame & Panel - set dimensions
JFrame f = new JFrame();
f.setSize(new Dimension(800,600));
JPanel p = new JPanel();
p.setSize(new Dimension(800,600));
//Create a Tree Model. Give it a String at the root.
m = new DefaultTreeModel(new DefaultMutableTreeNode("Root"));
//Create a tree and add the Model to it
t = new JTree();
t.setModel(m);
//Try a Tree Model Listener
m.addTreeModelListener(new TreeModelListener() {
private void doSomething() {
//Should fire whenever a node is added to the model
System.out.println("Responding to TreeModelListener");
}
#Override
public void treeStructureChanged(TreeModelEvent e) {
doSomething();
}
#Override
public void treeNodesRemoved(TreeModelEvent e) {
doSomething();
}
#Override
public void treeNodesInserted(TreeModelEvent e) {
doSomething();
}
#Override
public void treeNodesChanged(TreeModelEvent e) {
doSomething();
}
});
//Add listener to a button which adds nodes to the tree when clicked
JButton addNode = new JButton("Add node");
addNode.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("New Node");
DefaultMutableTreeNode root = (DefaultMutableTreeNode) m.getRoot();
root.add(newNode);
}
});
JScrollPane s = new JScrollPane(t);
p.add(s);
p.add(addNode);
p.setVisible(true);
f.add(p);
f.setVisible(true);
}
}

that's because the model doesn't know about the addition, it happens under its feet. Use the methods on DefaultTreeModel to do the insertion:
model.insertNodeInto(newNode, root, root.getChildCount())
Edit
a TreeNode is just a (more or less) dumb data structure. As you can see in the api, it's not an Observable, so there is no way for the model which uses that data structure to detect if anything changed on the node. To make it aware of the change, you have to do one of two things
use the node manipulation methods of the model
update the node and notify the model manually (calling nodesWereInserted)
The first is the preferable way (keeps control where it belongs), the second might be needed in more complex contexts (though I would strongly recommend to never do it, that's why SwingX DefaultTreeTableModel doesn't have them exposed :)

Related

How to make nearly 1,70,000 marker dots on JMapViewer? [duplicate]

I've got a slight problem, I'm writing a gps tracking app to track several objects at once. The data comes in over a serial interface, this is coming in fine from what I can tell. The issue is that I need to continually update the JPanel where the map is created and displayed.
public JPanel mapDisplay(){
JPanel mapPanel = new JPanel();
mapPanel.setSize(560, 540);
Coordinate start = new Coordinate (-34.9286, 138.6);
trackMap.addMapMarker(new MapMarkerDot(1Lat, 1Lon));
trackMap.setDisplayPosition(start,8);
System.out.println(1Lat);
mapPanel.add(trackMap);
mapPanel.setVisible(true);
return mapPanel;
}
This is what I have and it's happy to display the point once but won't update. If I print out the 1Lat variable in the serial method it continually prints, however it only does it once here.
A lot of the answers I've found refer to setting markers by arrays, however that won't work in this case as the objects I'm tracking could be anywhere.
Any help would be greatly appreciated :)
Is it possible to use a worker thread and not use an ArrayList? I would run the risk of missing data if I do.
Not necessarily. In a SwingWorker, your implementation of the doInBackground() method can publish() results as they become available. Note in particular that "Results from multiple invocations of publish() are often accumulated for a single invocation of process()." In your process(), simply loop through the List<Coordinate>, update the route and repaint() the map.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
import org.openstreetmap.gui.jmapviewer.Coordinate;
import org.openstreetmap.gui.jmapviewer.JMapViewer;
import org.openstreetmap.gui.jmapviewer.MapPolygonImpl;
/**
* #see http://stackoverflow.com/a/37193636/230513
*/
public class MapWorkerTest {
private final List<Coordinate> route = new ArrayList<>();
private void display() {
JFrame f = new JFrame("MapWorker");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JMapViewer map = new JMapViewer() {
#Override
public Dimension getPreferredSize() {
return new Dimension(640, 480);
}
#Override
public String getToolTipText(MouseEvent e) {
Coordinate c = (Coordinate) getPosition(e.getX(), e.getY());
return c.getLat() + " " + c.getLon();
}
};
map.setToolTipText("");
Coordinate start = new Coordinate(-34.9286, 138.6);
route.add(start);
MapPolygonImpl poly = new MapPolygonImpl(route);
poly.setColor(Color.blue);
map.addMapPolygon(poly);
map.setDisplayPosition(start, 10);
f.add(map);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
new MapWorker(map, start).execute();
}
private class MapWorker extends SwingWorker<Void, Coordinate> {
private final JMapViewer map;
private Coordinate last;
public MapWorker(JMapViewer map, Coordinate start) {
this.map = map;
this.last = start;
}
#Override
protected Void doInBackground() throws Exception {
while (!isCancelled()) {
last = new Coordinate(last.getLat() + 0.0025, last.getLon() + 0.01);
publish(last);
Thread.sleep(1000);
}
return null;
}
#Override
protected void process(List<Coordinate> chunks) {
for (Coordinate c : chunks) {
route.add(c);
}
map.repaint();
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new MapWorkerTest()::display);
}
}
Multiple route management left as a exercise.

JavaFX walk widget tree

Is there a simple (uniform) way to recursively descend a JavaFX widget tree starting from a defined node (possibly from the Scene itself)?
The following code:
static class Visitor {
public void visit(Node node){
...
}
}
protected void walkWidgets(Node n, Visitor cb) {
if (n instanceof Parent) {
Parent p = (Parent) n;
for (Node c : p.getChildrenUnmodifiable()) {
walkWidgets(c, cb);
}
}
cb.visit(n);
}
... does not work because the "children" of some containers (e.g.: SplitPane, BorderPane, etc.) are not listed in their children Property.
To overcome this I should specialize the code to allow for all the quirks of all different widgets. This is particularly annoying when You start using widget libs beyond the "standard" provision.
Am I missing something? (I surely hope so!)
This seems to work fine: it gets all the child nodes in the BorderPane, SplitPane and TabPane.
import java.util.function.Consumer;
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class WalkComponentTree extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
root.setTop(new Label("Title"));
SplitPane splitPane = new SplitPane();
root.setCenter(splitPane);
ListView<String> list = new ListView<>();
list.getItems().addAll("One", "Two", "Three");
TabPane tabPane = new TabPane();
Tab tab1 = new Tab();
tab1.setContent(new TextArea());
Tab tab2 = new Tab();
tab2.setContent(new Label("Tab 2"));
tabPane.getTabs().addAll(tab1, tab2);
splitPane.getItems().addAll(tabPane, list);
Button button = new Button("Walk tree");
button.setOnAction(event -> walkTree(root, node ->
System.out.println(node.getClass())));
root.setBottom(button);
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private void walkTree(Node node, Consumer<Node> visitor) {
visitor.accept(node);
if (node instanceof Parent) {
((Parent) node).getChildrenUnmodifiable()
.forEach(n -> walkTree(n, visitor));
}
}
public static void main(String[] args) {
launch(args);
}
}
Note you can also use node.lookupAll("*");, though this is less robust as it only works once css has been applied to the node.
In my opinion you are correct to say that there is no "standard" way to walk through the JavaFX scene graph.
One reason is, that Tab is not derived from Node, so you will not get a List of Tabs from Parent.getChildrenUnmodifiable(). Another reason is, that ScrollPane and TitledPane do not publish their content node in Parent.getChildrenUnmodifiable(). Some other Panes like Accordion, SplitPane and Toolbar also don't publish their children with Parent.getChildrenUnmodifiable(). So you need to handle this yourself.
For more Information please see:
Walking the JavaFX Scene Graph

Command Pattern Usefulness when using JComponents

So, I'm developing a program using the Swing library and I obviously have buttons and menu items. Some of these are supposed to do the same stuff, and I thought using the Command Pattern should be the way to do it, e.g. I have a "save" button and a "save" menu item and they have to implement the same saving algorithm.
Command Pattern seems to be ok but I can't get who's the receiver in all that. Isn't a comand supposed to work on an object which implements some sort of "receiver interface", so that you can use different commands on different receivers coupling them aribtrarily? It looks like there's no "receiver" in my implementation of the pattern.
Another doubt i have is should a command be implemented as a singleton, since you could potentially call its functions from differents parts of the same project, and it would be handly to instantiate it only once and make it statically invokable?
Thank you.
" I obviously have buttons and menu items. Some of these are supposed to do the same stuff,"
As #nIcEcOw noted, that's what Actions are for. This Answer Shows exactly this.
As stated in the How to use Actions :
An Action can be used to separate functionality and state from a component. For example, if you have two or more components that perform the same function, consider using an Action object to implement the function. An Action object is an action listener that provides not only action-event handling, but also centralized handling of the state of action-event-firing components such as tool bar buttons, menu items, common buttons, and text fields. The state that an action can handle includes text, icon, mnemonic, enabled, and selected status.
An There only three Actions. Ont to open, save, and new. Each Action has an ActionCommand, and icon, and and action to perform. Both the JMenuItem and JToolBar button share the same Action and do the same thing. Here is the code you can run.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
public class ActionTest {
public ActionTest() {
ImageIcon openIcon = new ImageIcon(
ActionTest.class.getResource("/resources/image/open.gif"));
ImageIcon saveIcon = new ImageIcon(
ActionTest.class.getResource("/resources/image/save.gif"));
ImageIcon newIcon = new ImageIcon(
ActionTest.class.getResource("/resources/image/new.gif"));
Action openAction = new AbstractAction("Open", openIcon) {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Open File");
}
};
Action saveAction = new AbstractAction("Save", saveIcon) {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Save File");
}
};
Action newAction = new AbstractAction("New", newIcon) {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("New File");
}
};
JMenuItem openMenuItem = new JMenuItem(openAction);
JMenuItem saveMenuItem = new JMenuItem(saveAction);
JMenuItem newMenuItem = new JMenuItem(newAction);
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
fileMenu.add(openMenuItem);
fileMenu.add(saveMenuItem);
fileMenu.add(newMenuItem);
menuBar.add(fileMenu);
JToolBar toolBar = new JToolBar();
toolBar.add(Box.createHorizontalGlue());
toolBar.setBorder(new LineBorder(Color.LIGHT_GRAY, 1));
toolBar.add(newAction);
toolBar.add(openAction);
toolBar.add(saveAction);
JFrame frame = new JFrame("Toolbar and Menu Test");
frame.setJMenuBar(menuBar);
frame.add(toolBar, BorderLayout.PAGE_START);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ActionTest();
}
});
}
}
As stated in the quote from the above mentioned tutorial, you can do more than just add an image and an action command to the Action. You can use it to set mnemonics and accelorators. Here is a custom Action class that takes
An action command String
an icon
a description for tooltips
a mnemonic
and a key accelorator.
private class MyAction extends AbstractAction {
String name;
public MyAction(String name, Icon icon) {
super(name, icon);
this.name = name;
}
public MyAction(String name, Icon icon, String desc,
Integer mnemonic, KeyStroke accelorator) {
super(name, icon);
putValue(Action.SHORT_DESCRIPTION, desc);
putValue(Action.MNEMONIC_KEY, mnemonic);
putValue(Action.ACCELERATOR_KEY, accelorator);
this.name = name;
}
#Override
public void actionPerformed(ActionEvent e) {
switch (name) {
case "Open":
System.out.println("Open");
break;
case "New":
System.out.println("New");
break;
case "Save":
System.out.println("Save");
break;
}
}
}
Here's an instantiation of this Action
Action newAction = new MyAction("New", newIcon,
"Creates a new file",
new Integer(KeyEvent.VK_N),
KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK));
And here's the new result. You will see the actionCommand in the menu, along with the key mnemonics and accelerators, tooltips, and you will see the jtoolbar buttons share the same traits. You will also see in the final code, that never once once a component created. All you do is add the Action to the JToolBar and the JMenu and let them work their magic.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ActionInterfaceDemo extends JFrame {
public ActionInterfaceDemo() {
ImageIcon openIcon = new ImageIcon(ActionInterfaceDemo.class.getResource("/resources/image/open.gif"));
ImageIcon saveIcon = new ImageIcon(ActionInterfaceDemo.class.getResource("/resources/image/save.gif"));
ImageIcon newIcon = new ImageIcon(ActionInterfaceDemo.class.getResource("/resources/image/new.gif"));
Action openAction = new MyAction("Open", openIcon,
"Opens a file",
new Integer(KeyEvent.VK_O),
KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK));
Action saveAction = new MyAction("Save", saveIcon,
"Saves a file",
new Integer(KeyEvent.VK_S),
KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK));
Action newAction = new MyAction("New", newIcon,
"Creates a new file",
new Integer(KeyEvent.VK_N),
KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK));
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
setJMenuBar(menuBar);
menuBar.add(fileMenu);
fileMenu.add(newAction);
fileMenu.add(openAction);
fileMenu.add(saveAction);
JToolBar toolBar = new JToolBar("Alignment");
toolBar.setBorder(BorderFactory.createLineBorder(Color.BLUE));
toolBar.add(Box.createHorizontalGlue());
toolBar.add(newAction);
toolBar.add(openAction);
toolBar.add(saveAction);
add(toolBar, BorderLayout.PAGE_START);
add(new JScrollPane(new TextArea(10, 40)), BorderLayout.CENTER);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setTitle("Action Interface Demo");
pack();
setLocationByPlatform(true);
setVisible(true);
}
private class MyAction extends AbstractAction {
String name;
public MyAction(String name, Icon icon) {
super(name, icon);
this.name = name;
}
public MyAction(String name, Icon icon, String desc,
Integer mnemonic, KeyStroke accelorator) {
super(name, icon);
putValue(Action.SHORT_DESCRIPTION, desc);
putValue(Action.MNEMONIC_KEY, mnemonic);
putValue(Action.ACCELERATOR_KEY, accelorator);
this.name = name;
}
#Override
public void actionPerformed(ActionEvent e) {
switch (name) {
case "Open":
System.out.println("Open");
break;
case "New":
System.out.println("New");
break;
case "Save":
System.out.println("Save");
break;
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
public void run() {
new ActionInterfaceDemo();
}
});
}
}
UPDATE
The better explain the relationship of Action and Command Patterns
As noted in Command Pattern
The command pattern is a commonly used pattern which encapsulates a method call or action-like code into a single class. The advantages of being able to package a method (or methods) into a class become evident when you have multiple invokers for a single action (for example a button and a menu item may perform the same action).
In Swing and Borland Delphi programming, an Action is a command object. In addition to the ability to perform the desired command, an Action may have an associated icon, keyboard shortcut, tooltip text, and so on. A toolbar button or menu item component may be completely initialized using only the Action object.
So basically Swing uses the concept of the command pattern through the use of Actions
As for OP's question
"Command Pattern seems to be ok but I can't get who's the receiver in all that."
As for the receiver, the wiki uses a text editor as an example and defines the receiver as such
Receiver, Target Object: the object that is about to be copied, pasted, moved, etc. The receiver object owns the method that is called by the command's execute method. The receiver is typically also the target object. For example, if the receiver object is a cursor and the method is called moveUp, then one would expect that the cursor is the target of the moveUp action. On the other hand, if the code is defined by the command object itself, the target object will be a different object entirely.
The main more components of a Command Pattern are stated as follows
Four terms always associated with the command pattern are command, receiver, invoker and client.
Client, Source, Invoker: the button, toolbar button, or menu item clicked, the shortcut key pressed by the user.
So to put it all together:
The MenuItem (client) invokes it
Action (command object) which calls it actionPerformed which in turn
Invokes an method on the receiver.
The wiki article is good read with a Java example
When two or more components are mean to do exactly the same thingy, one should look at Action, which reduces the duplicate code.
Small example for further help :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ActionExample {
private JFrame frame;
private JButton button;
private JMenuItem exitItem;
private Action commonActions;
private class CommonActions extends AbstractAction {
public CommonActions(String title, String desc) {
super(title);
putValue(SHORT_DESCRIPTION, desc);
}
#Override
public void actionPerformed(ActionEvent ae) {
JOptionPane.showMessageDialog(frame,
"Closing Frame", "Information", JOptionPane.INFORMATION_MESSAGE);
frame.dispose();
}
};
private void displayGUI() {
frame = new JFrame("Action Example");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
commonActions = new CommonActions("Exit", "To Exit the Application");
JPanel contentPane = new JPanel();
button = new JButton();
button.setAction(commonActions);
contentPane.add(button);
frame.setJMenuBar(getMenuBar());
frame.setContentPane(contentPane);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JMenuBar getMenuBar() {
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
exitItem = new JMenuItem(commonActions);
fileMenu.add(exitItem);
menuBar.add(fileMenu);
return menuBar;
}
public static void main(String[] args) {
Runnable runnable = new Runnable() {
#Override
public void run() {
new ActionExample().displayGUI();
}
};
EventQueue.invokeLater(runnable);
}
}
ADDED an example with SINGLETON PATTERN (though I am not sure of this approach(about how good this approach is))
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ActionExample {
private JFrame frame;
private JButton button;
private JMenuItem exitItem;
private void displayGUI() {
frame = new JFrame("Action Example");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
CommonActions.setValues(frame);
JPanel contentPane = new JPanel();
button = new JButton();
button.setAction(CommonActions.getInstance());
contentPane.add(button);
frame.setJMenuBar(getMenuBar());
frame.setContentPane(contentPane);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JMenuBar getMenuBar() {
JMenuBar menuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File");
exitItem = new JMenuItem(CommonActions.getInstance());
fileMenu.add(exitItem);
menuBar.add(fileMenu);
return menuBar;
}
public static void main(String[] args) {
Runnable runnable = new Runnable() {
#Override
public void run() {
new ActionExample().displayGUI();
}
};
EventQueue.invokeLater(runnable);
}
}
class CommonActions extends AbstractAction {
private static CommonActions commonActions = null;
private static JFrame frame = null;
static {
try {
commonActions = new CommonActions("Exit", "To Exit the Application");
} catch (Exception e) {
throw new RuntimeException("BINGO, an error");
}
}
private CommonActions(String title, String desc) {
super(title);
putValue(SHORT_DESCRIPTION, desc);
}
public static CommonActions getInstance() {
return commonActions;
}
public static void setValues(JFrame f) {
frame = f;
}
#Override
public void actionPerformed(ActionEvent ae) {
JOptionPane.showMessageDialog(frame,
"Closing Frame", "Information", JOptionPane.INFORMATION_MESSAGE);
frame.dispose();
}
}

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;
}
}

NullPointerException at java.awt.Window.access$700(Window.java:132) while painting JPanel

I'm trying to paint component inside paint(Graphics) method of JPanel.
The following code snippet works just fine, a JButton is painted nicely in my JPanel:
#Override
public void paint(Graphics g) {
super.paint(g);
JButton btn = new JButton("hello");
Dimension dim = btn.getPreferredSize();
btn.setSize(dim.width, dim.height);
btn.paint(g); // paint the button
}
The code snippet works perfectly also for other components (JLabel, JTree, ...) except JPanel.
The following code will cause very strange NullPointerException at java.awt.Window.access$700(Window.java:132).
#Override
public void paint(Graphics g) {
super.paint(g);
JPanel panel = new JPanel();
panel.setSize(10, 10);
panel.paint(g); // paint the panel
}
Here the full stacktrace:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at java.awt.Window.access$700(Window.java:132)
at java.awt.Window$1.isOpaque(Window.java:3458)
at javax.swing.RepaintManager.getVolatileOffscreenBuffer(RepaintManager.java:983)
at javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1395)
at javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:294)
at javax.swing.RepaintManager.paint(RepaintManager.java:1224)
at javax.swing.JComponent.paint(JComponent.java:1015)
at test.paintcontainer.TestPaintContainerMain$TestContentPane.paint(TestPaintContainerMain.java:48)
at javax.swing.JComponent.paintChildren(JComponent.java:862)
at javax.swing.JComponent.paint(JComponent.java:1038)
at javax.swing.JLayeredPane.paint(JLayeredPane.java:567)
at javax.swing.JComponent.paintChildren(JComponent.java:862)
at javax.swing.JComponent.paintToOffscreen(JComponent.java:5131)
at javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:278)
at javax.swing.RepaintManager.paint(RepaintManager.java:1224)
at javax.swing.JComponent.paint(JComponent.java:1015)
at java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:21)
at sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:60)
at sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:97)
at java.awt.Container.paint(Container.java:1780)
at java.awt.Window.paint(Window.java:3375)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:796)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:713)
at javax.swing.RepaintManager.seqPaintDirtyRegions(RepaintManager.java:693)
at javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(SystemEventQueueUtilities.java:125)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:641)
at java.awt.EventQueue.access$000(EventQueue.java:84)
at java.awt.EventQueue$1.run(EventQueue.java:602)
at java.awt.EventQueue$1.run(EventQueue.java:600)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:611)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
Any idea how to solve this problem? I need to paint JPanel inside paint(Graphics) method.
I wrote a simple test application which you can copy-paste to reproduce the aforementioned exception:
package test.paintcontainer;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
public class TestPaintContainerMain extends JFrame {
public static void main(String[] args) {
TestPaintContainerMain test = new TestPaintContainerMain();
test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
test.setBounds(0, 0, 300, 200);
test.setContentPane(new TestContentPane());
test.setVisible(true);
}
static class TestContentPane extends JPanel {
JRadioButton paintButtonCheck;
JRadioButton paintPanelCheck;
public TestContentPane() {
paintButtonCheck = createRadioButton("paint button", true);
paintPanelCheck = createRadioButton("paint panel", false);
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(paintButtonCheck);
buttonGroup.add(paintPanelCheck);
add(paintButtonCheck);
add(paintPanelCheck);
}
#Override
public void paint(Graphics g) {
super.paint(g);
g.translate(100, 100);
if (paintButtonCheck.isSelected()) {
createButton().paint(g);
} else {
createPanel().paint(g);
}
}
private JButton createButton() {
JButton button = new JButton("button");
button.setSize(button.getPreferredSize().width, button.getPreferredSize().height);
return button;
}
private JPanel createPanel() {
JPanel panel = new JPanel();
panel.setBackground(Color.GREEN);
panel.add(createButton());
panel.setSize(panel.getPreferredSize().width, panel.getPreferredSize().height);
return panel;
}
private JRadioButton createRadioButton(String title, boolean selected) {
JRadioButton radio = new JRadioButton(title, selected);
radio.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
TestContentPane.this.repaint();
}
});
return radio;
}
}
}
This is most likely not a bug in Swing, but more of a problem because you are trying to paint a component which has not yet been realized, meaning it has no active graphic context. You can realize a component by adding it to already realized component like your JFrame - which itself gets realized by setVisible(true).
Also one should probably never call JComponent.paint(Graphics) manually, because this is the job of Swing (more precisely the Event Dispatcher Thread) - it even says so in the documentation of the paint method:
Applications should not invoke paint directly, but should instead use the repaint method to schedule the component for redrawing.
What you can call is the method printAll(Graphics g), which paints the component and all its subcomponents. Also in Swing one should also not override paint but paintComponent.
So here is a test code:
JButton button = createButton();
JPanel panel = createPanel();
public TestContentPane() {
paintButtonCheck = createRadioButton("paint button", true);
paintPanelCheck = createRadioButton("paint panel", false);
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(paintButtonCheck);
buttonGroup.add(paintPanelCheck);
add(paintButtonCheck);
add(paintPanelCheck);
//Hack, just prove something (realize both components)
add(panel);
add(button);
}
...
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.translate(100, 100);
if (paintButtonCheck.isSelected()) {
button.paintAll(g);
} else {
panel.paintAll(g);
}
g.translate(-100, -100);
}
This should work (although you will obviously have two components on the screen you don't want). Also note "reset" the graphics object, because it will still be used afterwards by Swing.
So this is the theory, but it's not yet an actual solution.
My solution to your problem is: "Don't do it like this"!
Components are not like images, in the sense that they don't look the same everywhere. The output of the paintAll call will be different, depending on how (or where) the components were realized.
So one suggestion is to show actual components. Create your tooltip box, add your panel and your button and let them draw themselves. You can even subclass these components and override their paintComponent() methods, add transparency and all. It will require some work, but Swing was never known to be easy.
I just found a solution.
The only modification of a sample code from my question is that called panel.setDoubleBuffered(false) on JPanel I was trying to paint.
However, I would still consider the exeption to be a Swing bug. If double buffering should be turned off by design you shouldn't get NullPointerException but some other, more meaningful exception which explains the condition.
Here is a fixed sample application:
package test.paintcontainer;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
public class TestPaintContainerMain extends JFrame {
public static void main(String[] args) {
TestPaintContainerMain test = new TestPaintContainerMain();
test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
test.setBounds(0, 0, 300, 200);
test.setContentPane(new TestContentPane());
test.setVisible(true);
}
static class TestContentPane extends JPanel {
JRadioButton paintButtonCheck;
JRadioButton paintPanelCheck;
public TestContentPane() {
paintButtonCheck = createRadioButton("paint button", false);
paintPanelCheck = createRadioButton("paint panel", true);
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(paintButtonCheck);
buttonGroup.add(paintPanelCheck);
add(paintButtonCheck);
add(paintPanelCheck);
}
#Override
public void paint(Graphics g) {
super.paint(g);
g.translate(100, 100);
if (paintButtonCheck.isSelected()) {
createButton().paint(g);
} else {
createPanel().paint(g);
}
}
private JButton createButton() {
JButton button = new JButton("button");
button.setSize(button.getPreferredSize().width, button.getPreferredSize().height);
return button;
}
private JPanel createPanel() {
JPanel panel = new JPanel();
panel.setBackground(Color.GREEN);
panel.add(createButton());
// --------------------------------
panel.setDoubleBuffered(false); // <-- TURN OFF DOUBLE BUFFERING
// --------------------------------
panel.setSize(panel.getPreferredSize().width, panel.getPreferredSize().height);
return panel;
}
private JRadioButton createRadioButton(String title, boolean selected) {
JRadioButton radio = new JRadioButton(title, selected);
radio.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
TestContentPane.this.repaint();
}
});
return radio;
}
}
}