GUI tidak berfungsi setelah menulis ulang ke MVC

123

Saya berlatih pemrograman gaya MVC. Saya memiliki game Mastermind dalam satu file, berfungsi dengan baik (mungkin terlepas dari fakta bahwa tombol "Periksa" tidak terlihat di awal).

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

Tetapi ketika saya menulis ulang ke model, view, file controller - dan ketika saya mengklik Pin kosong (itu harus diperbarui, dan dicat ulang dengan warna baru) - pencatatan terjadi. Adakah yang bisa melihat masalah di sini? Saya telah mencoba menempatkan cat ulang () di tempat yang berbeda, tetapi tidak berhasil sama sekali: /

Utama:

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;
    }
}

Melihat:

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 );
    }
}

Pengontrol:

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)  {}
}
trevor_nise
sumber
5
Baik kode lama dan baru memiliki masalah terkait pencampuran komponen AWT dan Swing. Lihat juga stackoverflow.com/questions/2687871
trashgod
jadi masalah tanpa pembaruan mungkin disebabkan oleh itu?
trevor_nise
Saya telah menambahkan dan contoh yang dapat memandu desain ulang Anda.
trashgod

Jawaban:

147

Seperti yang telah Anda temukan, pola Model – View – Controller bukanlah obat mujarab, tetapi menawarkan beberapa keuntungan. Berakar di MVC , arsitektur model terpisah Swing dibahas dalam Tinjauan Arsitektur Swing . Berdasarkan garis besar ini , contoh berikut menunjukkan implementasi MVC dari game yang jauh lebih sederhana yang mengilustrasikan prinsip serupa. Perhatikan bahwa Modelmengelola satu Piece, dipilih secara acak. Menanggapi pilihan pengguna, Viewmemanggil check()metode tersebut, sambil mendengarkan respon dari Modelmelalui update(). The Viewkemudian update sendiri menggunakan informasi yang diperoleh dari Model. Demikian pula, Controllermungkin reset()yangModel. Secara khusus, tidak ada gambar di Modeldan tidak ada logika permainan di View. Permainan yang agak lebih kompleks ini dirancang untuk menggambarkan konsep yang sama.

Tambahan: Saya telah memodifikasi contoh asli untuk menunjukkan bagaimana MVC memungkinkan seseorang untuk meningkatkan Viewtanpa mengubah sifat file Model.

Tambahan: Seperti yang diamati @akf, MVC bergantung pada pola pengamat . Anda Modelmembutuhkan cara untuk memberi tahu Viewtentang perubahan. Beberapa pendekatan digunakan secara luas:

Tambahan: Beberapa pertanyaan umum tentang pengontrol Ayun dibahas di sini dan di sini .

Cuplikan Layar

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 /programming/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;
    }
}
tempat sampah
sumber
1
@trevor_nise: Saya telah memperbarui contoh di atas. Anda mungkin merasa berguna untuk membandingkan revisi.
trashgod
2
Bagi siapa pun yang penasaran, Fowler memasang artikel berikut pada tahun 2006: martinfowler.com/eaaDev/SeparatedPresentation.html
James P.
4
Lihat juga Desain Aplikasi Java SE Dengan MVC .
trashgod
20
Jawaban yang bagus, tetapi tampaknya agak aneh bagi saya seorang Controller yang mewarisi JPanel dan ditambahkan ke panel utama. Bukankah pengontrol seharusnya sesuatu yang logis dan dengan demikian tidak terlihat? Apa yang saya lewatkan?
miguelcobain
1
@miguelcobain: Pengamatan yang bagus; Saya ingin mengilustrasikan bagaimana pengontrol dapat mengubah tampilan dan model melalui implementasi terpisah dari pola di mana tombol menggabungkan tampilan dan model. Controltidak mengesampingkan metode JPanel, jadi pabrik statis mungkin lebih baik.
sampah
20

Saat melihat melalui Swing, salah satu cara desainer secara konsisten menerapkan pembaruan komponen View dalam implementasi MVC-nya adalah melalui callback Observer / Observable. Contohnya dapat dilihat di AbstractTableModel, yang memiliki berbagai fireTable*Changed/Updated/etcmetode yang akan mengingatkan semua TableModelListenerpengamat modnya ke model.

Salah satu opsi yang Anda miliki adalah menambahkan tipe listener ke Modelkelas Anda , lalu memberi tahu pengamat terdaftar Anda tentang mod apa pun ke status model Anda. Anda Viewharus menjadi pendengar, dan harus mengecat ulang dirinya sendiri setelah menerima pembaruan.

EDIT: +1 ke trashgod. anggap ini kata-kata alternatif untuk penjelasannya.

akf
sumber