Java Swing – Mô hình MVC

1.9/5 - (31 votes)

Trong phần này chúng ta sẽ tìm hiểu về mô hình mà các component trong Java Swing sử dụng.

Bộ thư viện Swing được thiết kế dựa theo mô hình MVC (Model View Controller) cho phép thao tác với dữ liệu một cách tiện lợi và nhanh chóng.

Mô hình MVC

Mô hình MVC mà chúng ta thường thấy chia ứng dụng làm 3 phần: mô hình (Model), View và Controller. Trong đó model đại diện cho dữ liệu trong ứng dụng, view làm nhiệm vụ định dạng hiển thị cho dữ liệu và cuối cùng Controller xử lý sự kiện, có thể là sự kiện từ người dùng hoặc cũng chính nó tự sinh ra sự kiện. Nguyên gốc của mô hình MVC xuất phát từ ý tưởng chia tầng dữ liệu và tầng giao diện bằng một tầng ở giữa là Controller.

Thư viện Swing thì khác một tí, cả 2 phần View và Controller được gom lại làm một và được đặt tên là UI.

Trong Swing, tất cả mọi component đều có mô hình (model) của riêng nó, các mô hình lại được chia làm 2 loại là mô hình trạng thái và mô hình dữ liệu. Mô hình trạng thái lưu trữ trạng thái của component, ví dụ mô hình lưu giữ trạng thái của một button là đang được click hay đang được thả. Mô hình dữ liệu lưu trữ dữ liệu của component, chẳng hạn như một label lưu dữ liệu về text mà nó đang hiển thị.

Bản thân các component trong Swing đều được kế thừa từ một lớp model của riêng chúng, chẳng hạn như JButton kế thừa từ JAbstractButton, bên trong JAbstractButton có một thuộc tính là một đối tượng ButtonModel (bạn có thể tìm đọc trong bộ source của Java Swing), đối tượng này cung cấp các phương thức để lấy dữ liệu, thường thì nếu chúng ta muốn lấy dữ liệu thì chúng ta phải lấy đối tượng model này, nhưng lớp JButton thì lại có các phương thức lấy dữ liệu trực tiếp từ model luôn, nên bạn có thể lấy dữ liệu trực tiếp từ JButton mà không cần phải lấy đối tượng model trước.

public int getValue() { 
    return getModel().getValue(); 
}

Ví dụ như phương thức getValue() ở trên (đây là đoạn code phương thức getValue() của một component trong mã nguồn Java Swing), chúng ta lấy dữ liệu trực tiếp luôn chứ không cần phải lấy một đối tượng component nào đó rồi mới lấy dữ liệu. Hơn nữa việc thao tác trực tiếp với component là không an toàn.

Khi một component được tạo thì mặc định một đối tượng model sẽ được tạo, chẳng hạn như với JButton thì lớp đại diện cho model của nó là DefaultButtonModel.

public JButton(String text, Icon icon) {
  // Create the model
  setModel(new DefaultButtonModel());

  // initialize
  init(text, icon);
}

Ở trên là đoạn code khởi tạo JButton trong bộ mã nguồn của Java Swing.

Model của JButton

