Multiple JLabel MouseListeners - java

I'm trying to add several JLabels to a JPanel along with mouse listeners using a loop. These JLabels are going to have mouse listeners so that they change their icon when clicked (Using label.setIcon()). However, I only want to have one "selected" at a time. So, I need them to know when another label is clicked so it knows to turn itself off before the new label gets selected. However, my problem is that because I'm adding these labels with a loop they all have the same MouseListener.
Can anyone teach me a simple way to accomplish this?

This is a short example, how you could implement it (please note, that I didn't use the icon, but change the label instead):
public class MouseListenerExample extends JFrame {
public static class MyMouseListener extends MouseAdapter {
private static final Collection<JLabel> labels = new ArrayList<JLabel>();
private final JFrame frame;
public MyMouseListener(JFrame frame, JLabel label) {
this.frame = frame;
labels.add(label);
}
#Override
public void mouseClicked(MouseEvent e) {
for (JLabel label : labels) {
String text = label.getText();
if (text.startsWith("X ")) {
label.setText(text.substring(2));
}
}
JLabel currentLabel = (JLabel) e.getComponent();
currentLabel.setText("X " + currentLabel.getText());
}
}
public MouseListenerExample() {
super("MouseListener Example");
Container c = getContentPane();
c.setLayout(new FlowLayout());
for (int i = 0; i < 10; i++) {
JLabel jLabel = new JLabel("Label " + i);
c.add(jLabel);
jLabel.addMouseListener(new MyMouseListener(this, jLabel));
}
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
new MouseListenerExample();
}
}
The main idea is, that you create a new MouseListener for each label, but keep a list of labels outside of each listener's scope (in this example I just use a static variable, but you could also have a field containing the list of labels in the frame.

Related

Components in JPanel only show after Resizing

The Buttons in my JPanel don't show up when it's loaded, only when I resize the window or move my mouse over it. In other discussions the use of "validate()" or "repaint()" was suggested, but that doesn't work for me.
I'm using a basic model view controller design and I am pretty sure that I'm doing everything else correctly.
Just in case you wonder, of course more panels will be added to the frame, that's the purpose of the update() and changeCards() methods.
Here's my frame:
public class View extends JFrame {
private MainMenuPanel mainMenu;
private final String MAIN_MENU_CONSTRAINTS = "MAIN_MENU";
public View() {
super();
init();
mainMenu = new MainMenuPanel();
add(mainMenu;MAIN_MENU_CONSTRAINTS);
validate();
repaint(0,0,getWidth(),getHeight());
setVisible(true);
}
private void init() {
setVisible(false);
setTitle("Test");
// set card-layout
setRootPaneCheckingEnabled(false);
CardLayout cl = new CardLayout();
this.setLayout(cl);
// expand frame to whole display size
setExtendedState(MAXIMIZED_BOTH);
// set unclosable
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public void update (Mode mode) {
switch (mode) {
case MAIN_MENU:
changeCard(MAIN_MENU_CONSTRAINTS);
break;
}
}
public void changeCard(String card) {
// update cards
CardLayout cl = (CardLayout) getLayout();
cl.show(this, card);
}
}
And here's the Panel:
public class MainMenuPanel extends Panel implements ActionListener{
private JButton startButton;
private JButton quitButton;
private final String START_ACTION_COMMAND = "START";
private final String QUIT_ACTION_COMMAND = "QUIT";
private MainMenuPanelListenerImpl listener;
public MainMenuPanel() {
super();
init();
initComponents();
configureComponents();
configureListeners();
addComponents();
revalidate();
}
private void init() {
setLayout(null);
}
private void initComponents() {
startButton = new JButton();
quitButton = new JButton();
}
private void configureComponents() {
startButton.setText("Start");
quitButton.setText("End");
startButton.setBounds((int)(0.5*getWidth()-200), (int)(0.5*getHeight()-75), 400, 75);
quitButton.setBounds((int)(0.5*getWidth()-200), (int)(0.5*getHeight()+25),400,75);
}
private void configureListeners() {
startButton.addActionListener(this);
startButton.setActionCommand(START_ACTION_COMMAND);
quitButton.addActionListener(this);
quitButton.setActionCommand(QUIT_ACTION_COMMAND);
}
private void addComponents() {
add(startButton);
add(quitButton);
startButton.validate();
quitButton.validate();
}
#Override
public void actionPerformed(ActionEvent e) {
switch (e.getActionCommand()) {
case START_ACTION_COMMAND:
listener.start();
break;
case QUIT_ACTION_COMMAND:
System.exit(0);
break;
}
}
public void setListener(MainMenuPanelListenerImpl listener) {
this.listener = listener;
}
}
After you paint the elements, you put the setvisible(true) because if you put it before, the Jframe will paint no elements
Well first of all you mix the old AWT-Components like Panel with newer SWING-Components like JFrame. Those don´t really work well together so I would try to fix that first. I would highly recommend using SWING or if you want to learn the newest Java GUI Library then JavaFX.
Don´t use the method repaint in the constructor of your JFrame, actually you shouldn´t use repaint in SWING at all. Nor do you need validate in the constructor. If you want to position your JFrame somewhere you should use something like this this.setLocation(0,0)
And to the main question: The panel probably only shows it´s components after resizing because you add it to the JFrame the wrong way. In SWING there is something called a content pane where you should add all of your stuff onto (except JMenuBar but that is a different story).
Simply set the layout of the content pane to the card layout that you want to use and then add your panel onto the content pane.
Here a link regarding the panel levels: https://docs.oracle.com/javase/tutorial/uiswing/components/toplevel.html

Dynamically add jcheckbox in jpanel

I have to add dynamically jcheckboxes in a panel when the user write something into a form. That's my code
Main
public class EmptyFrame extends JFrame{
private static final long serialVersionUID = 1L;
/*top panels*/
//to add technicians
private JButton newTechnician;
private NewTechForm ntForm;
private JPanel panelForm;
private JPanel panelChekBoxes;
TechCheckBoxGroup techniciansGroup;
private List<String> technicians;
//main container
private Container pane = getContentPane();
//components
GroupLayout gl = new GroupLayout(pane);
EmptyFrame(){
preinit();
init();
}
private void preinit(){
panelChekBoxes=new JPanel();
panelForm=new JPanel();
techniciansGroup=new TechCheckBoxGroup(panelChekBoxes);
}
private void init(){
/*top options*/
ntForm=new NewTechForm(panelForm);
newTechnician=new JButton("Add technician");
newTechnician.addActionListener(
new AddTechnicianAction(techniciansGroup,ntForm)
);
ntForm.getPanel().add(newTechnician);
/*end top options*/
for(String technic : technicians){
techniciansGroup.addCheckBoxes(
new JCheckBox(technic));
}
createWindowLayout(
new JLabel("Technicians"),
techniciansGroup.getCheckBoxes(),
ntForm.getPanel());
}
public void createWindowLayout(JComponent... arg) {
pane = getContentPane();
gl = new GroupLayout(pane);
pane.setLayout(gl);
gl.setAutoCreateContainerGaps(true);
gl.setAutoCreateGaps(true);
gl.setHorizontalGroup(gl.createParallelGroup()
.addGroup(gl.createSequentialGroup()
.addComponent(arg[0])
.addComponent(arg[1])
.addComponent(arg[2])
)
);
gl.setVerticalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
.addGroup(gl.createParallelGroup()
.addComponent(arg[0])
.addComponent(arg[1])
.addComponent(arg[2]))
);
pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
EmptyFrame ex = new EmptyFrame();
ex.setVisible(true);
});
}
}
in the main are presents a form and a checkbox group the first is the ntForm and the second one is the techniciansGroup. When i insert a name inside the form i would like to add a checkbox inside the checkbox group, here are the button, the checkbox group and the form classes:
AddTechnicianAction
this would be the class where everything would happened
public class AddTechnicianAction implements ActionListener{
TechCheckBoxGroup technicians;
NewTechForm form;
JTable table;
public AddTechnicianAction(TechCheckBoxGroup arg0, NewTechForm arg1){
technicians=arg0;
form=arg1;
}
#Override
public void actionPerformed(ActionEvent arg0) {
System.out.println("Add new tech: "+this.form.getSurnameText().getText()+" "+this.form.getNameText().getText());
technicians.addCheckBoxes(new JCheckBox(this.form.getSurnameText()+" "+this.form.getNameText()));
System.out.println(technicians);
}
}
NewTechForm
this is the form
public class NewTechForm {
private JLabel nameLabel;
private JLabel surnameLabel;
private JTextField nameText;
private JTextField surnameText;
private JPanel panel;
public NewTechForm(JPanel panel){
nameLabel= new JLabel("Nome: ", JLabel.RIGHT);
surnameLabel = new JLabel("Cognome: ", JLabel.CENTER);
nameText = new JTextField(6);
surnameText = new JTextField(6);
this.panel=panel;
panel.add(nameLabel);
panel.add(nameText);
panel.add(surnameLabel);
panel.add(surnameText);
}
public JLabel getNameLabel() {
return nameLabel;
}
public JTextField getNameText() {
return nameText;
}
public JTextField getSurnameText() {
return surnameText;
}
public JLabel getSurnameLabel() {
return surnameLabel;
}
public JPanel getPanel() {
return panel;
}
}
The problem is that inside TechCheckBoxGroup something happens, but not the things that i'm expecting to. The panel have a new checkbox after the action is performed but it seems that that panel (the obne inside TachCheckBoxGroup) is not the one inside the main class, and infact nothing were rendered in the window. There is clearly something that i didn't understand about the scoping in swing, what's the better practice to do what i'm trying to? Or this is the good way and i miss something?
I think that it is important to have answers on stack overflow, so for that reason I post my answer to my problem even if it's not really easy to see that was the right solution, now I explain better. While I'm trying to solve the problem of the wrong behaviour I've asked my self if I was doing the wrong considerations, and so it was, because i was trying to let comunicate all the components without a real
mediator
in fact it was impossible to catch the event in the main window with the code that i had written above. Seaching and searching i finally find this great answer here. So i basically change the NewTechForm classes making it a jpanel with a form inside, same thing for the CheckGroupBox, i'll made it a panel with the check box inside, and i send all the event to a listener in the main window.

