web-dev-qa-db-ja.com

MVCパターンとSwing

「実際のSwingライフ」で実際に把握するのが最も難しいと思う設計パターンの1つは、MVCパターンです。私はこのサイトでパターンについて議論している記事のかなりの数を見てきましたが、まだJava = Swingアプリケーション。

テーブル、いくつかのテキストフィールド、いくつかのボタンを含むJFrameがあるとします。おそらく、TableModelを使用して、基礎となるデータモデルとJTableを「ブリッジ」します。ただし、通常、フィールドのクリア、フィールドの検証、フィールドのロック、ボタンアクションを担当するすべての関数は、JFrameに直接配置されます。しかし、それはコントローラーとパターンのビューを混同していませんか?

私が見る限り、JTable(およびモデル)を見るとMVCパターンを「正しく」実装することができますが、JFrame全体を見ると物が濁ります。

これに関して他の人がどうなっているのか、本当に聞きたいです。 MVCパターンを使用してユーザーにテーブル、いくつかのフィールド、およびいくつかのボタンを表示する必要がある場合、どのように対処しますか?

72
sbrattla

SwingでのMVCに関して私が強くお勧めする本は、FreemanとFreemanによる「Head First Design Patterns」です。 MVCの非常に包括的な説明があります。

概要

  1. あなたはユーザーです-ビューと対話します。ビューはモデルへのウィンドウです。ビューに対して何かを実行すると([再生]ボタンをクリックするなど)、ビューはコントローラーに何をしたかを伝えます。それを処理するのはコントローラーの仕事です。

  2. コントローラーはモデルにその状態を変更するよう要求します。コントローラーはアクションを実行し、それらを解釈します。ボタンをクリックした場合、それが何を意味するのか、そのアクションに基づいてモデルをどのように操作するのかを把握するのは、コントローラーの仕事です。

  3. コントローラーは、ビューの変更を要求することもあります。コントローラーがビューからアクションを受け取ると、ビューに結果として変更するよう指示する必要がある。たとえば、コントローラはインターフェイスの特定のボタンまたはメニュー項目を有効または無効にすることができます。

  4. モデルは、その状態が変更されたときにビューに通知します。モデルで何かが変更されたとき、実行したアクション(ボタンをクリックするなど)または他の内部変更(プレイリスト内の次の曲が開始されたなど)の場合、モデルはビューに状態が変更されたことを通知します。

  5. ビューはモデルに状態を要求します。ビューはモデルから表示された状態を直接取​​得します。たとえば、新しい曲の再生が開始されたことをモデルがビューに通知すると、ビューはモデルに曲名を要求して表示します。ビューは、コントローラーがビューの変更を要求した結果として、モデルに状態を要求する場合もあります。

enter image description here ソース (「クリーミーコントローラー」とは何かを疑問に思っている場合は、コントローラーをクリーミーセンター、ビューをトップビスケット、モデルをボトムビスケットとするオレオクッキーを考えてください。 )

興味がある場合は、 here !からMVCパターンに関するかなり面白い曲をダウンロードできます。

Swingプログラミングで直面する可能性のある問題の1つは、SwingWorkerとEventDispatchスレッドをMVCパターンに統合することです。プログラムによっては、ビューまたはコントローラーでSwingWorkerを拡張し、リソースを集中的に使用するロジックが配置されるdoInBackground()メソッドをオーバーライドする必要がある場合があります。これは、典型的なMVCパターンと簡単に融合でき、Swingアプリケーションに典型的です。

編集#1

さらに、MVCをさまざまなパターンの一種の複合体と見なすことが重要です。たとえば、コントローラーが戦略パターンを使用する一方で、モデルはオブザーバーパターンを使用して実装できます(ビューをモデルのオブザーバーとして登録する必要があります)。

編集#2

さらに、具体的にあなたの質問に答えたいと思います。 Viewにテーブルボタンなどを表示する必要があります。これは明らかにActionListenerを実装します。 actionPerformed()メソッドで、イベントを検出し、コントローラーの関連メソッドに送信します(ビューはコントローラーへの参照を保持していることを思い出してください)。そのため、ボタンがクリックされると、イベントがビューによって検出され、コントローラーのメソッドに送信され、コントローラーはボタンなどを無効にするようにビューに直接要求する場合があります。次に、コントローラーはモデルと対話し、モデルを変更します(ほとんどの場合、ゲッターメソッドとセッターメソッド、およびオブザーバーを登録して通知する他のメソッドがあります)。モデルが変更されるとすぐに、登録されたオブザーバーの更新が呼び出されます(これはあなたの場合のビューになります)。したがって、ビューは自動的に更新されます。

