Java - Calling methods in Swing whilst dealing with Inheritance and Interfaces - java

My problem is quite hard to word but here is the basic outline:
I have an interface:
public interface TheInterface {
/**
*
* Returns a string
*/
public String getStuff();
}
I have an abstract class that implements this interface:
public abstract class GenericClass implements TheInterface {
public GenericClass() {
// TODO Auto-generated constructor stub
}
#Override
public String getStuff() {
return "Random string";
}
}
I then have a class that extends GenericClass
public class GUIClass extends GenericClass {
private myFrame myNewFrame;
public GUIClass() {
super();
myNewFrame = new myFrame();
}
}
As you can see, the GenericClass has a frame:
import javax.swing.JFrame;
public class myFrame extends JFrame {
private myPanel topPanel;
public myFrame() {
topPanel= new myPanel();
add(topPanel);
setSize(400,200);
//setLocation(200,200);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("Test Program");
setVisible(true);
}
}
And inside that frame is a panel which contains a label:
import java.awt.GridLayout;
import java.awt.Label;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class myPanel extends JPanel {
private JLabel myLabel;
public myPanel() {
setLayout(new GridLayout(0,2));
add (new Label("This label should contain the content of getStuff(): "));
myLabel=new JLabel();
add (myLabel);
}
}
And what I want to do here is to call getStuff() from the GenericClass and have it displayed inside that label. However at the moment I have no access to it and it seems that my design is flawed. I would appreciate it if anyone could help to rearrange or change this so that it would be possible to call that method in the label in the most efficient way without multiple cases of the same code.
Thanks.

You could use the Observer pattern:
public interface StuffObserver {
/**
*
* Pass whatever you want, perhaps getStuff(),
* but that method might be removed by the time we're done here
* (depends on what else might need to query/track it without,
* an observer)
*/
private void onStuffChanged(String newStuff);
}
Your Panel class is now
public class myPanel extends JPanel implements StuffObserver
which contains
private void onStuffChanged(String newStuff)
{
Runnable changeText = new Runnable() {
public void run() {
myLabel.setText(newStuff);
}
};
SwingUtilities.invokeLater(changeText);
}
make sure you have myLabel referencing the actual label you added to panel (your current code around that might not be what you want?)
From here, have perhaps GenericClass or it's subclass GUIClass can have a List of StuffObservers (with methods to add or delete from)
private List<StuffObservers> stuffObservers = new ArrayList<>();
public void addStuffObserver(StuffObserver ob)...
// looks familar? Same way Swing has addActionListener() on some components
public void deleteStuffObserver(StuffObserver ob)...
GUIClass can simply call something like:
myNewFrame = new myFrame();
addStuffObserver(myNewFrame.getPanel());
Your GenericClass or GUIClass can also do the following whenever it changes what the outcome of getStuff() can be:
for (StuffObserver ob : stuffObservers)
{
ob.onStuffChanged(someStringRepresentingWhatYouWouldChangeGetStuffTo);
}
And get rid of getStuff() now. Anytime you change the state that getStuff() would have returned, your JLabel will now auto update to display that data.

I would suggest you construct and manage your GUI components directly in GUIClass instead of auto-managing them in custom subclasses.
public class GUIClass extends GenericClass {
private JFrame frame;
private JPanel panel;
private JLabel label;
public GUIClass() {
super();
initialisation();
setLabelText(getStuff());
}
private void initialisation() {
// Label
this.label = new JLabel();
this.label.setText(getStuff());
// Panel
this.panel = new JPanel();
this.panel.setLayout(new GridLayout(0, 2));
this.panel.add(this.label);
// Frame
this.frame = new JFrame();
this.frame.add(this.panel);
this.frame.setSize(400, 200);
this.frame.setLocation(200, 200);
this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.frame.setTitle("Test Program");
this.frame.setVisible(true);
}
private void setLabelText(String text) {
this.label.setText(text);
}
}
This is a design suggestion, so I might have forgotten some elements from your original code, but I think you can get the idea!

Related

Is there a way to halt a method until a button is pressed?

