MVC bertemu Swing

Antarmuka pengguna perangkat lunak yang baik sering kali menemukan asal-usulnya dalam antarmuka pengguna yang ada di dunia fisik. Pertimbangkan sejenak tombol sederhana seperti salah satu tombol pada keyboard di depan Anda. Dengan tombol seperti itu terdapat pemisahan yang jelas antara bagian-bagian penyusun mekanisme tombol dan bagian penyusun façade-nya. Blok penyusun yang disebut tombol keyboard sebenarnya terdiri dari dua bagian. Once piece memberikan perilaku seperti kancing. Potongan lainnya bertanggung jawab atas penampilannya.

Konstruksi ini ternyata merupakan fitur desain yang kuat. Ini mendorong penggunaan kembali daripada desain ulang. Karena tombol keyboard Anda dirancang seperti itu, Anda dapat menggunakan kembali desain mekanisme tombol, dan mengganti bagian atas tombol untuk membuat kunci baru daripada mendesain setiap tombol dari awal. Ini menciptakan penghematan yang substansial dalam upaya dan waktu desain.

Tidak mengherankan, manfaat serupa terjadi ketika teknik ini diterapkan pada pengembangan perangkat lunak. Salah satu implementasi yang umum digunakan dari teknik ini dalam perangkat lunak adalah pola desain yang disebut Model / View / Controller (MVC).

Itu semua baik dan bagus, tetapi Anda mungkin bertanya-tanya bagaimana ini berkaitan dengan komponen antarmuka pengguna Swing di Java Foundation Classes (JFC). Baiklah, aku akan memberitahumu.

Sementara pola desain MVC biasanya digunakan untuk membangun seluruh antarmuka pengguna, perancang JFC menggunakannya sebagai dasar untuk setiap komponen antarmuka pengguna Swing. Setiap komponen antarmuka pengguna (baik tabel, tombol, atau scrollbar) memiliki model, tampilan, dan pengontrol. Lebih lanjut, potongan model, tampilan, dan pengontrol dapat berubah, bahkan saat komponen sedang digunakan. Hasilnya adalah perangkat antarmuka pengguna dengan fleksibilitas yang hampir tak tertandingi.

Mari saya tunjukkan cara kerjanya.

Pola desain MVC

Jika Anda tidak terbiasa dengan pola desain MVC, saya sarankan Anda meninjau "Observer and Observable", salah satu artikel saya sebelumnya yang membahas topik ini secara lebih mendetail dan memberikan dasar untuk kolom bulan ini.

Seperti yang saya sebutkan beberapa saat yang lalu, pola desain MVC memisahkan komponen perangkat lunak menjadi tiga bagian berbeda: model, tampilan, dan pengontrol.

The Model adalah bagian yang mewakili negara dan tingkat rendah perilaku komponen. Ia mengelola negara dan melakukan semua transformasi di negara bagian itu. Model tersebut tidak memiliki pengetahuan khusus tentang pengontrol atau tampilannya. Sistem itu sendiri memelihara hubungan antara model dan tampilan dan memberi tahu tampilan ketika model mengubah status.

The pandangan adalah bagian yang mengelola tampilan visual dari negara diwakili oleh model. Sebuah model bisa memiliki lebih dari satu view, tapi itu biasanya tidak terjadi pada set Swing.

The kontroler adalah bagian yang mengelola interaksi pengguna dengan model. Ini memberikan mekanisme di mana perubahan dilakukan pada keadaan model.

Menggunakan contoh tombol keyboard, modelnya sesuai dengan mekanisme tombol, dan tampilan serta pengontrol sesuai dengan façade tombol tersebut.

Gambar berikut mengilustrasikan cara memecah komponen antarmuka pengguna JFC menjadi model, tampilan, dan pengontrol. Perhatikan bahwa tampilan dan pengontrol digabungkan menjadi satu bagian, adaptasi umum dari pola MVC dasar. Mereka membentuk antarmuka pengguna untuk komponen tersebut.

Sebuah tombol secara detail

Untuk lebih memahami bagaimana pola MVC terkait dengan komponen antarmuka pengguna Swing, mari kita pelajari lebih dalam tentang set Swing. Seperti yang saya lakukan bulan lalu, saya akan menggunakan komponen tombol di mana-mana sebagai referensi.

