Advanced Uses of the Observer Pattern in Java
Multicasting, Property Changes, and Other Issues |
Prof. David Bernstein |
Computer Science Department |
bernstdh@jmu.edu |
import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * A Component that can be used to enter secret combinations * using the keyboard. A combination can consist of any number * of characters. The combination is never displayed. * Users indicates that they are done by pressing [Enter]. * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class CombinationField extends JPanel implements KeyListener { private static final long serialVersionUID = 1L; private ActionListener actionListener = null; Color background; private int id = ActionEvent.ACTION_FIRST; private JLabel label; private String combination; /** * Default Constructor. */ public CombinationField() { super(); setLayout(new BorderLayout()); label = new JLabel("", SwingConstants.CENTER); add(label, BorderLayout.CENTER); background = getBackground(); reset(); setFocusable(true); requestFocusInWindow(); } /** * Add an ActionListener to this Component. The ActionListener * will be informed when the user presses [Enter]. * * @param l The ActionListener to add */ public synchronized void addActionListener(ActionListener l) { actionListener = AWTEventMulticaster.add(actionListener, l); } /** * Fire an ActionEvent to all listeners. */ private void fireEvent() { ActionEvent ae = new ActionEvent(this, id, "CombinationEntered"); id++; actionListener.actionPerformed(ae); } /** * Get the most recent combination that was entered * since the last call to reset(). * * @return The combination */ public String getCombination() { String result = combination; combination = ""; return result; } /** * Handle keyTyped messages. * * @param e The event that generated the message */ public void keyTyped(KeyEvent e) { } /** * Handle keyPressed messages. * * @param e The event that generated the message */ public void keyPressed(KeyEvent e) { setBackground(Color.YELLOW); repaint(); Toolkit.getDefaultToolkit().beep(); } /** * Handle keyReleased messages. * * @param e The event that generated the message */ public void keyReleased(KeyEvent e) { setBackground(background); int code = e.getKeyCode(); if (code == KeyEvent.VK_ENTER) { removeKeyListener(this); setText("Disabled"); if (actionListener != null) fireEvent(); } else { combination += e.getKeyChar(); } } /** * Remove an ActionListener from this Component. * * @param l The ActionListener to remove */ public synchronized void removeActionListener(ActionListener l) { actionListener = AWTEventMulticaster.remove(actionListener, l); } /** * Reset the state of this Component. */ public void reset() { combination = ""; addKeyListener(this); label.setText("Enter A Combination"); } /** * Set the text on this Component. * * @param text The text */ public void setText(String text) { label.setText(text); } }
import java.awt.*; import java.beans.*; /* * An encapsulation of a rectangle. * * This implementation differs in significant ways from both the Rectangle class * and the Rectangle2D inner classes. Perhaps most importantly, it's * attributes are private so that it can serve as a property change subject. * * @author Prof. David Bernstein, James Madison Univerity * @version 1.0 */ public class Rectangle { private static final int SQUARE_WIDTH = 3; private static final int SQUARE_GAP = 1; private boolean selected; private int height, width, x, y; private PropertyChangeSupport support; /** * Explicit value constructor. * * @param x The x-coordinate of the upper-left corner * @param y The y-coordinate of the upper-left corner * @param width The width * @param height The height */ public Rectangle(int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; this.height = height; selected = false; support = new PropertyChangeSupport(this); } /** * Add a PropertyChangeListener to this Rectangle. * The given object will actually listen to four different properties: * x, y, width, and height. * * @param listener The listener to add */ public void addPropertyChangeListener(PropertyChangeListener listener) { support.addPropertyChangeListener("x", listener); support.addPropertyChangeListener("y", listener); support.addPropertyChangeListener("width", listener); support.addPropertyChangeListener("height", listener); } /** * Create a square visual grabber (for dragging a corner of this Rectangle). * * @param x The x-coordinate of the point of interest * @param y The y-coordinate of the point of interest * @param deltaX The horizontal offset (-1 for West, 1 for East) * @param deltaY The vertical offset (-1 for North, 1 for South) * @return */ private int[] createGrabber(int x, int y, int deltaX, int deltaY) { int x1 = x + deltaX * SQUARE_GAP; int y1 = y + deltaY * SQUARE_GAP; int x2 = x1 + deltaX * SQUARE_WIDTH; int y2 = y1 + deltaY * SQUARE_WIDTH; int sx = Math.min(x1, x2); int sy = Math.min(y1, y2); int[] result = {sx, sy, SQUARE_WIDTH, SQUARE_WIDTH}; return result; } /** * Return true if this Rectangle contains the given Point. * * @param p The Point of interest * @return true if p is contained in this Rectangle; false otherwise */ public boolean contains(Point p) { return (p.x >= x) && (p.x <= x + width) && (p.y >= y) && (p.y <= y + height); } /** * Get the four corners for this Rectangle. * * @return The four corners; */ public Point[] corners() { Point[] result = new Point[4]; result[0] = new Point(x, y); // Upper-left result[1] = new Point(x + width, y); // Upper-right result[2] = new Point(x + width, y + height); // Lower-left result[3] = new Point(x, y + height); // Lower-left return result; } /** * Draw a rectangular area. * * @param g The rendering engine to use * @param r The x, y, width, and height of the rectangular area */ private void drawRect(Graphics g, int...r ) { g.drawRect(r[0], r[1], r[2], r[3]); } /** * Fill a rectangular area. * * @param g The rendering engine to use * @param r The x, y, width, and height of the rectangular area */ private void fillRect(Graphics g, int...r ) { g.fillRect(r[0], r[1], r[2], r[3]); } /** * Get the height. * * @return The height */ public int getHeight() { return height; } /** * Get the width. * * @return The width */ public int getWidth() { return width; } /** * Get the x-coordinate of the upper-left corner. * * @return The x-coordinate */ public int getX() { return x; } /** * Get the y-coordinate of the upper-left corner. * * @return The x-coordinate */ public int getY() { return y; } /** * Return true if this Rectangle is currently selected. * * @return true if selected; false otherwise */ public boolean isSelected() { return selected; } /** * Render this Rectangle. * * @param g The rendering engine to use */ public void paint(Graphics g) { drawRect(g, x, y, width, height); if (selected) { Color old = g.getColor(); g.setColor(Color.BLUE); paintGrabbers(g, x, y); paintGrabbers(g, x+width, y); paintGrabbers(g, x+width, y+height); paintGrabbers(g, x, y+height); g.setColor(old); } } /** * Render the visual grabbers for a particular point/corner. * * @param g The rendering engine to use * @param x The x-coordinate of the point/corner * @param y The y-coordinate of the point/corner */ private void paintGrabbers(Graphics g, int x, int y) { fillRect(g, createGrabber(x, y, -1, -1)); // NORTH_WEST grabber fillRect(g, createGrabber(x, y, -1, 1)); // NORTH_EAST grabber fillRect(g, createGrabber(x, y, 1, 1)); // SOUTH_EAST grabber fillRect(g, createGrabber(x, y, 1, -1)); // SOUTH_WEST grabber } /** * Remove a PropertyChangeListener (for all properties). * * @param listener The listener to remove. */ public void removePropertyChangeListener(PropertyChangeListener listener) { support.removePropertyChangeListener(listener); } /** * Select this Rectangle or not. * * @param selected true to select; false to unselect */ public void setSelected(boolean selected) { this.selected = selected; } /** * Set the height. * * @param height The height */ public void setHeight(int height) { support.firePropertyChange("height", this.height, height); this.height = height; } /** * Set the width. * * @param width The width */ public void setWidth(int width) { support.firePropertyChange("width", this.width, width); this.width = width; } /** * Set the x-coordinate of the upper-left corner. * * @param x The x-coordinate */ public void setX(int x) { support.firePropertyChange("x", this.x, x); this.x = x; } /** * Set the y-coordinate of the upper-left corner. * * @param y The y-coordinate */ public void setY(int y) { support.firePropertyChange("y", this.y, y); this.y = y; } }
import java.beans.*; import javax.swing.*; /** * A PropertyLabel is a JLabel that responds to appropriate * propertyChange messages by changing its text. * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class PropertyLabel extends JLabel implements PropertyChangeListener { private static final long serialVersionUID = 1L; private String property; /** * Explicit value constructor. * * @param property The property being displayed * @param value The initial value to display */ public PropertyLabel(String property, Object value) { super(value.toString()); this.property = property; setBorder(BorderFactory.createTitledBorder(property)); } /** * Handle propertyChange messages. * * @param evt The event that generated the message */ public void propertyChange(PropertyChangeEvent evt) { if (property.equals(evt.getPropertyName())) { String text = evt.getNewValue().toString(); setText(text); } } }
import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * A RectanglePanel can be used to display and edit a Rectangle. * * @author Prof. David Bernstein, James Madison University * */ public class RectanglePanel extends JPanel implements MouseListener, MouseMotionListener { private static final long serialVersionUID = 1L; private int draggedCorner; private Rectangle rect; /** * Explicit value constructor. * * @param rect The Rectangle to display/edit */ public RectanglePanel(Rectangle rect) { this.rect = rect; addMouseListener(this); addMouseMotionListener(this); } /** * Handle mouseClicked messages. * * @param e The event that generated the message */ public void mouseClicked(MouseEvent e) { Point p = e.getPoint(); if (!rect.isSelected()) { if (rect.contains(p)) rect.setSelected(true); else rect.setSelected(false); repaint(); } } /** * Handle mouseDragged messages. * * @param e The event that generated the message */ public void mouseDragged(MouseEvent e) { if (rect.isSelected()) { Point finish = e.getPoint(); int xMin = rect.getX(); int yMin = rect.getY(); int xMax = xMin + rect.getWidth(); int yMax = yMin + rect.getHeight(); if (draggedCorner == 0) { xMin = finish.x; yMin = finish.y; } else if (draggedCorner == 1) { xMax = finish.x; yMin = finish.y; } else if (draggedCorner == 2) { xMax = finish.x; yMax = finish.y; } else if (draggedCorner == 3) { xMin = finish.x; yMax = finish.y; } rect.setX(Math.min(xMin, xMax)); rect.setY(Math.min(yMin, yMax)); rect.setWidth(Math.abs(xMax - xMin)); rect.setHeight(Math.abs(yMax - yMin)); repaint(); } } /** * Handle mouseEntered messages. * * @param e The event that generated the message */ public void mouseEntered(MouseEvent e) { } /** * Handle mouseExited messages. * * @param e The event that generated the message */ public void mouseExited(MouseEvent e) { } /** * Handle mouseMoved messages. * * @param e The event that generated the message */ public void mouseMoved(MouseEvent e) { } /** * Handle mousePressed messages. * * @param e The event that generated the message */ public void mousePressed(MouseEvent e) { if (rect.isSelected()) { Point p = e.getPoint(); Point[] corners = rect.corners(); double min = Double.POSITIVE_INFINITY; for (int i=0; i<corners.length; i++) { double d = p.distance(corners[i]); if (d < min) { min = d; draggedCorner = i; } } } } /** * Handle mouseReleased messages. * * @param e The event that generated the message */ public void mouseReleased(MouseEvent e) { if (rect.isSelected()) { rect.setSelected(false); repaint(); } } /** * Render this component. * * @param g The rendering engine to use */ public void paint(Graphics g) { super.paint(g); rect.paint(g); } }
Observer
and Observable
?