Is there a better way to have JPanels communicate? - java

I'm working on a game which consists entirely of menus/screens which you interact with using buttons, essentially a text-based game but without the typing. To do this I have been working with Swing to create a basic UI, following this video series.
In order to have actions performed in one panel affect the state of another, I have followed the tutorial and created a UIListener interface, which extends EventListener. When a button is pressed in one JPanel, the fireUIEvent method of the panel is called, which in turn calls the sole method of the interface, uiEventOccurred, which is implemented in an anonymous class inside the main JFrame class. Then the JFrame can do whatever it wants with the event, and modify other JPanels it contains accordingly.
For example, I have a panel that represents the town. It has a button you click to travel to the dungeon. When this button is clicked, the town panel is replaced by the dungeon panel, which has a button you can click to return to the town.
I'll post the relevant classes:
the main frame class
public class GameMainFrame extends JFrame{
public JPanel rightPanel;
public GameMainFrame(String title) {
super(title);
/* Setting The Overall Layout */
setLayout(new BorderLayout());
/* Creating Individual UI Panels */
UIPanel uip_town = new Town_UI();
UIPanel uip_dung = new Dungeon_UI();
/* Creating A Nested Panel */
rightPanel = new JPanel();
rightPanel.setLayout(new BorderLayout());
rightPanel.add(uip_text, BorderLayout.WEST);
rightPanel.add(uip_town, BorderLayout.NORTH);
/* Creating A Listener To React To Events From The UI Panels */
uip_town.addUIListener(new UIListener() {
public void uiEventOccurred(UIEvent event) {
System.out.println("event text: " + event.getText());
if (event.getText().equals("goto_dungeon")) {
rightPanel.remove(uip_town);
rightPanel.add(uip_dung, BorderLayout.NORTH);
validate();
} else if (event.getText().equals("goto_town")) {
rightPanel.remove(uip_dung);
rightPanel.add(uip_town, BorderLayout.NORTH);
validate();
}
}
});
/* Adding Panels To The Content Pane Of The Frame */
Container c = getContentPane();
c.add(uip_info, BorderLayout.WEST);
c.add(rightPanel, BorderLayout.CENTER);
}
}
UIPanel class
public class UIPanel extends JPanel {
private EventListenerList listenerList;
public UIPanel() {
listenerList = new EventListenerList();
}
public void fireUIEvent(UIEvent event) {
Object[] listeners = listenerList.getListenerList();
for (int i = 0; i < listeners.length; i += 2) {
if (listeners[i] == UIListener.class) {
((UIListener) listeners[i + 1]).uiEventOccurred(event);
}
}
}
public void addUIListener(UIListener listener) {
listenerList.add(UIListener.class, listener);
}
public void removeUIListener(UIListener listener) {
listenerList.remove(UIListener.class, listener);
}
}
Town_UI class
public class Town_UI extends UIPanel {
public Town_UI() {
Dimension size = getPreferredSize();
size.height = 466;
setPreferredSize(size);
setBorder(BorderFactory.createTitledBorder("Town"));
JButton btn_dungeon_enter = new JButton("Enter the dungeon");
add(btn_dungeon_enter);
btn_dungeon_enter.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
fireUIEvent(new UIEvent(this, "goto_dungeon"));
}
});
}
}
Dungeon_UI class
public class Dungeon_UI extends UIPanel {
public Dungeon_UI() {
Dimension size = getPreferredSize();
size.height = 466;
setPreferredSize(size);
setBorder(BorderFactory.createTitledBorder("Dungeon"));
JButton btn_town_return = new JButton("Return to town");
add(btn_town_return);
btn_town_return.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
fireUIEvent(new UIEvent(this, "goto_town"));
}
});
}
}
UIEvent object
public class UIEvent extends EventObject {
private String text;
public UIEvent(Object source, String text) {
super(source);
this.text = text;
}
public String getText() {
return text;
}
}
UIListener interface
public interface UIListener extends EventListener {
public void uiEventOccurred(UIEvent e);
}
I have two problems with this. Number one, it is currently bugged. When I run the application, the Town_UI panel appears with its button "Enter the dungeon", and upon clicking on it the frame correctly updates, replacing the Town_UI panel with the Dungeon_UI panel. However, clicking on the "Return to town" button on the dungeon panel doesn't do anything. When trying to debug this I found that the cause was in the UIPanel method fireUIEvent. When the "Return to town" button is pressed, this method is correctly called, but when it creates the Object array using getListenerList, the array is empty, so it skips the for-loop and no action is performed. Because I am unfamiliar with ActionEvents and EventListeners in Java, though, I am not sure how to fix this.
My other problem is that all of the behaviors that occur when a button is pressed are handled in the anonymous class in the GameMainFrame class. This means that if I want to have a bunch of different buttons all do different things (which I do plan on implementing), I'll need a whole bunch of conditionals in the anonymous class to determine what button is pressed and perform the corresponding behavior. I feel like it would be a lot cleaner if the button behavior could be implemented in the anonymous ActionListener class of each button, but I'm not sure how I should restructure my code so that I can do this but also allow any action on any panel able modify any other panel.
In the tutorial I watched he claimed that this implementation of EventListeners was what you should do in order to avoid making your code a rat's nest, but it seems like that is where my code is heading if I use this implementation for what I am trying to do.

