Материал: Портянкин И. Swing

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

Списки

295

Модели

Примененные нами в первом примере конструкторы класса JList, наполняющие список данными из массива или вектора, конечно, замечательны, и в тех случаях, когда вам надо быстро и просто создать небольшой список, они незаменимы. Но по настоящему удобно работать с данными компонентов Swing, в том числе и с данными списков, позволяют модели. Мы уже прекрасно знаем, что они отделяют обработку данных от представления этих данных на экране, давая программисту возможность сосредоточиться поочередно на каждой задаче и таким образом применить знаменитый лозунг «разделяй и властвуй». Более того, конструкторы класса JList создают списки которые нельзя изменять и дополнять, что как правило, весьма некстати.

Обязанности моделей списков JList описаны в интерфейсе ListModel. Интерфейс этот очень прост, требуя от вас реализовать всего четыре метода. Два метода служат для присоединения и удаления слушателей событий, происходящих при обновлении данных списка (помните, при обсуждении модели MVC мы отмечали, что модель оповещает присоединенные к ней виды при изменении данных, и это позволяет виду всегда показывать верные данные); один метод возвращает элемент, находящийся на некоторой позиции списка; еще один метод позволяет списку узнать, сколько данных в данный момент содержит модель. Несложно, но достаточно для работы списка. Заметьте, что по умолчанию модель списка не подразумевает добавления и удаления элементов.

Сразу же бежать и писать свою собственную модель «с нуля» необязательно. Во многих ситуациях достаточно стандартной модели, поставляемой вместе с библиотекой Swing, она называется DefaultListModel. Фактически модель эта представляет собой динамический массив, который в дополнение к своим основным обязанностям еще и способен оповещать об изменениях в себе заинтересованных слушателей. Вы можете работать с этой моделью так же, как и с привычным вам контейнером данных: добавлять и удалять данные, вставлять их на любые позиции, производить перебор элементов и не заботиться о деталях, таких как выделение достаточного места под элементы, хранящиеся в массиве. Давайте рассмотрим пример:

//UsingListModel.java

//Использование стандартной модели списка import javax.swing.*;

import java.awt.event.*; import java.awt.*;

public class UsingListModel extends JFrame { // наша модель

private DefaultListModel dlm; public UsingListModel() {

super("UsingListModel"); setDefaultCloseOperation(EXIT_ON_CLOSE);

//заполним модель данными dlm = new DefaultListModel(); dlm.add(0, "Кое-что"); dlm.add(0, "Кое-что еще"); dlm.add(0, "Еще немного");

//создаем кнопку и пару списков

JButton add = new JButton("Обновить");

296

ГЛАВА 11

add.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {

dlm.add(0, "Новинка!");

}

});

JList list1 = new JList(dlm); JList list2 = new JList(dlm);

//добавляем компоненты setLayout(new FlowLayout()); add(add);

add(new JScrollPane(list1)); add(new JScrollPane(list2));

//выведем окно на экран setSize(400, 200); setVisible(true);

}

public static void main(String[] args) { SwingUtilities.invokeLater(

new Runnable() {

public void run() { new UsingListModel(); } });

}

}

В примере мы создаем экземпляр модели DefaultListModel и используем его для хранения наших данных и вывода их на экран. Как видите, методы этой модели разительно напоминают методы стандартных контейнеров данных Java, и на самом деле всю работу по хранению данных модель DefaultListModel адресует находящемуся внутри нее вектору Vector, и даже названия методов для простоты сохранены. Мы добавляем в модель несколько элементов и передаем ее двум спискам для вывода данных на экран. Запустив программу с примером, вы увидите, как два списка, использующих одну и ту же модель, синхронно выводят одни и те же данные. В этом прелесть архитектуры MVC — неважно, откуда и как получены данные и как они будут выводиться: вы получаете данные и выбираете подходящий способ их отображения. Модели и виды сами позаботятся о том, чтобы все вовремя и правильно появлялось на экране.

Списки

297

Для демонстрации динамического поведения данных модели в панель содержимого была включена кнопка JButton. Щелкая на этой кнопке, вы будете добавлять данные в оба списка, потому что они используют одну и ту же модель, а все необходимые для обновления вида «переговоры» между моделью и списками будут проходить автоматически. Обновление экрана и проверка корректности компонентов также выполняются автоматически2. Однако помните, что динамически добавлять много данных в стандартную модель, которая уже присоединена к виду (или к нескольким видам), — нежелательная практика, ведущая к снижению производительности.

СОВЕТ

Основную часть данных подготавливайте и добавляйте в модель перед ее присоединением к виду (или видам). Это к тому же позволяет работать с моделью из отдельного потока, пока у ней нет связи с графическими компонентами. Присоединенная к виду модель оповещает его о каждом обновлении данных, и частое обновление данных может привести к серьезному снижению производительности.

В вашем приложении может быть множество диалоговых окон, особенно это касается приложений, работающих с базами данных. Здесь преимущества моделей просто неоценимы: чтобы каждый раз не получать данные, готовить их и выводить на экран, а затем запоминать новое состояние данных, вы просто храните данные в одной простой модели, которую и используете везде в своем приложении.