I have been coding for a card game and cannot get my method to wait for a button to be pressed.
The general code goes like this. When I run the code, doTask() has a segment where it needs to wait for a button to be pressed. However, the method does not wait for the button and just loops through.
I am currently thinking to have a while loop with a boolean (buttonIsPressed) which will be triggered true in actionPerformed(e). However, is there a simpler way to do this?
Any help would be appreciated.
Thank you.
public class Test {
public Test()
{
// all vars instantiated
while (!(taskLeft==0))
{
doTask();
taskLeft--;
}
}
private class Handler implements ActionListener
{
public void actionPerformed(ActionEvent arg0) {
// update information in doTask()
}
}
}
Yours is a classic XY Problem where you ask how to solve a specific code problem when the best solution is to use a completely different approach. You're thinking of how do I code this event-driven GUI program to work as a linear console program, and that's not how you should approach this. Instead look at changing object state and basing response of the object to events based on its state.
So get rid of the while loop, and instead do your task when the button is pushed based on the state of the GUI. The details of any solution will depend on the details of your problem, something you may wish to share with us.
So for instance, here taskLeft represents a "state" of the TaskEx object, and your Handler's response will depend on the state of this variable.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class TaskEx extends JPanel {
private int taskLeft = 10;
public void doTask() {
//
}
private class Handler implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (taskLeft > 0) {
doTask();
taskLeft--;
}
}
}
}
An actually functioning example:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class TaskEx extends JPanel {
private int taskLeft = 10;
private JLabel taskCountLabel = new JLabel(String.valueOf(taskLeft));
public TaskEx() {
JPanel northPanel = new JPanel();
northPanel.add(new JLabel("Tasks Left:"));
northPanel.add(taskCountLabel);
JPanel centerPanel = new JPanel();
centerPanel.add(new JButton(new Handler("Push Me")));
setLayout(new BorderLayout());
add(northPanel, BorderLayout.PAGE_START);
add(centerPanel, BorderLayout.CENTER);
}
public void doTask() {
taskCountLabel.setText(String.valueOf(taskLeft));
}
private static void createAndShowGui() {
TaskEx mainPanel = new TaskEx();
JFrame frame = new JFrame("Task Example");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
createAndShowGui();
});
}
private class Handler extends AbstractAction {
public Handler(String name) {
super(name);
putValue(MNEMONIC_KEY, (int) name.charAt(0));
}
#Override
public void actionPerformed(ActionEvent e) {
if (taskLeft > 0) {
taskLeft--;
doTask();
}
}
}
}

Why wont my custom JButton display its name?