Related

Add listener to JPanel

I have a custom class CustomField that extends JPanel. As I often have to reuse the same pattern, my custom class is made of 2 JLabels and 2 JComboBox.
It's quite simple; the first JComboBox has ON/OFF choices and the second JComboBox is only visible if the first is set to "ON". I can manage this part.
The part that I however don't know who to design it well is that CustomField instances are in another class that is the main JFrame and in this JFrame, some parts will be visible only if the JComboBox from the CustomField class is set to "ON". I thought about using a MouseAdapter, but I don't know it is good practice.
Here is my CustomField class:
public class CustomField extends JPanel {
/**
*
*/
private static final long serialVersionUID = 1L;
JLabel text, portText;
JComboBox<String> testCB, option;
public CustomField(String text, String opt, String tst) {
this.text = new JLabel(text);
String[] onOffOpt= {"OFF", "ON"};
this.option = new JComboBox<String>(onOffOpt);
this.option.setSelectedItem(opt);
this.option.addItemListener(new ItemListener(){
#Override
public void itemStateChanged(ItemEvent ie) {
portText.setVisible(option.getSelectedIndex() == 1);
testCB.setVisible(option.getSelectedIndex() == 1);
}
});
this.portText = new JLabel("Test:");
String[] testChoices = {"Test", "Test2"};
this.testCB = new JComboBox<String>(testChoices);
this.testCB.setSelectedItem(tst);
this.setLayout(new FlowLayout());
add(this.text);
add(this.option);
add(this.portText);
add(this.testCB);
}
}
And here is the main JFrame:
public class Main {
CustomField cf = new CustomField("test", "ON, "Test2");
public static void main(String s[]) {
JFrame frame = new JFrame("Application");
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
panel.add(cf);
JLabel labelTest = new JLabel("Label that should be visible or not");
panel.add(labelTest);
frame.add(panel);
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Basically, I want that the labelTest visibily changes according to the CustomField settings. In the way that it is made, I can not put the labelTest in the CustomField class.
Is there a clean way to do what I want? Should I redesign the actual thing and put all the fields in the same class?
Thanks!
First, you want to expose the combobox's state with a method in CustomField:
public boolean isOn() {
return testCB.getSelectedIndex() == 1;
}
You can get an idea for how listening for state is done by looking at the method signatures in the documentation for various Swing components, which use the standard JavaBean listener pattern: You’ll want to add three public methods, and one protected method:
public void addChangeListener(ChangeListener listener) {
listenerList.add(ChangeListener.class, listener);
}
public void removeChangeListener(ChangeListener listener) {
listenerList.remove(ChangeListener.class, listener);
}
public ChangeListener[] getChangeListeners() {
return listenerList.getListeners(ChangeListener.class);
}
protected void fireChangeListeners() {
ChangeEvent event = new ChangeEvent(this);
for (ChangeListener listener : getChangeListeners()) {
listener.stateChanged(event);
}
}
(The listenerList field is inherited from JComponent.)
Now, you can simply add a call to fireChangeListeners(); whenever you detect that the user has changed the value of the On/Off combobox—that is, you’ll want to call it in your ItemListener.
As you can probably guess, your Main class can now call cf.addChangeListener, and inside that listener adjust the visibility of your label based on the value returned by cf.isOn().
You can learn a lot more by reading these.

Access an extended JPanel from a listener in another class

I would like to access an extended JPanel from a listener in another class, in order to re-size that panel when the button is clicked.
I tried to access it using Buttons.this.setPreferredSize...., but i got this error
No enclosing instance of the type Buttons is accessible in scope
My Buttons class looks like this
public class Buttons extends JPanel {
public Buttons() {
//code
}
}
and the other from where i want to change the size of the Panel
public class InterfaceCalc extends JPanel {
// others codes
expandIcon = new ImageIcon("src\\img\\expand.png");
expand = new JButton("", expandIcon);
expand.setBorderPainted(false);
expand.setContentAreaFilled(false);
expand.setFocusPainted(false);
expand.setOpaque(false);
expand.setMargin(new Insets(0, 0, 0, 0));
expand.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (!on) {
Buttons.sqrt.setVisible(true);
Buttons.log.setVisible(true);
Buttons.cos.setVisible(true);
Buttons.sin.setVisible(true);
// this is where is want to access the panel
Buttons.this.setPreferredSize(new Dimension());
on = true;
} else {
Buttons.sqrt.setVisible(false);
Buttons.log.setVisible(false);
Buttons.cos.setVisible(false);
Buttons.sin.setVisible(false);
on = false;
}
}
});
// others codes
}
How can achieve that ?
I fixed the problem by creating an instance of Buttons outside the listener. before ,the extended panel was added directly like this in this.add(new Buttons()) in theInterfaceCalc panel, now after i created the instance I added it to the panel and i used it to change the size.