Kami akan mulai dengan modelnya.

Model

Perilaku model dalam ilustrasi tombol di atas ditangkap oleh ButtonModelantarmuka. Sebuah contoh model tombol merangkum keadaan internal dari satu tombol dan menentukan bagaimana tombol berperilaku. Metodenya dapat dikelompokkan menjadi empat kategori - yaitu:

  • Status internal kueri
  • Memanipulasi keadaan internal
  • Tambahkan dan hapus pemroses acara
  • Peristiwa kebakaran

Komponen antarmuka pengguna lainnya memiliki model khusus komponennya sendiri. Mereka semua, bagaimanapun, menyediakan kelompok metode yang sama.

Tampilan dan pengontrol

Perilaku tampilan dan pengontrol dalam ilustrasi tombol di atas ditangkap oleh ButtonUIantarmuka. Kelas yang mengimplementasikan antarmuka ini bertanggung jawab untuk membuat representasi visual tombol dan menangani input pengguna yang diberikan melalui keyboard dan mouse. Metodenya dapat dikelompokkan menjadi tiga kategori - yaitu:

  • Cat
  • Kembalikan informasi geometris
  • Tangani acara AWT

Komponen antarmuka pengguna lainnya memiliki tampilan / pengontrol khusus komponennya sendiri. Mereka semua, bagaimanapun, menyediakan kelompok metode yang sama.

Perancah

Programmers do not typically work with model and view/controller classes directly. In fact, to the casual observer, their presence is veiled. They hide behind an ordinary component class -- a subclass of java.awt.Component. The component class acts as the glue, or scaffolding, that holds the MVC triad together. Many of the methods present on the component class (paint(), for example) are nothing more than wrappers that pass along the method invocation to either the model or the view/controller.

Because the component classes are subclasses of class Component, a programmer can freely mix Swing components with regular AWT components. However, because the Swing set contains components that functionally mimic the regular AWT components, mixing the two is usually not necessary.

A concrete example