Trying to display images in multiple JPanels

I'm creating a program that features a grid of 12 JPanels. When the "add image" button is pressed, an image appears in the first JPanel in the grid and a counter is incremented by one. From then onwards, every time the "add image" is clicked again, an image would be added to the next JPanel. For some reason, the button only adds an image to the first JPanel and then stops working. Here's the code I've got so far.
public class ImageGrid extends JFrame {
static JPanel[] imageSpaces = new JPanel[12];
int imageCounter = 0;
ImageGrid() {
this.setTitle("Image Grid");
setSize(750, 750);
setDefaultCloseOperation(EXIT_ON_CLOSE);
JPanel p3 = new JPanel();
p3.setLayout(new GridLayout(3, 4, 10, 5));
p3.setBackground(Color.WHITE);
p3.setOpaque(true);
p3.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5));
for (int j = 0; j < imageSpaces.length; j++) {
imageSpaces[j] = setImageSpace();
p3.add(imageSpaces[j]);
}
MyButtonPanel p1 = new MyButtonPanel();
add(p1, BorderLayout.SOUTH);
add(p3, BorderLayout.CENTER);
}
public JPanel setImageSpace() {
JPanel test;
test = new JPanel();
test.setOpaque(true);
test.setPreferredSize(new Dimension(100, 100));
return test;
}
class MyButtonPanel extends JPanel implements ActionListener {
final JButton addImage = new JButton("Add Image");
ImageIcon lorryPicture = new ImageIcon(ImageGrid.class.getResource("/resources/lorry.png"));
JLabel lorryImage = new JLabel(lorryPicture);
MyButtonPanel() {
add(addImage);
addImage.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == addImage) {
imageSpaces[imageCounter].add(lorryImage);
revalidate();
repaint();
imageCounter++;
}
}
}
public static void main(String[] args) {
ImageGrid test = new ImageGrid();
test.setVisible(true);
}
}
You should be revalidating and repainting the panel, (which is the containter being affected by the addition), not the frame
imageSpaces[imageCounter].add(lorryImage);
imageSpaces[imageCounter].revalidate();
imageSpaces[imageCounter].repaint();
Diclaimer: This may work as a simple fix, but also note that a component (in this case your JLabel lorryImage) can only have one parent container. The reason the above fix still works is because you don't revalidate and repaint the previous panel, the label was added to. So you may want to think about doing it correctly, and adding a new JLabel to each panel.
if (e.getSource() == addImage) {
JLabel lorryImage = new JLabel(lorryPicture);
imageSpaces[imageCounter].add(lorryImage);
imageSpaces[imageCounter].revalidate();
imageSpaces[imageCounter].repaint();
imageCounter++;
}
Disclaimer 2: You should add a check, to only add a label if the count is less than the array length, as to avoid the ArrayIndexOutOfBoundsException
Side Notes
Swing apps should be run from the Event Dispatch Thread (EDT). You can do this by wrapping the code in the main in a SwingUtilities.invokeLater(...). See more at Initial Threads
You could also just use a JLabel and call setIcon, instead of using a JPanel

