GUI không hoạt động sau khi viết lại thành MVC


123

Tôi đang thực hành lập trình kiểu MVC. Tôi có một trò chơi Mastermind trong một tệp duy nhất, hoạt động tốt (có thể ngoài thực tế là nút "Kiểm tra" là vô hình khi bắt đầu).

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

Nhưng khi tôi viết lại nó thành mô hình, xem, tập tin bộ điều khiển - và khi tôi nhấp vào Ghim trống (cần được cập nhật và sơn lại bằng màu mới) - điều đáng chú ý xảy ra. Bất cứ ai có thể nhìn thấy bất kỳ vấn đề ở đây? Tôi đã thử đặt sơn lại () ở những nơi khác nhau, nhưng đơn giản là nó không hoạt động: /

Chủ yếu :

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

Mô hình :

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

Lượt xem:

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

Điều khiển:

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
Cả mã cũ và mã mới đều có vấn đề liên quan đến việc trộn các thành phần AWT và Swing. Xem thêm stackoverflow.com/questions/2687871
thùng rác

Vì vậy, vấn đề không có cập nhật có thể được gây ra bởi điều đó?
trevor_nise


Tôi đã thêm và ví dụ có thể hướng dẫn thiết kế lại của bạn.
thùng rác

Câu trả lời:


147

Như bạn đã phát hiện ra, mẫu Trình điều khiển Chế độ Xem mô hình không phải là thuốc chữa bách bệnh, nhưng nó cung cấp một số lợi thế. Bắt nguồn từ MVC , kiến ​​trúc mô hình có thể tách rời được thảo luận trong Tổng quan về Kiến trúc xoay . Dựa trên phác thảo này , ví dụ sau đây cho thấy việc triển khai MVC của một trò chơi đơn giản hơn nhiều minh họa các nguyên tắc tương tự. Lưu ý rằng việc Modelquản lý một đơn Piece, được chọn ngẫu nhiên. Để đáp ứng với lựa chọn của người dùng, người ta Viewgọi check()phương thức trong khi lắng nghe phản hồi từ Modelthông qua update(). Sau Viewđó tự cập nhật bằng cách sử dụng thông tin thu được từ Model. Tương tự, Controllercó thể reset()cácModel. Đặc biệt, không có bản vẽ trong Modelvà không có logic trò chơi trong View. Trò chơi có phần phức tạp hơn này được thiết kế để minh họa các khái niệm tương tự.

Phụ lục: Tôi đã sửa đổi ví dụ ban đầu để cho thấy cách MVC cho phép một người nâng cao Viewmà không thay đổi bản chất của Model.

Phụ lục: Như @akf quan sát, bản lề MVC trên mẫu quan sát viên . Bạn Modelcần một cách để thông báo Viewvề những thay đổi. Một số phương pháp được sử dụng rộng rãi:

  • Trong ví dụ dưới đây, Modelmở rộng Observablecho đơn giản.

  • Một cách tiếp cận phổ biến hơn sử dụng một EventListenerList, như được hiển thị trong Converterứng dụng và được đề xuất bởi số lượng lớn các giao diện con EventListenervà các lớp triển khai.

  • Tùy chọn thứ ba là sử dụng một PropertyChangeListener, như được hiển thị ở đâyở đây .

Phụ lục: Một số câu hỏi phổ biến về bộ điều khiển xoay được giải quyết ở đâyđây .

chụp màn hình

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

1
@trevor_nise: Tôi đã cập nhật ví dụ trên. Bạn có thể thấy hữu ích để so sánh các phiên bản.
thùng rác

2
Đối với bất kỳ ai tò mò, Fowler đã đưa ra bài viết sau vào năm 2006: martinfowler.com/eaaDev/SeparatedPftimeation.html
James P.


20
Câu trả lời tuyệt vời, nhưng có vẻ hơi lạ đối với tôi, một Người điều khiển kế thừa JPanel và được thêm vào bảng điều khiển chính. Bộ điều khiển không phải là một cái gì đó hợp lý và do đó không thể nhìn thấy? Tôi đang thiếu gì?
Miguelcobain

1
@miguelcobain: Quan sát tốt; Tôi muốn minh họa cách bộ điều khiển có thể thay đổi cả chế độ xem và mô hình thông qua triển khai mẫu riêng biệt trong đó nút kết hợp chế độ xem và mô hình. Controlghi đè không có phương pháp nào JPanel, vì vậy một nhà máy tĩnh có thể tốt hơn.
thùng rác

20

Khi xem qua Swing, một cách mà các nhà thiết kế luôn sử dụng cập nhật các thành phần View trong triển khai MVC của nó là thông qua các cuộc gọi lại Observer / Observable. Một ví dụ có thể được nhìn thấy trong AbstractTableModelđó, có nhiều fireTable*Changed/Updated/etcphương thức sẽ cảnh báo tất cả các TableModelListenerquan sát viên của mod về mô hình.

Một tùy chọn bạn có là thêm một loại trình nghe vào Modellớp của bạn , sau đó thông báo cho người quan sát đã đăng ký của bạn về bất kỳ mod nào cho trạng thái của mô hình của bạn. Bạn Viewnên là người nghe và nó sẽ tự sơn lại khi nhận được bản cập nhật.

EDIT: +1 vào thùng rác. coi đây là một từ ngữ thay thế cho lời giải thích của mình.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.