-1

I am trying to make a button, that upon clicked, changes the grid color of the rectangles I have drawn.

Here is my drawn grid. I would like the menu items I have under grid, when pressed, change the rectangle outline colors of the drawn grid. I have tried a couple methods, but seem to have trouble implementing it.

Here is my Menu Class

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MyMenu
extends MenuBar
implements ActionListener {
   Menu paperMenu;
   MenuItem c1, c2, c3;
   GUI gui;
   Cell cell;
   
   public MyMenu(GUI gui) {
      this.gui = gui;
      this.cell = cell;
      paperMenu = new Menu("Grid");
      c1 = new MenuItem("Red");
      c2 = new MenuItem("Green");
      c3 = new MenuItem("Random");
      paperMenu.add(c1);
      paperMenu.add(c2); 
      paperMenu.add(c3);
      this.add(paperMenu);
      
      c1.addActionListener(this);
      c2.addActionListener(this);
      c3.addActionListener(this);
   }
   
   
   public void actionPerformed(ActionEvent e) {
       
       if (e.getSource()==c1) {
           
       }
   } 
}

Here is my Board class

import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.*;


public class Board
extends Canvas {
    private Cell[][] array;

    public Board(int rows, int cols, int size) {
        array = new Cell[rows][cols];
        for (int r = 0; r < rows; r++) {
            int y = r * size;
            for (int c = 0; c < cols; c++) {
                int x = c * size;
                array[r][c] = new Cell(x, y, size);
            }   
        }
        setSize(cols*size,rows*size);

    }
    
    public void draw(Graphics g) {
        for (Cell[] row : array) {
            for (Cell cell : row) {
                cell.paint(g);
            }
        }
    }
    
    public Cell getCell(int r, int c) {
        return array[r][c];
    }
    
    public void paint(Graphics g) {
        draw(g);
    }
}

And Finally, here is my Cell Class

import java.awt.Color;
import java.awt.Graphics;
import java.awt.*;

public class Cell
{
   private final int x;
   private final int y;
   private final int size;
   int rows;
   int cols;
   Cell[][] array = new Cell[rows][cols];
   public Color currentcolor = Color.BLACK;
   public Cell(int x, int y, int size) {
       this.x = x;
       this.y = y;
       this.size = size;
   }
   
   public void fillArray() {
       for (int r = 0; r < rows; r++) {
           int y = r * size;
           for (int c = 0; c < cols; c++) {
               int x = c * size;
               array[r][c] = new Cell(x, y, size);
           }
       }
   }
   
   
   public void paint(Graphics g) {
       g.setColor(Color.BLACK);
       g.drawRect(x, y, size, size);
   }
}

I obviously need to make it, so when the button is pressed, the g.setColor under the Cell class and paint method is changed and then repainted, but I am unsure on how to do this?

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • I think, first, you might want to learn the difference between AWT and Swing (and why you might want to prefer using the latter) – MadProgrammer Apr 12 '22 at 00:26
  • There are a few problems as outlined in the fantastic answer from MadProgrammer below, but at the core of it, you simply need to store a Color value/variable in the Board class which you change when a different menu button is pressed. – sorifiend Apr 12 '22 at 01:23
  • @sorifiend I figured that was the basic premise. So if i create a variable in the cell class for color, lets say private Color gridcolor = Color.BlACK (being the default) and have the paint method do g.setColor(gridcolor), how do I transfer this over to the MyMenu class to change the variable when the button is pressed. The button code would be something like gridcolor = Color.RED and repaint() right? –  Apr 12 '22 at 01:33
  • Correct. To make this work we need a reference to your `Board` object so that we can set the colour, for example, to get the reference in your MyMenu class we would create a class variable and set it inside the Board constructor using `this.board = ui.getBoard();`, and obviously we would create a getter method in the UI class that returns the board. then in the action listener you can use `board.setColour(newColour);`. The runnable example from MadProgrammer does this in a more complicated way, but it is also a more correct way that separates the logic and allows future canges. – sorifiend Apr 12 '22 at 01:51

1 Answers1

3

First, you might want to understand the difference between Swing and AWT (and why you might want to prefer the latter)

If you're just starting out, you might also consider using JavaFX instead.

You will also want to investigate things like:

