3

MVC パターンに従って GUI を開発しています。

-GUIview: Swing コンポーネント (JFrame といくつかの JTables)。-GUIcontroller: リスナー (ここに追加され、内部クラスで定義されています) -GUImodel: データの変更と保存、変更イベントの起動。

モデルの変更は、この例のように、コントローラーを介して (直接ではなく) ビューに渡されます。

また、View クラスに含まれるさまざまな JTable に対して、さまざまなカスタマイズされた JTableModel (AbstractTableModel を拡張) を作成しました。すべての JTableModel は、パッケージ「GUImodel」内の異なるクラスで定義されています。各 JTableModel は、ArrayList と、ArrayList を操作するいくつかのメソッドを定義します。

MVC のガイドラインによると、モデルはビューについて何も認識してはなりません。実際、main() メソッドは次のように定義されています。

GUImodel model = new GUImodel();
GUIcontroller controller = new GUIcontroller();
GUIview view = new GUIview(controller, model);

controller.addView(view);
controller.addModel(model);

view.setVisible(true);
controller.addControllerListerners();

私の問題は次のとおりです。GUImodel 内でメソッドを実行している場合 (たとえば、JButton が押され、外部ファイルからデータをロードする必要があるため)、いくつかの JTableModels を変更する必要があります (その ArrayList にデータ/行を追加するため)。 JTable に反映された変更を取得します。私の最初のアイデアは次のとおりです。

ArrayList newArrayList = fileLoader(filePath);  //create ArrayList and load info
guiView.getTable1Model().updateArrayList(newArrayList);  //update JTableModel ArrayList

ただし、GUImodel は GUIview から完全に独立している必要があるため、このアプローチは有効ではありません。

何か案が?

4

4 に答える 4

3

MVC は主にデータのカプセル化に関係するパターンであり、Observer という別のパターンを使用して変更を伝達することを理解しておくとよいでしょう。モデルはデータ カプセル化器として、ビューとコントローラーについて何も認識しませんが、オブザーバブルとして、変更が発生したときに通知する必要があるオブザーバーがあることを認識します。

Smalltalk-80システムのModel-View-Controllerユーザーインターフェースパラダイムの説明、ページ4はそれをよく説明しています:

変更通知を管理するために、依存オブジェクトとしてのオブジェクトの概念が開発されました。モデルのビューとコントローラーは、モデルの依存関係としてリストに登録され、モデルの一部の側面が変更されるたびに通知されます。モデルが変更されると、メッセージがブロードキャストされ、すべての従属モデルに変更が通知されます。このメッセージは (引数を使用して) パラメーター化できるため、多くの種類のモデル変更メッセージが存在する可能性があります。各ビューまたはコントローラーは、適切なモデルの変更に適切な方法で応答します。

概念を説明するために、独自の Observer/Observable クラスから始めることができます。

public interface Observer {
    public void update(int message);
}
public interface Observable {
    public void registerObserver(Observer observer);
}

public class Model implements Observable {
    List<Observer> observers;

    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    public void loadFile(String path) {
        // load file and change data
        foreach (Observer observer: observers)
            observer.update(READ_NEW_DATA);
    }

    public ArrayList getData() { return data; }
}

public class View implements Observer {
    public void update(int message) {
        doWhateverWith(model.getData());
    }
}

public class Controller implements Observer {
    public void update(int message) {
        doWhateverWith(model.getData());
    }

    public void onClick() {
        model.loadFile("someFile");
    }
}

ご覧のとおり、モデルはビューとコントローラーの内部動作について何も知りません。ArrayList を返すことが彼らにとって特に役立つかどうかさえわかりません (ただし、実際にはそのようにしたいと思います)。したがって、この点で、独立が達成されます。

Obervable と Observers の間の通信には独立性はありませんが、それは MVC パターンの要件の一部ではありません。

GUI を既存の Swing Observer パターン (リスナー) の上にヒッチハイクさせたい場合、クラスは適切なクラスから継承する必要があります。

public class Model extends AbstractTableModel...

public class View implements TableModelListener...

public class Controller implements CellEditorListener...