Transparent JTextField on a transparent JPanel

Not sure if this has been discussed before. But I am having a odd issue with transparent JTextFields added on a transparent JPanel. For some reason (I could not dig enough to find why) there are additional paintings that get carried out. Perhaps there are dirty regions that needs to be dealt with? not sure.
Let me present this simple example:
public class TextFieldGame extends JPanel {
public static void main(String [] args){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame someFrame = new JFrame("Is this odd?");
someFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
someFrame.setSize(200,600);
someFrame.add(new TextFieldGame());
someFrame.setVisible(true);
}
});
}
public TextFieldGame() {
setupContentPane();
}
private void setupContentPane() {
setLayout(new BorderLayout());
final CanvasPanel canvasPanel = new CanvasPanel();
add(canvasPanel, BorderLayout.CENTER);
add(new ControlPanel(canvasPanel), BorderLayout.SOUTH);
}
public static class ControlPanel extends JPanel {
private final CanvasPanel canvasPanel;
ControlPanel(CanvasPanel canvasPanel) {
this.canvasPanel = canvasPanel;
setupContentPane();
}
private void setupContentPane() {
setLayout(new FlowLayout(FlowLayout.RIGHT));
final JButton load = new JButton("load");
add(load);
load.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
canvasPanel.addChildComponent(getComponent());
canvasPanel.revalidate();
canvasPanel.repaint();
}
});
}
private JComponent getComponent() {
final JPanel container = new JPanel();
container.setLayout(new BoxLayout(container, BoxLayout.PAGE_AXIS));
container.setOpaque(false);
for (int i = 0; i < 10; i++) {
final JTextField textField = new JTextField("why you no work?") {
#Override
public Dimension getMaximumSize() {
return new Dimension(Short.MAX_VALUE, getPreferredSize().height);
}
};
textField.setOpaque(false);
container.add(textField);
}
return container;
}
}
public static class CanvasPanel extends JPanel {
private int paintCount = 0;
CanvasPanel() {
setupContentPane();
}
public void setupContentPane() {
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
setBackground(Color.white);
}
public void addChildComponent(JComponent component) {
component.setAlignmentY(TOP_ALIGNMENT);
add(component);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("paint count: " + ++paintCount);
}
}
}
I have added the System out statement to show the count of paint task.
When first loaded, there will be 2/3 paints - fair enough. Then if you are to press the "Load" button, 10 transparent JTextFields will be added to a transparent JPanel, and this transparent JPanel will be added to the CanvasPanel. (Canvas panel is in turn a subchild of the JFrame). You will notice upon doing this, 11 additional paint jobs will be done.
But in theory (well in my understanding) after "Load" is pressed, only one paint job should be carried out. That is because, I have added only one child to CanvasPanel (the child itself might have 10 textfields, but they should all be painted in one shot).
Just to test my understanding, if you are to use 10 JLabels instead of the 10 JTextFields, only one paint job is carried out after "Load" is pressed. Which is what it should be.
Also, if you are to keep JTextField opaque, only one paint job is carried out. (Just tested that if instead of JTextField, a JTextArea is used, one paint is done)
What is going on? Note that JLabel is transparent by default, so I am not sure why transparency of JTextField component causing these additional paints.
Please help/

