Java Swing: 多模型和自定义渲染器的 JTable

3

我有一个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;
    }
};

在 SwingWorker 线程中,我根据变量 status 将不同的模型设置到我的表格(myFirstTableModel,mySecondTableModel)中,并显示带有“请稍候”的模态对话框。

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));
                }
            });

但是我认为当等待对话框遮挡我的表格时,会调用prepareRenderer。但是不同的模型还没有被应用。

很明显,我得到了:

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

我能让表格调用prepareRenderer吗?该如何使这个混乱的工作正确运行?


在这里发布代码时,您可能希望将其压缩一些。首先,人们不喜欢浏览无用的信息以获取帮助。此外,这样做可能会让您自己找到答案(我经常这样做)。 - Mike Adler
Refresh() 方法不易理解,请改进。 - Kowser
1
请学习Java命名规范并遵循它们。 - kleopatra
1
在进行实际转换之前,一定要始终进行类型检查,尤其是当你知道存在不同的类型时;-) - kleopatra
5个回答

4
最好不要将数据(业务)域的细节混杂到视图中。在您的情况下,您可以通过以下方式实现清晰分离:
  • 定义一个具有状态概念(需要关注,已关注等)的接口
  • 让您的自定义模型实现该接口
  • 在视图中,通过该接口访问模型的状态
类似以下伪代码(未编译,仅供参考):
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文档、维基、片段和swinglabs-demo):

  • 实现自定义HighlightPredicate:这决定了是否应该在视觉上“装饰”给定的单元格。它只有一个方法需要实现,通过ComponentAdapter可以读取数据。
  • 使用谓词配置预定义的一种Highlighter(有很多种)
  • 将Highlighter添加到表格中

以下是从ComponentAdapter api文档中摘录的片段

     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

直接访问不属于模型的数据是不被支持的。虽然这个问题已经存在一段时间了,但似乎从来没有显得足够重要以至于要解决它:) 而且它有点违反了基本思想:使用一个通用的抽象来访问数据,而不需要知道底层组件或模型的类型(对于表格、列表和树,高亮器/-谓词和字符串值完全相同)。

在此基础上,您可以通过适配器的目标组件间接地获取它:

  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
      }
  }

请问您能否提供一个关于如何配置JXTable高亮器的教程链接,以便当(table.getValueAt==9)时,该行变为红色。先感谢您了。 - bunnyjesse112
这个可以用!非常感谢。但我还有一个问题。在我的表格模型中,我使用对象列表作为数据,并仅显示这些对象的某些属性,并根据模型中存在但未显示的某些属性重新着色行,使用方法getObjectAtRow(int row)。如何更改adapter.getValue(temperatureColumn)以访问模型? - bunnyjesse112
我想我成功了。非常感谢您的解释。 - bunnyjesse112

3

我在我的表格中设置了不同的模型(myFirstTableModel,mySecondTableModel),具体取决于情况。

然后您需要在prepareRenderer(...)代码中使用不同的逻辑来支持这两个模型。

在这种情况下,您可能需要2个不同的表格。然后,您可以更改滚动窗格视口中的表格,而不是更改表格中的模型。


1
嗯...不,其实不是。如果有的话,这只是在prepareRenderer中进行特定视觉配置的产物(而不是像SwingX中的高亮器那样以一般方式支持)。 - kleopatra

3

首先,您可能希望通过调用以下方法来强制事件等待模型重新加载后再执行:

SwingUtilities.invokeLater(...)

围绕它们。或者可能强制模型加载优先。不太确定,但

SwingUtilities.invokeAndWait(...)

也许这正是你想要的。

其次,你可能想查看来自SwingX的JXTable。具体而言,高亮功能可能会使你的生活更加轻松。在我的当前项目中,这节省了我大量的代码 - 现在我不必维护它们了(耶!)


谢谢您的回复。我是一个复制粘贴程序员,请耐心等待。您能否给出一个根据模型值突出显示行的小例子? - bunnyjesse112
1
抱歉,现在不行。底线是:我懒得浏览你的代码并清理它,而且我不太确定你的问题意思,所以我无法编写有意义的代码来回答它。如果你清理你的代码示例或重新表述上面的评论,我会做其中之一。这并不是要像听起来那么苛刻。当然我愿意帮助你。但我没有时间和意愿从头开始为你完成工作 - 我想我在这方面不是唯一的。 - Mike Adler
哦,抱歉。我不是指从头开始。我是指一个小片段,其中HighlighterPipeline被配置为如果单元格有“-22”,那么该行将变成红色。如果是8,则为蓝色。我在网上找不到任何示例。使用正则表达式进行过滤在我的情况下并不理想。我需要像这样的东西:if (table.getValueAt==-22) hl.setBackground(Color.RED); - bunnyjesse112
只需访问swingx.java.net,点击“启动”按钮并导航到高亮部分。您将找到全面的代码示例,可以相对容易地根据自己的需求进行调整。唯一的缺点是示例代码被演示应用程序中使用的注入系统所混淆,这对您实际上并不会有用。但是...复制和粘贴的程序员也必须工作,通过浏览代码并确定需要剪切的内容来完成工作 :-) - Mike Adler

2

对于行颜色,SwingX项目中的JXTable确实是一个很好的建议。

除此之外,为什么要覆盖这个prepareRenderer方法来修改由渲染器首先创建的组件的设置呢?

针对这个特定的问题,我会在切换模型时更改表格上的渲染器,并将所有逻辑都放在我的渲染器中。

如果您还没有自己的渲染器,那么只需装饰默认渲染器并将您现在在prepareRenderer中应用于默认渲染器返回的组件的代码应用即可。


1

你的方法存在问题:

  • 很明显,你的JTable依赖于两种不同的TableModel实现。
  • 如果你遵循良好的设计原则,最终会创建两个不同的JTable实现,并将它们与你的特定TableModel耦合。

我的建议是采用两种不同的JTable实现。并在注意到status值变化时更改JTable


除此之外,statusmodel是两个不同的属性,在您的情况下需要进行同步。
因此,您需要了解在后台线程中需要更改什么以及在EDT上需要更改什么。

2
你最终会创建两个不同的JTable实现 - 不,绝对不能这样:所有JSomething都应该被使用,数据变化应该在...嗯...模型中实现。 - kleopatra
@kleopatra 如果回答是“不,从来没有”,那么当执行new javax.swing.JTable(){}时就已经违反了。你能否否认?此外,你能否否认JTable与两个不同的表格模型耦合?但是肯定可以使用TableCellRenderer进行改进。 - Kowser
1
是的,它已经做到了 - 但这并不是让它进一步恶化的理由 :-) 自定义渲染器是一个可选方案,但问题在于每行统一的可视配置需要在所有提供不同渲染器的渲染组件之间进行:它们都必须了解值的具体信息。顺便说一句,Rob的方法(在prepareRenderer中配置)与JXTable相同 - 后者通过可插拔高亮显示器非常普遍地实现了这一点。 - kleopatra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接