mouseEntered and mouseExited method questions

Working on a homework assignment and I'm having issues with figuring out how to print a line of text when a mouse cursor enters and exits a certain colored area (a JPanel called panel in this case) while using the MouseListener interface.
I choose a color from the bottom panel (either Red, blue, or yellow) and then when I move to the upper panel it should be able to print which color the mouse has entered in while the mouse is in the panel and which color I exited from when my mouse finds itself outside of said panel...
Hopefully that makes sense. Here's a code snippet of what I have so far. This is for the color RED:
class RedButtonListener implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
panel.setBackground(Color.RED);
class RedMouseListener implements MouseListener
{
public void mouseEntered(MouseEvent event)
{
}
public void mouseExited(MouseEvent event)
{
}
public void mousePressed(MouseEvent event) { }
public void mouseReleased(MouseEvent event) { }
public void mouseClicked(MouseEvent event) { }
}
}
}
ActionListener redListener = new RedButtonListener();
bRed.addActionListener(redListener);
Here's a relevant question from Stack Overflow Mouse moved event within JPanel
I'd recommend once you ensure that triggers are being correctly listened to (try printing "Hello World") From there you need to get the communication of the color state within the mouse event. If everything is within the same instance you can just access the variables needed within the event listener.
Here are the docs on MouseEvent http://docs.oracle.com/javase/7/docs/api/java/awt/event/MouseEvent.html
Make sure you added the mouse listener try the following
panel.addMouseListener(new RedMouseListener());
start simple making the following work
public void mouseEntered(MouseEvent event)
{
System.out.println("Hello World!");
}
if you need to access the color of the panel within the event listener try the following snippet
panel.getBackground();
This returns a Color object.
Its worth mentioning the extra class declaration can be avoided by using an anonymous inner class. see How are Anonymous (inner) classes used in Java? These overridden methods are essentially a sub class of MouseListener but we don't need to call it by name.
panel.addMouseListener(new MouseListener(){
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseClicked(MouseEvent e) {}
});
Introduction
Since this is such an old question, I thought I'd put together a simple Swing GUI to illustrate how a MouseListener works.
Here's the GUI before I do anything.
The main panel in the center will take on the selector color when the mouse is moved into the selection area. The main panel will return to it's original color when the mouse is moved out of the selection area.
Here's the GUI when my mouse is in the blue selection area.
Explanation
If you're not familiar with Java Swing, Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Learning Swing with the NetBeans IDE section. Pay close attention to the Concurrency in Swing and the Laying Out Components Within a Container sections.
When I create a Swing GUI, I use the model/view/controller (MVC) pattern. This pattern allows me to separate my concerns and focus on one part of the application at a time.
The MVC pattern implies that you create the model first, then the view, then the controller. This is more of an iterative process than a waterfall.
In Java Swing, the MVC pattern means:
The view reads information from the model.
The view may not update the model.
The controller updates the model and repaints / revalidates the view.
The model consists of one or more plain Java getter/setter classes.
The view consists of a JFrame and however many JPanels it takes to create the GUI.
There's usually not one controller "to rule them all". Each listener acts as an independent controller to manage its part of the model and view.
This is a simple application, so it consists of two model classes, one view class, and one controller class. The model is not updated at all in this example.
I did not code this entire application at one time. I wrote a few lines of code and ran tests. I made lots of changes to the code before I was satisfied with how it worked.
Model
The ColorSelection class is a plain Java getter/setter class that holds the color name, background color, and foreground color.
The MouseMovementModel class is a plain Java getter/setter class that holds the ColorSelection instances. The GUI builds the selection JPanel based on this information. If you want to add another selection color, you would add it here.
View
The view consists of a JFrame, a selection JPanel, and the main JPanel.
The JFrame has a default BorderLayout. The selection JPanel goes into the NORTH section and the main JPanel goes into the CENTER section. Only one component can be placed in a section. That component is usually a JPanel.
The selection JPanel uses a FlowLayout to hold the color selection JPanels. The color selection JPanels are created based on the number of ColorSelection instances in the application model.
A color selection JPanel is a simple JPanel created to use the information from a ColorSelection instance.
The main JPanel is a simple JPanel that will show a background color. The controller will be responsible for changing the background color.
Controller
The ColorListener class extends a MouseAdapter. The MouseAdapter class implements the MouseListener, MouseMotionListener, and MouseWheelListener interfaces. Using the MouseAdapter class allows me to implement just the mouse listener methods I'm writing code for.
The mouseEntered method sets the main JPanel to the ColorSelection color. The code is really simple. It updates the view with the ColorSelection background color.
The mouseExited method sets the main JPanel back to its original color.
Code
Here's the complete runnable code. I made the additional classes inner classes so I could post the code as one block.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class MouseMovementExample implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new MouseMovementExample());
}
private final JPanel mainPanel;
private final JPanel[] colorPanel;
private final MouseMovementModel model;
public MouseMovementExample() {
this.model = new MouseMovementModel();
this.mainPanel = createMainPanel();
this.colorPanel = new JPanel[model.getSelections().length];
}
#Override
public void run() {
JFrame frame = new JFrame("Mouse Movement Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createSelectionPanel(), BorderLayout.NORTH);
frame.add(mainPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createSelectionPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
ColorSelection[] selections = model.getSelections();
for (int index = 0; index < selections.length; index++) {
ColorSelection selection = selections[index];
ColorListener listener = new ColorListener(this, selection);
colorPanel[index] = createColorPanel(selection, listener);
panel.add(colorPanel[index]);
}
return panel;
}
private JPanel createColorPanel(ColorSelection selection, ColorListener listener) {
JPanel panel = new JPanel(new BorderLayout());
panel.setBackground(selection.getBackgroundColor());
panel.addMouseListener(listener);
panel.setPreferredSize(new Dimension(200, 100));
JLabel label = new JLabel(selection.getColorName());
label.setFont(panel.getFont().deriveFont(Font.BOLD, 36f));
label.setForeground(selection.getForegroundColor());
label.setHorizontalAlignment(JLabel.CENTER);
panel.add(label, BorderLayout.CENTER);
return panel;
}
private JPanel createMainPanel() {
JPanel panel = new JPanel();
panel.setPreferredSize(new Dimension(200, 200));
return panel;
}
public Color getMainPanelBackground() {
return this.mainPanel.getBackground();
}
public void setMainPanelBackground(Color color) {
this.mainPanel.setBackground(color);
}
public class ColorListener extends MouseAdapter {
private final Color originalBackgroundColor;
private final ColorSelection selection;
private final MouseMovementExample view;
public ColorListener(MouseMovementExample view, ColorSelection selection) {
this.view = view;
this.selection = selection;
this.originalBackgroundColor = view.getMainPanelBackground();
}
#Override
public void mouseEntered(MouseEvent e) {
view.setMainPanelBackground(selection.getBackgroundColor());
}
#Override
public void mouseExited(MouseEvent e) {
view.setMainPanelBackground(originalBackgroundColor);
}
}
public class MouseMovementModel {
private final ColorSelection[] selections;
public MouseMovementModel() {
this.selections = new ColorSelection[3];
this.selections[0] = new ColorSelection("Red", Color.RED, Color.WHITE);
this.selections[1] = new ColorSelection("Green", Color.GREEN, Color.BLACK);
this.selections[2] = new ColorSelection("Blue", Color.BLUE, Color.WHITE);
}
public ColorSelection[] getSelections() {
return selections;
}
}
public class ColorSelection {
private final Color backgroundColor, foregroundColor;
private final String colorName;
public ColorSelection(String colorName, Color backgroundColor, Color foregroundColor) {
this.colorName = colorName;
this.backgroundColor = backgroundColor;
this.foregroundColor = foregroundColor;
}
public Color getBackgroundColor() {
return backgroundColor;
}
public Color getForegroundColor() {
return foregroundColor;
}
public String getColorName() {
return colorName;
}
}
}

JXTitledPanels in a JPanel a user needs to be able to choose one and click a remove button

I have a JPanel with n number of JXTitledPanels. The user should be able to click the JXTitledPanel and hit a remove button to remove it.
My question is how do I know what JXTitlePanel the user has selected.
here is a screen from my program basically I want a user to click "Hospitals", click remove and the Hospitals Table will disappear.
I'd probably add a "remove" control into the right decoration position. This way you could pass a reference to the control of the JXTiltedPane
titledPane.addRightDecoration(new MyRemoveControl(titkedPane));
Or such
#madprogrammer probably has the simplest answer, but if you don't want to change the look of the application, you could combine your button's actionListener with a mouseListener for the panels.
The mouseListener portion saves the last panel clicked, and the actionListener just removes the panel that was registered by the mouseListener.
Here's a quick sample I cooked up - it doesn't use JXTitledPane but that shouldn't matter because they're all in the same hierarchy.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TempProject extends JFrame{
public TempProject(){
Box mainContent = Box.createVerticalBox();
//Create Button
JButton removePanel = new JButton("RemovePanel");
RemoveListener listener = new RemoveListener(mainContent);
removePanel.addActionListener(listener);
mainContent.add(removePanel);
//Create Panels
mainContent.add(getPanel(Color.red, listener));
mainContent.add(getPanel(Color.orange, listener));
mainContent.add(getPanel(Color.pink, listener));
mainContent.add(getPanel(Color.magenta, listener));
setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
setContentPane(mainContent);
pack();
setVisible(true);
}
public JPanel getPanel(Color color, RemoveListener l){
JPanel result = new JPanel();
result.setBackground(color);
result.add(new JLabel(color.toString()));
result.addMouseListener(l);
return result;
}
public static void main(String args[])
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
new TempProject();
}
});
}
public static class RemoveListener extends MouseAdapter implements ActionListener{
Component lastSelectedComponent = null;
Container master; //The panel containing the ones being listened to
public RemoveListener(Container master){
this.master = master;
}
#Override
public void mouseClicked(MouseEvent arg0) {
lastSelectedComponent = (Component)arg0.getSource();
}
#Override
public void actionPerformed(ActionEvent e) {
if(lastSelectedComponent != null){
master.remove(lastSelectedComponent);
master.repaint();
}
}
}
}

