Java Swing: Jtable со многими моделями и пользовательским рендерером - PullRequest
3 голосов
/ 06 декабря 2011

У меня есть jtable, в котором я перекрашиваю строки в зависимости от значений из модели, что-то вроде этого:

resultTable = new javax.swing.JTable(){
    private Border outside = new MatteBorder(1, 0, 1, 0, Color.BLACK);
    private Border inside = new EmptyBorder(0, 1, 0, 1);
    private Border highlight = new CompoundBorder(outside, inside);
    public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
        Component c = super.prepareRenderer(renderer, row, column);
        JComponent jc = (JComponent) c;
        //  Color row based on a cell value
        if (!isRowSelected(row)) {
            c.setBackground(getBackground());
            int modelRow = convertRowIndexToModel(row);
            if (getStatus().equals("status1")) {
                myFirstTableModel model = (myFirstTableModel ) resultTable.getModel();                    
                if ((model.getObjectAtRow(modelRow).getMsg().getRegNumIn() == 3)) {
                    c.setBackground(new Color(255, 244, 148));//YELLOW - needs attension
                } 
            } else if (getStatus().equals("status2")) {
                mySecondTableModel model = (mySecondTableModel) resultTable.getModel();

                if (model.getObjectAtRow(modelRow).getMsg().getTask() == 2) {
                    c.setBackground(new Color(210, 245, 176));//GREEN - got attension
                } 
            } 
        } else if (isRowSelected(row)) {
            jc.setBorder(highlight);
            c.setBackground(new Color(201, 204, 196));
        }
        return c;
    }
};

Я устанавливаю разные модели для своей таблицы (myFirstTableModel, mySecondTableModel) в зависимости от var status в потоке SwingWorker и отображает модальное диалоговое окно с «Please wait».

final WaitDialog dialog = new WaitDialog(new javax.swing.JFrame(), true);
    dialog.addWindowListener(new java.awt.event.WindowAdapter() {
});
SwingWorker worker = new SwingWorker() {
    @Override
    protected Object doInBackground() throws Exception {
        setStatus("status2");
        Refresh();
        return 0;
    }
    @Override
    public void done() {
        dialog.dispose();
    }
};

worker.execute();
dialog.setVisible(true);

изменение модели в методе Refresh ():

if (getMainFrameStatus().equals("status2")) {
     @Override
                public void run() {
                    //Update the model here

                    resultTable.setModel(new mySecondTableModel(data));
                }
            });

Но я думаю prepareRendere вызывается, когда wait dialog запутывает мой стол. Но другая модель пока не применяется.

И, очевидно, я получаю

Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: myFirstTableModel cannot be cast to mySecondTableModel at mySecondTableModel model = (mySecondTableModel) resultTable.getModel();

Могу ли я разрешить вызов таблицы prepareRenderer? Как сделать так, чтобы этот беспорядок работал правильно?

Ответы [ 5 ]

4 голосов
/ 06 декабря 2011

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

  • определив интерфейс, который имеет понятие статуса (потребности-внимание, внимание-внимание ...)
  • Пользовательская модель реализует этот интерфейс
  • в виде, доступ к статусу модели через этот интерфейс

что-то вроде (не скомпилировано, просто фрагмент псевдокода)

public interface StatusAware {

      enum Status {

           NORMAL,
           GOT_ATTENTION,
           NEEDS_ATTENTION,
           ...

      }
      public Status getStatus(int modelIndex);
} 

public class MyFirstTableModel extends AbstractTableModel implements StatusAware {

       public Statuc getStatus(int modelRow) {
           boolean needsAttention = getObjectAtRow(modelRow).getMsg().getRegNumIn() == 3;
           return needsAttention ? NEEDS_ATTENTION : NORMAL;
       }

       ....
}


public class MySecondTableModel extends AbstractTableModel implements StatusAware {

       public Statuc getStatus(int modelRow) {
           return // the status of the given row
       }

       ....
}

public class MyTable extends JTable { // if you insist on not using JXTable 


