Что-то не так с реализацией Swing MVC для JList?

Некоторое время назад я спросил этот вопрос. Все решения являются обходными способами.

Теперь этого не может быть. Я чувствую, что здесь что-то не так, но я не могу сказать, является ли это модель Swing MVC, которая является концептуально неправильной или если я думаю, что это концептуально неправильно.

Вот проблема снова. Я использую JList для реализации списка миниатюр для страниц документа. Если пользователь выбирает другую эскиз из списка, эта страница загружается. Для этого я добавил ListSelectionListener в JList, который при изменении выбора загружает эту страницу. Но пользователь может также изменить страницу с помощью другого элемента управления. Естественно, я хочу, чтобы это отразилось в списке миниатюр, выбрав эту страницу здесь. Поэтому я setSelectedIndex() обновить JList. К сожалению, у этого есть нежелательный эффект повышения ListSelectionEvent, который заставляет слушателя перезагружать страницу.

Теперь, что здесь не так? Я просто изменил модель из другого места, поэтому, естественно, я хочу, чтобы представление обновлялось, но я не хочу, чтобы он запускал события. Действительно ли Swing не реализует MVC? Или я пропущу здесь пункт?

Ответы

Ответ 1

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

В связи с этим проблема с Swing заключается в том, что часть контроллера MVC часто несколько разделяется между компонентом представления и моделью, поэтому может быть трудно централизовать эту логику. В некотором смысле интерфейс Action исправляет это для событий actionPerformed(), помещая логику в одно место и позволяя ей делиться разными компонентами, но это не помогает для других типов событий или когда есть несколько различные классы событий, которые необходимо скоординировать.

Таким образом, ответ должен следовать шаблону, который намекнул в Swing, но не был явным: выполните только запрошенное обновление, если состояние действительно изменится, иначе ничего не сделайте. Пример этого в самом JList: если вы попытаетесь установить выбранный индекс JList на тот же самый индекс, который уже выбран, ничего не произойдет. Никакие события не будут запущены, никаких обновлений не произойдет: запрос на обновление фактически игнорируется. Это хорошая вещь. Это означает, что вы можете, например, прослушивать свой JList, который будет отвечать на только что выбранный элемент, а затем, в свою очередь, попросить тот же JList повторно выбрать тот же элемент, и вы не будете застревать в патологически рекурсивный цикл. Если все модельные контроллеры в приложении делают это, то нет проблем с несколькими повторяющимися событиями, которые срабатывают повсеместно - каждый компонент будет обновлять себя (и, впоследствии, отключать события), если это необходимо, и если он обновляет то он может отключить все события обновления, которые он хочет, но только те компоненты, которые еще не получили сообщение, сделают что-нибудь с этим.

Ответ 2

Это ожидаемое поведение.

Из Model-View-Controller [Википедия]:

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

Итак, когда вы вызываете setSelectedIndex в JList, вы обновляете свою модель, которая затем уведомляет каждую ListSelectionListener. Это не будет MVC, если бы вы могли "тихо" обновить модель, не сообщив кому-либо об этом.

Ответ 3

Swing не является точно MVC, но имеет корни в MVC (разница заключается в том, что представление и контроллер в Swing более тесно связаны, чем в других MVC, см. Swing architecture для получения более подробной информации).

Но это, возможно, не проблема, с которой вы столкнулись. Похоже, вам нужно проверить в прослушивателях события, тип события и решить, игнорировать его или нет: если событие было создано в списке, измените его. Если был вызван какой-то другой элемент управления, не делайте этого.

Ответ 4

Что сказал @dogbane.

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

Так, например, когда вы обновляете список из внешнего выделения, у вас будет код вроде...

try {
    setSelectionAdjusting(true);
    /* ... your old update code ... */
} finally {
    setSelectionAdjusting(false);
}

и в коде обновления для ListSelectionListenerEvent

public void valueChanged(ListSelectionEvent e) {
    if (!isSelectionAdjusting()) {
        /* ... do what you did before ...*/
    }
}

Проблемы с охватом и доступом остаются в качестве упражнения для читателя. Вам нужно будет написать setSelectionAdjusting и, возможно, установить его и на других объектах.

Ответ 5

Я все еще чувствую, что здесь что-то концептуально неправильно.

Я сопереживаю, но это может помочь считать, что у вас нет простого JList наблюдения ListSelectionModel. Вместо этого у вас есть JList и некоторый-другой-контроль, наблюдающий гибридную модель выбора. В примере @Taisin гибрид представляет собой CustomSelectionModel, который расширяет DefaultListSelectionModel и позволяет безмолвные изменения. При совместимости также можно разделить модель, как предлагается в этом вопросе и ответе, и это SharedModelDemo из учебника.

Для справки, этот thread цитирует статью Java SE Application Design с MVC: проблемы с дизайном приложений, в котором более подробно рассматривается проблема.

Ответ 6

Я всегда делаю это так:

    public class MyDialog extends JDialog {
        private boolean silentGUIChange = false;

    public void updateGUI {
        try {
            silenGUIChange = true;

            // DO GUI-Updates here:
            textField.setText("...");
            checkBox.setSelected (...);

        }
        finally {
            silentGUIChange = false;
        }
    }

    private void addListeners () {
        checkBox.addChangeListener (new ChangeListener () {
           public void stateChanged (ChangeEvent e) {
              if (silentGUIChange)
                 return;

              // update MODEL
              model.setValue(checkBox.isSelected());
          }
         });
    }

}

Ответ 7

Обычно, как работает слушатель, он будет "уходить" каждый раз, когда происходит событие, которое оно ожидает. Если я должен был предположить, что это непонимание с вашей стороны.