I have my custom component which holds it's data in my custom data class. My component extends JComponent while data is fully custom.
What is conventional pattern a data class can notify component it was changed? Should I just implement some event inside data class and let component subscribe to it when data is set? Or there is predefined pattern is Swing library?
I am looking, for example, at ListModel<E> interface and see that is just has addListDataListener(ListDataListener l) and removeListDataListener(ListDataListener l). Is this that mechanism model notifies List?
For the record, this is mostly my personal opinion (since this is more of an opinion question).
Typically with a Swing application it's best to separate your code out into Model/View/Control (MVC). This means that the actual Swing components are your Viow, your listeners are your Control, and your code that actually does stuff is the Model. In this case both your View and Model only know about the Control (and the Control knows about both the View and Model).
So if your Model updates - it notifies the Control, which updates the View. It's the same thing with the View (Listeners in the View execute, notifying the Control, which updates the Model).
The advantage of this is that it loosely couples the View and the Model (the view only cares about showing stuff to the user, and the model only cares about the data, and they don't care what each other are doing so long as they get the right information).
Here's an example (for simplicity they're all in one file, but usually you'd have the MVC each in their own file at the very least):
import java.awt.event.*;
import java.util.ArrayList;
import javax.swing.*;
public class MVCSeparation {
// Model: (Number crunching math-y) or (data processing) stuff
public static class Model{
ArrayList<String> data = new ArrayList<String>();
public void addData(String value){
data.add(value);
}
public int getCount(){
return data.size();
}
public String randomValue(){
String result = "";
if(data.size() > 0){
int index = (int)(Math.random() * data.size());
System.out.println(index);
result = data.get(index);
}
System.out.println("Getting Value: " + result);
return result;
}
}
// View: Pretty graphics and visuals
public static class View extends Box{
JLabel text = new JLabel("Random Value:");
JTextField newItem = new JTextField(10);
JButton submit = new JButton("Submit");
public View(){
super(BoxLayout.Y_AXIS);
add(text);
add(newItem);
add(submit);
}
public void setSubmitAction(ActionListener submitAction){
submit.addActionListener(submitAction);
}
public void setDisplayText(String value){
text.setText("Random Value: " + value);
}
public String getText(){
String result = newItem.getText();
newItem.setText("");
return result;
}
public void startupApp(){
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(this);
frame.validate();
frame.pack();
frame.setVisible(true);
}
}
// Processing User Interactions and Data Updates (links two above together)
public static class Control{
Model m = new Model();
View v = new View();
public Control(){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
v.setSubmitAction(new SubmitText());
v.startupApp();
}});
//Randomly update label
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
v.setDisplayText(m.randomValue());
}});
}
}
// Listener to notify us of user interactions on the View
public class SubmitText implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
m.addData(v.getText());
}
}
}
public static void main(String[] args) {
new Control();
}
}
You can use Observable or PropertyChangeListener.
Related
I want to know how to add a GUI to my program. I have started creating a java program in Blue J and the first class of the program is a class which has been extended by other classes.
Now I have to make a GUI too but from my understanding I can only implement an interface as the GUI extends the class Frame. The problem is I want to create a GUI of my class, it has instance variables too so is there a work around? Could I make my first class an interface without altering the extensions too much?
code:
public class Players /* Class name */
{
private int attack; /* Instance variables* */
private int defence;
private int jump;
public Players(int a, int d, int j) /* Constructor being defined */
{
int total = a + d + j;
if((total) == 100)
{
attack = a;
defence = d;
jump = j;
}
else
{
System.out.println("Make stats add to 100");
}
}
public Players()/* Default contructor if not user defined */
{
attack = 34;
defence = 33;
jump = 33;
}
public void addAttack(int a)
{
attack += a;
}
public void addDefence(int a)
{
defence += a;
}
public void addJump(int a)
{
jump += a;
}
public void getBasicStats()
{
System.out.println(attack + " " + defence + " " + jump);
}
}
This is my first class and my superclass for most of the other classes
I suggest learning how to use Swing. You will have several different classes interacting together. In fact, it is considered good practice to keep separate the code which creates and manages the GUI from the code which performs the underlying logic and data manipulation.
Another suggestion:
Learn JavaFX and download SceneBuilder from Oracle: here
At my university they have stopped teaching Swing and started to teach JavaFX, saying JavaFX has taken over the throne from Swing.
SceneBuilder is very easy to use, drag and drop concept. It creates a FXML file which is used to declare your programs GUI.
How will I declare aan instance variable inside the GUI class?
Like as shown bellow, you could start with something like this, note that your application should be able to hand out your data to other classes, for instance I changed getBasicStats() to return a String, this way you can use your application class anywhere you want, I guess this is why you were confused about where to place the GUI code...
public class PlayersGUI extends JFrame {
private static final long serialVersionUID = 1L;
private Players players; // instance variable of your application
private PlayersGUI() {
players = new Players();
initGUI();
}
private void initGUI() {
setTitle("This the GUI for Players application");
setPreferredSize(new Dimension(640, 560));
setLocation(new Point(360, 240));
JPanel jPanel = new JPanel();
JLabel stat = new JLabel(players.getBasicStats());
JButton attack = new JButton("Attack!");
attack.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
players.addAttack(1);
}
});
JButton hugeAttack = new JButton("HUGE Attack!");
hugeAttack.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
players.addAttack(10);
}
});
JButton defend = new JButton("Defend");
defend.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
players.addDefence(1);
}
});
JButton showStats = new JButton("Show stats");
showStats.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
stat.setText(players.getBasicStats());
}
});
jPanel.add(stat);
jPanel.add(attack);
jPanel.add(hugeAttack);
jPanel.add(defend);
jPanel.add(showStats);
add(jPanel);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
}
public static void main(String... args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
PlayersGUI pgui = new PlayersGUI();
pgui.pack();
pgui.setVisible(true);
}
});
}
}
I would recommend using netbeans to start with. From there you can easily select pre created classes such as Jframes. Much easier to learn. You can create a GUI from there by dragging and dropping buttons and whatever you need.
Here is a youtube tut to create GUI's in netbeans.
https://www.youtube.com/watch?v=LFr06ZKIpSM
If you decide not to go with netbeans, you are gonna have to create swing containers withing your class to make the Interface.
Still learning Java.
Again Swing has caused me to ask this but it is really a general OO question. If I have a master class (that contains main()) it creates a new object "A" that does something, the master class now has a reference to that object, how does object "B" get access to the attributes of that object?
The only way I can think of is for the master class to create a new object "B", passing object "A" as a parameter to the constructor, which I suppose is O.K. but doesn't this make event handling potentially difficult.
For example, and perhaps this is a poor design which is causing the problem. I have a master class with the programme logic, that creates a standard Swing frame, with a menu, the menu items having action listeners. But the actionlistener needs to interact with external objects.
So some code (ignoring the details) :
The main class, containing the programme logic and the save and load methods, etc :
public final class TheProgramme implements WindowListener }
private static final TheProgramme TP = new TheProgramme();
// Declare Class variables, instance variables etc.
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShewGUI();
}
});
}
private static void createAndShewGUI() {
TP.populateAndShew();
}
private void populateAndShew() {
final StandardFrame sF = new StandardFrame("TheProgramme");
theFrame = sF.getMainFrame();
theFrame.addWindowListener(this);
theFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
theFrame.pack(); theFrame.setVisible(true);
}
...
}
So we have created a standard frame object which has created a menu, empty panel and status bar, but with event listeners on the menu items :
public class StandardFrame {
// Declare instance variables that must be visible to the ActionListener inner class
public StandardFrame(String theTitle) {
mainFrame = new JFrame(theTitle);
mainFrame.setJMenuBar(createMenuBar()); // ... the menu bar and ...
mainFrame.setContentPane(createBlankPanel()); // ... a blank panel
java.net.URL imageURL = TheProgramme.class.getResource("images/icon.png");
if (imageURL != null) {
ImageIcon icon = new ImageIcon(imageURL);
mainFrame.setIconImage(icon.getImage());
}
}
public JMenuBar createMenuBar() {
ActionListener menuEvents = new MenuListener();
JMenuBar aMenuBar = new JMenuBar();
JMenu fileMenu = new JMenu("File"); fileMenu.setMnemonic(KeyEvent.VK_F);
...
aMenuBar.add(fileMenu);
...
JMenuItem newItem = new JMenuItem("New", KeyEvent.VK_N); newItem.addActionListener(menuEvents);
newItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK));
...
fileMenu.add(newItem);
...
return aMenuBar;
}
}
class MenuListener implements ActionListener {
public void actionPerformed(ActionEvent ae) {
String actionCommand = ae.getActionCommand();
switch (actionCommand) {
case "New":
// !!! here we need to call a method in an object to create a new document object !!!
break;
case "Reformat":
// !!! here we need to call a method in the object created above
}
}
}
The first problem is the actionlistener on the menu items calls a method to create an object but it is not a call to a static method.
The second problem is it needs to be be able to call a method in that new object later on as a result of another menu choice.
The classic way to do this in Model-View-Controller is to bind the objects together at runtime. The controller, your action listener, takes parameters to indicate which view and model it is to act on.
This use of parameters is also called "Dependency Injection," as Aqua mentions.
public static void main( String[] args )
{
Model model = new Model();
View view = new View();
ActionListener listener = new MyActionListener( model, view );
view.addActionListener( listener );
}
private static class MyActionListener implements ActionListener
{
private Model model;
private View view;
public MyActionListener( Model model, View view )
{
this.model = model;
this.view = view;
}
}
In Java you can cheat a little since the ActionEvent has a pointer to the source of the event (normally the view/JComponent that generated the event.
private static class MyActionListener implements ActionListener
{
private Model model;
public MyActionListener( Model model )
{
this.model = model;
}
#Override
public void actionPerformed( ActionEvent e )
{
JComponent source = (JComponent) e.getSource();
// source == "view"...
}
}
To set a new document, you can create a "document holder" class. The "new" menu item puts a new document in the holder class. All other menu items "get" the document from the holder class. This is a fairly strict OO paradigm which uses no static methods or fields, although it is a little tedious.
Set up:
public static void main( String[] args )
{
ModelDocumentHolder model = new ModelDocumentHolder();
View view = new View();
ActionListener listener = new NewDocument( model );
view.addActionListener( listener );
View view2 = new View();
view2.addActionListener( new RegularListener( model ) );
}
New document listener:
private static class NewDocument implements ActionListener
{
private ModelDocumentHolder model;
public NewDocument( ModelDocumentHolder model )
{
this.model = model;
}
#Override
public void actionPerformed( ActionEvent e )
{
model.setDoc( new Document() );
}
}
Most other menu items:
private static class RegularListener implements ActionListener
{
private ModelDocumentHolder model;
public RegularListener( ModelDocumentHolder model )
{
this.model = model;
}
#Override
public void actionPerformed( ActionEvent e )
{
JComponent source = (JComponent) e.getSource();
Document doc = model.getDoc();
// do stuff...
}
}
The holder class:
private static class ModelDocumentHolder
{
private Document doc;
public Document getDoc()
{
return doc;
}
public void setDoc( Document doc )
{
this.doc = doc;
}
}
Generally, (and it's really hard to know if this is an answer to your question, as it's quite vague) this is what setModel(...) and addListener(...) are for.
The "constructor coordinator" aka "master class", creates the models (the swing Model classes). It creates the views (JWidgets) and it sets the models of the views. In Swing it is easy to rely upon the default constructed model (populated with the JWidget default constructor), but maybe in your case, you should find the one widget causing problems and rewrite it make the model setting explicit.
If you have extended a Jwhatever, then keep in mind that setModel(...) typically does something like
if (this.model != null) {
this.model.removeListener(this);
}
// clear the cached last "view" of the model
clearCachedData(...);
if (model != null) {
this.model = model;
// restore the "view" of the new model.
grabCachedData(...);
this.model.addListener(this);
}
I hope my interpretation of the question is correct. You can inject/provide whatever object you need to the action implementation. Here is an example that uses an interface for better abstraction as a callback from actionPerformed. When action completes it call its callback to notify whoever is interested. In this case, the panel is notified and updates its text area with some text.
The interface:
public interface ActionCallback {
public void documentCreated(String name);
}
Here is the UI:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public class TestAction extends JPanel implements ActionCallback {
private JTextArea area;
public TestAction() {
setLayout(new BorderLayout());
area = new JTextArea();
add(new JScrollPane(area));
}
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void documentCreated(String name) {
area.append(String.format("Created %s\n", name));
}
public static class NewAction extends AbstractAction {
private ActionCallback callback;
private Component parent;
public NewAction(ActionCallback callback, Component parent){
super("New");
this.callback = callback;
this.parent = parent;
}
#Override
public void actionPerformed(ActionEvent e) {
String value = JOptionPane.showInputDialog(parent, "Name", "new name");
if (value != null){
callback.documentCreated(value);
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
TestAction panel = new TestAction();
frame.add(panel);
JMenuBar menuBar = new JMenuBar();
JMenu menu = new JMenu("Menu");
menuBar.add(menu);
JMenuItem item = new JMenuItem(new NewAction(panel, frame));
menu.add(item);
frame.setJMenuBar(menuBar);
frame.pack();
frame.setVisible(true);
}
});
}
}
Passing a reference of yourself (A) to an other object (B) tends to happen quite frequently in GUI code. You could use an context object, which you pass to all classes and which contains references to relevant references and might hold some global information. In simple cases, "main program class" is used as context and passed around.
Depending on what classes you use, this might also be useful: Component#getParent().
I'm writing a simple pikachu game in Java, and I use swing.Timer with JProgress Bar, my code is like this:
public static void TimeBarListener(final View scr){
ActionListener updateProBar = new ActionListener() {
#Override
public void actionPerformed(ActionEvent actionEvent) {
int val = scr.timeBar.getValue();
if (val <= 0) {
scr.gameOver();
scr.timer = null;
return;
}
scr.timeBar.setValue(--val);
}
};
int t = n*400;
scr.timer = new Timer(t, updateProBar);
}
The "src" here is a class which extends JFrame to display the game I wrote, and 'n' is the number of pikachu pieces on a level. It works perfectly but after I add "timer", there are lots of problems occured:
I set the variable 't' change by level, but seems like it doesn't work ( I test the value, it was the right value but seems like 'timer' could'n get it). The time ran out too fast, faster if I click more on the pieces, and even if I set it longer it didn't change anything.
When I clicked "New Game" the second times, timer ran out even faster. But if I close the programs and then run again, the time return normal.
If the time ran out and then I click the "New Game" button again, It appears for 1 second then return to the "Game Over screen". Sometimes it works, but "IndexArrayOutOfBounds" Ecception appears.
I want to use the "Pause" button, which means that timer must pause and then continue to run, too. Is there anyway that I can do it?
I guess my problems are based on the code
if (val <= 0) {
scr.gameOver();
scr.timer = null;
return;
}
which makes the Game Over screen appears if the timer run out. But I'm new to this and I cant understand how I works, so I can't think of any solutions myself, or maybe it's not the problem.
Hope that I'll get some helps from you guys. Thanks a lot :)
Your problem is that you don't use correct architecture pattern. You should separate your business logic from your presentation. Also you should store variables (like time_left) in model, not in the controller (i.e. ActionListener). Please read about: Model View Controller pattern. It's a basic pattern and it'll solve most of yours problems.
Basic Example
Editor.java - main class
public class Editor {
public static void main(String[] args) {
Model model = new Model();
View view = new View(model);
new Controller(model, view);
}
}
View.java
public class View extends JFrame {
private Model model;
private JButton btn;
public View(Model model) {
this.model = model;
this.btn = new JButton("Test");
}
public void addViewControlListener(ActionListener al){
btn.addActionListener(al);
}
}
Controller.java
public class Controller {
public Controller(Model model, View view) {
view.addViewControlListener(new ViewControlListener(model, view));
}
}
ViewControlListener.java - implements ActionListener
public class ViewControlListener implements ActionListener {
private Model model;
private View view;
public ViewControlListener(Model model, View view){
this.model = model;
this.view = view;
}
public void actionPerformed(ActionEvent e) {
//update model
//refresh view
}
}
As you can see I have the one place (Controller.java) where I create listeners and add
these to components in view. You created multiple instances of the same listeners and lost control.
Also my ActionListener (ViewControlListener.java) holds instances of model and view, so it can update variables in model and refresh view.
Your application
And now something more about your application. You need thread (not realy action listener) that would decrement time variable and update view to show actual state (but only when game is active).
So you could create in model leftTime and isActive variables with setters and getters:
private int leftTime;
private boolean isActive;
public int getLeftTime() {
return leftTime;
}
public void setLeftTime(int leftTime) {
this.leftTime = leftTime;
}
public void decLeftTime() {
this.leftTime--;
}
public boolean isActive() {
return isActive;
}
public void setActive(boolean isActive) {
this.isActive = isActive;
}
And create thread that decrement time every second and repaint the view:
public class Timer extends Thread {
private Model model;
private View view;
public Timer(Model model, View view) {
this.model = model;
this.view = view;
}
public void run() {
while(true) { //could be better
if(model.isActive()) {
model.decLeftTime();
view.repaint();
}
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Then create and start that thread in Controller:
public class Controller {
public Controller(Model model, View view) {
view.addViewControlListener(new ViewControlListener(model, view));
...
Timer timer = new Timer(model, view);
timer.start();
}
}
In view you would add some component that shows left time from model and that's it.
PS do not forget set leftTime on game start.
I've hit the infinite loop problem in Swing. Done some research and come across SwingWorker threads but not really sure how to implement them. I've knocked together a simple program that shows the problem. One button starts the infinite loop and I want the other button to stop it but of course due to the Swing single thread problem the other button has frozen. Code below and help appreciated:-
public class Model
{
private int counter;
private boolean go = true;
public void go()
{
counter = 0;
while(go)
{
counter++;
System.out.println(counter);
}
}
public int getCounter()
{
return counter;
}
public void setGo(boolean value)
{
this.go = value;
}
}
public class View extends JFrame
{
private JPanel topPanel, bottomPanel;
private JTextArea messageArea;
private JButton startButton, cancelButton;
private JLabel messageLabel;
private JScrollPane scrollPane;
public View()
{
setSize(250, 220);
setTitle("View");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
topPanel = new JPanel();
bottomPanel = new JPanel();
messageArea = new JTextArea(8, 20);
messageArea.setEditable(false);
scrollPane = new JScrollPane(messageArea);
messageLabel = new JLabel("Message Area");
topPanel.setLayout(new BorderLayout());
topPanel.add(messageLabel, "North");
topPanel.add(scrollPane, "South");
startButton = new JButton("START");
cancelButton = new JButton("CANCEL");
bottomPanel.setLayout(new GridLayout(1, 2));
bottomPanel.add(startButton);
bottomPanel.add(cancelButton);
Container cp = getContentPane();
cp.add(topPanel, BorderLayout.NORTH);
cp.add(bottomPanel, BorderLayout.SOUTH);
}
public JButton getStartButton()
{
return startButton;
}
public JButton getCancelButton()
{
return cancelButton;
}
public void setMessageArea(String message)
{
messageArea.append(message + "\n");
}
}
public class Controller implements ActionListener
{
private Model theModel;
private View theView;
public Controller(Model model, View view)
{
this.theModel = model;
this.theView = view;
view.getStartButton().addActionListener(this);
view.getCancelButton().addActionListener(this);
}
public void actionPerformed(ActionEvent ae)
{
Object buttonClicked = ae.getSource();
if(buttonClicked.equals(theView.getStartButton()))
{
theModel.go();
}
else if(buttonClicked.equals(theView.getCancelButton()))
{
theModel.setGo(false);
}
}
}
public class Main
{
public static void main(String[] args)
{
Model model = new Model();
View view = new View();
Controller controller = new Controller(model, view);
view.setVisible(true);
}
}
You can do it easily without implementing any timer, you just need to add two lines to your actionPerformed method:
public void actionPerformed(ActionEvent ae)
{
Object buttonClicked = ae.getSource();
if(buttonClicked.equals(theView.getStartButton()))
{
theModel.setGo(true); //make it continue if it's just stopped
Thread t = new Thread(new Runnable() { public void run() {theModel.go();}}); //This separate thread will start the new go...
t.start(); //...when you start the thread! go!
}
else if(buttonClicked.equals(theView.getCancelButton()))
{
theModel.setGo(false);
}
}
As your Model.go() is running in a separate thread, the Event Dispatch Thread is free to do its stuff, like drawing the button released again, instead of hanging with the button down.
There's a catch! however, because the thread running Model.go() will run wildly!, it's virtually called as many times per second as your system can.
If you plan to implement some animation or the like, then you will need to:
use a Timer,
or
add some sleep time to the new thread.
Example if you go with threads:
public void go()
{
counter = 0;
while(go)
{
counter++;
System.out.println(counter);
try {
Thread.sleep(1500); //Sleep for 1.5 seconds
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
As you can see I added Thread.sleep(1500) being 1500 the time in milliseconds (1.5 seconds). Thread.sleep can be interrupted for some reasons, so you must catch the InterruptedException.
It's not necessary to go deeper on handling correctly the InterruptedException in this particular case, but if you feel curious about it you can read this nice article.
You are blocking the Event Dispatch Thread (EDT). The thread is responsible to process painting and other UI related requests. Once EDT is blocked the UI will become frozen since it cannot process any events. For more details see The Event Dispatch Thread tutorial.
Consider using timers (How to Use Swing Timers), SwingWorker or an auxiliary background thread. Background thread can communicate with EDT using SwingUtilities.invokeLater(). This mechanism is already implemented in SwingWorker, so it may be easier to go with it. It depends on functionality that is required.
Use a javax.swing.Timer to do the go() work once, (with some optional delay), using start() and stop() in the event handling.
I decided to use a SwingWorker thread and below is the updated Controller class. It does what I need it to do but my question is, is it the correct way and is it clean code? Also, I've tried getting the output of the model.go() method into the view's textarea as per the commented out lines but not been succesful, anyone know how?
public class Controller implements ActionListener
{
private Model theModel;
private View theView;
private SwingWorker<Void, Void> worker;
public Controller(Model model, View view)
{
this.theModel = model;
this.theView = view;
view.getStartButton().addActionListener(this);
view.getCancelButton().addActionListener(this);
}
public void actionPerformed(ActionEvent ae)
{
Object buttonClicked = ae.getSource();
if(buttonClicked.equals(theView.getStartButton()))
{
theModel.setGo(true);
worker = new SwingWorker<Void, Void>()
{
#Override
protected Void doInBackground()
{
// theView.setMessageArea(theModel.getCounterToString());
return theModel.go();
}
#Override
protected void done()
{
// theView.setMessageArea(theModel.getCounterToString());
}
};
worker.execute();
}
else if(buttonClicked.equals(theView.getCancelButton()))
{
theModel.setGo(false);
}
}
}
public class Model
{
public Void go()
{
counter = 0;
while(go)
{
counter++;
System.out.println(counter);
}
return null;
}
I want my GUI to make some checks when a JOptionPane appears.
Because I can't find any other way, I though I can do those each time the application window loses focus(its just checking a string). For that reason I added the following code on my JFrame:
appFrame.addWindowListener(new WindowAdapter() {
#Override
public void windowLostFocus(WindowEvent e) {
System.out.println("Focus Lost");
}
#Override
public void windowClosing(WindowEvent e) {
//some other stuff here that work
}
});
The window closing listener works fine. Although when the JFrame isn't focused nothing happens. Shouldn't "Focus Lost" be printed each time I switch from JFrame to some other window? Also, will this method be triggered when a JOptionPane is shown?
The key to me is that you want a change in the GUI triggered by a change of a String variable. The best way I see to solve this is to make the String variable a bound property by using PropertyChangeListenerSupport. This way you can have the GUI attach a PropertyChangeListener to the class that holds the String variable and then be notified when it changes allowing you to update the GUI appropriately.
If you go this route, consider giving the observed class a SwingPropertyChangeSupport field so that the listeners will be notified on the Swing event thread and hopefully avoid any Swing concurrency issues.
Here's a brief example:
import java.awt.Dimension;
import java.awt.event.*;
import java.beans.*;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class ShowPropertyChangeSupport {
#SuppressWarnings("serial")
private static void createAndShowGui() {
final MainGUI mainGui = new MainGUI("Title");
final ObservedClass observedClass = new ObservedClass();
observedClass.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
if (pcEvt.getPropertyName().equals(ObservedClass.BOUND_PROPERTY)) {
mainGui.setTitle(pcEvt.getNewValue().toString());
}
}
});
mainGui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainGui.pack();
mainGui.setLocationRelativeTo(null);
mainGui.setVisible(true);
int timerDelay = 6000; // every 6 seconds
new Timer(timerDelay, new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
String result = JOptionPane.showInputDialog(mainGui,
"Please enter a String", "Set GUI title", JOptionPane.PLAIN_MESSAGE);
if (result != null) {
observedClass.setBoundProperty(result);
}
}
}){{setInitialDelay(1000);}}.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
// ** note that I don't like extending JFrame,
// but will do this for sake of example simplicity
class MainGUI extends JFrame {
public MainGUI(String title) {
super(title);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 300);
}
}
class ObservedClass {
public static final String BOUND_PROPERTY = "bound property";
private String boundProperty = "";
private SwingPropertyChangeSupport spcSupport = new SwingPropertyChangeSupport(
this);
public SwingPropertyChangeSupport getSpcSupport() {
return spcSupport;
}
public void setSpcSupport(SwingPropertyChangeSupport spcSupport) {
this.spcSupport = spcSupport;
}
public String getBoundProperty() {
return boundProperty;
}
public void setBoundProperty(String boundProperty) {
String oldValue = this.boundProperty;
String newValue = boundProperty;
this.boundProperty = newValue;
spcSupport.firePropertyChange(BOUND_PROPERTY, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
spcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
spcSupport.removePropertyChangeListener(listener);
}
}
The key to all this in my mind is to use the listener so that the class with the bound property -- the String being listened to -- has no knowledge of the GUI, the listener, and the GUI, likewise has no knowledge of the class with the bound property. They are fully decoupled.
I'm not going to go into why you are doing what you are doing, but it is not working as you expect for the following reason:
WindowAdapter is a convenience class so you can create one listener and register it for multiple types of events. You have only registered it for one set of events, you need to also register it for focus events via: Window.addWindowFocusListener()
WindowAdapter adapter = new WindowAdapter() {
#Override
public void windowLostFocus(WindowEvent e) {
System.out.println("Focus Lost");
}
#Override
public void windowClosing(WindowEvent e) {
//some other stuff here that work
}
};
appFrame.addWindowListener(adapter);
appFrame.addWindowFocusListener(adapter);
1) JOptionPane / modal JDialog have got modality issue, but modality could be advantage if all containers have got own owner, for real workaround you need to know (I'll talking about how can I do test that)
numbers of Window[], and if isDisplayable(), then you can use follows
you can get SwingUtilities#getAccessibleIndexInXxx can returns AccessibleState
KeyboardFocusManager (very interesting methods for multi-touch) returns getXxxFocusXxx methods
Focus, FocusSubsystem is pretty asynchronous,
2) Please, with due respect, I don't know why you needed that, for why reasons I need to know about that, there is about business rules, you always need to know ...., and if is done on EDT
Focus, FocusSubsystem is pretty asynchronous,