SwingでUIアーキテクチャパターン
@ITさんのこちらの記事「開発者が知っておくべき、6つのUIアーキテクチャ・パターン」に触発されて、あとは自分の考えを纏めるために、以上の記事で紹介されているアーキテクチャパターンをSwingとJavaで実装して見ようかと思います。
サンプルは同じく「BMIによる肥満度判断」を使用させて頂きます。
まずは「フォームとコントロール」から、全体のコードは以下の通り。
import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import java.awt.Color; public class Form extends javax.swing.JFrame implements DocumentListener{ public static void main( String args[] ){ SwingUtilities.invokeLater( new Runnable(){ public void run(){ new Form().setVisible( true ); } } ); } /** * Creates new form Form */ public Form(){ initComponents(); initListeners(); } private void initListeners(){ heightField.getDocument().addDocumentListener( this ); weightField.getDocument().addDocumentListener( this ); } public void insertUpdate( DocumentEvent e ){ textChanged(); } public void removeUpdate( DocumentEvent e ){ textChanged(); } public void changedUpdate( DocumentEvent e ){ } public void textChanged(){ float height; float weight; try{ height =Float.valueOf( heightField.getText().trim() ); weight = Float.valueOf( weightField.getText().trim() ); } catch ( NumberFormatException ignored ){ bmiField.setText( "" ); bmiField.setBackground( Color.WHITE ); return; } if( height == 0f ){ return; } float bmi = weight / ( height * height ); bmiField.setText( Float.toString( bmi ) ); Color bg; if(bmi < 18.5f){ bg = Color.WHITE; } else if( bmi < 20.0f){ bg = Color.YELLOW; }else if( bmi < 30.0f ){ bg = Color.ORANGE; }else{ bg = Color.RED; } bmiField.setBackground( bg ); } /** * This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ @SuppressWarnings( "unchecked" ) // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents(){ java.awt.GridBagConstraints gridBagConstraints; bmiPanel = new javax.swing.JPanel(); heightLabel = new javax.swing.JLabel(); heightField = new javax.swing.JTextField(); mLabel = new javax.swing.JLabel(); weightLabel = new javax.swing.JLabel(); weightField = new javax.swing.JTextField(); kgLabel = new javax.swing.JLabel(); bmiLabel = new javax.swing.JLabel(); bmiField = new javax.swing.JTextField(); setDefaultCloseOperation( javax.swing.WindowConstants.EXIT_ON_CLOSE ); setTitle( "BMI checker" ); setLocationByPlatform( true ); setResizable( false ); bmiPanel.setBorder( javax.swing.BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); bmiPanel.setLayout( new java.awt.GridBagLayout() ); heightLabel.setText( "Height:" ); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START; gridBagConstraints.insets = new java.awt.Insets( 4, 2, 4, 6 ); bmiPanel.add( heightLabel, gridBagConstraints ); heightField.setName( "heightLabel" ); // NOI18N heightField.setPreferredSize( new java.awt.Dimension( 100, 21 ) ); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.insets = new java.awt.Insets( 4, 4, 4, 4 ); bmiPanel.add( heightField, gridBagConstraints ); mLabel.setText( "m" ); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.insets = new java.awt.Insets( 4, 2, 4, 2 ); bmiPanel.add( mLabel, gridBagConstraints ); weightLabel.setText( "Weight:" ); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 1; gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START; gridBagConstraints.insets = new java.awt.Insets( 4, 2, 4, 6 ); bmiPanel.add( weightLabel, gridBagConstraints ); weightField.setName( "weightLabel" ); // NOI18N weightField.setPreferredSize( new java.awt.Dimension( 100, 21 ) ); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 1; gridBagConstraints.insets = new java.awt.Insets( 4, 4, 4, 4 ); bmiPanel.add( weightField, gridBagConstraints ); kgLabel.setText( "kg" ); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 2; gridBagConstraints.gridy = 1; gridBagConstraints.insets = new java.awt.Insets( 4, 2, 4, 2 ); bmiPanel.add( kgLabel, gridBagConstraints ); bmiLabel.setText( "BMI:" ); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 2; gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START; gridBagConstraints.insets = new java.awt.Insets( 4, 2, 4, 6 ); bmiPanel.add( bmiLabel, gridBagConstraints ); bmiField.setBackground( new java.awt.Color( 255, 255, 255 ) ); bmiField.setEditable( false ); bmiField.setDisabledTextColor( new java.awt.Color( 0, 0, 0 ) ); bmiField.setName( "bmiLabel" ); // NOI18N bmiField.setPreferredSize( new java.awt.Dimension( 100, 21 ) ); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 2; gridBagConstraints.insets = new java.awt.Insets( 4, 4, 4, 4 ); bmiPanel.add( bmiField, gridBagConstraints ); getContentPane().add( bmiPanel, java.awt.BorderLayout.CENTER ); pack(); }// </editor-fold>//GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTextField bmiField; private javax.swing.JLabel bmiLabel; private javax.swing.JPanel bmiPanel; private javax.swing.JTextField heightField; private javax.swing.JLabel heightLabel; private javax.swing.JLabel kgLabel; private javax.swing.JLabel mLabel; private javax.swing.JTextField weightField; private javax.swing.JLabel weightLabel; // End of variables declaration//GEN-END:variables }
今回、SwingのGUI部分はNetBeansのGUIビルダー Matisseを使用して作成してます。
Matisseは非常に使いやすいので、Swingをやる方にはNetBeansがお勧めです。
閑話休題、以下解説。
処理はすべてイベントハンドラへ、ということなので、BMIの計算・画面を更新する処理はtextChanged() メソッドに実装しています。
このメソッドはinsertUpdateメソッド及びremoveUpdateメソッドから呼び出しています。
insertUpdateメソッド及びremoveUpdateメソッドは、インターフェイス DocumentListener が宣言するメソッドであり、これはJTextFieldのモデルとなるDocumentに登録するリスナーです。
public void textChanged(){ float height; float weight; try{ height =Float.valueOf( heightField.getText().trim() ); weight = Float.valueOf( weightField.getText().trim() ); } catch ( NumberFormatException ignored ){ bmiField.setText( "" ); bmiField.setBackground( Color.WHITE ); return; } if( height == 0f ){ return; } float bmi = weight / ( height * height ); bmiField.setText( Float.toString( bmi ) ); Color bg; if(bmi < 18.5f){ bg = Color.WHITE; } else if( bmi < 20.0f){ bg = Color.YELLOW; }else if( bmi < 30 ){ bg = Color.ORANGE; }else{ bg = Color.RED; } bmiField.setBackground( bg ); }
そしてこのリスナーを身長の入力欄と、体重の入力欄のJTextFieldに登録しているので、どちらかの入力値が変更されるごとにinsertUpdateメソッド及びremoveUpdateメソッド経由で、textChanged()メソッドが呼び出され、画面が更新されることになります。
private void initListeners(){ heightField.getDocument().addDocumentListener( this ); weightField.getDocument().addDocumentListener( this ); }
このコードのメリットとしては、クラスが一つで済むのと、すべての処理がtextChanged()にあるので(この程度の規模の処理であれば)読みやすいということです。
反面、画面を構築するコードとBMIを計算するコード、画面を更新するコードが混ざり合っているためコードの共有やユニットテストがやりにくいことです。また、リソースを使う画面を表示する処理が実際に動いてしまうため、テストコードの実行が非常に遅くなってしまいます。
次回、MVCに続きます。