As this will all effect the way in which you approach designing your solutions to your problems.

For example, do you really need to extend from MenuBar? What new functionality are you bringing to the class, which couldn't be achieved through a builder or factory pattern?

Each individual menu item could also have it's action handling isolating, so that you're dealing with single response for each action, rather then, what will become, a messy if-else statement in the future (ie Single Responsibility Principle)

Having said all that, your basic problem comes down to, providing some way to notify the Board that it needs to update the gridColor when a menu item is selected.

This can be solved through the use a Observer Pattern. You've already used this, ActionListener is a implementation of the observer pattern.

Now, there is no reason for MyMenu to have full control over GUI (which I'm assuming is some kind Frame), it would be better to make some kind of delegate which is responsible for coordinating messaging between the menu bar and the interested party (this can some times be referred to as a controller, but I'm not going that far).

The first thing we need is to define the functionality we're willing to expose, for example...

public interface BoardConfigurable {
    public void setBoardGridColor(Color color);
}

This is a simple contract for telling the interested party that the board grid color should be changed.

(Before some jumps down my throat, we could have a interface called, I don't, MyMenuListener, which then implements BoardConfigurable and possibly a bunch of other interfaces, which further allows for decoupling of the basic code, but this is what I would consider separating the action handling into isolated units of work - but this is going way beyond what is required right now)

We then update MyMenu to accept an instance of this delegate/observer and update the actionPerformed method to call it when ever an appropriate action is triggered.

public class MyMenu
        extends MenuBar
        implements ActionListener {
    Menu paperMenu;
    MenuItem c1, c2, c3;
    BoardConfigurable boardConfig;
    Cell cell;

    public MyMenu(BoardConfigurable boardConfig) {
        this.boardConfig = boardConfig;
        this.cell = cell;
        paperMenu = new Menu("Grid");
        c1 = new MenuItem("Red");
        c2 = new MenuItem("Green");
        c3 = new MenuItem("Random");
        paperMenu.add(c1);
        paperMenu.add(c2);
        paperMenu.add(c3);
        this.add(paperMenu);

        c1.addActionListener(this);
        c2.addActionListener(this);
        c3.addActionListener(this);
    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == c1) {
            boardConfig.setBoardGridColor(Color.RED);
        } else if (e.getSource() == c2) {
            boardConfig.setBoardGridColor(Color.GREEN);
        } else if (e.getSource() == c3) {
            boardConfig.setBoardGridColor(Color.BLUE);
        }
    }
}

We then need to update the Board (and Cell) to accept this change

public class Board extends Canvas {
    private Cell[][] array;
    
    private int rows;
    private int cols;
    private int size;
    
    private Color gridColor = Color.BLACK;

    public Board(int rows, int cols, int size) {
        this.rows = rows;
        this.cols = cols;
        this.size = size;
        array = new Cell[rows][cols];
        for (int r = 0; r < rows; r++) {
            int y = r * size;
            for (int c = 0; c < cols; c++) {
                int x = c * size;
                array[r][c] = new Cell(x, y, size);
            }
        }
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(cols * size, rows * size);
    }

    public Color getGridColor() {
        return gridColor;
    }

    public void setGridColor(Color gridColor) {
        this.gridColor = gridColor;
        repaint();
    }

    public void draw(Graphics g) {
        for (Cell[] row : array) {
            for (Cell cell : row) {
                cell.paint(g, getGridColor());
            }
        }
    }

    public Cell getCell(int r, int c) {
        return array[r][c];
    }

    public void paint(Graphics g) {
        super.paint(g);
        draw(g);
    }
}


public class Cell {
    private final int x;
    private final int y;
    private final int size;

    public Cell(int x, int y, int size) {
        this.x = x;
        this.y = y;
        this.size = size;
    }

    public void paint(Graphics g, Color gridColor) {
        g.setColor(gridColor);
        g.drawRect(x, y, size, size);
    }
}

note: Cell is making use of dependency injection here, as it really doesn't need to store the color AGAIN. It's also possible that the size could be passed this way, but, it might be useful for determine if a user clicked this cell or not

Then GUI needs to implement BoardConfigurable...

public class GUI extends Container implements BoardConfigurable {
    private Board board;
    
