My goal is to create a list with a length controlled by another component where each item's value can be edited.
My attempt uses an editable JComboBox that has a certain number of elements. In my code below, however, the selected index keeps changing to -1, which does not allow me to modify the item. Is there a way to select and edit an item using JComboBox?
//cb is a JComboBox with elements of type ComboItem. idx is defined elsewhere.
cb.addItemListener(new ItemListener() {
#SuppressWarnings("unchecked")
#Override
public void itemStateChanged(ItemEvent e) {
if(e.getStateChange() == ItemEvent.SELECTED)
idx = ((JComboBox<ComboItem>) e.getSource()).getSelectedIndex();
System.out.println("idx:"+idx);
}
});
//Pressing enter should commit changes.
cb.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() {
#Override
public void keyReleased(KeyEvent e) {
if(e.getKeyChar() == KeyEvent.VK_ENTER) {
String parse = ((JTextComponent) cb.getEditor().getEditorComponent()).getText();
parse = parse.substring(parse.lastIndexOf(":")+1).replaceAll("[^0-9]+", ""); //Processes edits.
cb.getItemAt(idx).change("Layer "+idx, Integer.parseInt(parse)); //This method should change the
System.out.println("selected item:"+cb.getSelectedItem()); // data for each item.
}
}
});
//Editing the text in the JComboBox and pressing the enter key should update the selected item.
JComboBox is not required, so feel free to suggest a different component if it is a better choice for this task.
After a day's worth of trial and error, I finally made a working solution. Instead of using a JComboBox, which was probably not designed to perform the desired task, I made a JScrollPane that adds a child JPanel every time a button is pressed. Each panel has a text field object that can be customized and a button to delete it. In my case, I added a DocumentFilter that allows positive < 5 digit integers.
I cannot figure out how to remove the spacing between the added panels before the scroll bar appears, so please comment a solution if you have one. Also, if there are any other improvements that can be made, please comment those suggestions as well.
Scroll Panel
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import javax.swing.JLabel;
#SuppressWarnings("serial")
public class TestScrollPane extends JPanel {
private int w,h;
private JPanel content;
private JScrollPane scroll;
private JButton add;
private JLabel getTextLabel;
private JButton getTextBtn;
/**
* Create the panel.
*/
public TestScrollPane(int width, int height) {
setLayout(null);
w = width; h = height;
scroll = new JScrollPane();
scroll.setBounds(0, 0, w, h);
scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
add(scroll);
content = new JPanel();
scroll.setViewportView(content);
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
add = new JButton("+");
add.setBounds(0, h, 89, 23);
add(add);
getTextLabel = new JLabel("");
getTextLabel.setBounds(10, 425, 215, 14);
add(getTextLabel);
getTextBtn = new JButton("Get Text");
getTextBtn.setBounds(225, 425, 215, 14);
getTextBtn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
String a[] = getText(),
b = "";
b += "[";
for(int i = 0; i < a.length - 1; i++)
b += a[i]+", ";
b += a[a.length-1]+"]";
System.out.println(b);
getTextLabel.setText(b);
}
});
add(getTextBtn);
add.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("adding "+content.getComponentCount());
content.add(new ScrollItem(content.getComponentCount()));
validate();
repaint();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(w, h);
}
public String[] getText() {
String out[] = new String[content.getComponentCount()],s;
for(int i = 0; i < out.length; i++)
out[i] = (s = ((ScrollItem) content.getComponent(i)).out) == null ? "0" : s;
return out;
}
private class ScrollItem extends JPanel {
private JTextField text;
private JButton del;
private int idx;
private String out;
public ScrollItem(int id) {
idx = id;
setBounds(0, idx*20, w-5, 20);
setLayout(null);
text = new JTextField();
text.setBounds(0, 0, (w-5)*3/4, 20);
text.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
out = text.getText();
}
});
AbstractDocument d = (AbstractDocument) text.getDocument();
d.setDocumentFilter(new TextFilter(4));
del = new JButton("X");
del.setBounds((w-5)*3/4, 0, (w-5)/4, 20);
del.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
content.remove(idx);
for(int i = idx; i < content.getComponentCount(); i++)
((ScrollItem) content.getComponent(i)).moveUp();
content.validate();
content.repaint();
System.out.println("Removed "+idx);
}
});
add(text);
add(del);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(w-5, 20);
}
public void moveUp() {
idx--;
content.validate();
content.repaint();
}
}
private class TextFilter extends DocumentFilter {
private int max;
public TextFilter(int maxChars) {
max = maxChars;
}
#Override
public void insertString(FilterBypass fb, int offs, String str, AttributeSet a) throws BadLocationException {
System.out.println("insert");
if ((fb.getDocument().getLength() + str.length()) <= max && str.matches("\\d+"))
super.insertString(fb, offs, str, a);
else
showError("Length: "+((fb.getDocument().getLength() + str.length()) <= max)+" | Text: "+str.matches("\\d+")+" | Str: "+str);
}
#Override
public void replace(FilterBypass fb, int offs, int length, String str, AttributeSet a) throws BadLocationException {
System.out.println("replace");
if ((fb.getDocument().getLength() + str.length() - length) <= max && str.matches("\\d+"))
super.replace(fb, offs, length, str, a);
else
showError("Length: "+((fb.getDocument().getLength() + str.length() - length) <= max)+" | Text: "+str.matches("\\d+")+" | Str: "+str);
}
private void showError(String cause) {
JOptionPane.showMessageDialog(null, cause);
}
}
}
Test Window
import java.awt.EventQueue;
import javax.swing.JFrame;
public class TestWindow {
private JFrame frame;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
TestWindow window = new TestWindow();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public TestWindow() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 435, 500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestScrollPane(430, 247));
}
}
Related
I wrote a program that creates a sudoku board according to the difficulty level chosen by the user. There's a basic GUI using JFrame and JPanel.
The board itself is built using a 2D array of JTextFields to allow for editing by the user and I made a table of JButtons representing digits 1-9.
I'm trying to make it so when I press a digit button while my cursor is on the relevant text field, it'll input that number to the field.
I think there's a problem with how I defined the buttons but would love a hand.
/*Java Program to solve Sudoku problem using Backtracking*/
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.*;
public class Solver extends Board {
Solver(int N, int K) {
super(N, K);
}
private static void createWindow() {
JFrame frame = new JFrame("Sudoku");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
createUI(frame);
frame.setSize(250, 80);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static void createUI(final JFrame frame) {
JPanel panel = new JPanel();
LayoutManager layout = new FlowLayout();
panel.setLayout(layout);
JButton button = new JButton("Play");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
String result = (String) JOptionPane.showInputDialog(
frame,
"Difficulty Glossary:\n\n Hard - 50/81 blank spaces\n Medium - 35/81 blank spaces\n Easy - 20/81 blank spaces\n\nChoose your desired difficulty:\n\tHard: 1\n\tMedium: 2\n\tEasy: 3\nIf your input doesn't match one of these digits, the board generated will be on easy mode.",
"Difficulty Glossary",
JOptionPane.PLAIN_MESSAGE,
null,
null,
"3"
);
optionBoard();
play(Integer.parseInt(result));
}
});
panel.add(button);
frame.getContentPane().add(panel, BorderLayout.CENTER);
}
public static void optionBoard(){
}
public static void play(int level) {
int N = 9, K = 0;
switch (level) {
case 1:
K = 50;
break;
case 2:
K = 35;
break;
default:
K = 20;
break;
}
Solver sudoku = new Solver(N, K);
sudoku.fillValues();
createBoard(sudoku.puzzle);
}
public static void createBoard(int[][] puzzle) {
final Border fieldBorder = BorderFactory.createLineBorder(Color.BLACK);
final JPanel grid = new JPanel(new GridLayout(9, 9));
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
final JTextField field = new JTextField(2);
if (puzzle[i][j] != 0) {
field.setText(puzzle[i][j] + "");
} else {
field.setText("");
}
field.setHorizontalAlignment(JTextField.CENTER); //Center text horizontally in the text field.
field.setBorder(fieldBorder); //Add the colored border.
grid.add(field);
}
}
final JPanel digits = new JPanel(new GridLayout(3, 3));
int num=1;
for (int i = 1; i < 4; i++) {
for (int j = 1; j < 4; j++) {
final JButton digit = new JButton(num+"");
num++;
digits.add(digit);
}
}
final JPanel centeredGrid = new JPanel(new GridBagLayout());
centeredGrid.add(digits);
centeredGrid.add(grid);
final JFrame frame = new JFrame("Sudoku Board");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(centeredGrid);
frame.setSize(400,400);
frame.setVisible(true);
JButton button = new JButton("Check");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
}
});
// centeredGrid.add(button);
}
// Driver code
public static void main(String[] args) {
createWindow();
}
}
I've only provided the relevant GUI class since the mathematical logic part of building the board is solid and works fine. That's what the Board class is.
One way to do this could be to:
Put the JTextFields in an ArrayList<JTextField> so that you can get them later as you will likely need access to them during the game
Give the class a field, private JTextField selectedField = null;, set initially to null. This will point to the last JTextField that has been clicked (the last one to gain focus)
Give the JTextFields FocusListeners when created. If any field gains focus, set the selectedField to refer to it
Or as per Rob Camick, use a TextAction as the button's ActionListener, which will track the last text component that holds the focus.
In your number JButton's ActionListener, check that selectedField is not null
If not null, then set the button's actionCommand (its text) to be the selectedField's text.
Also, if you need more help, consider creating and posting a valid MRE. Your current code does not compile for us.
Proof of concept (and example of a MRE):
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
public class Solver2 extends JPanel {
private static final float FONT_SIZE = 24f;
private int gridEdgeSize = 9;
private List<JTextField> fieldList = new ArrayList<>();
private JTextField selectedField = null;
public Solver2() {
JPanel gridPanel = new JPanel(new GridLayout(gridEdgeSize, gridEdgeSize, 2, 2));
gridPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2));
gridPanel.setBackground(Color.BLACK);
for (int i = 0; i < gridEdgeSize; i++) {
for (int j = 0; j < gridEdgeSize; j++) {
JTextField textField = new JTextField(2);
textField.setHorizontalAlignment(SwingConstants.CENTER);
textField.setFont(textField.getFont().deriveFont(Font.BOLD, FONT_SIZE));
textField.setBorder(null);
textField.addFocusListener(new TextFieldFocusListener());
gridPanel.add(textField);
fieldList.add(textField);
}
}
JPanel numberPanel = new JPanel(new GridLayout(0, 3));
for (int i = 1; i < 10; i++) {
String text = String.valueOf(i);
JButton button = new JButton(text);
button.setFont(button.getFont().deriveFont(Font.BOLD, FONT_SIZE));
button.addActionListener(e -> buttonListener(e));
numberPanel.add(button);
}
setLayout(new GridLayout(1, 2));
add(gridPanel);
add(numberPanel);
}
private void buttonListener(ActionEvent e) {
String text = e.getActionCommand();
if (selectedField != null) {
selectedField.setText(text);
}
}
private class TextFieldFocusListener extends FocusAdapter {
#Override
public void focusGained(FocusEvent e) {
selectedField = (JTextField) e.getComponent();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
Solver2 mainPanel = new Solver2();
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
Side Note
If you want to restrict the JTextField from allowing keyboard or copy/paste entry, then one way could be to use a DocumentFilter added to each JTextField's Document.
You could give the class a private boolean field, say,
public class Solver2 extends JPanel {
// .....
private boolean blockTextInput = true;
and a private nested class that extends DocumentFilter. This class will prevent the document from allowing String inserts, removals or replacements as long as blockTextInput is true:
private class MyDocFilter extends DocumentFilter {
#Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
throws BadLocationException {
if (blockTextInput) {
return;
}
super.insertString(fb, offset, string, attr);
}
#Override
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
if (blockTextInput) {
return;
}
super.remove(fb, offset, length);
}
#Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
throws BadLocationException {
if (blockTextInput) {
return;
}
super.replace(fb, offset, length, text, attrs);
}
}
then in the JTextField creation loop, set the filter:
for (int i = 0; i < gridEdgeSize; i++) {
for (int j = 0; j < gridEdgeSize; j++) {
JTextField textField = new JTextField(2);
textField.setHorizontalAlignment(SwingConstants.CENTER);
textField.setFont(textField.getFont().deriveFont(Font.BOLD, FONT_SIZE));
textField.setBorder(null);
textField.addFocusListener(new TextFieldFocusListener());
// ***** here ****
((PlainDocument) textField.getDocument()).setDocumentFilter(new MyDocFilter());
gridPanel.add(textField);
fieldList.add(textField);
}
}
Then change the number JButton's ActionListeners to change blockTextInput before and after setting the JTextField's text:
private void buttonListener(ActionEvent e) {
String text = e.getActionCommand();
if (selectedField != null) {
blockTextInput = false;
selectedField.setText(text);
blockTextInput = true;
}
}
Done
The latest iteration of the program that uses a single FocusListener and a DocumentFilter and that incorporates Rob Camick's suggestion to use TextAction for the button's ActionListener:
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;
import javax.swing.text.TextAction;
#SuppressWarnings("serial")
public class Solver3 extends JPanel {
private static final float FONT_SIZE = 24f;
private int gridEdgeSize = 9;
private List<JTextField> fieldList = new ArrayList<>();
private boolean blockTextInput = true;
private MyTextAction myTextAction = new MyTextAction("");
private JPanel outerGridPanel = new JPanel(new GridLayout(3, 3, 3, 3));
private JPanel[][] innerGridPanels = new JPanel[3][3];
public Solver3() {
outerGridPanel.setBackground(Color.BLACK);
outerGridPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 4));
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
innerGridPanels[i][j] = new JPanel(new GridLayout(3, 3, 1, 1));
innerGridPanels[i][j].setBackground(Color.BLACK);
outerGridPanel.add(innerGridPanels[i][j]);
}
}
for (int i = 0; i < gridEdgeSize; i++) {
for (int j = 0; j < gridEdgeSize; j++) {
JTextField textField = new JTextField(2);
textField.setHorizontalAlignment(SwingConstants.CENTER);
textField.setFont(textField.getFont().deriveFont(Font.BOLD, FONT_SIZE));
textField.setBorder(null);
((PlainDocument) textField.getDocument()).setDocumentFilter(new MyDocFilter());
innerGridPanels[i / 3][j / 3].add(textField);
fieldList.add(textField);
}
}
JPanel numberPanel = new JPanel(new GridLayout(0, 3));
for (int i = 1; i < 10; i++) {
String text = String.valueOf(i);
JButton button = new JButton(text);
button.setFont(button.getFont().deriveFont(Font.BOLD, 2 * FONT_SIZE));
button.addActionListener(myTextAction);
numberPanel.add(button);
}
setLayout(new GridLayout(1, 2));
add(outerGridPanel);
add(numberPanel);
}
private class MyTextAction extends TextAction {
public MyTextAction(String name) {
super(name);
}
#Override
public void actionPerformed(ActionEvent e) {
String text = e.getActionCommand();
JTextComponent selectedComponent = getFocusedComponent();
if (selectedComponent != null) {
blockTextInput = false;
selectedComponent.setText(text);
blockTextInput = true;
}
}
}
private class MyDocFilter extends DocumentFilter {
#Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
throws BadLocationException {
if (blockTextInput) {
return;
}
super.insertString(fb, offset, string, attr);
}
#Override
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
if (blockTextInput) {
return;
}
super.remove(fb, offset, length);
}
#Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
throws BadLocationException {
if (blockTextInput) {
return;
}
super.replace(fb, offset, length, text, attrs);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
Solver3 mainPanel = new Solver3();
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
I have a class called SuggestionField It will show the user a list of items to autocomplete his input. he let of inputs is displayed in a JDialog(suggestionFrame). I need to set the JDialogs position to be below its parent, but I can not get the parents (JTextField) X and Y relative to the screen.
Example of the issue
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.Arrays;
public class Runner {
public static void main(String[] args) {
JFrame frame = new JFrame();
String[] items = new String[]{"Tiger", "Wolf", "Car", "Cat", "Space", "Sing", "Scene"};
SuggestionField suggestionField = new SuggestionField(new ArrayList<>(Arrays.asList(items)), frame);
frame.setDefaultCloseOperation(3);
frame.setSize(100, 65);
frame.setLocationRelativeTo(null);
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(1,1));
panel.add(suggestionField);
frame.add(panel);
frame.setVisible(true);
}
public static class SuggestionField extends JTextField implements DocumentListener, KeyListener {
String[] values = new String[0];
ArrayList<String> displayValues = new ArrayList<>(0);
JDialog suggestionFrame;
JPanel suggestionPanel = new JPanel();
Color backGround = new Color(109, 104, 104, 133);
Color selectBackGround = new Color(109, 104, 104, 133);
Color textColor = new Color(5, 19, 88, 255);
Color selectTextColor = new Color(115, 134, 238, 255);
BoxLayout bl = new BoxLayout(suggestionPanel, BoxLayout.Y_AXIS);
int selectedEntry = 0;
public SuggestionField(ArrayList<String> values, JFrame parentDisplay) {
this.getDocument().addDocumentListener(this);
this.addKeyListener(this);
suggestionFrame = new JDialog(parentDisplay);
suggestionFrame.add(suggestionPanel);
suggestionFrame.setUndecorated(true);
suggestionFrame.setAlwaysOnTop(true);
suggestionFrame.setFocusable(false);
suggestionPanel.setFocusable(false);
bl.maximumLayoutSize(suggestionPanel);
this.values = values.toArray(this.values).clone();
}
public boolean updateSuggestions() {
boolean added = false;
boolean add;
displayValues.clear();
for (int i = 0;i < values.length;i++) {
add = true;
for (int k = 0;k < this.getText().length() && k < values[i].length();k++) {
if (values[i].toUpperCase().charAt(k) != this.getText().toUpperCase().charAt(k)) {
add = false;
break;
}
}
if (add && !values[i].equalsIgnoreCase(this.getText()) && values[i].length() > this.getText().length()) {
added = true;
displayValues.add(values[i]);
}
}
return added;
}
private void updateDisplay() {
suggestionFrame.setSize(this.getWidth(), 16 * displayValues.size());
suggestionPanel.removeAll();
suggestionPanel.setLayout(new BoxLayout(suggestionPanel, BoxLayout.Y_AXIS));
for (int i = 0;i < displayValues.size();i++) {
JLabel a = new JLabel(displayValues.get(i));
if (i == selectedEntry) {
a.setBackground(selectBackGround);
a.setForeground(selectTextColor);
} else {
a.setBackground(backGround);
a.setForeground(textColor);
}
suggestionPanel.add(a);
}
suggestionPanel.revalidate();
suggestionFrame.revalidate();
suggestionFrame.setLocation(this.getX(), this.getY() + this.getHeight());
suggestionFrame.setVisible(true);
}
#Override
public void insertUpdate(DocumentEvent e) {
if (updateSuggestions()) {
updateDisplay();
} else suggestionFrame.setVisible(false);
selectedEntry = 0;
this.requestFocus();
}
#Override
public void removeUpdate(DocumentEvent e) {
if (updateSuggestions()) {
updateDisplay();
} else suggestionFrame.setVisible(false);
selectedEntry = 0;
this.requestFocus();
}
#Override
public void changedUpdate(DocumentEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
int ID = e.getKeyCode();
switch (ID) {
case 40:
if (selectedEntry < displayValues.size() - 1) selectedEntry++;
updateDisplay();
break;
case 38:
if (selectedEntry > 0) selectedEntry--;
updateDisplay();
break;
case 10:
this.setText(displayValues.get(selectedEntry));
break;
case 27:
suggestionFrame.setVisible(false);
break;
}
this.requestFocus();
this.grabFocus();
}
#Override
public void keyReleased(KeyEvent e) {
}
}
}
Is there any way that inside the updateDisplay() (line 97) method I could get the absolute position of the JTextField for suggestionFrame
You can use SwingUtilities.convertPointToScreen to do this.
Just pass (x, y+height) and your input field to get the starting point to display your dropdown.
JButton component = new JButton();
Point pt = new Point(component.getLocation());
pt.y += component.getHeight();
SwingUtilities.convertPointToScreen(pt, component);
// pt is now in screen coords... so you can use it to position the pop-up
The full documentation can be found here: https://docs.oracle.com/javase/7/docs/api/javax/swing/SwingUtilities.html
I need to set the JDialogs position to be below its parent..
Use Window.setLocationRelativeTo(Component).
This will just place the JDialog ontop of the JTextFiled
Actually, it's more subtle than that. It will take into account if the dialog would otherwise be off the screen, for example, and move it back on. That is something that would need to be explicitly handled, if basing the position on the 'location on screen'.
I have implemented my own closeable JTabbedPane (essentially following advice from here - by extending JTabbedPane and overriding some methods and calling setTabComponentAt(...)). It works perfectly except one thing - when there are too many tabs to fit on one row (when there are 2 or more rows of tabs), the cross button/icon is not aligned to the right of the tab but it remains next to the tab title, which looks ugly. I've tried the demo from Java tutorials and it suffers from the same problem.
What I want is that the cross button/icon is always aligned to the very right, but the text is always aligned to the center. Can this be achieved by some layouting tricks? Note: I do not want to implement a custom TabbedPaneUI as this leads to other problems.
UPDATE I'm forced to use Java 6
The complete code is below, just run it and add 5 or more tabs.
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.URL;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
/**
* CloseableTabbedPane is a tabbed pane with a close icon on the right side of all tabs making it possible to close a tab.
* You can pass an instance of TabClosingListener to one of the constructors to react to tab closing.
*
* #author WiR
*/
public class CloseableTabbedPane extends JTabbedPane {
public static interface TabClosingListener {
/**
* #param aTabIndex the index of the tab that is about to be closed
* #return true if the tab can be really closed
*/
public boolean tabClosing(int aTabIndex);
/**
* #param aTabIndex the index of the tab that is about to be closed
* #return true if the tab should be selected before closing
*/
public boolean selectTabBeforeClosing(int aTabIndex);
}
private TabClosingListener tabClosingListener;
private String iconFileName = "images/cross.gif";
private String selectedIconFileName = "images/cross_selected.gif";
private static Icon CLOSING_ICON;
private static Icon CLOSING_ICON_SELECTED;
private class PaintedCrossIcon implements Icon {
int size = 10;
#Override
public void paintIcon(Component c, Graphics g, int x, int y) {
g.drawLine(x, y, x + size, y + size);
g.drawLine(x + size, y, x, y + size);
}
#Override
public int getIconWidth() {
return size;
}
#Override
public int getIconHeight() {
return size;
}
}
public CloseableTabbedPane() {
super();
}
public CloseableTabbedPane(TabClosingListener aTabClosingListener) {
super();
tabClosingListener = aTabClosingListener;
}
/**
* Sets the file name of the closing icon along with the optional variant of the icon when the mouse is over the icon.
*/
public void setClosingIconFileName(String aIconFileName, String aSelectedIconFileName) {
iconFileName = aIconFileName;
selectedIconFileName = aSelectedIconFileName;
}
/**
* Makes the close button at the specified indes visible or invisible
*/
public void setCloseButtonVisibleAt(int aIndex, boolean aVisible) {
CloseButtonTab cbt = (CloseButtonTab) getTabComponentAt(aIndex);
cbt.closingLabel.setVisible(aVisible);
}
#Override
public void insertTab(String title, Icon icon, Component component, String tip, int index) {
super.insertTab(title, icon, component, tip, index);
setTabComponentAt(index, new CloseButtonTab(component, title, icon));
}
#Override
public void setTitleAt(int index, String title) {
super.setTitleAt(index, title);
CloseButtonTab cbt = (CloseButtonTab) getTabComponentAt(index);
cbt.label.setText(title);
}
#Override
public void setIconAt(int index, Icon icon) {
super.setIconAt(index, icon);
CloseButtonTab cbt = (CloseButtonTab) getTabComponentAt(index);
cbt.label.setIcon(icon);
}
#Override
public void setComponentAt(int index, Component component) {
CloseButtonTab cbt = (CloseButtonTab) getTabComponentAt(index);
super.setComponentAt(index, component);
cbt.tab = component;
}
//note: setToolTipTextAt(int) must NOT be overridden !
private Icon getImageIcon(String aImageName) {
URL imageUrl = CloseableTabbedPane.class.getClassLoader().getResource(aImageName);
if (imageUrl == null) {
return new PaintedCrossIcon();
}
ImageIcon result = new ImageIcon(imageUrl);
if (result.getIconWidth() != -1) {
return result;
} else {
return null;
}
}
private class CloseButtonTab extends JPanel {
private Component tab;
private JLabel label;
private JLabel closingLabel;
public CloseButtonTab(Component aTab, String aTitle, Icon aIcon) {
tab = aTab;
setOpaque(false);
setLayout(new GridBagLayout());
setVisible(true);
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(0, 0, 0, 5);
label = new JLabel(aTitle);
label.setIcon(aIcon);
add(label, gbc);
if (CLOSING_ICON == null) {
CLOSING_ICON = getImageIcon(iconFileName);
CLOSING_ICON_SELECTED = getImageIcon(selectedIconFileName);
}
closingLabel = new JLabel(CLOSING_ICON);
closingLabel.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
JTabbedPane tabbedPane = (JTabbedPane) getParent().getParent();
int tabIndex = indexOfComponent(tab);
if (tabClosingListener != null) {
if (tabClosingListener.selectTabBeforeClosing(tabIndex)) {
tabbedPane.setSelectedIndex(tabIndex);
}
if (tabClosingListener.tabClosing(tabIndex)) {
tabbedPane.removeTabAt(tabIndex);
}
} else {
tabbedPane.removeTabAt(tabIndex);
}
}
#Override
public void mouseEntered(MouseEvent e) {
if (CLOSING_ICON_SELECTED != null) {
closingLabel.setIcon(CLOSING_ICON_SELECTED);
}
}
#Override
public void mouseExited(MouseEvent e) {
if (CLOSING_ICON_SELECTED != null) {
closingLabel.setIcon(CLOSING_ICON);
}
}
});
gbc.insets = new Insets(0, 0, 0, 0);
add(closingLabel, gbc);
}
}
static int count = 0;
/**
* For testing purposes.
*
*/
public static void main(String[] args) {
final JTabbedPane tabbedPane = new CloseableTabbedPane();
tabbedPane.addTab("test" + count, new JPanel());
count++;
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.add(tabbedPane, BorderLayout.CENTER);
JButton addButton = new JButton("Add tab");
addButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
tabbedPane.addTab("test" + count, new JPanel());
count++;
}
});
mainPanel.add(addButton, BorderLayout.SOUTH);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(700, 400);
frame.getContentPane().add(mainPanel);
frame.setVisible(true);
}
}
Here is one possible implementation using JLayer:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.plaf.*;
public class CloseableTabbedPaneTest {
public JComponent makeUI() {
UIManager.put("TabbedPane.tabInsets", new Insets(2, 2, 2, 50));
final JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.addTab("aaaaaaaaaaaaaaaa", new JPanel());
tabbedPane.addTab("bbbbbbbb", new JPanel());
tabbedPane.addTab("ccc", new JPanel());
JPanel p = new JPanel(new BorderLayout());
p.add(new JLayer<JTabbedPane>(tabbedPane, new CloseableTabbedPaneLayerUI()));
p.add(new JButton(new AbstractAction("add tab") {
#Override public void actionPerformed(ActionEvent e) {
tabbedPane.addTab("test", new JPanel());
}
}), BorderLayout.SOUTH);
return p;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new CloseableTabbedPaneTest().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class CloseableTabbedPaneLayerUI extends LayerUI<JTabbedPane> {
private final JPanel p = new JPanel();
private final Point pt = new Point(-100, -100);
private final JButton button = new JButton("x") {
#Override public Dimension getPreferredSize() {
return new Dimension(16, 16);
}
};
public CloseableTabbedPaneLayerUI() {
super();
button.setBorder(BorderFactory.createEmptyBorder());
button.setFocusPainted(false);
button.setBorderPainted(false);
button.setContentAreaFilled(false);
button.setRolloverEnabled(false);
}
#Override public void paint(Graphics g, JComponent c) {
super.paint(g, c);
if (c instanceof JLayer) {
JLayer jlayer = (JLayer) c;
JTabbedPane tabPane = (JTabbedPane) jlayer.getView();
for (int i = 0; i < tabPane.getTabCount(); i++) {
Rectangle rect = tabPane.getBoundsAt(i);
Dimension d = button.getPreferredSize();
int x = rect.x + rect.width - d.width - 2;
int y = rect.y + (rect.height - d.height) / 2;
Rectangle r = new Rectangle(x, y, d.width, d.height);
button.setForeground(r.contains(pt) ? Color.RED : Color.BLACK);
SwingUtilities.paintComponent(g, button, p, r);
}
}
}
#Override public void installUI(JComponent c) {
super.installUI(c);
((JLayer)c).setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
}
#Override public void uninstallUI(JComponent c) {
((JLayer)c).setLayerEventMask(0);
super.uninstallUI(c);
}
#Override protected void processMouseEvent(MouseEvent e, JLayer<? extends JTabbedPane> l) {
if (e.getID() == MouseEvent.MOUSE_CLICKED) {
pt.setLocation(e.getPoint());
JTabbedPane tabbedPane = (JTabbedPane) l.getView();
int index = tabbedPane.indexAtLocation(pt.x, pt.y);
if (index >= 0) {
Rectangle rect = tabbedPane.getBoundsAt(index);
Dimension d = button.getPreferredSize();
int x = rect.x + rect.width - d.width - 2;
int y = rect.y + (rect.height - d.height) / 2;
Rectangle r = new Rectangle(x, y, d.width, d.height);
if (r.contains(pt)) {
tabbedPane.removeTabAt(index);
}
}
l.getView().repaint();
}
}
#Override protected void processMouseMotionEvent(MouseEvent e, JLayer<? extends JTabbedPane> l) {
pt.setLocation(e.getPoint());
JTabbedPane tabbedPane = (JTabbedPane) l.getView();
int index = tabbedPane.indexAtLocation(pt.x, pt.y);
if (index >= 0) {
tabbedPane.repaint(tabbedPane.getBoundsAt(index));
} else {
tabbedPane.repaint();
}
}
}
Edit:
Here is an example using a GlassPane(Note: this is NOT tested at all):
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CloseableTabbedPaneTest2 {
public JComponent makeUI() {
UIManager.put("TabbedPane.tabInsets", new Insets(2, 2, 2, 50));
final JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.addTab("aaaaaaaaaaaaaaaa", new JPanel());
tabbedPane.addTab("bbbbbbbb", new JPanel());
tabbedPane.addTab("ccc", new JPanel());
JPanel p = new JPanel(new BorderLayout());
//p.setBorder(BorderFactory.createLineBorder(Color.RED, 10));
p.add(tabbedPane);
p.add(new JButton(new AbstractAction("add tab") {
#Override public void actionPerformed(ActionEvent e) {
tabbedPane.addTab("test", new JScrollPane(new JTree()));
}
}), BorderLayout.SOUTH);
EventQueue.invokeLater(new Runnable() {
#Override public void run() {
JPanel gp = new CloseableTabbedPaneGlassPane(tabbedPane);
tabbedPane.getRootPane().setGlassPane(gp);
gp.setOpaque(false);
gp.setVisible(true);
}
});
return p;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new CloseableTabbedPaneTest2().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class CloseableTabbedPaneGlassPane extends JPanel {
private final Point pt = new Point(-100, -100);
private final JButton button = new JButton("x") {
#Override public Dimension getPreferredSize() {
return new Dimension(16, 16);
}
};
private final JTabbedPane tabbedPane;
private final Rectangle buttonRect = new Rectangle(button.getPreferredSize());
public CloseableTabbedPaneGlassPane(JTabbedPane tabbedPane) {
super();
this.tabbedPane = tabbedPane;
MouseAdapter h = new Handler();
tabbedPane.addMouseListener(h);
tabbedPane.addMouseMotionListener(h);
button.setBorder(BorderFactory.createEmptyBorder());
button.setFocusPainted(false);
button.setBorderPainted(false);
button.setContentAreaFilled(false);
button.setRolloverEnabled(false);
}
#Override public void paintComponent(Graphics g) {
Point glassPt = SwingUtilities.convertPoint(tabbedPane, 0, 0, this);
for (int i = 0; i < tabbedPane.getTabCount(); i++) {
Rectangle tabRect = tabbedPane.getBoundsAt(i);
int x = tabRect.x + tabRect.width - buttonRect.width - 2;
int y = tabRect.y + (tabRect.height - buttonRect.height) / 2;
buttonRect.setLocation(x, y);
button.setForeground(buttonRect.contains(pt) ? Color.RED : Color.BLACK);
buttonRect.translate(glassPt.x, glassPt.y);
SwingUtilities.paintComponent(g, button, this, buttonRect);
}
}
class Handler extends MouseAdapter {
#Override public void mouseClicked(MouseEvent e) {
pt.setLocation(e.getPoint());
int index = tabbedPane.indexAtLocation(pt.x, pt.y);
if (index >= 0) {
Rectangle tabRect = tabbedPane.getBoundsAt(index);
int x = tabRect.x + tabRect.width - buttonRect.width - 2;
int y = tabRect.y + (tabRect.height - buttonRect.height) / 2;
buttonRect.setLocation(x, y);
if (buttonRect.contains(pt)) {
tabbedPane.removeTabAt(index);
}
}
tabbedPane.repaint();
}
#Override public void mouseMoved(MouseEvent e) {
pt.setLocation(e.getPoint());
int index = tabbedPane.indexAtLocation(pt.x, pt.y);
if (index >= 0) {
tabbedPane.repaint(tabbedPane.getBoundsAt(index));
} else {
tabbedPane.repaint();
}
}
}
}
I'm using this one: http://docs.oracle.com/javase/tutorial/uiswing/examples/components/TabComponentsDemoProject/src/components/ButtonTabComponent.java
The close button is painted by this itself so if can be placed anywhere.
I've created an Applet that creates a row of buttons, up to 15 buttons, when you push the "add to queue" button. I now want to decrement that row using a for loop. I want it to decrement from left to right. I can only get it to decrement from right to left. I know it has to do with the code in my "Remove" method, but I can't seem to figure out how to fix it. As a newbie I would appreciate any help you can provide.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Vector;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
public class Main extends javax.swing.JApplet {
private final int width = 60;
private final int height = 24;
private final int maxItems = 15;
private int x = 40 + width;
private int y = 260;
private int count = 1;
private JButton jAdd;
private JButton jRemove;
Vector<JButton> stack = new Vector<JButton>();
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
Main inst = new Main();
frame.getContentPane().add(inst);
((JComponent) frame.getContentPane()).setPreferredSize(inst
.getSize());
frame.pack();
frame.setVisible(true);
}
});
}
public Main() {
super();
initGUI();
}
private void initGUI() {
try {
this.setSize(719, 333);
getContentPane().setLayout(null);
{
jAdd = new JButton();
getContentPane().add(jAdd);
jAdd.setText("Add to Queue");
jAdd.setBounds(43, 300, 150, 24);
jAdd.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
jAddActionPerformed(evt);
}
});
}
{
jRemove = new JButton();
getContentPane().add(jRemove);
jRemove.setText("Remove from queue");
jRemove.setBounds(950, 300, 150, 24);
jRemove.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
jRemoveActionPerformed(evt);
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void jAddActionPerformed(ActionEvent evt) {
if (count > maxItems) {
JOptionPane.showMessageDialog(null, "The queue is full");
return;
}
JButton b = new JButton();
stack.add(0, b);
getContentPane().add(b);
int textCount = count;
b.setText("" +textCount++);
b.setBounds(x, y, width, height);
x = x + width;
count++;
}
private void jRemoveActionPerformed(ActionEvent evt) {
if (stack.isEmpty()) {
JOptionPane.showMessageDialog(null, "The queue is empty");
return;
}
JButton b = stack.remove(0);
this.remove(b);
for(int originalX = 880; originalX < 880; originalX--){
x = 880 - width;
}
repaint();
count--;
}
}
The issue is this:
stack.add(0, b);
You are always adding the new one to the start of the Vector (index 0). Remove that and you will see the behavior you want.
stack.add(b);
Is there a way to toggle a read-only mode so when you click any object in your window it simply returns what you clicked, ignoring the object's usual event handling? IE, while in this "read-only" mode, if you click on a Button, it simply returns the button, not actually pressing the button. Then I could do something like:
if ("thing pressed" == button) "do this";
else if ("thing pressed" == panel) "do that";
else "do nothing";
Here's my code, its a frame with 3 colored boxes. Clicking the 2nd box, the 3rd box, or the background will display a message. Clicking box 1 does nothing. I like using new mouse adapters so I want to do it this way.
Now what I want is when you click box 1, box 1 is treated as selected (if that helps you get the picture). Then if you click anywhere, including box 1 again, box 1 is deselected and nothing else (meaning that box 2, box 3. or the background's message will display). At that time, only if box 2 or 3 were clicked, they will still not display their normal message but a different message would be displayed.
I'm very sorry if I come off a little short.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Labels {
public static void main(String[] args) {
new Labels();
}
Square l1, l2, l3;
public Labels() {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
l1 = new Square();
l2 = new Square();
l3 = new Square();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(120, 150);
frame.setResizable(false);
panel.setVisible(true);
panel.setLayout(null);
l1.setLocation(5, 5);
l2.setLocation(5, 60);
l3.setLocation(60, 5);
l2.setColor("yellow");
l3.setColor("black");
l1.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
//do nothing
}
});
l2.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("Pushed label 2");
}
});
l3.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("Pushed label 3");
}
});
panel.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("pushed background");
}
});
frame.add(panel);
panel.add(l1);
panel.add(l2);
panel.add(l3);
}
class Square extends JLabel{
Color color = Color.blue;
public Square() {
// TODO Auto-generated constructor stub\
setVisible(true);
setSize(50,50);
}
public void paint(Graphics g) {
super.paint(g);
g.setColor(color);
g.fillRect(0, 0, 50, 50);
}
public void setColor(String color){
if (color == "white") this.color = Color.white;
else if (color == "black") this.color = Color.black;
else if (color == "yellow") this.color = Color.yellow;
else {
System.out.println("Invalid color");
return;
}
repaint();
}
}
}
Don't disable anything. Simply change the state of your class, perhaps by using a few boolean flag variables/fields and change these flags depending on what is pressed.
So have boolean fields called label1PressedLast, label2PressedLast, and label3PressedLast or something similar, and when a label is pressed, check the states of all other flags and have your program's behavior change depending on the state of these flags and the label that was just pressed. Then set all flags to false except for the one corresponding to the label that was just pressed.
For example, this little program reacts only if the first and then the third JLabel have been pressed:
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.*;
public class FlagEg extends JPanel {
private static final int LABEL_COUNT = 3;
private JLabel[] labels = new JLabel[LABEL_COUNT];
private boolean[] flags = new boolean[LABEL_COUNT];
public FlagEg() {
setLayout(new GridLayout(1, 0, 20, 0));
setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
// panel mouse listener
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent arg0) {
inactivateAll();
}
});
MouseListener labelsMouseListener = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent mouseEvt) {
myMousePressed(mouseEvt);
}
};
// create JLabels and add MouseListener
for (int i = 0; i < labels.length; i++) {
labels[i] = new JLabel("Label " + (i + 1));
labels[i].addMouseListener(labelsMouseListener);
labels[i].setOpaque(true);
labels[i].setBorder(BorderFactory.createLineBorder(Color.black));
add(labels[i]);
}
}
private void inactivateAll() {
for (int i = 0; i < labels.length; i++) {
labels[i].setBackground(null);
flags[i] = false;
}
}
private void myMousePressed(MouseEvent mouseEvt) {
JLabel label = (JLabel) mouseEvt.getSource();
// which label was pressed?
int index = -1;
for (int i = 0; i < labels.length; i++) {
if (label == labels[i]) {
index = i;
}
}
// check if first label and then third pressed:
if (flags[0] && index == 2) {
System.out.println("first and then third label pressed!");
}
// reset all labels and flags to initial state
inactivateAll();
// set pressed label background color and set flag of label just pressed
labels[index].setBackground(Color.pink);
flags[index] = true;
}
private static void createAndShowGui() {
FlagEg mainPanel = new FlagEg();
JFrame frame = new JFrame("Flag Example");
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(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Logic iteration two: only label 1 is the "primer" JLabel. This is actually easier to implement, because now you only need one boolean flag, that representing label 1 being pressed:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class FlagEg2 extends JPanel {
private static final int LABEL_COUNT = 3;
private JLabel[] labels = new JLabel[LABEL_COUNT];
private boolean label1Flag = false;
public FlagEg2() {
setLayout(new GridLayout(1, 0, 20, 0));
setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
// panel mouse listener
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent arg0) {
inactivateAll();
}
});
MouseListener labelsMouseListener = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent mouseEvt) {
myMousePressed(mouseEvt);
}
};
// create JLabels and add MouseListener
for (int i = 0; i < labels.length; i++) {
labels[i] = new JLabel("Label " + (i + 1));
labels[i].addMouseListener(labelsMouseListener);
labels[i].setOpaque(true);
labels[i].setBorder(BorderFactory.createLineBorder(Color.black));
add(labels[i]);
}
}
private void inactivateAll() {
for (int i = 0; i < labels.length; i++) {
labels[i].setBackground(null);
label1Flag = false;
}
}
private void myMousePressed(MouseEvent mouseEvt) {
JLabel label = (JLabel) mouseEvt.getSource();
// which label was pressed?
int index = -1;
for (int i = 0; i < labels.length; i++) {
if (label == labels[i]) {
index = i;
}
}
if (label1Flag) {
if (index == 1) {
System.out.println("Label 1 and label 2 pressed");
} else if (index == 2) {
System.out.println("Label 1 and label 3 pressed");
}
}
// reset all labels and flags to initial state
inactivateAll();
// if label1, then activate it
if (index == 0) {
labels[0].setBackground(Color.pink);
label1Flag = true;
}
}
private static void createAndShowGui() {
FlagEg2 mainPanel = new FlagEg2();
JFrame frame = new JFrame("Flag Example");
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(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
package javaapplication6;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
/**
*
* #author Jan Vorcak <vorcak#mail.muni.cz>
*/
public class Main {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
MouseListener listener = new MouseAdapter() {
private int count = 0;
#Override
public void mouseClicked(MouseEvent e) {
if(e.getComponent() instanceof JLabel) {
count++;
if (count >= 2) {
System.out.println("clicked 2 times on labels");
count = 0;
}
} else {
count = 0;
}
}
};
JFrame frame = new JFrame();
JPanel panel = new JPanel();
JLabel l1 = new JLabel("Label 1");
JLabel l2 = new JLabel("Label 2");
JLabel l3 = new JLabel("Label 3");
l1.addMouseListener(listener);
l2.addMouseListener(listener);
l3.addMouseListener(listener);
frame.addMouseListener(listener); // or panel.addMouseListener(listener);
panel.add(l1);
panel.add(l2);
panel.add(l3);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}
You could want to create a listener that do the job for using the putClientProperty method of JComponent.
public class JComponentClickCountListener extends MouseAdapter {
private final Integer ONE = 1;
#Override
public void mouseClicked(MouseEvent e) {
if (e.getComponent() instanceof JComponent) {
JComponent jComponent = (JComponent) e.getComponent();
Object property = jComponent.getClientProperty(JComponentClickCountListener.class);
if (property instanceof Number) {
property = ONE + ((Number) property).intValue();
}
else {
property = ONE;
}
jComponent.putClientProperty(JComponentClickCountListener.class, property);
}
}
}
Then in your code you can decide to have a single instace of that class for all of your components or create a new one each time.
This could give you the advantage of using the propertyChangeListener for future actions.
PS.
The code example do not represent all logic for OP question but i could by used as solid base. Later on i will try to update it. To cover that.
EDIT2:
I think that you should separate the logic, of selection and action over selected items. Then the task is divided into two tasks. First is the possibility to store the information about it state, clicked active, clicked again inactive. The second tasks it to operate on that status when a jComponent status was changed.
This is an simple example that i wrote, the functionality is to highlight the background of labels when the are selected and remove it when it was clicked again or the panel was clicked remove all selections.
This example is divided to three elements Enum, Iterface and class that manage the logic of selection
Enum - we store the possible statuses and a property key.
public enum JComponentActivationStatus {
NONE,
ACTIVE,
INACTIVE;
public static final String PROPERTY_KEY = JComponentActivationStatus.class.getCanonicalName();
}
Interface - provide a delegate for action to be taken when jcomponenet status change.
public abstract interface JComponenetActivationStatusChangeAction<T extends JComponent> {
public abstract void onActivation(T object);
public abstract void onDeactivation(T object);
}
Class - This class mange the status logic of jcomponents.
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent;
public class JComponenetActivationManager {
public static <T extends JComponent> T addMouseStatusControl(T jComponent) {
jComponent.addMouseListener(new JComponentMouseStatusModyfier());
return jComponent;
}
public static <T extends JComponent> T addActivationStatusChangeAction(T jComponenet, JComponenetActivationStatusChangeAction<T> statusChangeAction) {
jComponenet.addPropertyChangeListener(craeteJCompositeActivationStatusChangeListener(statusChangeAction));
return jComponenet;
}
public static <T extends JComponent> PropertyChangeListener craeteJCompositeActivationStatusChangeListener(JComponenetActivationStatusChangeAction<T> action) {
return new JComponentStatusPropertyChangeListener<T>(action);
}
/**
* Class that set the status for the JComponet after doubClicl
*/
private final static class JComponentMouseStatusModyfier extends MouseAdapter {
#Override
public void mouseClicked(MouseEvent e) {
if(e.getComponent() instanceof JComponent) {
JComponent jComponent = (JComponent) e.getComponent();
Object propertyValue = jComponent.getClientProperty(JComponentActivationStatus.PROPERTY_KEY);
if(JComponentActivationStatus.ACTIVE.equals(propertyValue)) { //We check that the ACTIVE status is already selected, if so we inactive.
propertyValue = JComponentActivationStatus.INACTIVE; //If so we inactive it.
} else {
propertyValue = JComponentActivationStatus.ACTIVE; // Otherwise we set it as active
}
jComponent.putClientProperty(JComponentActivationStatus.PROPERTY_KEY, propertyValue); // We use the property key form status
}
}
}
/**
* Help class that fire the actions after status is changed
*/
private static final class JComponentStatusPropertyChangeListener<T extends JComponent> implements PropertyChangeListener {
private final JComponenetActivationStatusChangeAction<T> statusChangeAction;
/**
*
*/
public JComponentStatusPropertyChangeListener(JComponenetActivationStatusChangeAction<T> statusChangeAction) {
if(statusChangeAction == null) {
throw new IllegalArgumentException("action can not be null at this point");
}
this.statusChangeAction = statusChangeAction;
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
if(JComponentActivationStatus.PROPERTY_KEY.equals(evt.getPropertyName())) {
if(JComponentActivationStatus.ACTIVE.equals(evt.getNewValue())) {
statusChangeAction.onActivation((T) evt.getSource());
}
if(JComponentActivationStatus.INACTIVE.equals(evt.getNewValue())){
statusChangeAction.onDeactivation((T) evt.getSource());
}
}
}
}
}
That class contain two public static method, that allow the developer to add the functionality to mange status to any jComponent object, add subscribe the action handler if any change occur.
At the end we have the main method that test our solution
public static void main(String[] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
JLabel l1 = new JLabel("Label 1");
JLabel l2 = new JLabel("Label 2");
JLabel l3 = new JLabel("Label 3");
panel.setBackground(Color.CYAN);
addMouseStatusControl(panel);
addMouseStatusControl(l1);
addMouseStatusControl(l2);
addMouseStatusControl(l3);
JComponenetActivationStatusChangeAction<JLabel> activeBackground = new JComponenetActivationStatusChangeAction<JLabel>() {
#Override
public void onActivation(JLabel object) {
object.setOpaque(true);
object.setBackground(Color.YELLOW);
}
#Override
public void onDeactivation(JLabel object) {
object.setOpaque(false);
object.setBackground(object.getParent().getBackground());
}
};
JComponenetActivationStatusChangeAction<JPanel> deactivateChildrens = new JComponenetActivationStatusChangeAction<JPanel>() {
#Override
public void onDeactivation(JPanel object) {
}
#Override
public void onActivation(JPanel object) {
for(Component component : object.getComponents()) {
if(component instanceof JComponent) {
((JComponent) component).putClientProperty(JComponentActivationStatus.PROPERTY_KEY,JComponentActivationStatus.INACTIVE);
}
}
}
};
addActivationStatusChangeAction(l1, activeBackground);
addActivationStatusChangeAction(l2, activeBackground);
addActivationStatusChangeAction(l3, activeBackground);
addActivationStatusChangeAction(panel, deactivateChildrens);
panel.add(l1);
panel.add(l2);
panel.add(l3);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
The solution is very flexible and extendable in case you will need to add more labels.
The example is for those that want to learn. Any comment would be appreciate.