I have to make a scrollable list where I can add a panel with 3 labels many times.
I kind of made it work but the first panels are stretched and occupy all the area of the JScrollPane and I can't figure out how to fix this, I tried changing layouts many times but still didn't manage to fix it.
I want the added panel to occupy a fixed size but I can't figure this out. Example in this picture:
https://i.stack.imgur.com/LNznP.png
The one on the left is the one that I get and the one on the right (edited) is how I want it to work.
This is my first day of Swing so the code is very likely a mess, sorry in advance.
Here is the code:
public class MainWindow extends JFrame implements ActionListener{
private JPanel viewportPanel;
private JButton addButton,remButton;
private JScrollPane scrollPane;
private int counter = 0;
private JLabel dateLabel,dateLabel_1,dateLabel_2;
public MainWindow(boolean run) {
//BUTTONS
addButton = new JButton("Add");
addButton.setLocation(521, 11);
addButton.setSize(101, 100);
addButton.addActionListener(this);
remButton = new JButton("Remove");
remButton.setLocation(521, 122);
remButton.setSize(101, 100);
remButton.addActionListener(this);
//SCROLLPANE
scrollPane = new JScrollPane();
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setBounds(10, 11, 501, 211);
add(scrollPane);
//PANELS
viewportPanel = new JPanel(new GridLayout(0,1));
scrollPane.setViewportView(viewportPanel);
//FRAME
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 650, 273);
setResizable(false);
//setIconImage(new ImageIcon("epic.png").getImage());
setLayout(null);
if(run) setVisible(true);
add(addButton);
add(remButton);
}
public void actionPerformed(ActionEvent e) {
if(e.getSource() == addButton) {
//LABELS
dateLabel = new JLabel("DATE");
dateLabel.setFont(new Font("Tahoma", Font.BOLD, 28));
dateLabel.setSize(500,500);
dateLabel_1 = new JLabel("LABEL1");
dateLabel_1.setFont(new Font("Tahoma", Font.BOLD, 22));
dateLabel_1.setBounds(10, 45, 481, 30);
dateLabel_2 = new JLabel("LABEL2");
dateLabel_2.setFont(new Font("Tahoma", Font.ITALIC, 22));
dateLabel_2.setBounds(10, 45, 481, 30);
//PANEL WITH ALL THE STUFF
JPanel componentPanel = new JPanel();
componentPanel.setLayout(new GridLayout(3, 1));
componentPanel.setMaximumSize(new Dimension(50,50));
componentPanel.setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.BLACK));
componentPanel.setBackground(Color.gray);
componentPanel.add(dateLabel);
componentPanel.add(dateLabel_1);
componentPanel.add(dateLabel_2);
viewportPanel.add(componentPanel); //add panel with labels to viewportpanel
counter++;
}
if(e.getSource() == remButton) {
Component[] componentList = viewportPanel.getComponents();
int lastElement = (componentList.length);
viewportPanel.remove(--lastElement);
--counter;
}
viewportPanel.revalidate();
viewportPanel.repaint();
}
}
Some help would be amazing!
First off, never do this:
setLayout(null);
Next, if you want things compressed at the top of a container, then use a layout that does this. such as a BorderLayout with the compressed items placed into a JPanel (perhaps one that uses a GridLayout) that is placed BorderLayout.PAGE_START
Actually, it looks as if your best solution is to us a JList, one that uses a custom renderer that places your time JLabel and two text JLabels into a JPanel and displays this in the list. So let's explore that.
First create a class to hold the data that is displayed by the JList, which looks to be a date and two lines of text:
public class ListItem {
private LocalDate date;
private String text1;
private String text2;
public ListItem(LocalDate date, String text1, String text2) {
super();
this.date = date;
this.text1 = text1;
this.text2 = text2;
}
public LocalDate getDate() {
return date;
}
public String getText1() {
return text1;
}
public String getText2() {
return text2;
}
}
Then let's create a renderer that a JList can use to display the above information in a JPanel. This is more complicated and requires that the class create a JPanel that places the labels where we want them, perhaps using a GridLayout with 1 column and 3 rows, plus some gaps between the rows: setLayout(new GridLayout(3, 1, 2 * gap, 2 * gap));. The class must implement the ListCellRenderer<T> interface which has one method: public Component getListCellRendererComponent(...). Java will pass in the paramters into this method, including a ListItem value, and we will use that value to fill in the JLabels that we add into this JPanel. Edited to highlight selected items.
import java.awt.*;
import java.time.format.DateTimeFormatter;
import javax.swing.*;
import javax.swing.border.Border;
#SuppressWarnings("serial")
public class ListItemRenderer extends JPanel implements ListCellRenderer<ListItem> {
private static final Color LIGHT_BLUE = new Color(204, 255, 255);
private static final Color REDDISH_GREY = new Color(205, 126, 121);
private DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy");
private static final int GAP = 2;
private Border emptyBorder = BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP);
private Border blackBorder = BorderFactory.createLineBorder(Color.BLACK);
private Border redBorder = BorderFactory.createLineBorder(REDDISH_GREY, 2);
private JLabel dateLabel = new JLabel();
private JLabel text1Label = new JLabel();
private JLabel text2Label = new JLabel();
public ListItemRenderer() {
dateLabel.setFont(dateLabel.getFont().deriveFont(Font.BOLD, 14f));
text1Label.setFont(text1Label.getFont().deriveFont(Font.BOLD));
text2Label.setFont(text1Label.getFont().deriveFont(Font.ITALIC));
int gap = 2;
setLayout(new GridLayout(0, 1, 2 * gap, 2 * gap));
setBorder(BorderFactory.createCompoundBorder(emptyBorder, blackBorder));
add(dateLabel);
add(text1Label);
add(text2Label);
}
#Override
public Component getListCellRendererComponent(JList<? extends ListItem> list, ListItem value, int index,
boolean isSelected, boolean cellHasFocus) {
if (value != null) {
String dateText = dateFormatter.format(value.getDate());
dateLabel.setText(dateText);
text1Label.setText(value.getText1());
text2Label.setText(value.getText2());
} else {
dateLabel.setText("");
text1Label.setText("");
text2Label.setText("");
}
if (isSelected ) {
setBackground(LIGHT_BLUE);
setBorder(BorderFactory.createCompoundBorder(emptyBorder, redBorder));
} else {
setBackground(null);
setBorder(BorderFactory.createCompoundBorder(emptyBorder, blackBorder));
}
return this;
}
}
And now the main program that puts this all together, edited to show removing items:
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.KeyEvent;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Random;
import javax.swing.*;
#SuppressWarnings("serial")
public class MainPanel extends JPanel {
DefaultListModel<ListItem> listModel = new DefaultListModel<>();
private JList<ListItem> jList = new JList<>(listModel);
private JScrollPane scrollPane = new JScrollPane(jList);
private JButton addButton = new JButton("Add");
private JButton removeButton = new JButton("Remove");
public MainPanel() {
jList.setPrototypeCellValue(new ListItem(LocalDate.now(),
"This is text 1 for testing. This is text 1 for testing. This is text 1 for testing",
"This is text 2 for testing. This is text 2 for testing. This is text 2 for testing"));
jList.setVisibleRowCount(4);
jList.setCellRenderer(new ListItemRenderer());
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
JPanel buttonPanel = new JPanel(new GridLayout(0, 1, 3, 3));
buttonPanel.add(addButton);
buttonPanel.add(removeButton);
addButton.setMnemonic(KeyEvent.VK_A);
removeButton.setMnemonic(KeyEvent.VK_R);
addButton.addActionListener(e -> addEvent());
removeButton.addActionListener(e -> removeEvent());
JPanel rightPanel = new JPanel();
rightPanel.add(buttonPanel);
int gap = 5;
setBorder(BorderFactory.createEmptyBorder(gap, gap, gap, gap));
setLayout(new BorderLayout());
add(scrollPane);
add(rightPanel, BorderLayout.LINE_END);
}
private void removeEvent() {
int[] selectedIndices = jList.getSelectedIndices();
for (int i = selectedIndices.length - 1; i >= 0; i--) {
listModel.remove(selectedIndices[i]);
}
}
private void addEvent() {
// this adds random stuff to the JList
String text1 = "Some random text: " + randomText();
String text2 = "Some random text: " + randomText();
listModel.addElement(new ListItem(LocalDate.now(), text1, text2));
// TODO: change this so that it adds *real* data to the JList
}
private String randomText() {
Random random = new Random();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 2 + random.nextInt(3); i++) {
for (int j = 0; j < 3 + random.nextInt(5); j++) {
char c = (char) ('a' + random.nextInt('z' - 'a'));
builder.append(c);
}
builder.append(" ");
}
return builder.toString();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new MainPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
Output would look like:
Related
I am trying to making a calculator.
Here the user can add multiple JTextFields to take his/her desired input with just one button click.
Now I want that the user will take the input in multiple JTextFields added by him and on clicking the Result button will show the sum of all. But I am always getting 0 as output.
Code:
public class Button extends JFrame {
private JPanel contentPane;
private JButton btnAdd;
private JButton btnResult;
private JTextField resultField;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Button frame = new Button();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public Button() {
initComponents();
}
static JTextField field = null;
//static JTextField fields[] = new JTextField[10];
private static int y = 0;
ArrayList<String> arr = new ArrayList<String>();
int ans, sum = 0;
private void initComponents() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 527, 414);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
btnAdd = new JButton("Add");
btnAdd.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
field = new JTextField();
field.setBounds(45, y += 60, 284, 32);
field.setAlignmentX(Component.CENTER_ALIGNMENT);
contentPane.add(field);
contentPane.revalidate();
contentPane.repaint();
}
});
btnAdd.setBounds(170, 341, 89, 23);
contentPane.add(btnAdd);
btnResult = new JButton("Result");
btnResult.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < arr.size(); i++) {
arr.add(field.getText());
sum += Integer.parseInt(arr.get(i));
}
resultField.setText(String.valueOf(sum));
}
});
btnResult.setBounds(383, 306, 89, 23);
contentPane.add(btnResult);
resultField = new JTextField();
resultField.setBounds(361, 275, 129, 20);
contentPane.add(resultField);
resultField.setColumns(10);
}
}
Please help how can I find the correct output?
Suggestions:
Again, when you create a data-entry text field, add it to the GUI and add it to an ArrayList of the data entry field type.
Then in the result button's ActionListener, iterate through this list using a for loop.
Inside of the for loop, get the entry field, get its text (via .getText() if using a JTextField), parse it to number via Integer.parseInt(...), and add it to a sum variable that is initialized to 0 prior to the for loop. Then display the result after the loop.
Also,
Best to use JSpinners that use a SpinnerNumberModel such as JSpinner spinner = new JSpinner(new SpinnerNumberModel(0, 0, 1000, 1)); instead of JTextField for number entry. This will limit the user to entering numbers only, and won't allow non-numeric text entry, a danger inherent in your current design.
Having to add your entry fields by button may be an over-complication
But if it is necessary, then best to add the spinners (or text fields if you must) to a JPanel that uses a proper layout manager, such a new GridLayout(0, 1) (variable number of rows, 1 column) and then add that to a JScrollPane so that you can see as many fields as has been entered.
If using a JSpinner, then you don't even need a "calculate result" button, since if you add a ChangeListener to each JSpinner, you can calculate the result on the fly whenever a spinner has had its data changed.
e.g.,
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class Button2 extends JPanel {
private List<JSpinner> spinnerList = new ArrayList<>();
private JButton resultButton = new JButton("Result");
private JButton addEntryFieldBtn = new JButton("Add Entry Field");
private JTextField resultField = new JTextField(6);
private JPanel fieldPanel = new JPanel(new GridLayout(0, 1, 4, 4));
public Button2() {
resultField.setFocusable(false);
resultButton.addActionListener(e -> calcResult());
resultButton.setMnemonic(KeyEvent.VK_R);
addEntryFieldBtn.addActionListener(e -> addEntryField());
JPanel topPanel = new JPanel();
topPanel.add(addEntryFieldBtn);
topPanel.add(resultButton);
topPanel.add(new JLabel("Result:"));
topPanel.add(resultField);
JPanel centerPanel = new JPanel(new BorderLayout());
centerPanel.add(fieldPanel, BorderLayout.PAGE_START);
JScrollPane scrollPane = new JScrollPane(centerPanel);
scrollPane.setPreferredSize(new Dimension(300, 300));
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
setLayout(new BorderLayout());
add(topPanel, BorderLayout.PAGE_START);
add(scrollPane);
}
private void calcResult() {
int sum = 0;
for (JSpinner spinner : spinnerList) {
sum += (int) spinner.getValue();
}
resultField.setText(String.valueOf(sum));
}
private void addEntryField() {
JSpinner spinner = new JSpinner(new SpinnerNumberModel(0, 0, 1000, 1));
spinner.addChangeListener(evt -> {
calcResult();
});
fieldPanel.add(spinner);
spinnerList.add(spinner);
revalidate();
repaint();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
Button2 mainPanel = new Button2();
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
This project revolves around a game that is a variation of TicTacToe called SOS. One of the requirements is that the game grid needs to have two size options. The smaller grid is 5x5 and the larger is 8x8.
My goal is to have the grid size change based off which radio button is selected. In my code below I have a commented out method to change the GRID_SIZE variable based off which radio button is selected. But it does not work where it is currently and I am struggling to come up with the solution. The other problem related to the grid size changing that I think I'll have is, I do not believe the way I create the grid now will allow for it to change live as the radio buttons are pushed.
I will need to be keeping track of what gets played in each cell of the grid (whether a player is placing an S or an O) So my thought is maybe there is a better way to create the grid for both the GUI and as a storage method for the moves played.
This project is my first java project and first GUI project of this depth. It is also the major project for one of my last classes to graduate so I'm taking this seriously and could really use the help. I know my code is probably not great, I'm here to improve so any help is welcomed.
package practice;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
#SuppressWarnings({ "serial", "unused"})
public class SOS_GUI extends JFrame {
public int GRID_SIZE = 8;
public Grid grid;
public SOS_GUI() {
GameBoard();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setTitle("SOS Practice");
this.setLocationRelativeTo(null);
setVisible(true);
}
public void GameBoard(){
// CONTENT PANE FOR HOLDING ALL GUI COMPONENTS
Container ContentPane = getContentPane();
// PANEL FOR GAME GRID
JPanel gameBoardCanvas = new JPanel();
gameBoardCanvas.setLayout(new GridLayout(GRID_SIZE, GRID_SIZE));
for (int x = 0; x < GRID_SIZE; x++) {
for (int y = 0; y < GRID_SIZE; y++) {
final Grid cell = new Grid(x, y);
gameBoardCanvas.add(cell);
}
}
// FOUR PANELS SURROUNDING GAME GRID
JPanel TopPanel = new JPanel();
JPanel BottomPanel = new JPanel();
JPanel LeftPanel = new JPanel();
JPanel RightPanel = new JPanel();
JLabel SpacerLabel = new JLabel(" || ");
// GAME MODE OOPTIONS - SIMPLE OR GENERAL
JLabel GameModeLabel = new JLabel("Game Mode :");
JRadioButton SimpleGameButton = new JRadioButton("Simple", true);
JRadioButton GeneralGameButton = new JRadioButton("General");
ButtonGroup GameModeButtons = new ButtonGroup();
GameModeButtons.add(SimpleGameButton);
GameModeButtons.add(GeneralGameButton);
// BOARD SIZE BUTTONS - SMALL(5X5) OR LARGE(8X8)
JLabel SizeOptionLabel = new JLabel("Board Size :");
JRadioButton SmallGridButton = new JRadioButton("Small", true);
JRadioButton LargeGridButton = new JRadioButton("Large");
ButtonGroup GridSizeButtons = new ButtonGroup();
GridSizeButtons.add(SmallGridButton);
GridSizeButtons.add(LargeGridButton);
// PLAY LETTER SETTINGS
JRadioButton PlayS_Button = new JRadioButton("S", true);
JRadioButton PlayO_Button = new JRadioButton("O");
ButtonGroup PlayLetterButtons = new ButtonGroup();
PlayLetterButtons.add(PlayS_Button);
PlayLetterButtons.add(PlayO_Button);
// BLUE PLAYER SETTINGS
JLabel BluePlayerLabel = new JLabel("Blue Player");
JRadioButton BlueHumanButton = new JRadioButton("Human", true);
JRadioButton BlueComputerButton = new JRadioButton("Computer");
ButtonGroup BluePlayerButtons = new ButtonGroup();
BluePlayerButtons.add(BlueHumanButton);
BluePlayerButtons.add(BlueComputerButton);
// RED PLAYER SETTINGS
JLabel RedPlayerLabel = new JLabel("Red Player");
JRadioButton RedHumanButton = new JRadioButton("Human");
JRadioButton RedComputerButton = new JRadioButton("Computer", true);
ButtonGroup RedPlayerButtons = new ButtonGroup();
RedPlayerButtons.add(RedHumanButton);
RedPlayerButtons.add(RedComputerButton);
// ADDING COMPONENTS TO TOP PANEL
TopPanel.add(GameModeLabel);
TopPanel.add(SimpleGameButton);
TopPanel.add(GeneralGameButton);
TopPanel.add(SpacerLabel);
TopPanel.add(SizeOptionLabel);
TopPanel.add(SmallGridButton);
TopPanel.add(LargeGridButton);
// ADDING COMPONENTS TO BOTTOM PANEL
BottomPanel.add(PlayS_Button);
BottomPanel.add(PlayO_Button);
// ADDING COMPONENTS TO LEFT PANEL
LeftPanel.add(BluePlayerLabel);
LeftPanel.add(BlueHumanButton);
LeftPanel.add(BlueComputerButton);
// ADDING COMPONENTS TO RIGHT PANEL
RightPanel.add(RedPlayerLabel);
RightPanel.add(RedHumanButton);
RightPanel.add(RedComputerButton);
// ADDING PANELS TO CONTENT PANE
ContentPane.setLayout(new BorderLayout());
ContentPane.add(TopPanel, BorderLayout.NORTH);
ContentPane.add(BottomPanel, BorderLayout.SOUTH);
ContentPane.add(LeftPanel, BorderLayout.WEST);
ContentPane.add(RightPanel, BorderLayout.EAST);
ContentPane.add(gameBoardCanvas, BorderLayout.CENTER);
TopPanel.setPreferredSize(new Dimension(50, 50));
BottomPanel.setPreferredSize(new Dimension(50, 50));
LeftPanel.setPreferredSize(new Dimension(100, 100));
RightPanel.setPreferredSize(new Dimension(100, 100));
ContentPane.setPreferredSize(new Dimension(550, 500));
}
// CLASS SETTING UP HOW THE GRID WILL BE CREATED
class Grid extends JPanel {
public static final int CELL_SIZE = 1;
private int xPos;
private int yPos;
public JLabel gridLabel;
public Grid (int x, int y) {
xPos = x;
yPos = y;
gridLabel = new JLabel("");
gridLabel.setFont(new Font("Serif", Font.BOLD, 40));
add(gridLabel);
setOpaque(true);
setLayout(new FlowLayout());
setBorder(BorderFactory.createBevelBorder(CELL_SIZE));
setBackground(new Color(200, 200, 200));
setPreferredSize(new Dimension(CELL_SIZE, CELL_SIZE));
}
}
/* POSSIBLE FUNCTION TO SET GRID_SIZE BASED OFF RADIO BUTTON INPUT? DOESNT WORK HERE
public getGridSize() {
if (GameBoard().SmallGridButton.isSelected() == true) {
GRID_SIZE = 5;
}
else if (GameBoard().LargeGridButton.isSelected() == true) {
GRID_SIZE = 8;
}
return GRID_SIZE;
}
*/
public static void main(String[] args) {
new SOS_GUI();
}
}
screenshot of the smaller 5x5 grid
screenshot of larger 8x8 grid
Again, I suggest that if you want to use components as your grid cell, that you either swap views (JPanels) using a CardLayout, or you swap out the grid cells when a JRadioButton is pressed.
I suggest:
Adding an ActionListener to the JRadioButton to be notified when it is pressed.
If you will swap components, then create a JPanel to hold the grid cells, say called gridHolder, and remove all components when the button is pressed.
Then add a new GridLayout layout manager to this JPanel, with constraints set depending on whih JRadioButton has been pressed.
Then re-adding grid cell components to this holder JPanel
Then relaying out all components in the GUI and resizing it by calling pack() on the top-level window, here a JFrame.
In the example below, I use JLabels to hold the grid cells since it is trivial to add text to these.
I store the row and column of each grid cell using the .putClientProperty(...) method and likewise can retrieve values using the .getClientProperty(...) method.
I call createGrid(...) in the constructor to create the grid with the default, small, size.
I call the same method whenever a JRadioButton has been pressed.
For example:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Window;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
import javax.swing.border.BevelBorder;
#SuppressWarnings("serial")
public class SosGrid2 extends JPanel {
private static final int SMALL_SIZE = 5;
private static final int LARGE_SIZE = 8;
private static final String[] SIZES = { "Small", "Large" };
private static final Dimension CELL_SIZE = new Dimension(60, 60);
private static final Color GRID_BG = new Color(200, 200, 200);
private static final String ROW = "row";
private static final String COL = "col";
private JPanel gridHolder = new JPanel();
private ButtonGroup gridSizeGroup = new ButtonGroup();
public SosGrid2() {
JPanel radioButtonPanel = new JPanel();
for (String size : SIZES) {
JRadioButton radioButton = new JRadioButton(size);
radioButton.setSelected(size.equals(SIZES[0]));
radioButton.setActionCommand(size);
gridSizeGroup.add(radioButton);
radioButtonPanel.add(radioButton);
radioButton.addActionListener(e -> radioListener());
}
createGrid(SMALL_SIZE);
setLayout(new BorderLayout());
add(gridHolder);
add(radioButtonPanel, BorderLayout.PAGE_END);
}
private void createGrid(int gridSize) {
gridHolder.removeAll();
gridHolder.setLayout(new GridLayout(gridSize, gridSize));
for (int row = 0; row < gridSize; row++) {
for (int col = 0; col < gridSize; col++) {
JLabel gridCell = createGridCell(row, col);
gridHolder.add(gridCell);
}
}
}
// factory method to create grid cell JLabels.
private JLabel createGridCell(int row, int col) {
JLabel label = new JLabel("", SwingConstants.CENTER);
label.setFont(label.getFont().deriveFont(Font.BOLD, 32f));
label.setOpaque(true);
label.setBackground(GRID_BG);
label.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
label.setPreferredSize(CELL_SIZE);
label.putClientProperty(ROW, row);
label.putClientProperty(COL, col);
label.addMouseListener(new MyMouseListener());
return label;
}
private class MyMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
JLabel gridCell = (JLabel) e.getSource();
int row = (int) gridCell.getClientProperty(ROW);
int col = (int) gridCell.getClientProperty(COL);
String message = String.format("Row: %d, Col: %d", row, col);
String title = "Cell Pressed";
int type = JOptionPane.PLAIN_MESSAGE;
JOptionPane.showMessageDialog(SosGrid2.this, message, title, type);
String text = gridCell.getText();
if (text.isEmpty()) {
gridCell.setText("X");
} else {
gridCell.setText("");
}
}
}
private void radioListener() {
ButtonModel btnModel = gridSizeGroup.getSelection();
if (btnModel != null) {
int gridSize = btnModel.getActionCommand().equals(SIZES[0]) ? SMALL_SIZE : LARGE_SIZE;
createGrid(gridSize);
Window jframe = SwingUtilities.getWindowAncestor(this);
jframe.pack();
jframe.setLocationRelativeTo(null);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
SosGrid2 mainPanel = new SosGrid2();
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
After adding an object of class Puzzle to Main everything displays mostly as it should. when I click any of the buttons some indexes of state should swap to the opposite so from true to false or from false to true.
unfortunately button clicking doesn't want to register for any of the buttons from the array yet it does register for a single button that was initialized by itself. How can I fix the problem?
my code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
public class Puzzle extends JFrame implements ActionListener
{
int doors = 8;
boolean [] state = new boolean[doors];
JButton [] levers = new JButton[doors];
JButton weird = new JButton("weird lever");
JLabel display = new JLabel();
Puzzle()
{
reset();
this.setSize(new Dimension(1920, 1080));
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setVisible(true);
this.setResizable(false);
this.setLayout(null);
this.add(display);
this.add(weird);
int num = levers.length;
int start = 50;
int size = (1920-(num+1)*start)/num;
char label = 'A';
display.setBounds(size*2, 150, 2000, 300);
display.setFont(new Font("Arial Black", Font.PLAIN, 200));
Display();
for(JButton i : levers)
{
i = new JButton(String.valueOf(label));
label++;
i.setBounds(start, 500, size, size);
start+=(size+50);
i.addActionListener(this);
i.setFont(new Font("Arial black", Font.PLAIN, size/2));
i.setFocusable(false);
this.add(i);
}
weird.setFocusable(false);
weird.setBounds(550, 800, 800, 200);
weird.setFont(new Font("Arial Black", Font.PLAIN, size/2));
weird.addActionListener(this);
}
#Override
public void actionPerformed(ActionEvent e)
{
/*if(e.getSource() == levers[0])
{
state[2] = Swap(state[2]);
Display();
}
if(e.getSource() == levers[1])
{
state[4] = Swap(state[4]);
state[6] = Swap(state[6]);
Display();
}
if(e.getSource() == levers[2])
{
state[2] = Swap(state[2]);
state[3] = Swap(state[3]);
state[6] = Swap(state[6]);
state[7] = Swap(state[7]);
Display();
}
if(e.getSource() == levers[3])
{
state[0] = Swap(state[0]);
state[2] = Swap(state[2]);
state[7] = Swap(state[7]);
Display();
}
if(e.getSource() == levers[4])
{
state[1] = Swap(state[1]);
state[3] = Swap(state[3]);
state[4] = Swap(state[4]);
state[5] = Swap(state[5]);
Display();
}
if(e.getSource() == levers[5])
{
state[0] = Swap(state[0]);
state[2] = Swap(state[2]);
state[6] = Swap(state[6]);
Display();
}
if(e.getSource() == levers[6])
{
state[1] = Swap(state[1]);
state[5] = Swap(state[5]);
Display();
}
if(e.getSource() == levers[7])
{
state[1] = Swap(state[1]);
state[2] = Swap(state[2]);
state[4] = Swap(state[4]);
state[5] = Swap(state[5]);
Display();
}
*/
if(e.getSource() == levers[0])
{
display.setText("A works");
}
if(e.getSource() == weird)
{
display.setText("test");
}
}
boolean Swap(boolean n)
{
return !n;
}
void Display()
{
StringBuilder toDisplay = new StringBuilder();
for (boolean j : state)
{
if (j)
{
toDisplay.append("| ");
} else
toDisplay.append("_ ");
}
display.setText(toDisplay.toString());
}
void reset ()
{
Arrays.fill(state, true);
}
}```
button clicking doesn't want to register for any of the buttons from the array yet it does register for a single button
System.out.println( levers[0] );
if(e.getSource() == levers[0])
{
display.setText("A works");
}
Add some debug code to your ActionListener and you will see that the value of levers[0] is "null".
for(JButton i : levers)
{
i = new JButton(String.valueOf(label));
label++;
i.setBounds(start, 500, size, size);
start+=(size+50);
i.addActionListener(this);
i.setFont(new Font("Arial black", Font.PLAIN, size/2));
i.setFocusable(false);
this.add(i);
}
You create the button, but you never add the instance of each button to the Array.
for(JButton i : levers)
Why would you use "i" as the variable name. Typically "i" is used as an index. Use a proper variable name, like "button". However, in this case you don't want to use a "for each" loop.
Instead you want a normal for loop so you can index your Array to add each button as you create it:
//for(JButton i : levers)
for (int i = 0; i < doors; i++)
{
JButton button = new JButton(String.valueOf(label));
levers[i] = button;
...
Other issues:
method names should NOT start with an upper case character.
components should be added to the frame BEFORE the frame is made visible.
components should be created on the Event Dispatch Thread (EDT).
Don't use a null layout and setBounds(). Swing was designed to be used with layout managers.
Don't hardcode screen sizes. Instead you can use frame.setExtendedState(JFrame.MAXIMIZED_BOTH);, so it will work for all screen sizes.
Introduction
Your code was too complex for me to understand. I like simple code. Short methods and simple classes.
Here's the GUI I came up with.
Here's the GUI after I clicked a couple of letter JButtons
Explanation
Oracle has a nifty tutorial, Creating a GUI With JFC/Swing, which will show you how to create a Swing GUI. Skip the Netbeans section.
Your code was missing a main method, so I added one. I started the Swing application with a call to the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.
The first thing I did was create a PuzzleModel class to hold the boolean array. It's a good idea to separate your model from your view and controller classes. This pattern is the model / view / controller (MVC) pattern.
A Swing JFrame can contain many JPanels. I created a segment JPanel to hold a JLabel and a JButton aligned vertically. I used the GridBagLayout to align the JLabel and JButton. Swing layout managers help you to avoid absolute positioning and the problems that come with absolute positioning.
I created a main JPanel to hold 8 segment JPanels. These JPanels are aligned with a FlowLayout.
As you can see, my JFrame is smaller than yours. You create as small a JFrame as possible. If the user wants to make it bigger, that's what the rectangle in the upper right corner is for.
Swing is meant to be designed from the inside out. You don't specify a JFrame size and try to make the components fit. You create the components and let Swing determine the size of the JFrame. If you want the JFrame I created to be bigger, increase the font sizes. Hint: A fraction or multiple of 72 points looks better on most displays.
I created two ActionListener classes, one for the alphabet JButtons and one for the lever JButton. This makes it easier to focus on the alphabet JButtons. All you have to do in the ActionListener is swap the appropriate isVertical boolean values when each JButton is left-clicked. I just flipped the corresponding boolean as a demonstration.
Code
Here's the complete runnable code.
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class PuzzleGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new PuzzleGUI());
}
private JLabel[] leverLabel;
private final PuzzleModel model;
public PuzzleGUI() {
this.model = new PuzzleModel();
}
#Override
public void run() {
JFrame frame = new JFrame("Weird Lever");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createMainPanel(), BorderLayout.CENTER);
frame.add(createButtonPanel(), BorderLayout.AFTER_LAST_LINE);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
System.out.println(frame.getSize());
}
private JPanel createMainPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
char c = 'A';
boolean[] isVertical = model.getIsVertical();
leverLabel = new JLabel[isVertical.length];
for (int i = 0; i < isVertical.length; i++) {
String labelText = (isVertical[i]) ? "|" : "-";
panel.add(createLeverButtonPanel(labelText, Character.toString(c), i));
c = (char) (((int) c) + 1);
}
return panel;
}
public void updateMainPanel() {
boolean[] isVertical = model.getIsVertical();
for (int i = 0; i < isVertical.length; i++) {
String labelText = (isVertical[i]) ? "|" : "-";
leverLabel[i].setText(labelText);
}
}
private JPanel createLeverButtonPanel(String labelText, String buttonText, int index) {
JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
Font font1 = new Font("Arial Black", Font.PLAIN, 144);
Font font2 = new Font("Arial Black", Font.PLAIN, 72);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
leverLabel[index] = new JLabel(labelText);
leverLabel[index].setFont(font1);
panel.add(leverLabel[index], gbc);
gbc.gridy++;
JButton button = new JButton(buttonText);
button.addActionListener(new AlphabetButtonListener());
button.setFont(font2);
panel.add(button, gbc);
return panel;
}
private JPanel createButtonPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
Font font2 = new Font("Arial Black", Font.PLAIN, 48);
JButton button = new JButton("Weird Lever");
button.addActionListener(new LeverButtonListener());
button.setFont(font2);
panel.add(button);
return panel;
}
public class AlphabetButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent event) {
JButton button = (JButton) event.getSource();
String text = button.getText();
char c = text.charAt(0);
int index = ((int) c - 'A');
model.swap(index);
updateMainPanel();
}
}
public class LeverButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent event) {
// TODO Auto-generated method stub
}
}
public class PuzzleModel {
private boolean[] isVertical;
public PuzzleModel() {
int doors = 8;
this.isVertical = new boolean[doors];
reset();
}
private void reset() {
Arrays.fill(isVertical, true);
}
public void swap(int index) {
isVertical[index] = !isVertical[index];
}
public boolean[] getIsVertical() {
return isVertical;
}
}
}
being new to programming, i'm having a slight issue resizing the text fields I've added to the JPanel. While I could go the route of creating individual panels with their own text field, I though it would be better to add all the components into one panel. Part of my overall idea is to have my add button reference the panel, containing the text fields, to add more text fields on the tracker for users to fill out, and while I can get the fields to display when I implement the setBounds method on my panel object, i'm having tough time figuring out how resize them in the panel itself. And if you have any other advice on my overall structure, I welcome it.
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Label;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class UI {
JFrame frame;
JLabel Title,Name, CheckOut, CheckIn, Email;
JTextField NameField,CheckOutField, CheckInField, EmailField;
JButton Add, Delete;
JPanel buttons, textfields, primary;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UI window = new UI();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public UI(){
design();
}
private void design(){
frame = new JFrame("Form 48 Tracker");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(100, 100, 750, 400);
frame.getContentPane().setLayout(null);
Title = new JLabel();
Title.setText("Form 48 Tracker");
Title.setFont(new Font("Calibri", Font.PLAIN, 28));
Title.setBounds(233, 11, 200, 75);
frame.getContentPane().add(Title);
Title.setForeground(Color.BLACK);
Name = new JLabel();
Name.setText("Name");
Name.setFont(new Font("Calibri", Font.PLAIN, 15));
Name.setBounds(50, 80, 128, 20);
frame.getContentPane().add(Name);
Name.setForeground(Color.BLACK);
CheckOut = new JLabel();
CheckOut.setText("Check Out Date");
CheckOut.setFont(new Font("Calibri", Font.PLAIN, 15));
CheckOut.setBounds(200, 80, 128, 20);
frame.getContentPane().add(CheckOut);
CheckOut.setForeground(Color.BLACK);
CheckIn = new JLabel();
CheckIn.setText("Check In Date");
CheckIn.setFont(new Font("Calibri", Font.PLAIN, 15));
CheckIn.setBounds(350, 80, 128, 20);
frame.getContentPane().add(CheckIn);
CheckIn.setForeground(Color.BLACK);
Email = new JLabel();
Email.setText("Email");
Email.setFont(new Font("Calibri", Font.PLAIN, 15));
Email.setBounds(500, 80, 128, 20);
frame.getContentPane().add(Email);
Email.setForeground(Color.BLACK);
Add = new JButton("Add");
buttons = new JPanel();
buttons.add(Add);
buttons.setBounds(200, 270, 157, 50); //x , y , width , height//
frame.getContentPane().add(buttons);
Add.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
}
});
Delete = new JButton("Delete");
buttons = new JPanel();
buttons.add(Delete);
buttons.setBounds(605, 101, 128, 50);
frame.getContentPane().add(buttons);
primary = new JPanel();
NameField = new JTextField();
CheckOutField = new JTextField();
CheckInField = new JTextField();
EmailField = new JTextField();
primary.add(NameField);
primary.add(CheckOutField);
primary.add(CheckInField);
primary.add(EmailField);
primary.setBounds(50, 110, 128, 20);
frame.getContentPane().add(primary);
}
}
Let's concentrate on the code that's causing the problem, and only that code. I've created a minimal example program, one that has enough code to compile and run, and that demonstrates the problem, but that has no unnecessary code:
import javax.swing.*;
public class UiFoo {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Foo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null); // **** no!! ****
JPanel primary = new JPanel();
JTextField NameField = new JTextField();
JTextField CheckOutField = new JTextField();
JTextField CheckInField = new JTextField();
JTextField EmailField = new JTextField();
primary.add(NameField);
primary.add(CheckOutField);
primary.add(CheckInField);
primary.add(EmailField);
primary.setBounds(50, 110, 128, 20);
frame.getContentPane().add(primary);
frame.setSize(600, 250);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
So, if you run this code, you'll see 4 very small JTextFields. Why are they so small? Because you've not set the columns property for the JTextFields, and so they default to columns size 0 and show up like so:
So it's better if you can give the JTextField a columns property so that they have some width, e.g., make this change:
JPanel primary = new JPanel();
int columns = 8;
JTextField NameField = new JTextField(columns);
JTextField CheckOutField = new JTextField(columns);
JTextField CheckInField = new JTextField(columns);
JTextField EmailField = new JTextField();
But this just shows one JTextField, and cuts off the bottom as well:
Why? Because you're artificially constraining the size of the containing JPanel, primary via:
primary.setBounds(50, 110, 128, 20);
This containing JPanel is only 128 pixels wide by 20 pixels high, meaning that it won't even display one JTextField well.
One solution is to use a mix of layout managers and JPanels as well as a JScrollPane to allow for a grid of JPanels to be added, something like so (try it out!):
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.*;
#SuppressWarnings("serial")
public class UiFoo2 extends JPanel {
JPanel singleColumnPanel = new JPanel(new GridLayout(0, 1, 2, 2));
public UiFoo2() {
JButton addButton = new JButton("Add");
addButton.addActionListener(e -> {
JPanel rowPanel = new JPanel(new GridLayout(1, 4, 2, 2));
for (int i = 0; i < 4; i++) {
rowPanel.add(new JTextField(8));
}
singleColumnPanel.add(rowPanel);
singleColumnPanel.revalidate();
singleColumnPanel.repaint();
});
JPanel buttonPanel = new JPanel();
buttonPanel.add(addButton);
JPanel labelPanel = new JPanel(new GridLayout(1, 4, 2, 2));
labelPanel.add(new JLabel("Name", SwingConstants.CENTER));
labelPanel.add(new JLabel("Check Out Date", SwingConstants.CENTER));
labelPanel.add(new JLabel("Check In Date", SwingConstants.CENTER));
labelPanel.add(new JLabel("Email", SwingConstants.CENTER));
singleColumnPanel.add(labelPanel);
JPanel containerPanel = new JPanel(new BorderLayout(5, 5));
containerPanel.add(singleColumnPanel, BorderLayout.PAGE_START);
JScrollPane scrollPane = new JScrollPane(containerPanel);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
setPreferredSize(new Dimension(650, 400));
setLayout(new BorderLayout());
add(scrollPane, BorderLayout.CENTER);
add(buttonPanel, BorderLayout.PAGE_END);
}
private static void createAndShowGui() {
UiFoo2 mainPanel = new UiFoo2();
JFrame frame = new JFrame("UiFoo2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
This will create a row JPanel that holds four JTextFields that get added to the JScrollPane when the add JButton is pressed, and looks like so:
but we still can do better. Why not instead create a class to hold a row of data, something like so:
import java.util.Date;
public class Form48Customer {
private String name;
private Date checkIn;
private Date checkOut;
private String Email;
public Form48Customer(String name, Date checkIn, Date checkOut, String email) {
this.name = name;
this.checkIn = checkIn;
this.checkOut = checkOut;
Email = email;
}
public Date getCheckIn() {
return checkIn;
}
public void setCheckIn(Date checkIn) {
this.checkIn = checkIn;
}
public Date getCheckOut() {
return checkOut;
}
public void setCheckOut(Date checkOut) {
this.checkOut = checkOut;
}
public String getName() {
return name;
}
public String getEmail() {
return Email;
}
// should override hashCode and equals here
}
and then create a JTable complete with custom model to display objects of this type, and then display them in the GUI. This is much cleaner, more flexible, and extendable. Something like so:
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.*;
import java.util.Date;
import javax.swing.*;
import javax.swing.JFormattedTextField.AbstractFormatter;
import javax.swing.table.*;
#SuppressWarnings("serial")
public class Form48TrackerPanel extends JPanel {
public static final String TITLE = "Form 48 Tracker";
private static final String DATE_FORMAT_TXT = "MM/dd/yyyy";
private Form48TableModel model = new Form48TableModel();
private JTable table = new JTable(model);
private JButton addButton = new JButton("Add");
private JButton deleteButton = new JButton("Delete");
private JButton exitButton = new JButton("Exit");
public Form48TrackerPanel() {
final SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_TXT);
TableCellRenderer dateRenderer = new DefaultTableCellRenderer() {
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
if( value instanceof Date) {
value = dateFormat.format(value);
}
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
}
};
table.getColumnModel().getColumn(1).setCellRenderer(dateRenderer);
table.getColumnModel().getColumn(2).setCellRenderer(dateRenderer);
JLabel titleLabel = new JLabel(TITLE, SwingConstants.CENTER);
titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD, 28f));
addButton.addActionListener(new AddListener());
addButton.setMnemonic(KeyEvent.VK_A);
deleteButton.addActionListener(new DeleteListener());
deleteButton.setMnemonic(KeyEvent.VK_D);
exitButton.addActionListener(new ExitListener());
exitButton.setMnemonic(KeyEvent.VK_X);
JPanel buttonPanel = new JPanel(new GridLayout(1, 0, 5, 0));
buttonPanel.add(addButton);
buttonPanel.add(deleteButton);
buttonPanel.add(exitButton);
setPreferredSize(new Dimension(800, 500));
int ebGap = 8;
setBorder(BorderFactory.createEmptyBorder(ebGap, ebGap, ebGap, ebGap));
setLayout(new BorderLayout(ebGap, ebGap));
add(titleLabel, BorderLayout.PAGE_START);
add(new JScrollPane(table), BorderLayout.CENTER);
add(buttonPanel, BorderLayout.PAGE_END);
model.addRow(new Form48Customer("John Smith", new Date(), new Date(), "JSmith#Yahoo.com"));
model.addRow(new Form48Customer("Fred Flinstone", new Date(), new Date(), "FFlinstone#GMail.com"));
}
private class AddListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
AddForm48Panel addFormPanel = new AddForm48Panel();
int result = JOptionPane.showConfirmDialog(Form48TrackerPanel.this,
addFormPanel, "Add Customer", JOptionPane.OK_CANCEL_OPTION,
JOptionPane.PLAIN_MESSAGE);
if (result == JOptionPane.OK_OPTION) {
Form48Customer customer = addFormPanel.getForm48Customer();
model.addRow(customer);
}
}
}
private class DeleteListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
// TODO *** finish this code ***
}
}
private class ExitListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
Window win = SwingUtilities.getWindowAncestor(Form48TrackerPanel.this);
win.dispose();
}
}
private static void createAndShowGui() {
Form48TrackerPanel mainPanel = new Form48TrackerPanel();
JFrame frame = new JFrame(TITLE);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
#SuppressWarnings("serial")
class AddForm48Panel extends JPanel {
private static final int TFIELD_COLS = 10;
private static final String DATE_FORMAT_TXT = "MM/dd/yyyy";
private static final Format DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT_TXT);
private static final Insets INSETS = new Insets(5, 5, 5, 5);
private JTextField nameField = new JTextField(TFIELD_COLS);
private JFormattedTextField checkOutDateField = new JFormattedTextField(DATE_FORMAT);
private JFormattedTextField checkInDateField = new JFormattedTextField(DATE_FORMAT);
private JTextField emailField = new JTextField(TFIELD_COLS);
private JComponent[] fields = {nameField, checkOutDateField, checkInDateField, emailField};
private String[] labels = {"Name", "Check Out Date", "Check In Date", "Email"};
public AddForm48Panel() {
InputVerifier verifier = new DateFieldVerifier();
checkInDateField.setInputVerifier(verifier);
checkOutDateField.setInputVerifier(verifier);
setLayout(new GridBagLayout());
for (int i = 0; i < fields.length; i++) {
add(new JLabel(labels[i] + ":"), createGbc(0, i));
add(fields[i], createGbc(1, i));
}
}
public String getName() {
return nameField.getText();
}
public String getEmail() {
return emailField.getText();
}
public Date getCheckOut() {
return (Date) checkOutDateField.getValue();
}
public Date getCheckIn() {
return (Date) checkInDateField.getValue();
}
public Form48Customer getForm48Customer() {
String name = getName();
Date checkOut = getCheckOut();
Date checkIn = getCheckIn();
String email = getEmail();
return new Form48Customer(name, checkIn, checkOut, email);
}
private GridBagConstraints createGbc(int x, int y) {
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = x;
gbc.gridy = y;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbc.insets = INSETS;
gbc.anchor = x == 0 ? GridBagConstraints.WEST :GridBagConstraints.EAST;
gbc.fill = GridBagConstraints.HORIZONTAL;
return gbc;
}
private class DateFieldVerifier extends InputVerifier {
#Override
public boolean verify(JComponent input) {
if (input instanceof JFormattedTextField) {
JFormattedTextField ftf = (JFormattedTextField)input;
AbstractFormatter formatter = ftf.getFormatter();
if (formatter != null) {
String text = ftf.getText();
try {
formatter.stringToValue(text);
return true;
} catch (ParseException pe) {
return false;
}
}
}
return true;
}
#Override
public boolean shouldYieldFocus(JComponent input) {
boolean verify = verify(input);
if (!verify) {
String message = "Enter a valid date, e.g.: 01/05/2017";
String title = "Invalid Date Format";
int type = JOptionPane.ERROR_MESSAGE;
JOptionPane.showMessageDialog(input, message, title, type);
}
return verify;
}
}
}
#SuppressWarnings("serial")
class Form48TableModel extends DefaultTableModel {
private static final String[] COL_NAMES = {"Name", "Check Out Date", "Check In Date", "Email"};
public Form48TableModel() {
super(COL_NAMES, 0);
}
public void addRow(Form48Customer customer) {
Object[] rowData = {
customer.getName(),
customer.getCheckOut(),
customer.getCheckIn(),
customer.getEmail()
};
addRow(rowData);
}
public Form48Customer getRow(int row) {
String name = (String) getValueAt(row, 0);
Date checkIn = (Date) getValueAt(row, 1);
Date checkOut = (Date) getValueAt(row, 2);
String email = (String) getValueAt(row, 3);
return new Form48Customer(name, checkIn, checkOut, email);
}
#Override
public Class<?> getColumnClass(int columnIndex) {
switch (columnIndex) {
case 1:
return Date.class;
case 2:
return Date.class;
default:
break;
}
return super.getColumnClass(columnIndex);
}
}
Which would look like:
There is 3 panels which I created as seen in the image. The first panel is the "From" panel, second is "To" panel, and third is the buttons panel. So the question is, how can I put a new line for the "Enter Temperature: [ ]" so that it will be under neath the radio buttons? I am very new to Java swing/awt please bear with me.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import java.awt.*;
public class TemperatureConversion extends JFrame{
// component
JTextField txtFromTemp, txtToTemp;
JLabel lblFromTemp, lblToTemp;
JRadioButton radFromCelsius, radFromFahrenheit, radFromKelvin;
JRadioButton radToCelsius, radToFahrenheit, radToKelvin;
JPanel pnlFromRadioButton, pnlToRadioButton, pnlFromTemp, pnlButton;
ButtonGroup bgFrom, bgTo;
JButton btnConvert, btnExit;
// constructor
public TemperatureConversion(){
super("Temperature");
// assign objects
radFromCelsius = new JRadioButton("Celsius", true);
radFromFahrenheit = new JRadioButton("Fahrenheit");
radFromKelvin = new JRadioButton("Kelvin");
lblFromTemp = new JLabel("Enter Temperature: ");
pnlFromTemp = new JPanel();
btnConvert = new JButton("Convert");
btnExit = new JButton("Exit");
pnlButton = new JPanel();
txtFromTemp = new JTextField(3);
lblToTemp = new JLabel("Comparable Temperature: ");
txtToTemp = new JTextField(3);
// register the button to a listener
btnExit.addActionListener(new MyButtonListener());
btnConvert.addActionListener(new MyButtonListener());
// make the multiple choice exclusive but not a container
bgFrom = new ButtonGroup();
bgFrom.add(radFromCelsius);
bgFrom.add(radFromFahrenheit);
bgFrom.add(radFromKelvin);
// radio buttons
radToCelsius = new JRadioButton("Celsius");
radToFahrenheit = new JRadioButton("Fahrenheit", true);
radToKelvin = new JRadioButton("Kelvin");
// make the multiple choice exclusive
bgTo = new ButtonGroup();
bgTo.add(radToCelsius);
bgTo.add(radToFahrenheit);
bgTo.add(radToKelvin);
pnlFromRadioButton = new JPanel();
pnlToRadioButton = new JPanel();
// decorate the panel
pnlFromRadioButton.setBorder(BorderFactory.createTitledBorder("From"));
pnlToRadioButton.setBorder(BorderFactory.createTitledBorder("To"));
// add radiobutton to panel
pnlFromRadioButton.add(radFromCelsius);
pnlFromRadioButton.add(radFromFahrenheit);
pnlFromRadioButton.add(radFromKelvin);
pnlToRadioButton.add(radToCelsius);
pnlToRadioButton.add(radToFahrenheit);
pnlToRadioButton.add(radToKelvin);
// add button to panel
pnlButton.add(btnConvert);
pnlButton.add(btnExit);
// add label and txt field to panel
pnlFromRadioButton.add(lblFromTemp);
pnlFromRadioButton.add(txtFromTemp);
pnlToRadioButton.add(lblToTemp);
txtToTemp.setEditable(false);
pnlToRadioButton.add(txtToTemp);
// add panels to the frame
add(pnlFromRadioButton, BorderLayout.NORTH);
add(pnlToRadioButton, BorderLayout.CENTER);
add(pnlButton, BorderLayout.SOUTH);
setVisible(true);
setSize(400, 300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
}
// private inner class to handle button event
private class MyButtonListener implements ActionListener {
// must override actionPerformed method
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == btnConvert) {
if (radFromCelsius.isSelected())
System.out.print("exit");
} else if (e.getSource() == btnExit) {
System.exit(0);
}
}
}
public static void main(String[] args) {
new TemperatureConversion();
}
}
Nest more JPanels and use layout managers
For instance, in the JPanel where you want two lines, give it a BoxLayout oriented along the BoxLayout.PAGE_AXIS, and then add two more JPanels to this BoxLayout-using, a top JPanel with the radio buttons and bottom JPanel with the JLabel and JTextField (or whatever else you want in it).
Side note: this would be a great place to use an enum one called TempScale that had three values: CELSIUS, FAHRENHEIT, KELVIN. You could even give the enum the formulas for conversion to and from Kelvin.
For example:
import java.awt.Component;
import java.awt.event.*;
import java.util.HashMap;
import java.util.Map;
import javax.swing.*;
#SuppressWarnings("serial")
public class TempConversion2 extends JPanel {
private ToFromPanel fromPanel = new ToFromPanel("From", true);
private ToFromPanel toPanel = new ToFromPanel("To", false);
private ButtonPanel buttonPanel = new ButtonPanel(fromPanel, toPanel);
public TempConversion2() {
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
add(fromPanel);
add(toPanel);
add(buttonPanel);
}
private static void createAndShowGui() {
TempConversion2 mainPanel = new TempConversion2();
JFrame frame = new JFrame("Temp Convert");
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());
}
}
#SuppressWarnings("serial")
class ButtonPanel extends JPanel {
public ButtonPanel(ToFromPanel fromPanel, ToFromPanel toPanel) {
add(new JButton(new ConvertAction("Convert", fromPanel, toPanel)));
add(new JButton(new ExitAction("Exit", KeyEvent.VK_X)));
}
}
#SuppressWarnings("serial")
class ConvertAction extends AbstractAction {
private ToFromPanel fromPanel;
private ToFromPanel toPanel;
public ConvertAction(String name, ToFromPanel fromPanel, ToFromPanel toPanel) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
this.fromPanel = fromPanel;
this.toPanel = toPanel;
}
#Override
public void actionPerformed(ActionEvent e) {
String text = fromPanel.getText();
try {
double fromTemp = Double.parseDouble(text.trim());
TempScale fromScale = fromPanel.getTempScalesPanel().getSelectedTempScale();
double kelvinValue = fromScale.convertToKelvin(fromTemp);
TempScale toScale = toPanel.getTempScalesPanel().getSelectedTempScale();
double toValue = toScale.convertFromKelvin(kelvinValue);
String toValueString = String.format("%.2f", toValue);
toPanel.setText(toValueString);
} catch (NumberFormatException e1) {
Component parentComponent = fromPanel;
String message = "Text must be a valid number: " + text;
String title = "Invalid Text Entered";
int messageType = JOptionPane.ERROR_MESSAGE;
JOptionPane.showMessageDialog(parentComponent, message, title, messageType);
fromPanel.setText("");
}
}
}
#SuppressWarnings("serial")
class ExitAction extends AbstractAction {
public ExitAction(String name, int mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
}
#SuppressWarnings("serial")
class ToFromPanel extends JPanel {
private String title;
private TempScalesPanel tempScalesPanel = new TempScalesPanel();
private JTextField tempTextField = new JTextField(3);
public ToFromPanel(String title, boolean textFieldEnabled) {
this.title = title;
tempTextField.setFocusable(textFieldEnabled);
JPanel bottomPanel = new JPanel();
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.LINE_AXIS));
bottomPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
bottomPanel.add(new JLabel("Temperature:"));
bottomPanel.add(Box.createHorizontalStrut(8));
bottomPanel.add(tempTextField);
setBorder(BorderFactory.createTitledBorder(title));
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
add(tempScalesPanel);
add(bottomPanel);
}
public String getTitle() {
return title;
}
public TempScalesPanel getTempScalesPanel() {
return tempScalesPanel;
}
public String getText() {
return tempTextField.getText();
}
public void setText(String text) {
tempTextField.setText(text);
}
}
#SuppressWarnings("serial")
class TempScalesPanel extends JPanel {
private ButtonGroup buttonGroup = new ButtonGroup();
private Map<ButtonModel, TempScale> buttonTempMap = new HashMap<>();
public TempScalesPanel() {
for (TempScale tempScale : TempScale.values()) {
JRadioButton radioButton = new JRadioButton(tempScale.getName());
add(radioButton);
buttonGroup.add(radioButton);
buttonTempMap.put(radioButton.getModel(), tempScale);
// set first button as selected by default
if (buttonGroup.getSelection() == null) {
buttonGroup.setSelected(radioButton.getModel(), true);
}
}
}
public TempScale getSelectedTempScale() {
ButtonModel model = buttonGroup.getSelection();
return buttonTempMap.get(model);
}
}
This is the enum that I was talking about. Note that if you change the enum, and for instance add another temperature scale element, the program will automatically include it in the GUI and in the calculations. God I love Java and OOP.
public enum TempScale {
CELSIUS("Celsius", 1.0, -273.15),
FAHRENHEIT("Fahrenheit", 5.0 / 9.0, -459.67),
KELVIN("Kelvin", 1.0, 0.0);
private TempScale(String name, double ratioToKelvin, double absZero) {
this.name = name;
this.ratioToKelvin = ratioToKelvin;
this.absZero = absZero;
}
private String name;
private double ratioToKelvin;
private double absZero;
public String getName() {
return name;
}
public double getRatioToKelvin() {
return ratioToKelvin;
}
public double getAbsZero() {
return absZero;
}
public double convertToKelvin(double value) {
return (value - absZero) * ratioToKelvin;
}
public double convertFromKelvin(double kelvinValue) {
return (kelvinValue / ratioToKelvin) + absZero;
}
}
Always consider posting an MCVE.
For example you layout can be simplified and demonstrated with :
import java.awt.BorderLayout;
import java.awt.Label;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class TemperatureConversion extends JFrame{
JPanel pnlFromRadioButton, pnlToRadioButton, pnlFromTemp, pnlButton;
// constructor
public TemperatureConversion(){
pnlFromRadioButton = new JPanel();
pnlFromRadioButton.add(new Label("From Panel"));
pnlToRadioButton = new JPanel();
pnlToRadioButton.add(new Label("To Panel"));
pnlButton = new JPanel();
pnlButton.add(new Label("Buttons Panel"));
// add panels to the frame
add(pnlFromRadioButton, BorderLayout.NORTH);
add(pnlToRadioButton, BorderLayout.CENTER);
add(pnlButton, BorderLayout.SOUTH);
setVisible(true);
setSize(400, 300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new TemperatureConversion();
}
}
Suppose you want to an "Enter Temperature: [ ]" label to show in a different "line" under From buttons, your constructor will change to :
public TemperatureConversion(){
//set a layout manger. You could use grid layout
//GridLayout gridLayout = new GridLayout(4, 1);
//Or BoxLayout
BoxLayout boxLayout = new BoxLayout(getContentPane(), BoxLayout.Y_AXIS); // top to bottom
setLayout(boxLayout);
pnlFromRadioButton = new JPanel();
pnlFromRadioButton.add(new Label("From Panel"));
//create a panel to hold the desired label
pnlFromTemp = new JPanel();
pnlFromTemp.add(new JLabel("Enter Temperature: [ ]"));//add label
pnlToRadioButton = new JPanel();
pnlToRadioButton.add(new Label("To Panel"));
pnlButton = new JPanel();
pnlButton.add(new Label("Buttons Panel"));
// add panels to the frame
//the panel will show in the order added
add(pnlFromRadioButton);
add(pnlFromTemp);
add(pnlToRadioButton);
add(pnlButton);
setVisible(true);
setSize(400, 300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
Sorry I am new to forums and am not familiar with posting etiquette for codes #c0der. But using GridLayout solved my problem and I want to show you what I did, it is a big mess but here it is. Here is my bizarre code but don't know how to reduce it any further. This is how it is suppose to look as I wanted and because now I understand what you mean by "Nest more JPanels and use layout managers" #Hovercraft Full of Eels:
my temperature program
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import java.awt.*;
public class TemperatureConversion extends JFrame {
// component
JTextField txtFromTemp, txtToTemp;
JLabel lblFromTemp, lblToTemp, lblToTempbox;
JRadioButton radFromCelsius, radFromFahrenheit, radFromKelvin;
JRadioButton radToCelsius, radToFahrenheit, radToKelvin;
JPanel pnlFromRadioButton, pnlToRadioButton, pnlFrom, pnlTo, pnlButton;
JPanel pnlEnterTemp, pnlComparableTemp;
ButtonGroup bgFrom, bgTo;
JButton btnConvert, btnExit;
// constructor
public TemperatureConversion() {
super("Temperature");
// assign objects
radFromCelsius = new JRadioButton("Celsius", true);
radFromFahrenheit = new JRadioButton("Fahrenheit");
radFromKelvin = new JRadioButton("Kelvin");
lblFromTemp = new JLabel("Enter Temperature: ");
pnlFrom = new JPanel();
btnConvert = new JButton("Convert");
btnExit = new JButton("Exit");
pnlButton = new JPanel();
txtFromTemp = new JTextField(3);
lblToTemp = new JLabel("Comparable Temperature: ");
txtToTemp = new JTextField(3);
pnlTo = new JPanel();
pnlEnterTemp = new JPanel();
pnlComparableTemp = new JPanel();
pnlFromRadioButton = new JPanel();
pnlToRadioButton = new JPanel();
// register the button to a listener
btnExit.addActionListener(new MyButtonListener());
btnConvert.addActionListener(new MyButtonListener());
// make the multiple choice exclusive but not a container
bgFrom = new ButtonGroup();
bgFrom.add(radFromCelsius);
bgFrom.add(radFromFahrenheit);
bgFrom.add(radFromKelvin);
// radio buttons
radToCelsius = new JRadioButton("Celsius");
radToFahrenheit = new JRadioButton("Fahrenheit", true);
radToKelvin = new JRadioButton("Kelvin");
// make the multiple choice exclusive
bgTo = new ButtonGroup();
bgTo.add(radToCelsius);
bgTo.add(radToFahrenheit);
bgTo.add(radToKelvin);
pnlFrom.setLayout(new GridLayout(2, 1));
pnlFrom.add(pnlFromRadioButton);
pnlFrom.add(pnlEnterTemp);
pnlTo.setLayout(new GridLayout(2, 1));
pnlTo.add(pnlToRadioButton);
pnlTo.add(pnlComparableTemp);
// decorate the panel
pnlFrom.setBorder(BorderFactory.createTitledBorder("From"));
pnlTo.setBorder(BorderFactory.createTitledBorder("To"));
// add radiobutton to panel
pnlFromRadioButton.add(radFromCelsius);
pnlFromRadioButton.add(radFromFahrenheit);
pnlFromRadioButton.add(radFromKelvin);
pnlToRadioButton.add(radToCelsius);
pnlToRadioButton.add(radToFahrenheit);
pnlToRadioButton.add(radToKelvin);
// add button to panel
pnlButton.add(btnConvert);
pnlButton.add(btnExit);
// add label and txt field to panel
pnlEnterTemp.add(lblFromTemp);
pnlEnterTemp.add(txtFromTemp);
pnlComparableTemp.add(lblToTemp);
txtToTemp.setEditable(false);
pnlComparableTemp.add(txtToTemp);
// add panels to the frame
add(pnlFrom, BorderLayout.NORTH);
add(pnlTo, BorderLayout.CENTER);
add(pnlButton, BorderLayout.SOUTH);
setVisible(true);
setSize(400, 300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
}
// private inner class to handle button event
private class MyButtonListener implements ActionListener {
// must override actionPerformed method
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == btnConvert) {
if (radFromCelsius.isSelected() && radToFahrenheit.isSelected()) {
int strInput = Integer.parseInt(txtFromTemp.getText());
int celsius = strInput * 9 / 5 + 32;
txtToTemp.setText(Integer.toString(celsius));
} else if (radFromCelsius.isSelected() && radToCelsius.isSelected() ||
radFromFahrenheit.isSelected() && radToFahrenheit.isSelected() ||
radFromKelvin.isSelected() && radToKelvin.isSelected()) {
txtToTemp.setText(txtFromTemp.getText());
} else if (radToCelsius.isSelected() && radFromFahrenheit.isSelected()) {
int strInput = Integer.parseInt(txtFromTemp.getText());
int fahrenheit = (strInput - 32) * 5 / 9;
txtToTemp.setText(Integer.toString(fahrenheit));
} else if (radFromKelvin.isSelected() && radToCelsius.isSelected()) {
double strInput = Integer.parseInt(txtFromTemp.getText());
double celsius = strInput - 273.15;
txtToTemp.setText(Double.toString(celsius));
} else if (radFromKelvin.isSelected() && radToFahrenheit.isSelected()) {
double strInput = Integer.parseInt(txtFromTemp.getText());
double fahrenheit = strInput - 459.67;
txtToTemp.setText(Double.toString(fahrenheit));
} else if (radFromCelsius.isSelected() && radToKelvin.isSelected()) {
double strInput = Integer.parseInt(txtFromTemp.getText());
double celsius = strInput + 273.15;
txtToTemp.setText(Double.toString(celsius));
} else if (radFromFahrenheit.isSelected() && radToKelvin.isSelected()) {
double strInput = Integer.parseInt(txtFromTemp.getText());
double fahrenheit = strInput + 255.37;
txtToTemp.setText(Double.toString(fahrenheit));
}
} else if (e.getSource() == btnExit) {
System.exit(0);
}
}
}
public static void main(String[] args) {
new TemperatureConversion();
}
}
By the way #Hovercraft Full Of Eels, your solution is way more efficient and advanced than my level of thinking. I am newbie to programming in general so bear with me on my messy code and organization lol. I have barely dipped my feet into OOP. I do have a sense of how you used enum for TempScale and I thank you for your suggestion. I will keep these in my notes as references.