Mô hình được tạo sẵn cho JButton còn được dùng cho một số component phổ biến khác như check box, radio box, menu item… Loại mô hình của các component này là mô hình trạng thái.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.DefaultButtonModel;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class ButtonModel extends JFrame {
 
    private JButton okbtn;
    private JLabel enabledLbl;
    private JLabel pressedLbl;
    private JCheckBox cb;
 
    public ButtonModel()
    {
        initUI();
    }
 
    private void initUI()
    {
        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);

        okbtn = new JButton("OK");
        okbtn.addChangeListener(new DisabledChangeListener());
        cb = new JCheckBox();
        cb.setAction(new CheckBoxAction());

        enabledLbl = new JLabel("Enabled: true");
        pressedLbl = new JLabel("Pressed: false");
 
        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
            .addGroup(gl.createSequentialGroup()
                .addComponent(okbtn)
                .addGap(80)
                .addComponent(cb))
            .addGroup(gl.createParallelGroup()
                .addComponent(enabledLbl)
                .addComponent(pressedLbl)) 
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
            .addGroup(gl.createParallelGroup()
                .addComponent(okbtn)
                .addComponent(cb))
                .addGap(40)
            .addGroup(gl.createSequentialGroup()
                .addComponent(enabledLbl)
                .addComponent(pressedLbl))
        );

        pack();

        setTitle("ButtonModel");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
 
    private class DisabledChangeListener implements ChangeListener {

        @Override
        public void stateChanged(ChangeEvent e) {

            DefaultButtonModel model = (DefaultButtonModel) okbtn.getModel();

            if (model.isEnabled()) {
                enabledLbl.setText("Enabled: true");
            } else {
                enabledLbl.setText("Enabled: false");
            }

            if (model.isPressed()) {
                pressedLbl.setText("Pressed: true");
            } else { 
                pressedLbl.setText("Pressed: false");
            }
        }
    }
 
    private class CheckBoxAction extends AbstractAction {
 
        public CheckBoxAction() {
           super("Disabled");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (okbtn.isEnabled()) {
                okbtn.setEnabled(false);
            } else {
                okbtn.setEnabled(true);
            }
        }
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                ButtonModel ex = new ButtonModel();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta tạo một button, một checkbox và 3 label, các label này hiển thị dữ liệu về trạng thái của button như có đang được click hay không, đang bật hay đang bị vô hiệu hóa…

okbtn.addChangeListener(new DisabledChangeListener());

Chúng ta dùng lớp ChangeListener để lắng nghe sự thay đổi trạng thái của button.

DefaultButtonModel model = (DefaultButtonModel) okbtn.getModel();

Để lấy đối tượng model thì chúng ta dùng phương thức getModel().

if (model.isEnabled()) {
    enabledLbl.setText("Enabled: true");
} else {
    enabledLbl.setText("Enabled: false");
}

Chúng ta dùng phương thức isEnabled() để biết button đang dùng được hay đang bị vô hiệu hóa và cập nhật lên label.

if (okbtn.isEnabled()) {
    okbtn.setEnabled(false);
} else {
    okbtn.setEnabled(true);
}

Check box sẽ bật/tắt button nhưng ở đây chúng ta không dùng model mà dùng phương thức setEnabled() để thay đổi trực tiếp luôn.

public void setEnabled(boolean b) {
    if (!b && model.isRollover()) {
        model.setRollover(false);
    } 
    super.setEnabled(b);
    model.setEnabled(b);
}

Đoạn code trên là mã nguồn của phương thức JCheckBox.setEnable(), như bạn thấy thực chất khi gọi phương thức này JCheckBox tự động lấy model của nó và thiết lập thông tin từ model đó.

Capture

Tùy chỉnh Model

Trong phần này chúng ta làm lại những gì đã làm ở trên, chỉ khác là chúng ta sẽ sử dụng model do chúng ta tự thiết kế.

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.ButtonModel;
import javax.swing.DefaultButtonModel;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JLabel;


public class ButtonModel2 extends JFrame {

    private JButton okbtn;
    private JLabel enabledLbl;
    private JLabel pressedLbl;
    private JCheckBox cb;

    public ButtonModel2() {

        initUI();
    }
    
    private void initUI() {
        
        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);       
        
        okbtn = new JButton("OK");
        cb = new JCheckBox();
        cb.setAction(new CheckBoxAction());

        enabledLbl = new JLabel("Enabled: true");
        pressedLbl = new JLabel("Pressed: false");

        OkButtonModel model = new OkButtonModel();
        okbtn.setModel(model);        

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addGroup(gl.createSequentialGroup()
                        .addComponent(okbtn)
                        .addGap(80)
                        .addComponent(cb))
                .addGroup(gl.createParallelGroup()
                        .addComponent(enabledLbl)
                        .addComponent(pressedLbl))
        );

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup()
                        .addComponent(okbtn)
                        .addComponent(cb))
                .addGap(40)
                .addGroup(gl.createSequentialGroup()
                        .addComponent(enabledLbl)
                        .addComponent(pressedLbl))
        );

        pack();

        setTitle("MVC");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
    
    private class OkButtonModel extends DefaultButtonModel {

        @Override
        public void setEnabled(boolean b) {
            if (b) {
                enabledLbl.setText("Enabled: true");
            } else {
                enabledLbl.setText("Enabled: false");
            }

            super.setEnabled(b);
        }

        @Override
        public void setPressed(boolean b) {
            if (b) {
                pressedLbl.setText("Pressed: true");
            } else {
                pressedLbl.setText("Pressed: false");
            }

            super.setPressed(b);
        }
    }
    
    private class CheckBoxAction extends AbstractAction {
        
        public CheckBoxAction() {
            super("Disabled");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (okbtn.isEnabled()) {
                okbtn.setEnabled(false);
            } else {
                okbtn.setEnabled(true);
            }
        }
    }    

    public static void main(String[] args) {
        
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                ButtonModel2 ex = new ButtonModel2();
                ex.setVisible(true);
            }
        });
    }
}