    public GUI() {
        setLayout(new BorderLayout());
        board = new Board(3, 3, 50);
        
        add(board);
    }

    @Override
    public void setBoardGridColor(Color color) {
        board.setGridColor(color);
    }
}

And we can update build and run the UI...

GUI gui = new GUI();
MyMenu menu = new MyMenu(gui);

Frame frame = new Frame();
frame.add(gui);
frame.setMenuBar(menu);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);

An important skill to build is always questioning, "does this really need access to everything I'm passing?", "what will happen if I want to change the underlying implementation? How much will I need to change to achieve it?"

These will help you drive your designs and help build more flexible and decoupled code.

You might also want to become fairmular with the model-view-controller concept

Runnable example...

import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        GUI gui = new GUI();
        MyMenu menu = new MyMenu(gui);

        Frame frame = new Frame();
        frame.add(gui);
        frame.setMenuBar(menu);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public interface BoardConfigurable {
        public void setBoardGridColor(Color color);
    }

    public class GUI extends Container implements BoardConfigurable {
        private Board board;

        public GUI() {
            setLayout(new BorderLayout());
            board = new Board(3, 3, 50);

            add(board);
        }

        @Override
        public void setBoardGridColor(Color color) {
            board.setGridColor(color);
        }
    }

    public class MyMenu
            extends MenuBar
            implements ActionListener {
        Menu paperMenu;
        MenuItem c1, c2, c3;
        BoardConfigurable boardConfig;
        Cell cell;

        public MyMenu(BoardConfigurable boardConfig) {
            this.boardConfig = boardConfig;
            this.cell = cell;
            paperMenu = new Menu("Grid");
            c1 = new MenuItem("Red");
            c2 = new MenuItem("Green");
            c3 = new MenuItem("Random");
            paperMenu.add(c1);
            paperMenu.add(c2);
            paperMenu.add(c3);
            this.add(paperMenu);

            c1.addActionListener(this);
            c2.addActionListener(this);
            c3.addActionListener(this);
        }

        public void actionPerformed(ActionEvent e) {
            if (e.getSource() == c1) {
                boardConfig.setBoardGridColor(Color.RED);
            } else if (e.getSource() == c2) {
                boardConfig.setBoardGridColor(Color.GREEN);
            } else if (e.getSource() == c3) {
                boardConfig.setBoardGridColor(Color.BLUE);
            }
        }
    }

    public class Board extends Canvas {
        private Cell[][] array;

        private int rows;
        private int cols;
        private int size;

        private Color gridColor = Color.BLACK;

        public Board(int rows, int cols, int size) {
            this.rows = rows;
            this.cols = cols;
            this.size = size;
            array = new Cell[rows][cols];
            for (int r = 0; r < rows; r++) {
                int y = r * size;
                for (int c = 0; c < cols; c++) {
                    int x = c * size;
                    array[r][c] = new Cell(x, y, size);
                }
            }
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(cols * size, rows * size);
        }

        public Color getGridColor() {
            return gridColor;
        }

        public void setGridColor(Color gridColor) {
            this.gridColor = gridColor;
            repaint();
        }

        public void draw(Graphics g) {
            for (Cell[] row : array) {
                for (Cell cell : row) {
                    cell.paint(g, getGridColor());
                }
            }
        }

        public Cell getCell(int r, int c) {
            return array[r][c];
        }

        public void paint(Graphics g) {
            super.paint(g);
            draw(g);
        }
    }

    public class Cell {
        private final int x;
        private final int y;
        private final int size;

        public Cell(int x, int y, int size) {
            this.x = x;
            this.y = y;
            this.size = size;
        }

        public void paint(Graphics g, Color gridColor) {
            g.setColor(gridColor);
            g.drawRect(x, y, size, size);
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • this is an incredible response, thank you. I think the way I have my GUI class sets up complicates this, as if I delete the GUI implementation in My Menu, my Menu no longer works. Would I be able to show you that too and ask for any advice/ tips and how it would change your implementation? –  Apr 12 '22 at 01:18
  • 1
    Again, look at what functionality you need exposed. You can break these down into small `interface`s and make a `MyMenuListener` which basically implements all of them if you want - but, I would argue that `MyMenu` is generally a bad idea – MadProgrammer Apr 12 '22 at 01:50