Fast Jbutton clicks results in no action

Hey guys, I have a problem with a code that I've been writing.
I have a JFrame that contains two buttons. Each of these buttons has an action. The problem I'm having is with a JButton called "btnDone" that's supposed to get back to a previous screen. If I I keep pushing the button repeatedly, eventually the "btnDone" would stop doing the logic it's supposed to do. My code is as follows:
For the frame:
public class ItemLocatorPnl extends JPnl
{
private static final long serialVersionUID = 1L;
private Pnl pnl;
private JButton btnDone;
private JButton btnRefreshData;
public void setPnl(Pnl pnl) {
this.pnl = pnl;
}
public ItemLocatorPnl(Pnl pnl)
{
super();
this.pnl=pnl;
initialize();
}
private void initialize()
{
this.setSize(300, 200);
JPanel jContentPane = new JPanel();
jContentPane.setLayout(new MigLayout());
// (1) Remove window frame
setUndecorated(true);
// (3) Set background to white
jContentPane.setBackground(Color.white);
// (5) Add components to the JPnl's contentPane
POSLoggers.initLog.writeDebug("ItemLocator: Adding icon");
jContentPane.add(wmIconLabel, "align left");
POSLoggers.initLog.writeDebug("ItemLocator: Adding global controls");
jContentPane.add(createUpperPanel(), "align right, wrap");
POSLoggers.initLog.writeDebug("ItemLocator: Adding main panel");
jContentPane.add(pnl,"width 100%,height 100%, span 3");
// (6) Attach the content pane to the JPnl
this.setContentPane(jContentPane);
}
private JPanel createUpperPanel()
{
JPanel upperPanel=new JPanel();
MigLayout mig = new MigLayout("align right", "", "");
upperPanel.setLayout(mig);
upperPanel.setBackground(Color.WHITE);
// Create the Done button
btnDone= GraphicalUtilities.getPOSButton("<html><center>Done</center></html>");
btnDone.addActionListener(new ButtonListener());
// Create the Refresh Data button
btnRefreshData = GraphicalUtilities.getPOSButton("<html><center>Refresh<br>Data</center></html>");
btnRefreshData.addActionListener(new ButtonListener());
//Addiing buttons to the Panel
upperPanel.add(btnRefreshData, "width 100:170:200, height 100!");
upperPanel.add(btnDone, "width 100:170:200, height 100!");
return upperPanel;
}
public class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
try {
if (e.getSource() == btnRefreshData) {
Actual.refreshData();
} else if (e.getSource() == btnDone) {
Actual.backToMainScreen();
}
}
catch (Exception ex)
{
}
}
}
}
This is the method that the btnDone button calls upon clicking:
public static void backToMainScreen()
{
frame.setVisible(false);
frame.dispose();
}
This is the code that displays the JFrame:
public static void displayItemLocatorFrame()
{
pnl = new Pnl();
frame = new Frame(pnl);
frame.setVisible(true);
pnl.getSearchCriteria().requestFocus();
}
Please note that the "frame" object is static, and all of my methods are static, and they exist in a static class called Actual.
So in short, I just want to make sure that no matter how many times a user clicks on the button, and no matter how fast the clicks were, the frame should act normally.
Any suggestions? (I tried synchronizing my methods with no luck..)
I would generally prefer to use an Action for what you're trying to do.
So your code might look like this:
btnDone = new JButton(new CloseFrameAction());
...
private class CloseFrameAction extends AbstractAction
{
public CloseFrameAction()
{
super("Done");
}
public void actionPerformed(ActionEvent e)
{
frame.dispose();
setEnabled(false);
}
}
Notice the setEnabled(false) line - this should disable the button and prevent the user clicking on it again. Obviously I don't know what your exact requirements are but this is the general approach I would take.
The problem was with using a static panel that was instantiated with the click of the button each time. Removing "static" has finally fixed my problem! Thanks everyone for the help.

Categories