How to determine which panel holds a button that was pressed - Application Window

I have an array of Tile objects (Panel, Button to a tile) in a JPanel, 20x20. If button 1 is clicked, a thing happens, button 2 is pressed, a thing happens, etc.

I want a specific function to happen every time a button other than one in the top row is clicked (the top row of buttons all have functions assigned, the other 380 buttons do not have assigned functions).

So in the top buttons' cases I have the code:

if(e.getSource() == tiles[0][0].button)
    {
    //do stuff
    }
else if(e.getSource() == tiles[0][1].button)
    {
    //do stuff
    }

For the other buttons, I want something along the lines of:

JButton button;
button = e.getSource();
JPanel hostPanel = button.PanelInWhichButtonisContained();

but I'm not sure what the syntax or what sort I would to do achieve that task. I don't really have any code to present prior attempts because I'm not sure how to approach this task, but I haven't been able to find anything on the World Wide Web to help me in this task.

I know it's kind of beggar-y and lame to not be able to show anything that I've tried, but I'm super stuck and I've been staring at my screen for about an hour and a half trying to figure this out.

I'm currently just using default application window libraries and classes (javax.swing, java.awt, etc) but I'm completely open to downloading external libraries.

1 answer

  • answered 2018-11-07 23:13 Marco13

    Determining the "source" of an action like a button press in the actionPerformed method is usually brittle (and fortunately, hardly ever necessary).

    This means that this is highly questionable:

    class ButtonListener implements ActionListener {
        @Override 
        public void actionPerformed(ActionEvent e) {
            // DON'T DO THIS!
            if (e.getSource() == someButton) doThis();
            if (e.getSource() == someOtherButton) doThad();
        }
    }
    

    You should usually NOT do this.

    And of course, it's even worse to add casts and walk up some container hierarchy:

    // DON'T DO THIS!
    Object source = e.getSource();
    Component button = (Component)source;
    Component parent = button.getParent();
    if (parent == somePanel) doThis();
    if (parent == someOtherPanel) doThat();
    

    In basically all cases, it is far more flexible and elegant to attach a listener to the button that is specific for the button - meaning that it knows what the button should do.

    For individual buttons, this can be solved the old-fashioned way, using an anonymous inner class:

    class Gui {
    
        void create() {
            JButton startButton = new JButton("Start");
            startButton.addActionListener(new ActionListener() {
                @Override 
                public void actionPerformed(ActionEvent e) {
                    startSomething();
                }
            });
        }
    
        private void startSomething() { ... }
    }
    

    The same can be written far more concisely using lambda expressions with Java 8:

    class Gui {
    
        void create() {
            JButton startButton = new JButton("Start");
            startButton.addActionListener(e -> startSomething());
        }
    
        private void startSomething() { ... }
    }
    

    (A side note: I consider it as a good practice to only call a single method in the ActionListener implementation anyhow. The actionPerformed method should not contain many lines of code, and particularly no "business logic". There should be a dedicated method for what the button does - for example, to startSomething, as in the example above)


    For buttons that are contained in arrays, as in the example in your question, there is a neat trick to retain the information about the button that was clicked:

    class Gui {
    
        JButton buttons[];
    
        void create() {
    
            buttons = new JButton[5];
            for (int i=0; j<buttons.length; i++) { 
                int index = i;
                buttons[i] = new JButton("Button " + i);
                buttons[i].addActionListener(e -> clickedButton(index));
            }
        }
    
        private void clickedButton(int index) { 
            System.out.println("Clicked button at index " + index);
        }
    }
    

    In many cases, you then don't even have to keep the JButton buttons[] array any more. Often you can just create the buttons, add them to some panel, and then are only interested in the index that is passed to the clickedButton method. (The button[] array may be necessary in some cases, though - for example, if you want to change the label of a button after it was clicked).