firstly apologies if the title is brief, I've thought it over but can't come up with a summary short enough for my question.
I have a JPanel class which consists of JButtons.
I have my main Swing application class, which has Swing components, AS WELL AS the JPanel class. What I want to do is for the ActionEvents fired from my JPanel class to be dispatched to my Swing application class to process. I've searched examples on the net and forums (including this one), but can't seem to get it to work.
My JPanel class:
public class NumericKB extends javax.swing.JPanel implements ActionListener {
...
private void init() {
...
JButton aButton = new JButton();
aButton.addActionListener(this);
JPanel aPanel= new JPanel();
aPanel.add(aButton);
...
}
...
#Override
public void actionPerformed(ActionEvent e) {
Component source = (Component) e.getSource();
// recursively find the root Component in my main app class
while (source.getParent() != null) {
source = source.getParent();
}
// once found, call the dispatch the current event to the root component
source.dispatchEvent(e);
}
...
}
My main app class:
public class SimplePOS extends javax.swing.JFrame implements ActionListener {
private void init() {
getContentPane().add(new NumericKB());
pack();
}
#Override
public void actionPerformed(ActionEvent e) {
...
// this is where I want to receive the ActionEvent fired from my NumericKB class
// However, nothing happens
}
}
The reason for wanting to write a separate JPanel class is because I want to reuse this in other apps.
Also, the actual code, my main app class has many subcomponents which and the JPanel class is added into one of the subcomponents, thus the recursive .getParent() calls.
Any help would be much appreciated. Thank in advance! Cheers.
You cannot rethrow the event to parent because the parent has no support for delivering of ActionEvents. But in your case you can simply check whether your component has action support and call it. Something like this
public class NumericKB extends javax.swing.JPanel implements ActionListener {
...
private void init() {
...
JButton aButton = new JButton();
aButton.addActionListener(this);
JPanel aPanel= new JPanel();
aPanel.add(aButton);
...
}
...
#Override
public void actionPerformed(ActionEvent e) {
Component source = (Component) e.getSource();
// recursively find the root Component in my main app class
while (source.getParent() != null) {
source = source.getParent();
}
// once found, call the dispatch the current event to the root component
if (source instanceof ActionListener) {
((ActionListener) source).actionPerformed(e);
}
}
...
}
Related
Background
I had a simple game. I had one JPanel class and there were every thing (menu, game, game end).
Then I decided, that I should make my game better and I made two panels(one for menu, and second for game lvls).
Every thing were good, but my KeyAdapter class doesn't work at my JPanel. I don't know why it doesn't want to focus.
There is what I have:
Main class which extends JFrame and here I add my panels (and KeyListener to first panel)
public class JavaGame2 extends JFrame {
public JavaGame2(){
gamePanel = new GamePanel();
menuPanel = new MenuPanel();
setContentPane(menuPanel);
menuPanel.addKeyListener(new KeyListener() {
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_UP) {
menuPanel.changeCursor();
}
if (e.getKeyCode() == KeyEvent.VK_DOWN) {
menuPanel.changeCursor();
}
if (menuPanel.getCursorPos()==1){
if ((e.getKeyCode() == KeyEvent.VK_ENTER)) {
setContentPane(gamePanel);
//add(gamePanel);
}
}
else{
if ((e.getKeyCode() == KeyEvent.VK_ENTER)) {
System.exit(0);
}
}
}
});
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(800, 600);
setLocationRelativeTo(null);
setTitle("JavaGame2");
setResizable(false);
}
public static void main(String[] args) {
JFrame jgame = new JavaGame2();
jgame.setVisible(true);
}
}
MenuPanel class extends JPanel
public class MenuPanel extends JPanel implements ActionListener{
public MenuPanel(){
setFocusable(true);
setBackground(Color.BLACK);
setDoubleBuffered(true);
setSize(800,600);
}
public void actionPerformed(ActionEvent e) {
}
}
And here GamePanel class
public class GamePanel extends JPanel implements ActionListener{
public GamePanel (){
addKeyListener(new GameAdapter());
setFocusable(true);
requestFocus();
setBackground(Color.BLACK);
setDoubleBuffered(true);
setSize(800,600);
}
private class GameAdapter extends KeyAdapter{
public void keyReleased(KeyEvent e) {
ship.keyReleased(e);
}
public void keyPressed(KeyEvent e) {
ship.keyPressed(e);
}
}
}
It doesn't work. GamePanel don't want to focus, I tried to do every thing that I read.
I think u will say that JPanel is not focusable component. But when there was one panel it somehow worked.
How can I fix this focus problem?
Maybe u will say that u prefer don't use KeyAdapter, but I think it looks pretty nice in my code.
setFocusable()? or requestFocus()? requestFocusInWindow()? How should I use them? Maybe I have mistake before and this is not my first problem?
Thanks in advance.
And Thanks for editing.
Short answer, use the key bindings API, it allows you to control the level of focus that a component requires before key events are triggered.
Longer answer, the active panel needs to have keyboard focus. You can use requestFocusInWindow, but there is no guarantee that the component will actually receive focus. When to call this is a tricky thing. You could try overriding addNotify of the panels and calling to there, just make sure you call super.addNotify first, weird things happen when you don't
You will also need to consider what will happen if the component loses focus
As a side note:
setDoubleBuffered(true); is irrelevant, as Swing components are double buffered by default. Generally you might disable this if you wanted to print the component. No harm in calling it though
Calling setSize on your components is irrelevant, as you components will be under the control of a layout manager, which will determine the size of the component itself. You'd be better off overriding getPreferredSize and returning an appropriate size for the layout manager
Calling setSize on JFrame is also a bad idea. Frames have borders, this means that your viewable area will be the frame size - the frames border insets, which will be less the 800x600 you've specified. Better to utilise the previous comment and call pack on the JFrame, which will pack the frame around the content so that it meets the contents requirements...
Personally, I would also localise the KeyListener to the actually component itself, this allows the component to act as it's own controller making it more portable...IMHO
Updated with controller idea...
A "really" simplified concept would be to have some kind of "controller" that the menu and game panel could communicate through, for example...
public interface GameController {
public void showMenu();
public void showGame();
}
You would then pass a reference of this interface to the MenuPanel...
public class MenuPanel extends JPanel implements ActionListener{
public MenuPanel(GameController controller){
//...
}
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
And the GamePanel...
public class GamePanel extends JPanel implements ActionListener{
public GamePanel (GameController controller){
//...
}
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
You would, obviously, need to construct a implementation of the controller...
public class CardLayoutGameController implements GameController {
public static final String GAME_PANEL = "GamePanel";
public static final String MENU_PANEL = "MenuPanel";
private Container container;
private CardLayout cardLayout;
public CardLayoutGameController(Container parent, CardLayout layout) {
container = parent;
cardLayout = layout;
}
public void showMenu() {
cardLayout.show(container, MENU_PANEL);
}
public void showGame() {
cardLayout.show(container, GAME_PANEL);
}
}
You would then construct your UI around this controller, for example...
public class JavaGame2 extends JFrame {
public JavaGame2(){
CardLayout layout = new CardLayout();
setLayout(layout);
GameController controller = new CardLayoutGameController(getContentPane(), layout);
gamePanel = new GamePanel(controller);
menuPanel = new MenuPanel(controller);
add(gamePanel, CardLayoutGameController.GAME_PANEL);
add(menuPanel, CardLayoutGameController.MENU_PANEL);
controller.showMenu();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setTitle("JavaGame2");
setResizable(false);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
JFrame jgame = new JavaGame2();
jgame.setVisible(true);
}
});
}
}
Is this just an "example" to help spark the idea, haven't tested this, just hacked out here so it might blow up :P
I'm a beginner at java and want to make a JFrame with tabs containing a seperate JPanel. One panel has a list where it displays things that you select in a different panel, so I want this panel to always display a list of stuff that you have selected in a different panel (I hope that makes sense). To do this, I need to make a method to refresh the JList. This is the Farthest that I've gotten on that:
public class PanelClass extends JPanel {
private JList list;
private DefaultListModel listModel = new DefaultListModel();
private ArrayList<SomeOtherClass> objectArray = new ArrayList<SomeOtherClass>();
public PanelClass() {
list.setModel(listModel);
}
public void refresh() {
updateListModel();
list.setModel(listModel);
}
public void updateListModel() {
if (objectArray.isEmpty()) {
System.out.println("No Objects In Array!");
} else {
listModel.clear();
for (SomeOtherClass SOC : objectArray) {
// SOC.getName() just returns a string
listModel.addElement(SOC.getName());
}
}
}
public void addObjectToArray(SomeOtherClass SOC) {
objectArray.add(SOC);
}
}
Could someone please tell me how to make a "refresh" method to constantly keep the JList up to date?
The AWT/Swing event model is based upon the widgets being event sources (in the MVC paradigm, they are both view and controller). Different widgets source different event types.
Look at the java.awt.event (primarily), and javax.swing.event packages for the listener interfaces you'll need to implement and register in order to produce your desired effect.
Basically, you write a Listener implementation, and register it with Widget1. When Widget1 detects an event, it will notify you, and you can then use the information it provides to update Widget2.
For instance, if a button being clicked would add an object to your list, you might have something like below (I usually put this code in the encompassing JFrame class, and make it implement the listener interfaces; but you can choose to use inner classes or separate listener classes):
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MyFrame extends JFrame implements ActionListener {
private JButton button = new JButton("Click me!");
private DefaultListModel<String> listModel = new DefaultListModel<String>();
private JList<String> list = new JList<String>(listModel);
private int counter = 1;
public MyFrame() {
setTitle("Test Updates");
JTabbedPane tabs = new JTabbedPane();
add(tabs, BorderLayout.CENTER);
JPanel panel = new JPanel();
panel.add(list);
tabs.add("Selections", panel);
panel = new JPanel();
button.addActionListener(this);
panel.add(button);
tabs.add("Options", panel);
pack();
}
#Override
public void actionPerformed(final ActionEvent event) {
if (button.equals(event.getSource())) {
listModel.addElement("Item " + counter++);
}
}
/* Test it! */
public static void main(String[] args) {
final MyFrame frame = new MyFrame();
frame.addWindowListener(new WindowAdapter() {
#Override public void windowClosing(final WindowEvent e) {
frame.setVisible(false);
frame.dispose();
System.exit(0);
}
});
frame.setVisible(true);
}
}
This code sample is minimal, but it should give you an idea of how to go about implementing what you want.
You can do it in two way. First : Write it in infinite thread loop so that it will constantly update JList. Second : You can call refresh() method whenever new SOC objects are added in your ArrayList. It means you can call refresh() method from addObjectToArray() method which ultimately call the refresh method only when you have some change in your ArrayList.
FYI : I did it in my project and I went for second option.
anyone know or have an idea as to why my button disappears after i resize the applet?
this is my code:
import java.awt.event.*;
import javax.swing.*;
import acm.program.*;
public class button extends ConsoleProgram {
public void init(){
hiButton = new JButton("hi");
add(hiButton, SOUTH);
addActionListeners();
}
public void actionPerformed(ActionEvent e){
if(hiButton == e.getSource()){
println("hello") ;
}
}
private JButton hiButton;
}
I'm not sure if it is a good Idea to redefine the init-method. When I have a look at http://jtf.acm.org/javadoc/student/acm/program/ConsoleProgram.html I would expect that you have implement only the run-method. Overriding init without calling super.init() Looks strange to me.
Maybe I would be better to derive from JApplet directly for your first steps in Applet programming.
Assuming that
your ConsoleProgram extends (directly or indirectly) JApplet
You declared SOUTH as a static final variable that has the value BorderLayout.SOUTH (otherwise your code doesn't compile)
The code should work, no need to repaint (unless you would like to do some application-specific optimization). I just copied and pasted your code (by expliciting the two assumptions above), I see the applet and the button doesn't disappear on resize.
Anyway there are few "not good" things in the code:
First of all, a naming convention issue: the class name should be "Button" with the first letter capitalized (on top of that, it's a poor name for an Applet)
Second, action listeners should be attached before adding the component;
Third, as Oracle doc suggests here, the code that builds the GUI should be a job that runs on the event dispatcher thread. You can do that by wrapping the build gui code in a Runnable using a SwingUtilities.invokeAndWait(Runnable()
Have you tried calling super.init() at the start of your init() method?
Try explicitly using a layout for your Console and then use relative positioning.
To re-size a button in Applet:
public class Button extends JApplet implements ActionListener {
private JButton button;
public void init() {
Container container = getContentPane();
container.setLayout(null);
container.setBackground(Color.white);
button = new JButton("Press Me");
button.setSize(getWidth()/2,20);
button.setLocation(getWidth()/2-button.getSize().width/2, getHeight()/2-button.getSize().height/2);
container.add(button);
button.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
int width = (button.getSize().width == getWidth()/2) ? getWidth()/4 : getWidth()/2;
int height = button.getSize().height;
button.setSize(width,height);
button.setLocation(getWidth()/2-width/2, getHeight()/2-height/2);
}
}
To re-size a button in JFrame:
public class Button extends JFrame implements ActionListener {
private JButton button;
public Button(String title) {
Container container = getContentPane();
container.setLayout(null);
container.setBackground(Color.white);
setTitle(title);
setSize(400,400);
button = new JButton("Press Me");
button.setSize(getWidth()/2,20);
button.setLocation(getWidth()/2-button.getSize().width/2,
getHeight()/2-button.getSize().height/2);
container.add(button);
button.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
int width = (button.getSize().width == getWidth()/2) ? getWidth()/4 : getWidth()/2;
int height = button.getSize().height;
button.setSize(width,height);
button.setLocation(getWidth()/2-width/2, getHeight()/2-height/2);
}
public static void main(String[] args) {
Button button = new Button("Test");
button.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
button.setVisible(true);
}
}
Have you declared the repaint method...???
You are using swing. It needs to have declared a repaint.
Please define a custom repaint mwthod
Say, you have a subclass of JFrame, and use it to create your own custom JFrame. In this class (we'll call it mainFrame), we create a reference to another custom JFrame class (we'll call this one sidePanel).
In sidePanel, you have different buttons, radio buttons,..
My question is, is there a way to notify mainFrame the user presses on a button?
I've created a (untested) example of what I mean:
class mainFrame extends JFrame {
public mainFrame() {
super("main frame");
//...........
sidePanel panel = new sidePanel();
//...........
}
public static void main(String[] args) {
mainFrame mainF = new mainFrame();
//.........
}
}
And the sidePanel class:
class sidePanel extends JFrame {
public sidePanel() {
super("sidePanel frame");
//...........
JButton button1 = new JButton();
button1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
//Notify mainFrame somehow button is pressed
}});
//...........
}
}
To notify mainFrame of an event, the SidePanel instance (really bad name for a Frame) must have a reference to mainFrame. Pass mainFrame as an argument of the SidePanel constructor, and callback mainFrame from the actionPerformed method in SidePanel:
SidePanel panel = new SidePanel(this);
and in SidePanel:
public void actionPerformed(ActionEvent e) {
mainFrame.buttonHasBeenClicked();
...
}
This tightly couples both classes though. A way to decouple them is to make the SidePanel object accept listeners for custom events, and to fire such an event when the button is clicked. The mainFrame would construct the SidePanel instance, and add itself (or an inner anonymous class instance) as a listener to the sidePanel.
See this page for an example.
I have simple Swing GUI with main window JFrame and its main panel derive from JPanel. The panel has some buttons that can be clicked and generate events.
I want these events affect data stored in JFrame because it is my main application - it has some queues for thread, open streams and so on.
So how do I make my button in panel invoke callbacks in its parent frame? What is best practice of this for Java/Swing?
To invoke methods in the parent frame you need a reference to the parent frame. So your JPanel's constructor can be declared like this:
public MyPanel(MyFrame frame){
super();
this.frame = frame;
//the rest of your code
}
And in the JFrame you invoke this constructor like this:
panel = new MyPanel(this);//this refers to your JFrame
In the event handlers attached to your buttons you now have access to the frame and can invoke the various methods as needed.
button1.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
//do some stuff
frame.someMethod();//invoke method on frame
//do more stuff
}
});
Have a look on this tutorial for using SwingWorker.
Use addActionListener method on desired buttons specifying the class implementing ActionListener.
ActionListenerClass actionListenerObject = new actionListenerClass();
JButton b = new JButton("Button");
b.addActionListener(actionListenerObject);
public class ActionListenerClass implements ActionListener(){
//or better : actionListenerClass extends AbstractAction
public void actionPerformed(ActionEvent e) {
}
}
EDIT:
Yes, I know this. But the action
listener I want to be in parent JFrame
class - this is the problem
then extends JFrame class making the new derived class implementing the desired interface.
You can implement the ActionListener in your class that has the JFrame (or extends it):
class MyPanelClass {
public MyPanelClass(ActionListener al)
{
//...
JButton myButton = new JButton("Button");
myButton.addActionListener(al);
//...
}
}
class MainClass extends JFrame implements ActionListener {
public void someMethod() {
MyPanelClass mpc = new MyPanelClass(this);
}
#Override
public void ActionPerformed(ActionEvent ev) {
// your implementation
}
}