など。JTable は TableModelListener と CellEditorListener の両方を実装しているため、実際には View と Controller の複合体です。したがって、結合された ViewController クラスで JTable を拡張するか、それらを別々に持つかを選択できます。後者の場合、View は JTable を拡張し、コントロール Listener をオーバーライドして、イベントを Controller クラスに渡すことができます。しかし、それは価値がある以上の仕事のように思えます。

于 2013-06-10T19:04:31.897 に答える
2

Swing での MVC の私のスタイルは、モデルとビューがコントローラーと同様にお互いに気づかないことですが、コントローラーはビューとモデルをよく知っています。このようにして、コントローラーですべてのロジックを実行します。UI + 複雑なレイアウトの長いコードをビューに残し、アプリケーションがモデルに必要とするすべてのデータを考え、特定のデータをビューに表示するかどうかを決定しました。ボタンなどにリスナーを追加する機能を、view.getBtn().setAction(new ActionForThisOrThatInnerClass()) ある種のものを介してコントローラーに配置しました

あなたの場合、テーブルが使用するデータは、理想的には a の形式でメインモデルに保存する必要があることに同意しますが、それらのデータを処理するために a new をサブクラス化することはList気にしません。多くのことをするのに十分です。TableModelDefaultTableModel

要件をコーディングする場合の実行可能な例を次に示します

public class Sample {
    public static void main(String[] args){
        View view = new View();
        Model model = new Model();
        Controller controller = new Controller(view, model);

        JFrame frame = new JFrame("MVC Demo");
        frame.getContentPane().setLayout(new BorderLayout());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(view.getUI());
        frame.pack();
        frame.setVisible(true);

        view.getBtnFileLoader().doClick();
    }
}

class View{

    private JButton btnFileChooser;
    private JButton btnFileLoader;
    private JTable tblData;
    private JPanel pnlMain;

    public View(){
        pnlMain = new JPanel(new BorderLayout()){
            @Override public Dimension getPreferredSize(){
                return new Dimension(300, 400); 
            }
        };
        JPanel pnlFileLoader = new JPanel();
        pnlFileLoader.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        pnlFileLoader.setLayout(new BoxLayout(pnlFileLoader, BoxLayout.LINE_AXIS));

        JTextField txtFileDir = new JTextField();
        pnlFileLoader.add(txtFileDir);

        btnFileLoader = new JButton();
        pnlFileLoader.add(btnFileLoader);

        btnFileChooser = new JButton();
        pnlFileLoader.add(btnFileChooser);

        tblData = new JTable();
        JScrollPane pane = new JScrollPane(tblData);

        pnlMain.add(pane);
        pnlMain.add(pnlFileLoader, BorderLayout.PAGE_START);
    }

    public JPanel getUI(){
        return pnlMain;
    }

    public JButton getBtnFileLoader(){
        return btnFileLoader;
    }

    public JButton getBtnFileChooser(){
        return btnFileChooser;
    }

    public JTable getTblData(){
        return tblData;
    }
}

class Controller implements PropertyChangeListener{

    private View view;
    private Model model;
    private DefaultTableModel tmodel;

    public Controller(View view, Model model){
        this.view = view;
        this.model = model;

        model.addModelListener(this);
        setupViewEvents();
        setupTable();
    }
    private void setupTable(){
        tmodel = new DefaultTableModel();

        tmodel.addColumn("First Name");
        tmodel.addColumn("Last Name");
        tmodel.addColumn("Occupation");

        view.getTblData().setModel(tmodel);
    }

    private void setupViewEvents(){
        view.getBtnFileChooser().setAction(new AbstractAction("Choose"){
            @Override
            public void actionPerformed(ActionEvent arg0) {
                //choose the file then put the dir
                //in the txtfield
            }
        });

        view.getBtnFileLoader().setAction(new AbstractAction("Load"){
            @Override
            public void actionPerformed(ActionEvent arg0) {
                //validate if the dir in the textfield exists and the file is loadable
                //load the file specified in the textfield

                //assumming the list is already retrieved from the file
                //and the list contains the following person
                List<Person> list = new ArrayList<Person>();
                Person p1 = new Person("Bernardo", "Santos", "Developer");
                Person p2 = new Person("Robert", "Erasquin", "Architect");
                Person p3 = new Person("Klarrise", "Caparas", "Food Scientist");
                list.add(p1);
                list.add(p2);
                list.add(p3);

                //now update the model of the new value for the list
                model.setTheList(list);

            }
        });

    }