101
Dhruv Gairola

データが変更されたときにモデルによって通知されるビューであるという考えは好きではありません。その機能をコントローラーに委任します。その場合、アプリケーションロジックを変更しても、ビューのコードに干渉する必要はありません。ビューのタスクは、アプリケーションコンポーネント+レイアウトのみであり、それ以下でもありません。 Swingのレイアウトはすでに冗長なタスクです。なぜアプリケーションロジックに干渉するのでしょうか。

MVCについての私の考え(これは現在作業中ですが、これまでのところとても良いです):

  1. ビューは3つの中で最も愚かです。コントローラーとモデルについては何も知りません。その懸念は、スイングコンポーネントの補綴とレイアウトのみです。
  2. モデルも馬鹿げていますが、ビューほど馬鹿ではありません。次の機能を実行します。
    • a。そのセッターの1つがコントローラーによって呼び出されると、リスナー/オブザーバーに通知を送信します(私が言ったように、この役割をコントローラーに委任します)。 SwingPropertyChangeSupport は、この目的のために既に最適化されているため、これを達成するために好みます。
    • b。データベース対話機能。
  3. 非常にスマートなコントローラー。ビューとモデルを非常によく知っています。コントローラーには2つの機能があります。
    • a。ユーザーが操作したときにビューが実行するアクションを定義します。
    • b。モデルをリッスンします。私が言ったように、モデルのセッターが呼び出されると、モデルはコントローラーに通知を送信します。この通知を解釈するのはコントローラーの仕事です。ビューへの変更を反映する必要がある場合があります。

コードサンプル

景色 :

私が言ったように、ビューの作成はすでに冗長なので、独自の実装を作成してください:)

interface View{
    JTextField getTxtFirstName();
    JTextField getTxtLastName();
    JTextField getTxtAddress();
}

テストしやすいように、3つのインターフェイスを接続することが理想的です。モデルとコントローラーの実装のみを提供しました。

モデル:

public class MyImplementationOfModel implements Model{
    ...
    private SwingPropertyChangeSupport propChangeFirer;
    private String address;
    private String firstName;
    private String lastName;

    public MyImplementationOfModel() {
        propChangeFirer = new SwingPropertyChangeSupport(this);
    }
    public void addListener(PropertyChangeListener prop) {
        propChangeFirer.addPropertyChangeListener(prop);
    }
    public void setAddress(String address){
        String oldVal = this.address;
        this.address = address;

        //after executing this, the controller will be notified that the new address has been set. Its then the controller's
        //task to decide what to do when the address in the model has changed. Ideally, the controller will update the view about this
        propChangeFirer.firePropertyChange("address", oldVal, address);
    }
    ...
    //some other setters for other properties & code for database interaction
    ...
}

コントローラー:

public class MyImplementationOfController implements PropertyChangeListener, Controller{

    private View view;
    private Model model;

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

        //register the controller as the listener of the model
        this.model.addListener(this);

        setUpViewEvents();
    }

    //code for setting the actions to be performed when the user interacts to the view.
    private void setUpViewEvents(){
        view.getBtnClear().setAction(new AbstractAction("Clear") { 
            @Override
            public void actionPerformed(ActionEvent arg0) {
                model.setFirstName("");
                model.setLastName("");
                model.setAddress("");
            }
        });

        view.getBtnSave().setAction(new AbstractAction("Save") { 
            @Override
            public void actionPerformed(ActionEvent arg0) {
                ...
                //validate etc.
                ...
                model.setFirstName(view.getTxtFName().getText());
                model.setLastName(view.getTxtLName().getText());
                model.setAddress(view.getTxtAddress().getText());
                model.save();
            }
        });
    }

    public void propertyChange(PropertyChangeEvent evt){
        String propName = evt.getPropertyName();
        Object newVal = evt.getNewValue();

        if("address".equalsIgnoreCase(propName)){
            view.getTxtAddress().setText((String)newVal);
        }
        //else  if property (name) that fired the change event is first name property
        //else  if property (name) that fired the change event is last name property
    }
}

MVCがセットアップされているメイン:

public class Main{
    public static void main(String[] args){
        View view = new YourImplementationOfView();
        Model model = new MyImplementationOfModel();

        ...
        //create jframe
        //frame.add(view.getUI());
        ...

        //make sure the view and model is fully initialized before letting the controller control them.
        Controller controller = new MyImplementationOfController(view, model);

        ...
        //frame.setVisible(true);
        ...
    }
}
33
Bnrdo

MVCパターンは、ユーザーインターフェイスをどのように構成できるかのモデルです。したがって、Model、View、Controllerの3つの要素を定義します。

  • Modelモデルは、ユーザーに提示される何かの抽象化です。 Swingでは、GUIモデルとデータモデルを区別します。 GUIモデルは、 ButtonModel のようなUIコンポーネントの状態を抽象化します。データモデルは、 TableModel のように、UIがユーザーに提示する構造化データを抽象化します。
  • Viewビューは、ユーザーへのデータの表示を担当するUIコンポーネントです。したがって、レイアウト、描画などのUIに依存するすべての問題を担当します。 JTable
  • Controllerコントローラーは、ユーザーインタラクション(マウスモーション、マウスクリック、キー押下など)のために実行されるアプリケーションコードをカプセル化します。コントローラーは実行に入力が必要な場合があり、出力を生成します。モデルから入力を読み取り、実行の結果としてモデルを更新します。 UIを再構築することもあります(たとえば、UIコンポーネントを置き換えるか、完全に新しいビューを表示します)。ただし、コントローラが呼び出すだけの別のインターフェイスに再構築をカプセル化できるため、UIコンポーネントについて知ってはいけません。 swingでは、コントローラーは通常 ActionListener または Action で実装されます。

  • 赤=モデル
  • 緑=ビュー
  • 青=コントローラー

enter image description here

Buttonをクリックすると、ActionListenerが呼び出されます。 ActionListenerは他のモデルにのみ依存します。入力としていくつかのモデルを使用し、結果または出力として他のモデルを使用します。メソッドの引数と戻り値のようなものです。モデルは、更新されるとUIに通知します。そのため、コントローラーロジックがUIコンポーネントを認識する必要はありません。モデルオブジェクトはUIを知りません。通知は、オブザーバーパターンによって行われます。したがって、モデルオブジェクトは、モデルが変更された場合に通知を受けたい人がいることだけを知っています。

Java swingでは、モデルとコントローラーを実装するコンポーネントもいくつかあります。たとえば、 javax.swing.Action 。UIモデルを実装します(プロパティ:有効化、小さいアイコン、名前など)、および ActionListener を拡張するため、コントローラーです。

詳細な説明、サンプルアプリケーションおよびソースコードhttps://www.link-intersystems.com/blog/2013/07/ 20/the-mvc-pattern-implemented-with-Java-swing /

240行未満のMVCの基本:

public class Main {

    public static void main(String[] args) {
        JFrame mainFrame = new JFrame("MVC example");
        mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        mainFrame.setSize(640, 300);
        mainFrame.setLocationRelativeTo(null);

        PersonService personService = new PersonServiceMock();

        DefaultListModel searchResultListModel = new DefaultListModel();
        DefaultListSelectionModel searchResultSelectionModel = new DefaultListSelectionModel();
        searchResultSelectionModel
                .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        Document searchInput = new PlainDocument();

        PersonDetailsAction personDetailsAction = new PersonDetailsAction(
                searchResultSelectionModel, searchResultListModel);
        personDetailsAction.putValue(Action.NAME, "Person Details");

        Action searchPersonAction = new SearchPersonAction(searchInput,
                searchResultListModel, personService);
        searchPersonAction.putValue(Action.NAME, "Search");

        Container contentPane = mainFrame.getContentPane();

        JPanel searchInputPanel = new JPanel();
        searchInputPanel.setLayout(new BorderLayout());

        JTextField searchField = new JTextField(searchInput, null, 0);
        searchInputPanel.add(searchField, BorderLayout.CENTER);
        searchField.addActionListener(searchPersonAction);

        JButton searchButton = new JButton(searchPersonAction);
        searchInputPanel.add(searchButton, BorderLayout.EAST);

        JList searchResultList = new JList();
        searchResultList.setModel(searchResultListModel);
        searchResultList.setSelectionModel(searchResultSelectionModel);

        JPanel searchResultPanel = new JPanel();
        searchResultPanel.setLayout(new BorderLayout());
        JScrollPane scrollableSearchResult = new JScrollPane(searchResultList);
        searchResultPanel.add(scrollableSearchResult, BorderLayout.CENTER);

        JPanel selectionOptionsPanel = new JPanel();

        JButton showPersonDetailsButton = new JButton(personDetailsAction);
        selectionOptionsPanel.add(showPersonDetailsButton);

        contentPane.add(searchInputPanel, BorderLayout.NORTH);
        contentPane.add(searchResultPanel, BorderLayout.CENTER);
        contentPane.add(selectionOptionsPanel, BorderLayout.SOUTH);

        mainFrame.setVisible(true);
    }

}

