2010-06-18 8 views
117

Praktykuję programowanie w stylu MVC. Mam grę Mastermind w jednym pliku, działa dobrze (może poza faktem, że przycisk "Sprawdź" jest niewidoczny na początku).GUI nie działa po przepisaniu na MVC

http://paste.pocoo.org/show/226726/

Ale kiedy mam przepisany jej model, widok, kontroler plików - i gdy klikam na pustym PIN (który powinien być aktualizowany i odmalowane z nowym kolorem) - zauważając dzieje. Czy ktoś może zobaczyć tutaj jakieś problemy? Próbowałem umieszczenie repaint() w różnych miejscach, ale to po prostu nie działa w ogóle:/

główne:

public class Main { 
    public static void main(String[] args){ 
     Model model = new Model(); 
     View view = new View("Mastermind", 400, 590, model); 
     Controller controller = new Controller(model, view); 
     view.setVisible(true); 
    } 
} 

Model:

import java.util.Random; 

public class Model{ 
    static final int 
    LINE = 5, 
    SCORE = 10, OPTIONS = 20; 
    Pin pins[][] = new Pin[21][LINE]; 
    int combination[] = new int[LINE]; 
    int curPin = 0; 
    int turn = 1; 
    Random generator = new Random(); 
    int repaintPin; 
    boolean pinsRepaint=false; 
    int pinsToRepaint; 
    boolean isUpdate = true, isPlaying = true, isRowFull = false; 
    static final int HIT_X[] = {270,290,310,290,310}, HIT_Y[] = {506,496,496,516,516}; 

    public Model(){ 

     for (int i=0; i < SCORE; i++){ 
      for (int j = 0; j < LINE; j++){ 
       pins[i][j] = new Pin(20,0); 
       pins[i][j].setPosition(j*50+30,510-i*50); 
       pins[i+SCORE][j] = new Pin(8,0); 
       pins[i+SCORE][j].setPosition(HIT_X[j],HIT_Y[j]-i*50); 
      } 
     } 
     for (int i=0; i < LINE; i++){ 
      pins[OPTIONS][i] = new Pin(20, i+2); 
      pins[OPTIONS][i].setPosition(370,i * 50 + 56); 
     } 

    } 

    void fillHole(int color) { 
     pins[turn-1][curPin].setColor(color+1); 
     pinsRepaint = true; 
     pinsToRepaint = turn; 
     curPin = (curPin+1) % LINE; 
     if (curPin == 0){ 
      isRowFull = true; 
     } 
     pinsRepaint = false; 
     pinsToRepaint = 0; 
    } 

    void check() { 
     int junkPins[] = new int[LINE], junkCode[] = new int[LINE]; 
     int pinCount = 0, pico = 0; 

     for (int i = 0; i < LINE; i++) { 
      junkPins[i] = pins[turn-1][i].getColor(); 
      junkCode[i] = combination[i]; 
     } 
     for (int i = 0; i < LINE; i++){ 
      if (junkPins[i]==junkCode[i]) { 
       pins[turn+SCORE][pinCount].setColor(1); 
       pinCount++; 
       pico++; 
       junkPins[i] = 98; 
       junkCode[i] = 99; 
      } 
     } 
     for (int i = 0; i < LINE; i++){ 
      for (int j = 0; j < LINE; j++) 
       if (junkPins[i]==junkCode[j]) { 
        pins[turn+SCORE][pinCount].setColor(2); 
        pinCount++; 
        junkPins[i] = 98; 
        junkCode[j] = 99; 
        j = LINE; 
      } 
     } 
     pinsRepaint = true; 
     pinsToRepaint = turn + SCORE; 
     pinsRepaint = false; 
     pinsToRepaint=0; 

     if (pico == LINE){ 
      isPlaying = false; 
     } 
     else if (turn >= 10){ 
       isPlaying = false; 
     } 
     else{ 
      curPin = 0; 
      isRowFull = false; 
      turn++; 
     } 
    } 

    void combination() { 
     for (int i = 0; i < LINE; i++){ 
      combination[i] = generator.nextInt(6) + 1; 
     } 
    } 
} 

class Pin{ 
    private int color, X, Y, radius; 

    public Pin(){ 
     X = 0; Y = 0; radius = 0; color = 0; 
    } 