Now that we understand which Java classes correspond to which parts of the MVC pattern, we're ready to open the box and peek inside. What follows is a scaled-down tour of a set of model classes designed and built according to the MVC principles outlined above. Because the JFC library is so complex, I've narrowed the scope of my tour to include only one user interface component (if you guessed it to be the

Button

class, you'd be right).

Let's take a look at all the major players.

The Button class

The most obvious place to begin is with the code for the button component itself, because this is the class that most programmers will work with.

As I mentioned earlier, the Button user interface component class acts as the scaffolding for the model and the view/controller. Each button component is associated with one model and one view/controller. The model defines the button's behavior and the view/controller defines its appearance. The application can change either at any time. Let's look at the code for making the change.

public void setModel(ButtonModel buttonmodel) { if (this.buttonmodel != null) { this.buttonmodel.removeChangeListener(buttonchangelistener); this.buttonmodel.removeActionListener(buttonactionlistener);

buttonchangelistener = null; buttonactionlistener = null; }

this.buttonmodel = buttonmodel;

if (this.buttonmodel != null) { buttonchangelistener = new ButtonChangeListener(); buttonactionlistener = new ButtonActionListener();

this.buttonmodel.addChangeListener(buttonchangelistener); this.buttonmodel.addActionListener(buttonactionlistener); }

updateButton(); }

public void setUI(ButtonUI buttonui) { if (this.buttonui != null) { this.buttonui.uninstallUI(this); }

this.buttonui = buttonui;

if (this.buttonui != null) { this.buttonui.installUI(this); }

updateButton(); }

public void updateButton() { invalidate(); }

Take a moment to peruse the rest of the Button class before you move on.

The ButtonModel class

The button model maintains three pieces of state information: pressed/not-pressed, armed/not-armed, and selected/not-selected. These are boolean quantities.

A button is pressed if the user has pressed the mouse button while the mouse cursor is over the button, but has not yet released the mouse button. The button is pressed even if the user drags the mouse cursor off the button.

A button is armed if the button is pressed and the mouse cursor is over the button.

Some buttons may also be selected. This value is typically toggled on and off by repeatedly pressing the button.

The code below shows the default implementation for the pressed state. The code for the armed and selected states are similar. The ButtonModel class should be subclassed in order to redefine the default behavior.

private boolean boolPressed = false;

public boolean isPressed() { return boolPressed; }

public void setPressed(boolean boolPressed) { this.boolPressed = boolPressed;

fireChangeEvent(new ChangeEvent(button)); }

The button model also notifies other objects (called event listeners) about interesting events that occur. From the code above we see that a change event is fired every time the button's state changes. Here's how it happens.

private Vector vectorChangeListeners = new Vector();

public void addChangeListener(ChangeListener changelistener) { vectorChangeListeners.addElement(changelistener); }

public void removeChangeListener(ChangeListener changelistener) { vectorChangeListeners.removeElement(changelistener); }

protected void fireChangeEvent(ChangeEvent changeevent) { Enumeration enumeration = vectorChangeListeners.elements();

while (enumeration.hasMoreElements()) { ChangeListener changelistener = (ChangeListener)enumeration.nextElement();

changelistener.stateChanged(changeevent); } }

Take a moment to review the remainder of the ButtonModel class before you move on.

The ButtonUI class

The button view/controller is responsible for presentation. By default it simply draws a rectangle of the same color as the background. Subclasses redefine these methods to provide alternate look-and-feel's -- such as Motif, Windows 95, and Java provided with the JFC.

public void update(Button button, Graphics graphics) { ; }

public void paint(Button button, Graphics graphics) { Dimension dimension = button.getSize();

Color color = button.getBackground();

graphics.setColor(color);

graphics.fillRect(0, 0, dimension.width, dimension.height); }

The ButtonUI class doesn't handle AWT events itself. Instead, it employs a custom event listener class to translate low-level AWT user interface events into the high-level semantic events the button model expects. Here's the code to install and uninstall the event listener.

private static ButtonUIListener buttonuilistener = null;

public void installUI(Button button) { button.addMouseListener(buttonuilistener); button.addMouseMotionListener(buttonuilistener); button.addChangeListener(buttonuilistener); }

public void uninstallUI(Button button) { button.removeMouseListener(buttonuilistener); button.removeMouseMotionListener(buttonuilistener); button.removeChangeListener(buttonuilistener); }

The view/controller classes are really just bundles of methods. They don't contain any of their own state. Therefore, many instances of Button can share a single instance of ButtonUI. This code distinguishes between buttons by adding a special button identifier argument to each function call.

Take a moment to review the rest of the ButtonUI class before you move on.

The ButtonUIListener class

This ButtonUIListener class assists the Button class in converting mouse and keyboard input into operations on the underlying button model.

The listener class implements the MouseListener, MouseMotionListener, and ChangeListener interfaces and handles for the following events:

public void mouseDragged(MouseEvent mouseevent) { Button button = (Button)mouseevent.getSource();

ButtonModel buttonmodel = button.getModel();

if (buttonmodel.isPressed()) { if (button.getUI().contains(button, mouseevent.getPoint())) { buttonmodel.setArmed(true); } else { buttonmodel.setArmed(false); } } }

public void mousePressed(MouseEvent mouseevent) { Button button = (Button)mouseevent.getSource();

ButtonModel buttonmodel = button.getModel();

buttonmodel.setPressed(true);

buttonmodel.setArmed(true); }

public void mouseReleased(MouseEvent mouseevent) { Button button = (Button)mouseevent.getSource();

ButtonModel buttonmodel = button.getModel();

buttonmodel.setPressed(false);

buttonmodel.setArmed(false); }

public void stateChanged(ChangeEvent changeevent) { Button button = (Button)changeevent.getSource();

button.repaint(); }

Take a moment to peruse the rest of the ButtonUIListener class before you move on.

The code in action

Bagi Anda yang menemukan daftar kode sumber di atas agak terlalu steril, dan bagi Anda yang ingin melihat bagaimana semua ini cocok, saya menyajikan applet.

Anda memerlukan browser yang mendukung Java untuk melihat applet ini.

Applet ini membutuhkan browser yang mendukung JDK 1.1. Internet Explorer 4.0 memenuhi syarat, namun, Netscape 4.0 tidak - setidaknya langsung dari kotak. Jika Anda menggunakan Netscape 4.0, jelajahi //developer.netscape.com/software/jdk/download.html dan tingkatkan.