      public Component prepareRenderer(...) {
            Component comp = super(...)
            if (getModel() instanceof StatusAware {
                 Status status = ((StatusAware) getModel()).getStatus(convertRowIndexToModel(row));
                 if (NEEDS_ATTENTION == status) {
                       ...
                 } else if (...) {
                      ...
                 } 
            }
            return comp;
       }
}

Редактировать

Похоже на SwingX (кашель ... нет учебника, только api doc, wiki, фрагменты, демонстрация swinglabs):

  • реализовать пользовательский HighlightPredicate: это решает, должна ли данная ячейка быть визуально «украшена».Он имеет только один метод для реализации, обеспечивает доступ для чтения к данным через ComponentAdapter
  • , конфигурирует один из предопределенных маркеров (есть целая куча) с предикатом
  • , добавляет маркерк таблице

Фрагмент, взятый из ComponentAdapter api doc

     HighlightPredicate feverWarning = new HighlightPredicate() {
         int temperatureColumn = 10;

         public boolean isHighlighted(Component component, ComponentAdapter adapter) {
             return hasFever(adapter.getValue(temperatureColumn));
         }

         private boolean hasFever(Object value) {
             if (!value instanceof Number)
                 return false;
             return ((Number) value).intValue() > 37;
         }
     };

     Highlighter hl = new ColorHighlighter(feverWarning, Color.RED, null);
     table.addHighlighter(hl);

Редактировать 2

Доступ к данным, которые не являются частьюмодель не поддерживается напрямую.Хотя он какое-то время скрывался, он никогда не казался достаточно важным, чтобы прыгать за него :) Плюс он как бы нарушает основную идею: иметь общую абстракцию для доступа к данным, не зная тип базового компонента или модель (Highlighter/ -Predicate и StringValue одинаковы для таблицы, списка, дерева).

Имея это в виду, вы можете получить его косвенно через целевой компонент адаптера:

  if (adapter.getComponent() instanceof JTable) {
      JTable table = (JTable) adapter.getComponent();
      TableModel model = table.getModel();
      if (model instanceof MyModel) {
          int modelRow = adapter.convertRowIndexToModel(adapter.row);
          MyObject object = ((MyModel).getRowObjectAt(modelRow));
          ... // check the object
      }
  }
3 голосов
/ 06 декабря 2011

Я установил разные модели для своей таблицы (myFirstTableModel, mySecondTableModel) в зависимости от

Затем вам потребуется другая логика в коде prepareRenderer (...) для поддержки обеих моделей.

В этом случае вам, вероятно, понадобятся 2 разные таблицы.Затем вместо изменения модели в таблице вы изменили бы таблицу в области просмотра области прокрутки.

3 голосов
/ 06 декабря 2011

Во-первых, вы можете заставить события ждать до тех пор, пока модель не будет перезагружена, вызывая

SwingUtilities.invokeLater(...)

вокруг них.Или, возможно, заставить загрузку модели иметь приоритет.Не совсем уверен, но

SwingUtilities.invokeAndWait(...)

может быть тем, что вам нужно.

Во-вторых, вы можете проверить JXTable из SwingX .В частности, маркеры могут сделать вашу жизнь немного проще.В моем текущем проекте это сэкономило мне кучу кода - который я теперь не должен поддерживать (ууу!)

2 голосов
/ 06 декабря 2011

Для раскраски строк JXTable проекта SwingX действительно хорошее предложение.

Кроме того, почему вы все равно переопределяете этот метод prepareRenderer, чтобы изменить настройки компонента, который был создан средством визуализации.

Для этой конкретной проблемы я бы поменял рендерер на столе при переключении модели и поместил всю эту логику в рендерер.

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

1 голос
/ 06 декабря 2011

Проблемы с вашим подходом:

  • Понятно, что ваш JTable зависит от двух различных реализаций TableModel.
  • Если вы будете следовать хорошему дизайну, вы будетеВ итоге я создал две разные реализации JTable и соединил их с вашей specific TableModel.

Я предлагаю перейти на две разные реализации на JTable.И измените JTable, когда вы заметите изменение значения status.


Кроме этого, status & model - это два разных свойства, которые требуют синхронизации в вашем случае.

Итак, вам нужно знать, что нужно изменить в фоновом потоке и что в EDT.

...