    public Pin(int r,int c){ 
     X = 0; Y = 0; radius = r; color = c; 
    } 

    public int getX(){ 
     return X; 
    } 

    public int getY(){ 
     return Y; 
    } 

    public int getRadius(){ 
     return radius; 
    } 

    public void setRadius(int r){ 
     radius = r; 
    } 

    public void setPosition(int x,int y){ 
     this.X = x ; 
     this.Y = y ; 
    } 
    public void setColor(int c){ 
     color = c; 
    } 
    public int getColor() { 
     return color; 
    } 
} 

Widok:

import java.awt.*; 
import javax.swing.*; 

public class View extends Frame{ 
    Model model; 
    JButton checkAnswer; 
    private JPanel button; 
    private static final Color COLORS[] = {Color.black, Color.white, Color.red, Color.yellow, Color.green, Color.blue, new Color(7, 254, 250)}; 

    public View(String name, int w, int h, Model m){ 
     model = m; 
     setTitle(name); 
     setSize(w,h); 
     setResizable(false); 
     this.setLayout(new BorderLayout()); 

     button = new JPanel(); 
     button.setSize(new Dimension(400, 100)); 
     button.setVisible(true); 
     checkAnswer = new JButton("Check"); 
     checkAnswer.setSize(new Dimension(200, 30)); 
     button.add(checkAnswer); 
     this.add(button, BorderLayout.SOUTH); 
     button.setVisible(true); 
    } 

    @Override 
    public void paint(Graphics g) { 
     g.setColor(new Color(238, 238, 238)); 
     g.fillRect(0,0,400,590); 

     for (int i=0; i < model.pins.length; i++) { 
      paintPins(model.pins[i][0],g); 
      paintPins(model.pins[i][1],g); 
      paintPins(model.pins[i][2],g); 
      paintPins(model.pins[i][3],g); 
      paintPins(model.pins[i][4],g); 
     } 
    } 

    @Override 
    public void update(Graphics g) { 
     if (model.isUpdate) { 
      paint(g); 
     } 
     else { 
      model.isUpdate = true; 
      paintPins(model.pins[model.repaintPin-1][0],g); 
      paintPins(model.pins[model.repaintPin-1][1],g); 
      paintPins(model.pins[model.repaintPin-1][2],g); 
      paintPins(model.pins[model.repaintPin-1][3],g); 
      paintPins(model.pins[model.repaintPin-1][4],g); 
     } 
    } 

    void repaintPins(int pin) { 
     model.repaintPin = pin; 
     model.isUpdate = false; 
     repaint(); 
    } 

    public void paintPins(Pin p, Graphics g){ 
     int X = p.getX(); 
     int Y = p.getY(); 
     int color = p.getColor(); 
     int radius = p.getRadius(); 
     int x = X-radius; 
     int y = Y-radius; 

     if (color > 0){ 
      g.setColor(COLORS[color]); 
      g.fillOval(x,y,2*radius,2*radius); 
     } 
     else{ 
      g.setColor(new Color(238, 238, 238)); 
      g.drawOval(x,y,2*radius-1,2*radius-1); 
     } 
     g.setColor(Color.black); 
     g.drawOval(x,y,2*radius,2*radius); 
    } 
} 

Kontroler:

import java.awt.*; 
import java.awt.event.*; 

public class Controller implements MouseListener, ActionListener { 
    private Model model; 
    private View view; 

    public Controller(Model m, View v){ 
     model = m; 
     view = v; 

     view.addWindowListener(new WindowAdapter(){ 
      public void windowClosing(WindowEvent e){ 
      System.exit(0); 
     } }); 
     view.addMouseListener(this); 
     view.checkAnswer.addActionListener(this); 
     model.combination(); 
    } 

    public void actionPerformed(ActionEvent e) { 
     if(e.getSource() == view.checkAnswer){ 
      if(model.isRowFull){ 
       model.check(); 
      } 
     } 
    } 

    public void mousePressed(MouseEvent e) { 
     Point mouse = new Point(); 

     mouse = e.getPoint(); 
     if (model.isPlaying){ 
      if (mouse.x > 350) { 
       int button = 1 + (int)((mouse.y - 32)/50); 
       if ((button >= 1) && (button <= 5)){ 
        model.fillHole(button); 
        if(model.pinsRepaint){ 
         view.repaintPins(model.pinsToRepaint); 
        } 
       } 
      } 
     } 
    } 

