I have been told and have read that SWT Objects must be explicitly disposed by calling their dispose method. However, in my own testing with the following code, I have noted that at least Shells report themselves as disposed even though the dispose method is never called (nor appears) anywhere in my code.
import java.util.ArrayList;
import java.util.List;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class Test {
private static int numDisposals = 0;
private static List<Shell> shells = new ArrayList<Shell>();
public static void main(String[] args) {
Display d = Display.getDefault();
for (int i = 0; i < 3; i++) {
Shell s = new Shell(d);
shells.add(s);
s.setText(String.valueOf(i));
s.open();
s.addDisposeListener(new DisposeListener() {
#Override
public void widgetDisposed(DisposeEvent notUsed) {
numDisposals++;
printShellStatus();
}
});
}
while (numDisposals < 3) {
while (!d.readAndDispatch()) {
d.sleep();
}
}
printShellStatus();
}
public static void printShellStatus() {
System.out.println("Which shells are disposed?");
for (Shell shell : shells) {
if (shell.isDisposed()) {
System.out.println("I am disposed.");
} else if (!shell.isDisposed()) {
System.out.println("I am NOT disposed.");
}
}
}
}
So does Shell really need to be explicitly disposed? If so, how do you know when to dispose a Shell, and where should the dispose method appear?
The paper that you cite makes this clear:
Widgets themselves do not usually need to be disposed
programmatically. A shell and its children are disposed when the user
closes its window.
So while a shell does need to be disposed, the burden is not on you to do so. Nor do you need to call dispose on any of the children, as disposing a parent will do that for you. Again, from the link you cite:
When you dispose a Shell, its children are disposed. In fact,
disposing any Composite will dispose all of the Composite's children.
However, you do have to make sure that you dispose resources you create that are not children. For example: colors and fonts. You do explicitly need to call their dispose method. It's best to hook a dispose listener to the Composite you're using them in to do this. For example:
public class MyComposite extends Composite
{
private final Color color;
public MyComposite(Composite parent, int style)
{
super(parent, style);
color = new Color(getShell().getDisplay(), 255, 255, 255);
addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e)
{
color.dispose();
}
});
}
}
It's important to note, however, that you should not dispose Colors that you use but do not create. For example, do not dispose the system colors available from Display#getSystemColor().
Related
I'm having an error when trying to add a component to a JFrame.
This is the first class:
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
public class FrameG extends JFrame
{
private static final String MOVE_UP = "move up";
public static int frameID = 1;
private static JFrame window = new JFrame();
private static openWin frame = new frame01();
public static void main(String[] args) {
window.setSize(1500,900);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
window.setResizable(true);
frame.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), MOVE_UP);
frame.getActionMap().put(MOVE_UP, new movement());
mainloop();
}
private static void mainloop()
{
window.removeAll();
switch(frameID)
{
case 1:
frame = new frame01();
frame.setLayout(new FlowLayout());
System.out.println(frame);
window.add(frame);
break;
default:
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(FrameG.class.getName()).log(Level.SEVERE, null, ex);
}
mainloop();
}
}
class movement extends AbstractAction
{
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("hi");
}
}
and the second class (it extends a class with an abstract paint method that extends JComponent):
import java.awt.Graphics;
import java.awt.*;
import javax.swing.JComponent;
import java.awt.geom.*;
public class frame01 extends openWin{
#Override
public void paint(Graphics g) {
Graphics2D pic = (Graphics2D) g;
pic.setBackground(Color.BLACK);
}
}
The error may be the invalid part, but i'm not sure what it is:
frameg.frame01[,0,0,0x0,invalid,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=,minimumSize=,preferredSize=]
You code shows a distinct lack of understanding into how Swing works
This...
private static void mainloop()
{
window.removeAll();
switch(frameID)
{
case 1:
frame = new frame01();
frame.setLayout(new FlowLayout());
System.out.println(frame);
window.add(frame);
break;
default:
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(FrameG.class.getName()).log(Level.SEVERE, null, ex);
}
mainloop();
}
Is a bad idea of two reasons, the first been that it will eventually generate a StackOverflowException, the second been that it violates the single thread rules of Swing.
Normally, a while-loop would generally be better, but as you're dealing with Swing based components you should consider using either a Swing Timer or SwingWorker, depending on what you are hoping to achieve.
Constantly adding and remove components also seems like a weird idea, but there's no context to the question to know what it is you're hoping to achieve. Having said that, as a general rule, if you want to switch between views a CardLayout is generally considered the preferred solution
This...
public class frame01 extends openWin{
#Override
public void paint(Graphics g) {
Graphics2D pic = (Graphics2D) g;
pic.setBackground(Color.BLACK);
}
}
is simply doing nothing, other then breaking the paint chain, which could cause no end of weird paint artefacts to be generated. All you code does if temporarily changes the background color of the Graphics context, but since you don't paint anything with it, it's effectively meaningless
As a general rule, it's recommended to override the paintComponent method of Swing components (and call super.paintComponent before you do any custom painting).
I would highly recommend that you take a look at:
Painting in AWT and Swing
Performing Custom Painting
Concurrency in Swing
How to use Swing Timers
Worker Threads and SwingWorker
How to Use CardLayout
Your code has lots of problems. The line private static openWin frame = new frame01(); seems fishy as I said in my comment. But its not the worst part:
THIS:
private static void mainloop()
{
window.removeAll();
switch(frameID)
{
case 1:
frame = new frame01();
frame.setLayout(new FlowLayout());
System.out.println(frame);
window.add(frame);
break;
default:
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(FrameG.class.getName()).log(Level.SEVERE, null, ex);
}
mainloop();
}
Is BAD! And it probably breaks since you are constantly creating a new frame01() and adding it to your window.
Also DO NOT override paint but paintComponent. Another thing I dont get is why override it in a separate class and not in openWin itself.
Another thing, DO NOT SLEEP THE MAIN THREAD, if you really must then run your own and put IT to sleep.
What is the purpose of this code? I'm trying to understand it so I can help you redesign it.
PS: not really an answer, just a bit longer than a comment.
Never use a Thread and it's start and stops as a timer. Use javax.swing.Timer instead. Also you've created a recursive function to implement an infinite loop which is very wired and prone to fail. Swing has it's own thread and when you mix it up with another threads unconsciously it transform your code to a monster we are all afraid of.
EDIT: Everything displays correctly when either field or hunterField hold no objects in any location. field and hunterField both exclusively hold objects which extend the same class, so I guess it may have something to do with inheritance...?
I have created a simple Agent-Based Model using MASON. The back-end works find, but when I try displaying my agents only "wall" agents are displayed. (Wall Portrayal) My code is below... Any idea?
package sim.app.celai;
import java.awt.Color;
import javax.swing.JFrame;
import sim.app.tutorial3.Tutorial3WithUI;
import sim.display.Controller;
import sim.display.Display2D;
import sim.display.GUIState;
import sim.portrayal.grid.SparseGridPortrayal2D;
import sim.util.Bag;
public class FieldWithGUI extends GUIState {
public Display2D display;
public JFrame frame;
SparseGridPortrayal2D hunterPortrayal = new SparseGridPortrayal2D();
SparseGridPortrayal2D wallPortrayal = new SparseGridPortrayal2D();
SparseGridPortrayal2D childPortrayal = new SparseGridPortrayal2D();
public FieldWithGUI() {
super(new Field(System.currentTimeMillis()));
}
public void setupPortrayals() {
childPortrayal.setField(((Field) state).field);
hunterPortrayal.setField(((Field) state).hunterField);
wallPortrayal.setField(((Field) state).wallField);
childPortrayal.setPortrayalForAll(new sim.portrayal.simple.OvalPortrayal2D(Color.blue));
hunterPortrayal.setPortrayalForAll(new sim.portrayal.simple.OvalPortrayal2D(Color.red));
wallPortrayal.setPortrayalForAll(new sim.portrayal.simple.OvalPortrayal2D(Color.green));
display.reset();
display.repaint();
}
public void quit()
{
super.quit();
if (frame!=null) frame.dispose();
frame = null; // let gc
display = null; // let gc
}
public static void main(String[] args)
{
new FieldWithGUI().createController();
}
public void start()
{
super.start();
// set up our portrayals
setupPortrayals();
}
public void init(Controller c)
{
super.init(c);
// Make the Display2D. We'll have it display stuff later.
display = new Display2D(400,400,this); // at 400x400, we've got 4x4 per array position
frame = display.createFrame();
c.registerFrame(frame); // register the frame so it appears in the "Display" list
frame.setVisible(true);
// specify the backdrop color -- what gets painted behind the displays
display.setBackdrop(Color.black);
// attach the portrayals
display.attach(childPortrayal, "children");
display.attach(hunterPortrayal, "hunter");
display.attach(wallPortrayal, "wall");
}
}
What I am looking to do is have the user to be able to change perspectives from a KeyListener. If the user hits the specified key, than the perspective should change. Any ideas?
Even if I override the methods they still do not work. I have also tried KeyAdapter
package com.development.gameOne.environment.component;
import java.applet.Applet;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import com.development.gameOne.environment.applet.drawing.Perspective;
import com.development.gameOne.environment.applet.perspectives.p1.FirstPerspective;
import com.development.gameOne.environment.applet.perspectives.p2.SecondPerspective;
public class Component extends Applet implements KeyListener {
private static final long serialVersionUID = 1L;
private Dimension size = new Dimension(1280, 720);
private ArrayList<Perspective> perspectives = new ArrayList<Perspective>();
private boolean running = true;
private boolean switchPerspective = false;
public Component() {
setPreferredSize(size);
loadPerspectives();
addKeyListener(this);
setFocusable(true);
setVisible(true);
start();
}
private void loadPerspectives() {
perspectives.add(new FirstPerspective());
perspectives.add(new SecondPerspective());
}
public static void main(String[] args) {
new Component();
}
#Override
public void paint(Graphics g) {
while (running) {
for (Perspective p : perspectives) {
System.out.println(p.getPerspective());
while (!switchPerspective) {
System.out.println("Rendering");
p.start(g);
sleep(100);
}
switchPerspective = false;
}
sleep(10);
}
}
public static void sleep(int renderSpeed) {
try {
Thread.sleep(renderSpeed);
}
catch (Exception e) {}
}
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()){
case KeyEvent.VK_SHIFT:
System.out.println("KeyPressed");
switchPerspective = true;
break;
}
}
public void keyTyped(KeyEvent e) { }
public void keyReleased(KeyEvent e) {}
}
The program runs, but doesn't switch perspectives. I cannot seem to get the KeyListener
to work at all. I really have no idea what to do.
I don't think the issue is with your KeyListener, but is with your paint process
#Override
public void paint(Graphics g) {
while (running) {
for (Perspective p : perspectives) {
System.out.println(p.getPerspective());
while (!switchPerspective) {
System.out.println("Rendering");
p.start(g);
sleep(100);
}
switchPerspective = false;
}
sleep(10);
}
}
This will block the Event Dispatching Thread, preventing it from ever been able to process new events coming into the system
Take a look at Painting in AWT and Swing for details about how painting works in AWT.
The (simple) solution, in this case, would be to provide a other Thread which handles the timing between updates and simple call repaint when you want the UI updated.
A better solution would be take take advantage of the a BufferStrategy instead. It still require a Thread, but stops you from breaking the painting chain.
As a side note. AWT Applets are woefully out-of-date and were replaced by JApplet before 2000. Having said that, I would recommend against using applets at all, as they have enough problems which only increases the difficulty of starting development and focus on something like a JPanel added to an instance of a JFrame instead.
Take a look at Performing Custom Painting and Creating a GUI With JFC/Swing
I'd also drop the use of KeyListener as soon as you can in favour of Swing's Key bindings API. See How to Use Key Bindings for more details
I'd also avoid calling your applet Component, there already is a class called Component and this is just going to confuse matters...
And applets, definitely, should not have a main method. They are expected to be loaded by the browser directly and have a different, defined, life cycle.
How do I remove all listener from a SWT StyledText if I'm missing the instances? I tried to use the StyledText.getListeners(int) method to get all the instances, being able to remove them afterwards. But it's exhausting to find all the required int values. Is this the most straightforward way? Thank you!
Here is my temporary solution:
public void removeAllListener(StyledText st) {
int[] eventTypes = { 3007, 3011, SWT.Resize, SWT.Move, SWT.Dispose,
SWT.DragDetect, 3000, SWT.FocusIn, SWT.FocusOut, SWT.Gesture,
SWT.Help, SWT.KeyUp, SWT.KeyDown, 3001, 3002, SWT.MenuDetect,
SWT.Modify, SWT.MouseDown, SWT.MouseUp, SWT.MouseDoubleClick,
SWT.MouseMove, SWT.MouseEnter, SWT.MouseExit, SWT.MouseHover,
SWT.MouseWheel, SWT.Paint, 3008, SWT.Selection, SWT.Touch,
SWT.Traverse, 3005, SWT.Verify, 3009, 3010 };
for (int eventType : eventTypes) {
Listener[] listeners = st.getListeners(eventType);
for (Listener listener : listeners) {
st.removeListener(eventType, listener);
}
}
}
I had to copy some of the values since they are part of StyledText class declared with the default modifier. So I cannot access them.
I hope, I didn't miss any int values ;)
In general, there is no mechanism to do this. However, I managed to get it working by subclassing StyledText. The new class is called StyledTextWithListeners. You can just rename it if you want ;) . However, you will have to use
styledText.addListenerByUser(int eventType, Listener listener);
to add you Listeners rather than
styledText.addListener(int eventTyle, Listener listener);
This is necessary to discriminate between Listeners added by you and those added by SWT upon creation.
To remove all listeners added by the user (you) just call
styledText.removeAllListeners();
Here is the code:
import java.util.HashMap;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
public class MiniExample {
public static void main(String[] args) {
Display display = Display.getDefault();
final Shell shell = new Shell(display);
shell.setLayout(new GridLayout(1, false));
StyledTextWithListeners text = new StyledTextWithListeners(shell, SWT.BORDER);
text.setText("TEXT");
text.addListenerByUser(SWT.Verify, new Listener() {
#Override
public void handleEvent(Event arg0) {
}
});
text.removeAllListeners();
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
}
public static class StyledTextWithListeners extends StyledText
{
HashMap<Integer, Listener> listeners;
public StyledTextWithListeners(Composite parent, int style) {
super(parent, style);
}
public void addListenerByUser(int eventType, Listener listener)
{
addListener(eventType, listener);
System.out.println("Adding: " + listener.getClass().toString());
if(listeners == null)
listeners = new HashMap<Integer, Listener>();
listeners.put(eventType, listener);
}
public void removeAllListeners()
{
for(Integer type : listeners.keySet())
{
System.out.println("Removing: " + listeners.get(type).getClass().toString());
removeListener(type.intValue(), listeners.get(type));
}
listeners = new HashMap<Integer, Listener>();
}
}
}
It basically saves all the Listeners you add in a HashMap and removes them when you call removeAllListeners.
Keep in mind that I didn't take care of the case where you add a second Listener with the same eventType to the StyledText as done before. In this case, the old Listener will be replaced in the HashMap, but not removed from the StyledText. If this case can occur in your scenario, just add some code yourself.
I have a window with a MenuItem "maddbound3" with the following ActionListener:
maddbound3.addActionListener
(
new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
menu_addbound3();
}
}
);
When the menu is clicked this listener calls menu_addbound3() below:
void menu_addbound3()
{
while(getEditMode() != EditMode.NONE)
{
System.out.println("!... " + getEditMode());
synchronized(this)
{
try
{
wait();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
A MouseClicked event alters the value of the edit mode and issues a notifyAll() so that the while loop should exit. However, tests have shown that when the system is running through the while loop, the MouseClicked event never occurs on clicking the mouse.
Does the ActionListener block the MouseClicked event? How can I resolve this issue?
Thanks
Don't have a while(true) on the Swing event thread, and likewise don't call wait() on the Swing event thread -- you'll freeze the whole GUI making it completely unresponsive. You need to understand that the main Swing event thread or "event dispatch thread" is responsible for all Swing drawing and user interaction, and so if you tie it up with long-running or freezing code, you lock your entire GUI.
Instead, change the state of your program -- perhaps by setting a variable or two, and have the behavior of your program depend on this state. If you need more specific advice, please tell us what behavior you're trying to achieve, and we can perhaps give you a better way of doing it.
For more on the Swing event thread, please read: Lesson: Concurrency in Swing
Edit
You state:
When the user clicks the menu item I want to obtain information via a series of "discrete" mouse clicks from the window. Hence, on clicking the menu, the user would be prompted to "select a point in the window". So, what I need is for my ActionListener function (menu_addbound3) to then wait for a mouse click. Hence the wait/notify setup. A mouse click changes the edit_mode and notifyAll() causes the wait in the while loop to exit which then causes the while loop to exit and I can then prompt for my next bit of information within the menu_addbound3 function, repeating this as as I need to.
Thanks for the clarification, and now I can definitely tell you that you are doing it wrong, that you most definitely do not want to use the while loop or wait or notify. There are many ways to solve this issue, one could be to use some boolean or enum variables to give the program a state and then alter its behavior depending on the state. Your EditMode enum can be used in the MouseListener to let it know that its active, and then you could also give the MouseListener class a boolean variable windowPointSelected, set to false, and then only set it true after the first click has been made.
Edit 2
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class ProgState extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
private static final Color EDIT_COLOR = Color.red;
private EditMode editMode = EditMode.NONE;
private boolean firstPointSelected = false;
private JMenuBar jMenuBar = new JMenuBar();
private JTextField firstPointField = new JTextField(15);
private JTextField secondPointField = new JTextField(15);
public ProgState() {
add(firstPointField);
add(secondPointField);
JMenu menu = new JMenu("Menu");
menu.add(new JMenuItem(new AbstractAction("Edit") {
#Override
public void actionPerformed(ActionEvent arg0) {
setEditMode(EditMode.EDITING);
setFirstPointSelected(false);
}
}));
jMenuBar.add(menu);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent mEvt) {
if (getEditMode() == EditMode.EDITING) {
Point p = mEvt.getPoint();
String pStr = String.format("[%d, %d]", p.x, p.y);
if (!isFirstPointSelected()) {
firstPointField.setText(pStr);
setFirstPointSelected(true);
} else {
secondPointField.setText(pStr);
setEditMode(EditMode.NONE);
}
}
}
});
}
public void setEditMode(EditMode editMode) {
this.editMode = editMode;
Color c = editMode == EditMode.NONE ? null : EDIT_COLOR;
setBackground(c);
}
public EditMode getEditMode() {
return editMode;
}
public void setFirstPointSelected(boolean firstPointSelected) {
this.firstPointSelected = firstPointSelected;
}
public boolean isFirstPointSelected() {
return firstPointSelected;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public JMenuBar getJMenuBar() {
return jMenuBar;
}
private static void createAndShowGui() {
ProgState progState = new ProgState();
JFrame frame = new JFrame("EditMode");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(progState);
frame.setJMenuBar(progState.getJMenuBar());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
enum EditMode {
NONE, EDITING
}
From the discussion it seems that having your class assume a number of states is the best way to proceed. We can achieve this by one or more enum variables. The reason I found this so hard to grasp initially is that I couldn't see the benefit of having all of ones code in the MouseClicked function. This is ugly and unmanageable at best.
However, using multiple enums and splitting processing into a number of external functions, we do indeed achieve a nice system for what we want.