Chúng ta thiết kế lại giao diện như trong ví dụ trước, chỉ khác là chúng ta không dùng đến các listener nữa mà sử dụng đối tượng model kế thừa do chúng ta tự thiết kế.

ButtonModel model = new OkButtonModel();
okbtn.setModel(model); 

Để thiết lập model thì chúng ta sử dụng phương thức setModel().

private class OkButtonModel extends DefaultButtonModel {
...
}

Chúng ta tạo model button riêng và override một số phương thức cần thiết.

@Override
public void setEnabled(boolean b) {
    if (b) {
        enabledLbl.setText("Enabled: true");
    } else {
        enabledLbl.setText("Enabled: false");
    }

    super.setEnabled(b);
}

Chúng ta override phương thức setEnabled() và cập nhật lại text trên label, sau đó chúng ta phải gọi phương thức setEnabled() của lớp cha để cập nhật sự thay đổi.

Component có 2 model

Có một số component có tới 2 model, JList là một ví dụ, 2 model của JListListModel à ListSelectionModel. Trong đó ListModel là một model dữ liệu, model này lưu trữ danh sách các item trong list, cũng như tính chất của các item đó, ListSelectionModel là một model trạng thái, lưu trữ trạng thái của các item, chẳng hạn như item thứ mấy đang được chọn bởi người dùng…

import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.DefaultListModel;
import javax.swing.GroupLayout;
import static javax.swing.GroupLayout.Alignment.CENTER;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;

public class ListModels extends JFrame {

    private DefaultListModel model;
    private JList list;
    private JButton remallbtn;
    private JButton addbtn;
    private JButton renbtn;
    private JButton delbtn;

    public ListModels() {

        initUI();
    }