    public void mouseClicked(MouseEvent e) {} 
    public void mouseReleased(MouseEvent e){} 
    public void mouseEntered(MouseEvent e) {} 
    public void mouseExited(MouseEvent e) {} 
} 
+5

Zarówno stary jak i nowy kod mają problemy związane z mieszaniem komponentów AWT i Swing. Zobacz także http://stackoverflow.com/questions/2687871 – trashgod

+0

, więc problem z brakiem aktualizacji może być spowodowany przez to? –

+0

Tak. http://java.sun.com/products/jfc/tsc/articles/mixing/ – trashgod

Odpowiedz

142

Jak już odkryłeś, wzór Model–View–Controller nie jest panaceum, ale oferuje pewne zalety. Zrootowana w architekturze MVC, architektura modelu Swing do oddzielania jest omówiona w A Swing Architecture Overview. Bazując na this outline, poniższy przykład pokazuje implementację znacznie prostszej gry, która ilustruje podobne zasady. Zauważ, że Model zarządza pojedynczym Piece, wybranym losowo. W odpowiedzi na wybór użytkownika, View wywołuje metodę check() podczas odsłuchiwania odpowiedzi od Model przez update(). The View następnie aktualizuje się za pomocą informacji uzyskanych z Model. Podobnie, Controller może reset() the the . W szczególności nie ma rysunku w Model i żadnej logiki gry w View. Ten nieco bardziej złożony model game został zaprojektowany w celu zilustrowania tych samych pojęć.

Dodatek: Zmodyfikowałem oryginalny przykład, aby pokazać, w jaki sposób MVC pozwala ulepszyć View bez zmiany charakteru Model.

Dodatek: jak zauważa @akf, MVC zależy od observer pattern. Twój Model potrzebuje sposobu, aby powiadomić o zmianach View. Różne podejścia są szeroko stosowane:

Uzupełnienie: typowe pytania dotyczące kontrolerów Swing są adresowane here i here.

screen capture

import java.awt.BorderLayout; 
import java.awt.Color; 
import java.awt.Component; 
import java.awt.EventQueue; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.RenderingHints; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.util.Observable; 
import java.util.Observer; 
import java.util.Random; 
import javax.swing.Icon; 
import javax.swing.JButton; 
import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.JPanel; 

/** 
* @see https://stackoverflow.com/q/3066590/230513 
* 15-Mar-2011 r8 https://stackoverflow.com/questions/5274962 
* 26-Mar-2013 r17 per comment 
*/ 
public class MVCGame implements Runnable { 

    public static void main(String[] args) { 
     EventQueue.invokeLater(new MVCGame()); 
    } 

    @Override 
    public void run() { 
     JFrame f = new JFrame(); 
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     f.add(new MainPanel()); 
     f.pack(); 
     f.setLocationRelativeTo(null); 
     f.setVisible(true); 
    } 
} 

class MainPanel extends JPanel { 

    public MainPanel() { 
     super(new BorderLayout()); 
     Model model = new Model(); 
     View view = new View(model); 
     Control control = new Control(model, view); 
     JLabel label = new JLabel("Guess what color!", JLabel.CENTER); 
     this.add(label, BorderLayout.NORTH); 
     this.add(view, BorderLayout.CENTER); 
     this.add(control, BorderLayout.SOUTH); 
    } 
} 

/** 
* Control panel 
*/ 
class Control extends JPanel { 

    private Model model; 
    private View view; 
    private JButton reset = new JButton("Reset"); 

    public Control(Model model, View view) { 
     this.model = model; 
     this.view = view; 
     this.add(reset); 
     reset.addActionListener(new ButtonHandler()); 
    } 

    private class ButtonHandler implements ActionListener { 

     @Override 
     public void actionPerformed(ActionEvent e) { 
      String cmd = e.getActionCommand(); 
      if ("Reset".equals(cmd)) { 
       model.reset(); 
      } 
     } 
    } 
} 

/** 
* View 
*/ 
class View extends JPanel { 

    private static final String s = "Click a button."; 
    private Model model; 
    private ColorIcon icon = new ColorIcon(80, Color.gray); 
    private JLabel label = new JLabel(s, icon, JLabel.CENTER); 