class PersonDetailsAction extends AbstractAction {

    private static final long serialVersionUID = -8816163868526676625L;

    private ListSelectionModel personSelectionModel;
    private DefaultListModel personListModel;

    public PersonDetailsAction(ListSelectionModel personSelectionModel,
            DefaultListModel personListModel) {
        boolean unsupportedSelectionMode = personSelectionModel
                .getSelectionMode() != ListSelectionModel.SINGLE_SELECTION;
        if (unsupportedSelectionMode) {
            throw new IllegalArgumentException(
                    "PersonDetailAction can only handle single list selections. "
                            + "Please set the list selection mode to ListSelectionModel.SINGLE_SELECTION");
        }
        this.personSelectionModel = personSelectionModel;
        this.personListModel = personListModel;
        personSelectionModel
                .addListSelectionListener(new ListSelectionListener() {

                    public void valueChanged(ListSelectionEvent e) {
                        ListSelectionModel listSelectionModel = (ListSelectionModel) e
                                .getSource();
                        updateEnablement(listSelectionModel);
                    }
                });
        updateEnablement(personSelectionModel);
    }

    public void actionPerformed(ActionEvent e) {
        int selectionIndex = personSelectionModel.getMinSelectionIndex();
        PersonElementModel personElementModel = (PersonElementModel) personListModel
                .get(selectionIndex);

        Person person = personElementModel.getPerson();
        String personDetials = createPersonDetails(person);

        JOptionPane.showMessageDialog(null, personDetials);
    }

    private String createPersonDetails(Person person) {
        return person.getId() + ": " + person.getFirstName() + " "
                + person.getLastName();
    }

    private void updateEnablement(ListSelectionModel listSelectionModel) {
        boolean emptySelection = listSelectionModel.isSelectionEmpty();
        setEnabled(!emptySelection);
    }

}

class SearchPersonAction extends AbstractAction {

    private static final long serialVersionUID = 4083406832930707444L;

    private Document searchInput;
    private DefaultListModel searchResult;
    private PersonService personService;

    public SearchPersonAction(Document searchInput,
            DefaultListModel searchResult, PersonService personService) {
        this.searchInput = searchInput;
        this.searchResult = searchResult;
        this.personService = personService;
    }

    public void actionPerformed(ActionEvent e) {
        String searchString = getSearchString();

        List<Person> matchedPersons = personService.searchPersons(searchString);

        searchResult.clear();
        for (Person person : matchedPersons) {
            Object elementModel = new PersonElementModel(person);
            searchResult.addElement(elementModel);
        }
    }

    private String getSearchString() {
        try {
            return searchInput.getText(0, searchInput.getLength());
        } catch (BadLocationException e) {
            return null;
        }
    }

}

class PersonElementModel {

    private Person person;

    public PersonElementModel(Person person) {
        this.person = person;
    }

    public Person getPerson() {
        return person;
    }

    @Override
    public String toString() {
        return person.getFirstName() + ", " + person.getLastName();
    }
}

interface PersonService {

    List<Person> searchPersons(String searchString);
}

class Person {

    private int id;
    private String firstName;
    private String lastName;

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

    public int getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

}

class PersonServiceMock implements PersonService {

    private List<Person> personDB;

    public PersonServiceMock() {
        personDB = new ArrayList<Person>();
        personDB.add(new Person(1, "Graham", "Parrish"));
        personDB.add(new Person(2, "Daniel", "Hendrix"));
        personDB.add(new Person(3, "Rachel", "Holman"));
        personDB.add(new Person(4, "Sarah", "Todd"));
        personDB.add(new Person(5, "Talon", "Wolf"));
        personDB.add(new Person(6, "Josephine", "Dunn"));
        personDB.add(new Person(7, "Benjamin", "Hebert"));
        personDB.add(new Person(8, "Lacota", "Browning "));
        personDB.add(new Person(9, "Sydney", "Ayers"));
        personDB.add(new Person(10, "Dustin", "Stephens"));
        personDB.add(new Person(11, "Cara", "Moss"));
        personDB.add(new Person(12, "Teegan", "Dillard"));
        personDB.add(new Person(13, "Dai", "Yates"));
        personDB.add(new Person(14, "Nora", "Garza"));
    }