call JFrame from active running or background JFrame without calling new JFrame

I am having problem with JFrames.
Currently I have 2 JFrames running,
MainFrame with a button to call Frame2.
And from Frame2 with JButton, I wan to call the current running/background MainFrame without calling another new MainFrame.
Actually I am making a search function on Frame2 and when click button search, wanna display the results in the Main Frame.
If Frame2 inherits MainFrame then do this:
Frame2.getParent().getBackground();
There are a multitude of solutions for such problems. It really depends on what best suits your use case.
In the example below I use an interface to issue callbacks to MainFrame from Frame2. I assume the latter is a member of MainFrame. This sort of solution makes it easy for you to use the same Frame2 implementation in multiple implementations of MainFrame (a common search for more than one frame).
Note that code below is just skeleton code to demonstrate the pattern being used.
public class Frame2 extends JFrame {
private final Controller controller;
private JButton button;
public Frame2(Controller controller) {
this.controller = controller;
button = new JButton("Search");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// do search and create result object
Object results = new Object();
Frame2.this.controller.displaySearchResults(results);
}
});
}
public interface Controller {
// users implement this
public void displaySearchResults(Object results);
}
}
public class MainFrame extends JFrame {
private Frame2 search;
private JButton button;
public MainFrame() {
search = new Frame2(new ControllerImplementation());
button = new JButton("Show search");
button.addActionListener(new ShowSearch());
}
private class ShowSearch implements ActionListener {
public void actionPerformed(ActionEvent e) {
search.setVisible(true);
}
}
private class ControllerImplementation implements Frame2.Controller {
public void displaySearchResults(Object results) {
// display results by accessing members of MainFrame
}
}
}
This may help you achieve what you want.

Categories

Resources