    public View(Model model) { 
     super(new BorderLayout()); 
     this.model = model; 
     label.setVerticalTextPosition(JLabel.BOTTOM); 
     label.setHorizontalTextPosition(JLabel.CENTER); 
     this.add(label, BorderLayout.CENTER); 
     this.add(genButtonPanel(), BorderLayout.SOUTH); 
     model.addObserver(new ModelObserver()); 
    } 

    private JPanel genButtonPanel() { 
     JPanel panel = new JPanel(); 
     for (Piece p : Piece.values()) { 
      PieceButton pb = new PieceButton(p); 
      pb.addActionListener(new ButtonHandler()); 
      panel.add(pb); 
     } 
     return panel; 
    } 

    private class ModelObserver implements Observer { 

     @Override 
     public void update(Observable o, Object arg) { 
      if (arg == null) { 
       label.setText(s); 
       icon.color = Color.gray; 
      } else { 
       if ((Boolean) arg) { 
        label.setText("Win!"); 
       } else { 
        label.setText("Keep trying."); 
       } 
      } 
     } 
    } 

    private class ButtonHandler implements ActionListener { 

     @Override 
     public void actionPerformed(ActionEvent e) { 
      PieceButton pb = (PieceButton) e.getSource(); 
      icon.color = pb.piece.color; 
      label.repaint(); 
      model.check(pb.piece); 
     } 
    } 

    private static class PieceButton extends JButton { 

     Piece piece; 

     public PieceButton(Piece piece) { 
      this.piece = piece; 
      this.setIcon(new ColorIcon(16, piece.color)); 
     } 
    } 

    private static class ColorIcon implements Icon { 

     private int size; 
     private Color color; 

     public ColorIcon(int size, Color color) { 
      this.size = size; 
      this.color = color; 
     } 

     @Override 
     public void paintIcon(Component c, Graphics g, int x, int y) { 
      Graphics2D g2d = (Graphics2D) g; 
      g2d.setRenderingHint(
       RenderingHints.KEY_ANTIALIASING, 
       RenderingHints.VALUE_ANTIALIAS_ON); 
      g2d.setColor(color); 
      g2d.fillOval(x, y, size, size); 
     } 

     @Override 
     public int getIconWidth() { 
      return size; 
     } 

     @Override 
     public int getIconHeight() { 
      return size; 
     } 
    } 
} 

/** 
* Model 
*/ 
class Model extends Observable { 

    private static final Random rnd = new Random(); 
    private static final Piece[] pieces = Piece.values(); 
    private Piece hidden = init(); 

    private Piece init() { 
     return pieces[rnd.nextInt(pieces.length)]; 
    } 

    public void reset() { 
     hidden = init(); 
     setChanged(); 
     notifyObservers(); 
    } 

    public void check(Piece guess) { 
     setChanged(); 
     notifyObservers(guess.equals(hidden)); 
    } 
} 

enum Piece { 

    Red(Color.red), Green(Color.green), Blue(Color.blue); 
    public Color color; 

    private Piece(Color color) { 
     this.color = color; 
    } 
} 
+1

@trevor_nise: Zaktualizowałem powyższy przykład. Przydatne może okazać się porównanie wersji. – trashgod

+0

+1 Zastanawiasz się, jak można mieszać MVC z n-tier. –

+2

Dla każdego, kto jest ciekawy Fowler umieścić następujący artykuł w 2006 roku: http://martinfowler.com/eaaDev/SeparatedPresentation.html –

19

Gdy pominie Swing jeden sposób, że projektanci konsekwentnie zatrudniają aktualizacji widzenia elementów w jego realizacji MVC jest przez Obserwatora/obserwowalnych wywołań zwrotnych. Przykład można zobaczyć w AbstractTableModel, który ma różne metody , które zaalarmują wszystkich jego obserwatorów modów TableModelListener do modelu.

Jedną z opcji jest dodanie typu słuchacza do klasy Model, a następnie powiadom zarejestrowanych obserwatorów o wszelkich modyfikacjach do stanu modelu. Twój View powinien być słuchaczem i powinien odświeżyć się po otrzymaniu aktualizacji.

EDYCJA: +1 do trashgod. rozważ to alternatywne sformułowanie do swojego wyjaśnienia.

Powiązane problemy