Hi I am working on a project and I am having trouble adding JButtons. For some reason, it wont print its name.
This class is supposed to add the custom buttons
import java.awt.*;
import javax.swing.JPanel;
public class FightPanel extends JPanel {
public static final int WIDTH = 600;
public static final int HEIGHT = 600;
public FightPanel() {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setLayout(new BorderLayout());
FightButton test = new FightButton("test");
add(test);
}
}
This class is custom button class
import javax.swing.JButton;
public class FightButton extends JButton {
private String name;
public FightButton(String name) {
this.name = name;
setName(name);
}
}
The setName() method does not control the text that is displayed on a button. It is basically just used by application code to identify a component.
The setText(...) is used to set the text that is displayed in the button.
did now know that you have to use the super constructor to do that.
You don't have to but that is the easiest way to do what you want since you are passing the text as a parameter to your class.
If you want to change the text AFTER the component has been created then you would use the setText() method.
All you're doing is setting a non-displayed property of your new class and leaving the name state of the original parent class unchanged. Solution: use the super's constructor to help you out:
public FightButton(String name) {
super(name);
}
In fact there really is no need for your FlightButton to have a name field since all that does is shadow the JButton's name field. In fact if this is all you add to FlightButton, I would get rid of this class and simply use JButton objects. Myself, that is what I usually do -- use plain vanilla JButtons, but use custom AbstractActions, and I feel that is likely where your energies should go.
Edit
You state in comment:
thank you i did now know that you have to use the super constructor to do that.
You don't have to, as you could also use setter methods and the like, but if you want your constructor to behave like a JButton's similar constructor, best to use the parent's constructor.
Edit 2
For example:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.*;
public class FightPanel extends JPanel {
public static final int FP_WIDTH = 600;
public static final int FP_HEIGHT = 600;
public FightPanel() {
setLayout(new BorderLayout());
JButton test = new JButton(new FightAction("Test", KeyEvent.VK_T));
add(test);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(FP_WIDTH, FP_HEIGHT);
}
private static void createAndShowGui() {
FightPanel mainPanel = new FightPanel();
JFrame frame = new JFrame("FightPanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class FightAction extends AbstractAction {
public FightAction(String name, int mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(e.getActionCommand() + " pressed!");
}
}

How do I Change or Assign a Value to a Private Variable JTextField from another class?

Sorry if this is another stupid-idiotic questions for you, but I'm still a newbie in Java Programming Language.
I have 3 classes: InputClass, PreviewClass, and MainClass.
MainClass contains the main method to run the program. InputClass contains a private JTextField for input and a JButton for setting a text into the JTextField in the PreviewClass. PreviewClass contains a private JTextField to show the inputted text in the InputClass.
How exactly can I do that (assigning value to JTextField in PreviewClass) without creating an instance of the InputClassand then using getter-method-like to obtain the value it has, or, without making JTextField in the InputClass a static variable so I can access it with some static method?
Just to show you my point, here is the code:
InputClass
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class InputClass extends JPanel implements ActionListener{
private JTextField inputName;
private JButton inputButton;
public InputClass() {
setLayout(new FlowLayout());
inputName=new JTextField(15);
inputButton=new JButton("INPUT");
inputButton.addActionListener(this);
add(inputName);
add(inputButton);
}
#Override
public void actionPerformed(ActionEvent event) {
// How do I change/assign a text to the PreviewClass from here?
}
}
PreviewClass
import javax.swing.*;
import java.awt.*;
public class PreviewClass extends JPanel{
private JTextField namePreview;
public PreviewClass() {
setLayout(new FlowLayout());
namePreview=new JTextField(15);
namePreview.setEditable(false);
add(namePreview);
}
}
MainClass
import javax.swing.*;
import java.awt.*;
public class MainClass extends JFrame{
private static final int FRAME_WIDTH=250;
private static final int FRAME_HEIGHT=150;
private static final int FRAME_X_ORIGIN=400;
private static final int FRAME_Y_ORIGIN=300;
private InputClass inputPanel;
private PreviewClass previewPanel;
private JTabbedPane tabbedPane;
private Container contentPane;
public MainClass() {
contentPane=getContentPane();
contentPane.setLayout(new BorderLayout());
setTitle("How to Assign Value from Another Class");
setSize(FRAME_WIDTH, FRAME_HEIGHT);
setLocation(FRAME_X_ORIGIN, FRAME_Y_ORIGIN);
inputPanel=new InputClass();
previewPanel=new PreviewClass();
tabbedPane=new JTabbedPane();
tabbedPane.add("Input Name", inputPanel);
tabbedPane.add("Preview Name", previewPanel);
contentPane.add(tabbedPane, BorderLayout.CENTER);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args) {
MainClass frame=new MainClass();
frame.setVisible(true);
}
}
You have any number of possible solutions, all with pros and cons.
If I was facing an issue like this, I would be tempted to create a interface which would describe what could be changed and how and maybe even provide event notification when the internal state of the interface changes.
I would then create an instance of this interface and pass it to each of the classes. Those classes that need values from it, would read the values they need from it and, if available, attach some kind of change/property listener, so that they can monitor changes to the interface.
Those classes that need to make changes to the interface would do so as they need to.
As it's changed, the interface would fire updates letting any one who is listening know that changes have been made.
In this way, you decouple the classes from each and reduce the unnecessary exposure of other classes.
This is the "Model" in the MVC paradigm and describes the observer pattern
In InputClass, access the parent JFrame and get it's preview object and set the string value:
#Override
public void actionPerformed(ActionEvent event) {
// How do I change/assign a text to the PreviewClass from here?
((MainClass)SwingUtilities.getWindowAncestor(this)).getPreviewPanel().setValue(inputName.getText());
}
In PreviewClass (expose the setter method for changing the text):
public void setValue(String s){
namePreview.setText(s);
}
In MainClass (expose the getter method for accessing the Preview panel object):
public PreviewClass getPreviewPanel(){
return previewPanel;
}
You are right to avoid making it static - that would be a bad idea.
The solution here is to pass a reference to your preview class into your input class when you create the input class.
The input class stores that reference and then can do preview.inputRecieved(str) when the text field changes
Create theinputRecieved method which can then update the JLabel and/or do whatever other processing is needed.
Note that this also means that you can change how your preview window displays and organizes itself without having to change the input window. That encapsulation is an important Object Oriented design principle.
Pass the reference of previewClass to the constructor of Inputclass to set the desired value.
InputClass
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class InputClass extends JPanel implements ActionListener{
private JTextField inputName;
private JButton inputButton;
public InputClass(final PreviewClass perviewClassObj) {
setLayout(new FlowLayout());
inputName=new JTextField(15);
inputButton=new JButton("INPUT");
inputButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
perviewClassObj.setNamePreview(inputName.getText());
}
});
add(inputName);
add(inputButton);
}
#Override
public void actionPerformed(ActionEvent event) {
// How do I change/assign a text to the PreviewClass from here?
}
}
PreviewClass
import javax.swing.*;
import java.awt.*;
public class PreviewClass extends JPanel{
private JTextField namePreview;
/**
* #param namePreview the namePreview to set
*/
public void setNamePreview(String textContent) {
this.namePreview.setText(textContent);
}
public PreviewClass() {
setLayout(new FlowLayout());
namePreview=new JTextField(15);
namePreview.setEditable(false);
add(namePreview);
}
}
MainClass
import javax.swing.*;
import java.awt.*;
public class MainClass extends JFrame{
private static final int FRAME_WIDTH=250;
private static final int FRAME_HEIGHT=150;
private static final int FRAME_X_ORIGIN=400;
private static final int FRAME_Y_ORIGIN=300;
private InputClass inputPanel;
private PreviewClass previewPanel;
private JTabbedPane tabbedPane;
private Container contentPane;
public MainClass() {
contentPane=getContentPane();
contentPane.setLayout(new BorderLayout());
setTitle("How to Assign Value from Another Class");
setSize(FRAME_WIDTH, FRAME_HEIGHT);
setLocation(FRAME_X_ORIGIN, FRAME_Y_ORIGIN);
previewPanel=new PreviewClass();
inputPanel=new InputClass(previewPanel);
tabbedPane=new JTabbedPane();
tabbedPane.add("Input Name", inputPanel);
tabbedPane.add("Preview Name", previewPanel);
contentPane.add(tabbedPane, BorderLayout.CENTER);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args) {
MainClass frame=new MainClass();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
You can set InputClass an abstract class, override the actionPerformed method at instantiation and create a setter in your PreviewClass:
PreviewClass
public class PreviewClass extends JPanel{
...
public void setNamePreview(String name) {
inputName.setText(name);
}
}
InputClass
public abstract class InputClass extends JPanel implements ActionListener{
private JTextField inputName;
private JButton inputButton;
public InputClass() {
setLayout(new FlowLayout());
inputName=new JTextField(15);
inputButton=new JButton("INPUT");
inputButton.addActionListener(this);
add(inputName);
add(inputButton);
}
public String getInputNameValue(){
return inputName.getText();
}
}
MainClass
...
previewPanel=new PreviewClass();
inputPanel=new InputClass() {
#Override
public void actionPerformed(ActionEvent event) {
previewPanel.setNamePreview(inputPanel.getInputNameValue());
}
};
...

JPanel appearing to be two times bigger than set resolution

I must say I tried everything and I can't understand what's wrong with my code.
In my SIView class I create MainFrame which extends JFrame with specified resolution (let's say X,Y).
Then I create gamePanel which extends JPanel whith the same resolution as MainFrame, and add it to MainFrame. The problem is that effective resolution of the panel is twice as big (x*2, y*2). It's like the panel is being streched to be twice as big.
Frame will display only a quarter (upper left quarter) of the panel either with pack() or mannualy setting the size, unless I set it to double the resolution in which case It's ok, but that's not a proper way to do that(When calculating positions in the game I have to double everything or divide it by 2 to keep proper proportions). I even tried different Layout managers wthout any succes.
Here's the code of the main view class:
public class SIView implements Runnable {
private final MainFrame mainFrame;
private final GamePanel gamePanel;
public SIView(BlockingQueue<SIEvent> eventQueue) {
this.eventsQueue = eventQueue;
mainFrame = new MainFrame();
gamePanel = new GamePanel();
gamePanel.setVisible(true);
mainFrame.getContentPane().add(gamePanel);
// mainFrame.pack();
#Override
public void run() {
mainFrame.setVisible(true);
}
public void init() {
SwingUtilities.invokeLater(this);
}
//some code not related
}
the frame class:
public class MainFrame extends JFrame {
/**
*
*/
private static final long serialVersionUID = 6513420589842315661L;
public MainFrame() {
setTitle("Space Intruders");
setSize(new Dimension(SIParams.RES_X, SIParams.RES_Y));
setResizable(false);
setLayout(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
panel class:
public class GamePanel extends JPanel {
/**
*
*/
private static final long serialVersionUID = 8112087715257989968L;
private final PlayerShipView playerShip;
private final ArrayList<SmallEnemyShipView> smallEnemyShip;
private final ArrayList<LightMissleView> lightMissle;
public GamePanel() {
setPreferredSize(new Dimension(SIParams.RES_X, SIParams.RES_Y));
setMaximumSize(new Dimension(SIParams.RES_X, SIParams.RES_Y));
setBounds(0, 0, SIParams.RES_X, SIParams.RES_Y);
setBackground(new Color(0, 0, 0));
setLayout(new OverlayLayout(this));
setDoubleBuffered(true);
// TODO
playerShip = new PlayerShipView();
smallEnemyShip = new ArrayList<SmallEnemyShipView>();
lightMissle = new ArrayList<LightMissleView>();
this.add(playerShip);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
}
//some code not related
}
If I use LayoutManager and properly override getPreferredSize() in GamePanel, the code seems to work as expected:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.jfree.ui.OverlayLayout;
public class SIView implements Runnable {
public static interface SIParams {
int RES_X = 500;
int RES_Y = 400;
}
public static class GamePanel extends JPanel {
public GamePanel() {
setBackground(new Color(0, 0, 0));
setLayout(new OverlayLayout());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(SIParams.RES_X, SIParams.RES_Y);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
}
// some code not related
}
private JFrame mainFrame;
private GamePanel gamePanel;
#Override
public void run() {
mainFrame = createMainFrame();
gamePanel = new GamePanel();
mainFrame.getContentPane().add(gamePanel);
mainFrame.pack();
mainFrame.setVisible(true);
}
private JFrame createMainFrame() {
JFrame frame = new JFrame();
frame.setTitle("Space Intruders");
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
return frame;
}
public void init() {
SwingUtilities.invokeLater(this);
}
// some code not related
public static void main(String[] args) {
new SIView().init();
}
}
I also dropped the MainFrame class since it is not needed to extend JFrame in this context.
I just solved everything :D But I must say I feel stupid. The problem was with painting my components, in paintcomponent() method I painted rectangle on a relative position while changing component position also relatively. That gave the effect of 2xtimes movement etc. because while the component was moving the rectangle was moving inside of it too. I guess I have a lot to learn about Swing ;) Sorry for all this trouble ;)
PS. I didn't have to change anything in Panel/Frame classes except for using pack() method after everything.

JApplet contain a frame, or JApplet alone?

I have this project that I need to publish, but don't know which way will be best. Can somebody help me please.
I have a Gui application(Jframe). In there I have a Jpanel that contains some animations (implement runnable). So in my main method I would call the constructor first, so everything display nicely, then called Runner.start(). (Thread)
So basically the gui pop up and then the animation happens, to be specific the animation is just the title of my program that slides in.
Now I want to put this on the website so my students can use.
I do not want to use java web start, i want this to act as an applet.
So do i put this jframe into my applet?
or should I convert this whole thing from jframe to japplet? and is this applet need to implement Runnable?
The thing that bug me is that Japplet has no main method, so how can I specified when my Jpanel can execute its animation? I want the animation to occur after everything has load up on the screen, not before.
I guess put it as the last statement of init() method? Correct me if I am wrong.
Thanks,
I have a Gui application(Jframe). .. I want to put this on the website so my students can use.
While it is possible to convert the frame to an applet, a better option is to launch the frame from a link using Java Web Start.
you can do both:
MainGui
import java.awt.BorderLayout;
import javax.swing.*;
public class MainGui extends JPanel {
public MainGui() {
this(null);
}
public MainGui(MyJApplet applet) {
this.applet = applet;
if (!isApplet()) {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
} else
frame = null;
setLayout(new BorderLayout());
// setPreferredSize(new Dimension(640, 480));
SwingUtilities.invokeLater(new Runnable() {
public void run() {
MainGui.this.run();
}
});
}
String title() {
return "Title";
}
public void addContent() {
add(new JLabel("add content! top"));
}
void run() {
if (isApplet()) addContent();
else {
frame.setTitle(title());
frame.getContentPane().add(this, BorderLayout.CENTER);
addContent();
frame.pack();
System.out.println(getSize());
frame.setVisible(true);
}
}
boolean isApplet() {
return applet != null;
}
public static void main(String[] args) {
new MainGui(null);
}
protected final JFrame frame;
protected final MyJApplet applet;
private static final long serialVersionUID = 1;
}
MyJApplet
import java.awt.BorderLayout;
import javax.swing.JApplet;
public class MyJApplet extends JApplet {
public void start() {
}
public void init() {
getContentPane().setLayout(new BorderLayout());
addContent();
}
public void addContent() {
getContentPane().add(new MainGui(this), BorderLayout.CENTER);
}
public static void main(String[] args) {
new MainGui(null);
}
private static final long serialVersionUID = 1;
}

Categories