Подтвердить что ты не робот

Как синхронизировать модель Swing с быстро меняющейся "реальной" моделью?

Как известно, все, что связано с компонентами Swing, должно выполняться на потоке отправки событий. Это также относится к за компонентами, например TableModel. Достаточно легко в элементарных случаях, но все становится довольно сложным, если модель представляет собой "живой вид" чего-то, что должно работать на отдельном потоке, потому что оно быстро меняется. Например, живой взгляд на фондовый рынок на JTable. Фондовые рынки обычно не бывают на EDT.

Итак, каков предпочтительный шаблон для (de) пары модели Swing, которая должна быть на EDT, и "реальной", потокобезопасной модели, которая должна быть обновляемой из любого места и в любое время? Одним из возможных решений было бы на самом деле разделить модель на две отдельные копии: "настоящая" модель плюс своя копия Swing, которая представляет собой снимок "реальной" модели. Затем они (двунаправленно) синхронизируются по EDT время от времени. Но это похоже на раздувание. Действительно ли это единственный жизнеспособный подход, или существуют ли какие-либо другие, более стандартные способы? Полезные библиотеки? Что-нибудь?

4b9b3361

Ответ 1

Я могу порекомендовать следующий подход:

  • Поместите события, которые должны изменить таблицу в очереди "ожидающего события", и когда событие помещено в очередь , а очередь пуста, затем вызовите поток Dispatch для извлечения очереди все события и обновить модель таблицы. Эта оптимизация означает , что вы больше не вызываете поток отправки событий для каждого полученного события, что решает проблему потока отправки событий, не поддерживающую основной поток событий.
  • Избегайте создания нового Runnable при вызове потока диспетчеризации событий, используя внутренний класс без сохранения состояния для сбрасывания очереди ожидающих событий в вашей реализации панели таблицы.
  • Дополнительная дополнительная оптимизация: при сбрасывании очереди ожидающих событий минимизируется количество событий обновления таблицы, запускаемых путем запоминания, какие строки таблицы необходимо перекрасить, а затем запускать одно событие (или одно событие в строке) после обработки всех событий.

Пример кода

public class MyStockPanel extends JPanel {
  private final BlockingQueue<StockEvent> stockEvents;

  // Runnable invoked on event dispatch thread and responsible for applying any
  // pending events to the table model.
  private final Runnable processEventsRunnable = new Runnable() {
    public void run() {
      StockEvent evt;

      while ((evt = stockEvents.poll() != null) {
        // Update table model and fire table event.
        // Could optimise here by firing a single table changed event
        // when the queue is empty if processing a large #events.
      }
    }
  }

  // Called by thread other than event dispatch thread.  Adds event to
  // "pending" queue ready to be processed.
  public void addStockEvent(StockEvent evt) {
    stockEvents.add(evt);

    // Optimisation 1: Only invoke EDT if the queue was previously empty before
    // adding this event.  If the size is 0 at this point then the EDT must have
    // already been active and removed the event from the queue, and if the size
    // is > 0 we know that the EDT must have already been invoked in a previous
    // method call but not yet drained the queue (i.e. so no need to invoke it
    // again).
    if (stockEvents.size() == 1) {
      // Optimisation 2: Do not create a new Runnable each time but use a stateless
      // inner class to drain the queue and update the table model.
      SwingUtilities.invokeLater(processEventsRunnable);
    }
  }
}

Ответ 2

Насколько я понимаю, вы не хотите внедрять интерфейсы модели Swing в своей реальной модели, не так ли? Можете ли вы реализовать модель Swing как "представление" над частью реальной модели? Он преобразует доступ getValueAt() к чтению к вызовам реальной модели, и реальная модель уведомит модель Swing об изменениях, либо предоставив список изменений, либо предположив, что модель Swing позаботится о том, чтобы задать новые значения все, что в настоящее время отображается.

Ответ 3

Обычный подход - отправить "сигналы" какого-то типа, к которому пользовательский интерфейс прослушивает. В моем коде я часто использую центральный диспетчер, который отправляет сигналы, содержащие объект, который был изменен, имя поля/свойства плюс старое и новое значение. Сигнал не отправляется для случая oldValue.equals(newValue) или oldValue.compareTo(newValue) == 0 (последний для дат и BigDecimal).

Затем поток пользовательского интерфейса регистрирует слушателя для всех сигналов. Затем он исследует объект и имя, а затем переводит это на изменение в пользовательском интерфейсе, который выполняется через asyncExec().

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