14

For a school project, I need to make a simple paint application that can draw lines, ovals, and rectangles.

The assignment specifies that I need toolbar buttons and menu items for each type of shape.

I would like to go a little above and beyond, by making the buttons JToggleButtons in the toolbar and the menu items JRadioButtonMenuItems. Furthermore, I want it so that when you select one of the toolbar buttons, it deselects the other ones, selects the appropriate menu item, and deselects the other menu items. Same for selecting one of the menu items.

I know I can group any AbstractButton with a ButtonGroup, but I am not sure if this is the right way to go, because though it handles one "group" of buttons just fine, I am not sure it can handle two parallel groups.

Doing it without ButtonGroup would mean in each of the 6 event listeners I would have to manually deselect the other buttons, and each pair would call the same code to set the shape type.

I could also make two ButtonGroups, one for the menu, one for the toolbar, but then I also have to duplicate shape type setting code.

In either situation, I also run the risk of the menu setting a button which sets a menu item which sets a button, ad infintum.

What is the best way to tackle this problem?

(Bonus points for being able to solve the problem with the Netbeans GUI designer; It's just easier)

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
Austin Hyde
  • 26,347
  • 28
  • 96
  • 129
  • 5
    Bonus points for doing it without a GUI designer. You spend your time learning Java which is portable, rather the the IDE which is not. – camickr Oct 28 '10 at 03:43
  • @camickr - I actually prefer to use Textmate and the terminal over an IDE, it's just that the GUI designer makes things go *SO* much faster. My point was that I'd rather solve it in the designer and see how it did it than to struggle through editing the auto-generated code and spending hours figuring out why little things don't work. – Austin Hyde Oct 29 '10 at 12:51

1 Answers1

18

The Action interface is an effective approach "if you have two or more components that perform the same function," as discussed in How to Use Actions. In particular, an Action would allow your buttons and menu items to use the same code.

Addendum: The example below shows how a JMenu and a JToolBar can share the same Action for each of several files.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.io.File;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JToolBar;

/** @see http://stackoverflow.com/questions/4038605 */
public class FileMenu {

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {

            public void run() {
                new FileMenu().create();
            }
        });
    }

    void create() {
        File userDir = new File(System.getProperty("user.dir"));
        File[] files = userDir.listFiles();

        JMenu menu = new JMenu("Recent Files");
        JToolBar toolBar = new JToolBar(JToolBar.VERTICAL);
        JLabel label = new JLabel(" ", JLabel.CENTER);
        for (File f : files) {
            if (f.isFile() && !f.isHidden()) {
                RecentFile rf = new RecentFile(f, label);
                menu.add(new JMenuItem(rf.getAction()));
                toolBar.add(rf.getAction());
            }
        }
        JMenuBar menuBar = new JMenuBar();
        menuBar.add(menu);

        JFrame f = new JFrame("FileMenu");
        f.setJMenuBar(menuBar);
        f.add(toolBar, BorderLayout.CENTER);
        f.add(label, BorderLayout.SOUTH);
        f.pack();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

class RecentFile extends AbstractAction {

    private final File file;
    private final JLabel label;

    public RecentFile(final File file, final JLabel label) {
        this.file = file;
        this.label = label;
        this.putValue(Action.NAME, file.getName());
        this.putValue(Action.SHORT_DESCRIPTION, file.getAbsolutePath());
    }

    public void actionPerformed(ActionEvent e) {
        label.setText(file.getName());

    }

    public Action getAction() {
        return this;
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • 3
    +1, Actions are just as easy to write as an ActionListener but provide far more overall functionality. – camickr Oct 28 '10 at 03:45
  • 2
    Can you explain why you add (rf.getAction()) and not just (rf) to the toolbar in this code: `toolBar.add(rf.getAction());`, as well as in the line above? – user unknown Apr 30 '12 at 14:48
  • 2
    @userunknown: Good question; it looks like I adapted this from another example that used composition rather than extension. – trashgod Apr 30 '12 at 15:09
  • 1
    coming back to java after around 7 years of break - i am glad that the general design patterns from my 1996 java book still stand – clockw0rk Apr 17 '22 at 13:04
  • @clockw0rk: [_plus ça change, plus c'est la même chose_](https://en.wiktionary.org/wiki/plus_ça_change,_plus_c%27est_la_même_chose#French); see also [_Leveraging the observer pattern in JavaFX GUI design_](https://stackoverflow.com/q/31909941/230513). – trashgod Apr 17 '22 at 15:08