    private void createList() {

        model = new DefaultListModel();
        model.addElement("Amelie");
        model.addElement("Aguirre, der Zorn Gottes");
        model.addElement("Fargo");
        model.addElement("Exorcist");
        model.addElement("Schindler's list");

        list = new JList(model);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        list.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {

                if (e.getClickCount() == 2) {
                    int index = list.locationToIndex(e.getPoint());
                    Object item = model.getElementAt(index);
                    String text = JOptionPane.showInputDialog("Rename item", item);
                    String newitem = null;
                    if (text != null) {
                        newitem = text.trim();
                    } else {
                        return;
                    }

                    if (!newitem.isEmpty()) {
                        model.remove(index);
                        model.add(index, newitem);
                        ListSelectionModel selmodel = list.getSelectionModel();
                        selmodel.setLeadSelectionIndex(index);
                    }
                }
            }
        });
    }

    private void createButtons() {

        remallbtn = new JButton("Remove All");
        addbtn = new JButton("Add");
        renbtn = new JButton("Rename");
        delbtn = new JButton("Delete");

        addbtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {

                String text = JOptionPane.showInputDialog("Add a new item");
                String item = null;

                if (text != null) {
                    item = text.trim();
                } else {
                    return;
                }

                if (!item.isEmpty()) {
                    model.addElement(item);
                }
            }
        });

        delbtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent event) {

                ListSelectionModel selmodel = list.getSelectionModel();
                int index = selmodel.getMinSelectionIndex();
                if (index >= 0) {
                    model.remove(index);
                }
            }

        });

        renbtn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {

                ListSelectionModel selmodel = list.getSelectionModel();
                int index = selmodel.getMinSelectionIndex();
                if (index == -1) {
                    return;
                }

                Object item = model.getElementAt(index);
                String text = JOptionPane.showInputDialog("Rename item", item);
                String newitem = null;

                if (text != null) {
                    newitem = text.trim();
                } else {
                    return;
                }

                if (!newitem.isEmpty()) {
                    model.remove(index);
                    model.add(index, newitem);
                }
            }
        });

        remallbtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                model.clear();
            }
        });

    }

    private void initUI() {

        createList();
        createButtons();
        JScrollPane scrollpane = new JScrollPane(list);

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);
        gl.setAutoCreateGaps(true);

        gl.setHorizontalGroup(gl.createSequentialGroup()
                .addComponent(scrollpane)
                .addGroup(gl.createParallelGroup()
                        .addComponent(addbtn)
                        .addComponent(renbtn)
                        .addComponent(delbtn)
                        .addComponent(remallbtn))
        );

        gl.setVerticalGroup(gl.createParallelGroup(CENTER)
                .addComponent(scrollpane)
                .addGroup(gl.createSequentialGroup()
                        .addComponent(addbtn)
                        .addComponent(renbtn)
                        .addComponent(delbtn)
                        .addComponent(remallbtn))
        );

        gl.linkSize(addbtn, renbtn, delbtn, remallbtn);

        pack();

        setTitle("JList models");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                ListModels ex = new ListModels();
                ex.setVisible(true);
            }
        });
    }
}

Trong ví dụ này, chúng ta hiển thị một JList và 4 button. Các button sẽ điều khiển JList.

model = new DefaultListModel();
model.addElement("Amelie");
model.addElement("Aguirre, der Zorn Gottes");
...

Đầu tiên chúng ta tạo một model cho JList và thêm các item vào đó.

list = new JList(model);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

Tiếp theo chúng ta tạo một đối tượng JList và thiết lập chế độ chỉ cho phép chọn một item tại một thời điểm.

if (text != null) {
    item = text.trim();
} else {
    return;
}

if (!item.isEmpty()) {
    model.addElement(item);
}

Chúng ta chỉ thêm item vào list nếu item đó không rỗng.

ListSelectionModel selmodel = list.getSelectionModel();
int index = selmodel.getMinSelectionIndex();
if (index >= 0) {
    model.remove(index);
}

Đoạn code trên thực hiện xóa các item, đầu tiên chúng ta lấy ra số thứ tự của item đang được chọn bằng phương thức getMinSelectionIndex() từ model của JList. Sau đó dùng phương thức remove() để xóa item này.

Trong ví dụ này mình dùng cả 2 model và dùng cả 2 phương pháp là sử dụng model và dùng phương thức trực tiếp của component. Các phương thức add(), remove(), clear() là dùng trực tiếp phương thức của component, còn lấy item đang được chọn là lấy từ model.

Capture
5 1 vote
Article Rating
Subscribe
Thông báo cho tôi qua email khi
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Inline Feedbacks
View all comments
Hoàng Thống
6 năm trước

Very useful!

123
123
10 tháng trước
Reply to  Hoàng Thống

yes