Стандартная модель DefaultListModel очень удобна и позволяет гибко хранить и изменять любые данные. Однако бывают случаи, когда лучше определить собственную модель данных, особенно там, где данные хранятся в нестандартных структурах или их необходимо получить из необычного источника, например из сетевого соединения или базы данных. Давайте рассмотрим небольшой пример, в котором данные списка мы будем получать из простой базы данных. Это весьма вероятная ситуация в больших приложениях, данные которые почти всегда хранятся в базах данных или в серверных компонентах (таких как Enterprise JavaBeans или .NET).

Нам не придется полностью реализовывать модель «с нуля». Создатели Swing уже позаботились о поддержке любыми моделями механизма оповещения (встроили списки слушателей и обеспечили рассылку событий) в абстрактном классе AbstractListModel. Нужно лишь унаследовать от этого класса и получить данные списка, а заботиться о поддержке слушателей будут другие:

//DatabaseListModel.java

//Модель списка, работающая с базой данных package com.porty.swing;

import javax.swing.*;

2Списки увеличатся после добавления новых элементов, и самый длинный элемент не «влезет»

впанель прокрутки после появления вертикальной полосы прокрутки, что приведет к появлению горизонтальной полосы прокрутки, крайне нежелательного гостя. На самом деле лучше составлять интерфейс так, чтобы размеров списка вместе с панелью прокрутки заранее хватало под новые элементы (в этом могут помочь подходящие менеджеры расположения и известный нам метод setPrototypeCellValue()). Внезапное изменение состояния интерфейса запутывает пользователя, и вы легко увидите это, поменяв размер окна — менеджер FlowLayout поменяет их размеры на пред-

почтительные. При работе со списками очень важно правильно определять максимальную длину их элементов — это обезопасит нас от неприятных сюрпризов.

298 ГЛАВА 11

import java.sql.*; import java.util.*;

public class DatabaseListModel extends AbstractListModel { // здесь будем хранить данные

private ArrayList<String> data = new ArrayList<String>(); // загрузка из базы данных

public void setDataSource(ResultSet rs, String column) throws SQLException {

//получаем данные data.clear();

while ( rs.next() ) { data.add(rs.getString(column));

}

//оповещаем виды (если они есть)

fireIntervalAdded(this, 0, data.size());

}

// методы модели для выдачи данных списку public int getSize() {

return data.size();

}

public Object getElementAt(int idx) { return data.get(idx);

}

}

Сама по себе модель DatabaseListModel довольно проста. Она наследует от абстрактного класса моделей списков AbstractListModel и хранит данные в динамическом массиве ArrayList. Вся «соль» заключена в методе setDataSource(), который позволяет ей получать данные из соединения с базой данных. Для правильной работы этому методу необходимо передать результат запроса к базе данных (объект ResultSet), а также название столбца, данные которого будут использованы для заполнения списка (название столбца передается в строке column). Подразумевается, что в результате выполнения запроса столбец column появится в полученных данных (в объекте ResultSet), и в нем будут храниться текстовые данные. Впрочем, можно немного доработать нашу модель, чтобы она могла работать с любым типом данных.

Заметьте, что при получении всех записей из базы данных модель оповещает об этом своих слушателей. Для оповещения присоединенных к модели слушателей (а это чаще всего виды, отображающие ее данные) мы используем возможности базового класса AbstractListModel: вызываем методы fireXXX(), каждый из которых сообщает о некотором событии, как-то: обновление элементов списка, добавление в список новых данных или удаление данных из списка (эти методы просто создают подходящие экземпляры события ListDataEvent).

Наша модель очень проста, и отправляет события о изменениях в себе напрямую, что как правило означает перерисовку списка на экране. Это означает что мы должны вызывать метод setDataSource() только из потока рассылки событий, если модель уже пе-

Списки

299

редана в список JList. Однако до присоединения к списку мы вполне можем вызвать этот метод в отдельном потоке, чем мы вскоре и воспользуемся.

Более гибкая версия созданной в этом примере модели могла бы постепенно загружать данные, оповещая об этом список, и в случае если соединение с базой данных было бы медленным (например, сетевым), пользователь мог бы воочию видеть процесс загрузки данных в список и выбрать нужный ему элемент еще до полной загрузки. Однако это потребовало бы работы метода для загрузки данных в отдельном потоке, синхронизации списка data (в классе ArrayList для ускорения работы отключена синхронизация), и оповещения присоединенных к модели слушателей только из потока рассылки событий с помощью метода invokeLater(). Это несложно, и вы можете доработать нашу простую модель в качестве простого упражнения.

Созданная модель помещается в пакет com.porty.swing: так ее будет проще импортировать и многократно использовать в других программах. Не то чтобы новая модель была очень полезна, но для начального наброска интерфейса подходит весьма хорошо.

Остается проверить работу новой модели. Давайте напишем небольшой пример с ее использованием:

//DBListModelTest.java

//Использование модели списка

//для работы с базами данных import javax.swing.*;

import java.sql.*; import java.awt.*;

import com.porty.swing.*;

public class DBListModelTest { // параметры базы данных private static String

dsn = "jdbc:odbc:Library", uid = "",

pwd = "",

query = "select * from readers.csv";

public static void main(String[] args) throws Exception {

//инициализация JDBC Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

//объект-соединение с базой данных

Connection conn = DriverManager.getConnection(dsn, uid, pwd); Statement st = conn.createStatement();

ResultSet rs = st.executeQuery(query); // создаем модель

final DatabaseListModel dblm = new DatabaseListModel(); // загружаем данные

dblm.setDataSource(rs, "surname"); rs.close();

Источник: https://studfile.net/preview/15935977/