    public List<Person> searchPersons(String searchString) {
        List<Person> matches = new ArrayList<Person>();

        if (searchString == null) {
            return matches;
        }

        for (Person person : personDB) {
            if (person.getFirstName().contains(searchString)
                    || person.getLastName().contains(searchString)) {
                matches.add(person);
            }

        }
        return matches;
    }

}
21
René Link

別のプレーンなJavaクラスと別のコントローラーでモデルを作成できます。

次に、その上にSwingコンポーネントを配置できます。 JTableはビューの1つです(そしてテーブルモデルは事実上ビューの一部です-「共有モデル」からJTableに変換するだけです) 。

テーブルが編集されるたびに、そのテーブルモデルは「メインコントローラ」に何かを更新するように指示します。ただし、コントローラーはテーブルについて何も知らないはずです。したがって、呼び出しはupdateCustomer(customer, newValue)ではなくupdateCustomer(row, column, newValue)のようになります。

共有モデルのリスナー(オブザーバー)インターフェイスを追加します。一部のコンポーネント(テーブルなど)で直接実装できます。別のオブザーバーは、ボタンの可用性などを調整するコントローラーです。


それはそれを行う方法の1つですが、もちろん、ユースケースが過剰な場合は、単純化または拡張できます。

コントローラーをモデルとマージし、同じクラスで更新を処理し、コンポーネントの可用性を維持できます。 「共有モデル」をTableModelにすることもできます(ただし、テーブルで使用するだけでなく、少なくともテーブルの抽象化を漏らさない、より使いやすいAPIを提供することをお勧めします)

一方、更新用の複雑なインターフェース(CustomerUpdateListenerOrderItemListenerOrderCancellationListener)と、異なるビューの調整専用の専用コントローラー(またはメディエーター)を持つことができます。

それはあなたの問題がどれほど複雑かによる。

2
Konrad Garus

[〜#〜] gui [〜#〜] でプログラムを開発する場合、 mvc pattern はほとんどありますが、ぼやけています。

モデル、ビュー、コントローラーコードの分析は難しく、通常はリファクタリングタスクだけではありません。

コードが再利用可能になったとき、あなたはそれを知っています。 MVCを正しく実装している場合、 [〜#〜] tui [〜#〜] または [〜#〜] cli [〜#〜] を簡単に実装できるはずです。 =または [〜#〜] rwd [〜#〜] または mobile first design と同じ機能。さらに、既存のコードで実際に行うよりも、簡単に実行できます。

実際、モデル、ビュー、コントローラー間の相互作用は、他の分離パターンを使用して行われます(オブザーバーまたはリスナーとして)

この投稿では、直接の非MVCパターン( Q&D で行うように)から最終的な再利用可能な実装まで、詳細に説明していると思います。

http://www.austintek.com/mvc/

0
albfan

適切に分離するには、通常、Frameクラスが委任するコントローラークラスがあります。クラス間の関係を設定するにはさまざまな方法があります。コントローラーを実装してメインビュークラスで拡張するか、イベントが発生したときにフレームが呼び出すスタンドアロンコントローラークラスを使用できます。ビューは通常、リスナーインターフェイスを実装することにより、コントローラーからイベントを受け取ります。

MVCパターンの1つまたは複数の部分が些細な場合もあれば、「薄い」ため、不必要な複雑さを加えてそれらを分離する場合もあります。コントローラーが1行の呼び出しでいっぱいになっている場合、別のクラスに配置すると、基になる動作がわかりにくくなる可能性があります。たとえば、処理しているすべてのイベントがTableModelに関連し、単純な追加および削除操作である場合、そのモデル内のすべてのテーブル操作関数(およびそれを表示するために必要なコールバックを実装することを選択できます) JTable)。それは本当のMVCではありませんが、必要のない場所に複雑さを追加することを避けます。

どのように実装しても、コンポーネント、それらの関係が適切に記述されるように、クラス、メソッド、パッケージをJavaDocに忘れないでください!

0
AndyT

あなたの問題を解決するかもしれないMVCパターンの実装に関するいくつかの興味深い記事を見つけました。

0
T Ambre