Principle 13.8.2. EFFECTIVE DESIGN: Method Size.
A method that gets longer than 20 to 25 lines is probably trying to do too much and should be divided into separate methods, each with a clearly defined task.
Ralph Morelli, Ralph Walde, Beryl Hoffman
JMenuBar
is an implementation of a menu bar—a horizontal list of names that appears at the top of a window ( Figure 13.8.1).JMenu
objects to a JMenuBar
. A JMenu
is essentially a clickable area on a menu bar that is associated with a JPopupMenu
, a small window that pops up and displays the menu’s JMenuItem
s. A menu can also contain JSeparator
s, which are dividers that can be placed between menu items to organize them into logical groupings.JMenuItem
s.JMenu
and add the JMenuItem
s to it.JMenuBar
and add the JMenu
s to it.JFrame
or for the JApplet
. This is usually done in the application’s constructor or in the applet’s init()
method:JMenuBar mBar = new JMenuBar();// Create menu bar
this.setJMenuBar(mBar); // Add it to this window
init()
method. If the menu is large, you should break this task into subtasks and define a method for each subtask.private void initFileMenu() {
fileMenu = new JMenu("File"); // Create menu
mBar.add(fileMenu); // Add it to menu bar
openItem = new JMenuItem("Open"); // Open item
openItem.addActionListener( this );
openItem.setEnabled(false);
fileMenu.add(openItem);
saveItem = new JMenuItem("Save"); // Save item
saveItem.addActionListener(this);
saveItem.setEnabled(false);
fileMenu.add(saveItem);
fileMenu.addSeparator(); // Logical separator
quitItem = new JMenuItem("Quit"); // Quit item
quitItem.addActionListener(this);
fileMenu.add(quitItem);
} // initFileMenu()
ActionListener
. As we’ll see shortly, action events for menu items are handled the same way as action events for buttons. Finally, note how the setEnabled()
method is used to disable both the open and save menu items. Implementation of these actions is left as an exercise.ArrayList
. his function will be like an “Unlimited Undo” operation for cuts. For this example, we won’t place any limit on the size of the vector. Every cut the user makes will be inserted at the beginning of the vector. To go along with this feature we need a menu that can grow dynamically during the program. Each time the user makes a cut, the string that was cut will be added to the menu.private void initEditMenu() {
editMenu = new JMenu("Edit"); // Create edit menu
mBar.add(editMenu); // Add to menu bar
cutItem = new JMenuItem ("Cut"); // Cut item
cutItem.addActionListener(this);
editMenu.add(cutItem);
copyItem = new JMenuItem("Copy"); // Copy item
copyItem.addActionListener(this);
editMenu.add(copyItem);
pasteItem = new JMenuItem("Paste"); // Paste item
pasteItem.addActionListener(this);
editMenu.add(pasteItem);
editMenu.addSeparator();
selectItem = new JMenuItem("Select All");// Select
selectItem.addActionListener(this);
editMenu.add(selectItem);
editMenu.addSeparator();
cutsMenu = new JMenu("Recent Cuts");//Cuts submenu
editMenu.add(cutsMenu);
} // initEditMenu()
cutsMenu
will be used to hold the strings that are cut from the document. Initially, it will be empty.JMenuItem
actions is no different from handling JButton
actions. Whenever a user makes a menu selection, an ActionEvent
is generated. Programs that use menus must implement the actionPerformed()
method of the ActionListener
interface. In the text editor example, there are a total of six enabled menu items, including the recent cuts menu. This translates into a large if-else structure, with each clause handling a single menu item.actionPerformed()
method is used to handle the menu selections for the text editor:public void actionPerformed(ActionEvent e) {
JMenuItem m = (JMenuItem)e.getSource(); // Get selected menu item
if ( m == quitItem ) { // Quit
dispose();}
} else if (m == cutItem) { // Cut the selected text
scratchPad = display.getSelectedText(); // Copy text to scratchpad
display.replaceRange("", // and delete
display.getSelectionStart(), // from the start of selection
display.getSelectionEnd()); // to the end
addRecentCut(scratchPad); // Add text to the cuts menu
} else if (m == copyItem) // Copy text to scratchpad
scratchPad = display.getSelectedText();
} else if (m == pasteItem) { // Paste scratchpad to document at caret
display.insert(scratchPad, display.getCaretPosition()); // position
} else if ( m == selectItem ) {
display.selectAll(); // Select the entire document
} else {
JMenuItem item = (JMenuItem)e.getSource(); // Default is cutsMenu
scratchPad = item.getActionCommand(); // Put cut back in scratchpad
}
} // actionPerformed()
ActionEvent
and casting it into a JMenuItem
. It then checks each case of the if-else structure. Because the actions taken by this program are fairly short, they are mostly coded within the actionPerformed()
method itself. However, for most programs it will be necessary to write a separate method corresponding to each menu item and then call the methods from actionPerformed()
.JTextArea
, which contains instance methods that make it very easy to select, insert, and replace text. To copy a piece of text, the program need only get the text from the JTextArea
(getSelectedText()
) and assign it to the scratchpad
, which is represented as a String
. To paste a piece of text, the program inserts the contents of the scratchpad
into the JTextArea
at the location marked by the caret, a cursor-like character in the document that marks the next insertion point.cutsMenu
. All of the other menu items can be referred to by name. However, the menu items in the cutsMenu
are just snippets of a string that the user has previously cut from the text, so they can’t be referenced by name. Luckily, we don’t really need to. For any JMenuItem
, the getActionCommand()
method returns its text, which in this case is the previously cut text. So we just assign the cut text from the menu to the scratchpad
.scratchpad
, but it must also be inserted into the vector that is storing all the previous cuts. The addRecentCut()
method takes care of this last task. The basic idea here is to take the cut string and insert it at the beginning of the vector, so that cuts will be maintained in a last-in–first-out order. Then the cutsMenu
must be completely rebuilt by reading its entries out of the vector, from first to last. That way the most recent cut will appear first in the menu:private void addRecentCut(String cut) {
recentCuts.add(0,cut);
cutsMenu.removeAll();
for (int k = 0; k < recentCuts.size(); k++) {
JMenuItem item =
new JMenuItem((String)recentCuts.get(k));
cutsMenu.add( item );
item.addActionListener(this);
}
} // addRecentCut()
recentCuts ArrayList
stores the cut strings. Note the use of the insertElementAt()
method to insert strings into the vector and the elementAt()
method to get strings from the vector. (You may find it helpful to review the section on ArrayLists in Chapter 9.)cutsMenu
is reinitialized, using the removeAll()
method. Then the for loop iterates through the strings stored in the vector, making new menu items from them, which are then inserted into the cutsMenu
. In this way, the cutsMenu
is changed dynamically each time the user cuts a piece of text from the document.SimpleTextEditor
class is summarized in Figure 13.8.5 and its complete implementation is shown in Listing 13.8.6. It uses a BorderLayout
, with the JTextArea
placed at the center.import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class SimpleTextEditor extends JFrame implements ActionListener{
private JMenuBar mBar = new JMenuBar(); // Create the menu bar
private JMenu fileMenu, editMenu, cutsMenu; // Menu references and items
private JMenuItem cutItem, copyItem, pasteItem, selectItem,recentcutItem;
private JMenuItem quitItem, openItem, saveItem; // File items
private JTextArea display = new JTextArea(); // Here's where the editing occurs
private String scratchPad = ""; // Scratch pad for cut/paste
private ArrayList recentCuts = new ArrayList();
public SimpleTextEditor() {
super("Simple Text Editor"); // Set the window title
this.getContentPane().setLayout(new BorderLayout());
this.getContentPane().add("Center", display);
this.getContentPane().add(new JScrollPane(display));
display.setLineWrap(true);
this.setJMenuBar(mBar); // Set this program's menu bar
initFileMenu(); // Create the menus
initEditMenu();
} // SimpleTextEditer()
private void initEditMenu() {
editMenu = new JMenu("Edit"); // Create the edit menu
mBar.add(editMenu); // and add it to menu bar
cutItem = new JMenuItem ("Cut"); // Cut item
cutItem.addActionListener(this);
editMenu.add(cutItem);
copyItem = new JMenuItem("Copy"); // Copy item
copyItem.addActionListener(this);
editMenu.add(copyItem);
pasteItem = new JMenuItem("Paste"); // Paste item
pasteItem.addActionListener(this);
editMenu.add(pasteItem);
editMenu.addSeparator();
selectItem = new JMenuItem("Select All"); // Select item
selectItem.addActionListener(this);
editMenu.add(selectItem);
editMenu.addSeparator();
cutsMenu = new JMenu("Recent Cuts"); // Recent cuts submenu
editMenu.add(cutsMenu);
} // initEditMenu()
private void initFileMenu() {
fileMenu = new JMenu("File"); // Create the file menu
mBar.add(fileMenu); // and add it to the menu bar
openItem = new JMenuItem("Open"); // Open item
openItem.addActionListener( this );
openItem.setEnabled(false);
fileMenu.add(openItem);
saveItem = new JMenuItem("Save"); // Save item
saveItem.addActionListener(this);
saveItem.setEnabled(false);
fileMenu.add(saveItem);
fileMenu.addSeparator(); // Logical separator
quitItem = new JMenuItem("Quit"); // Quit item
quitItem.addActionListener(this);
fileMenu.add(quitItem);
} // initFileMenu()
public void actionPerformed(ActionEvent e) {
JMenuItem m = (JMenuItem)e.getSource();// Get selected menu item
if ( m == quitItem ) { // Quit
dispose();
} else if (m == cutItem) { // Cut the selected text
scratchPad = display.getSelectedText(); // Copy text to scratchpad
display.replaceRange("", // and delete
display.getSelectionStart(), // from the start of the selection
display.getSelectionEnd()); // to the end
addRecentCut(scratchPad); // Add the cut text to the cuts menu
} else if (m == copyItem) { // Copy the selected text to the scratchpad
scratchPad = display.getSelectedText();
} else if (m == pasteItem) { // Paste the scratchpad to the document at caret
display.insert(scratchPad, display.getCaretPosition()); // position
} else if ( m == selectItem ) {
display.selectAll(); // Select the entire document
} else {
JMenuItem item = (JMenuItem)e.getSource(); // Default is cutsMenu
scratchPad = item.getActionCommand(); // Put cut back in the scratchpad
}
} // actionPerformed()
private void addRecentCut(String cut) {
recentCuts.add(0,cut);
cutsMenu.removeAll();
for (int k = 0; k < recentCuts.size(); k++) {
JMenuItem item =
new JMenuItem((String)recentCuts.get(k));
cutsMenu.add( item );
item.addActionListener(this);
}
} // addRecentCut()
public static void main(String args[]) {
SimpleTextEditor f = new SimpleTextEditor();
f.setSize(300, 200);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
} // main()
} // SimpleTextEditor
SimpleTextEditor
applicationthis.getContentPane().add(new JScrollPane(display));
JScrollPane
and adds it to the application’s container. A JScrollPane
is one of Swing’s scrollbar classes. Its function is to manage the viewing and scrolling of a scrollable component, such as a JTextArea
. A JScrollPane
is actually a container, which is why it takes the display
as an argument. The display
is being added to the JScrollPane
.Component
can be added to a JScrollPane
. Once a component is added, the scroll pane will manage the scrolling functions for the component. The default constructor used in this example takes a single Component
parameter. This refers to the scrollable component, in this case to the JTextArea
. Another constructor that you might use takes the following form:public JScrollPane(Component comp, int vsbPolicy,
int hsbPolicy);
SimpleText
Editor
, you would have to shrink the window to the point where all of the text cannot be viewed (Figure 13.8.7). Because the text area in this example is wrapping the text, the horizontal scrollbar will never be needed. addRecentCut()
method so it limits the cuts stored in the ArrayList to the last ten cuts.addRecentCut()
method so that it doesn’t duplicate cuts already stored in the ArrayList.indexOf(String)
method.