    @Override
    @SuppressWarnings("unchecked")
    public void propertyChange(PropertyChangeEvent evt) {
        if(evt.getPropertyName().equals("theList")){

            List<Person> newVal = (List<Person>) evt.getNewValue();
            DefaultTableModel tmodel = (DefaultTableModel)view.getTblData().getModel();

            for(Person p : newVal){
                tmodel.addRow(new Object[]{p.getFirstName(), p.getLastName(), p.getOccupation()});
            }

        }
    }
}



class Model{

    private List<Person> theList;
    private SwingPropertyChangeSupport propChangeFirer;

    public Model(){
        propChangeFirer = new SwingPropertyChangeSupport(this);
    }

    public void setTheList(List<Person> theList){
        List<Person> oldVal = this.theList;
        this.theList = theList;

        //after the model has been updated, notify its listener about
        //the update, in our case the controller itself listens to the model
        propChangeFirer.firePropertyChange("theList", oldVal, theList);
    }

    public void addModelListener(PropertyChangeListener prop) {
        propChangeFirer.addPropertyChangeListener(prop);
    }

}

class Person{
        private String firstName;
        private String lastName;
        private String occupation;

        public Person(String firstName, String lastName, String occupation){
            this.firstName = firstName;
            this.lastName = lastName;
            this.occupation = occupation;
        }

        public String getFirstName() {
            return firstName;
        }
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
        public String getLastName() {
            return lastName;
        }
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
        public String getOccupation() {
            return occupation;
        }
        public void setOccupation(String occupation) {
            this.occupation = occupation;
        }
    }
于 2013-06-12T15:03:46.660 に答える
2

ただし、GUImodel は GUIview から完全に独立している必要があるため、このアプローチは有効ではありません。

Swing コンポーネント自体は MVC モデルを使用します。モデルの変更は、ビューの変更をトリガーする必要があります。問題は、これをどのように行うかです。

1 つの方法は、質問で説明したように、モデルがビュー インスタンスにアクセスできるようにすることです。

ArrayList newArrayList = fileLoader(filePath);  //create ArrayList and load info
guiView.getTable1Model().updateArrayList(newArrayList);  //update JTableModel ArrayList

もう 1 つの方法は、コントローラーがモデルを更新し、ビューを更新することです。これは、私が通常 Swing アプリケーションで行うことです。

model.loadArrayList(filePath);
frame.getFrame().getMainPanel().repaint();

もう 1 つの方法は、アクションを起動することです。これは、Swing コンポーネントが GUI を更新する方法です。

ArrayList newArrayList = fileLoader(filePath);  //create ArrayList and load info
fireAction(newArrayLiat);

メソッドはfireActionリスナーで機能します。からコピーした fire メソッドを次に示しAbstractListModelます。

protected void fireContentsChanged(Object source, int index0, int index1) {

    Object[] listeners = listenerList.getListenerList();
    ListDataEvent e = null;

    for (int i = listeners.length - 2; i >= 0; i -= 2) {
        if (listeners[i] == ListDataListener.class) {
            if (e == null) {
                e = new ListDataEvent(source,
                        ListDataEvent.CONTENTS_CHANGED, index0, index1);
            }
            ((ListDataListener) listeners[i + 1]).contentsChanged(e);
        }
    }
}

ビュークラスがビューを変更するコードを記述できるように、モデルクラスにリスナーを記述する必要があります。

EventListenerListの Javadoc には、リスナーに関する詳細情報があります。ありがとう カタリナ島

于 2013-06-10T14:50:59.117 に答える
2

ここで説明したように、モデルとビューを疎結合するのは正しいことです。独自のモデルをリッスンするようにJTable実装されており、リスニング テーブル自体を更新させるイベントが発生することは間違いありません。TableModelListenerAbstractTableModel

この場合、従属オブジェクト自体を としてmaster にTableModel追加します。その後、依存モデルは、マスターから伝播された変更を独自のリスナーに通知するために必要なイベントを発生させることができます。TableModelListenerTableModel

于 2013-06-10T14:47:20.107 に答える