当呈现一个JList时改变光标

3

我已经实现了我的目标,但我仍然认为应该有更高效的方法……让我举个例子。

简而言之,我的问题是:是否可以确定一个组件何时完成初始渲染。

我有一个JList,它连接到一个DefaultListModel,并通过扩展DefaultListCellRenderer的自定义渲染器进行绘制。

JList的目的是“翻页”查看日志文件,每次加载新页面时填充2500个元素。在我的计算机上,通常需要几秒钟来完全呈现JList,这实际上不是问题,因为将光标更改为等待光标是可以接受的,因为它会立即向用户提供反馈。不幸的是,我无法找到一种优雅的方法来知道初始渲染何时完成。

下面是我的渲染器代码,在其中您将看到我正在计算渲染器的迭代次数。如果该数字在0和N-20之间,则光标更改为等待光标。一旦达到N-20,它将恢复为默认光标。如我之前所提到的,这很好地解决了问题,但解决方案真的感觉像是一个hack。我已经实现了DefaultListModel的ListDataListener和JList的PropertyChangeListener,但两者都不能产生我要找的功能。

/**
 * Renders the MessageHistory List based on the search text and processed events
 */
public class MessageListRenderer extends DefaultListCellRenderer
{
    private static final long serialVersionUID = 1L;

    String lineCountWidth = Integer.toString(Integer.toString(m_MaxLineCount).length());

    @Override
    public Component getListCellRendererComponent(JList l, Object value, int index, boolean isSelected, boolean haveFocus) 
    {
        JLabel retVal = (JLabel)super.getListCellRendererComponent(l, value, index, isSelected, haveFocus);
        retVal.setText(formatListBoxOutput((String)value, index));

        // initial rendering is beginning - change the cursor
        if ((renderCounter == 0) && !renderCursorIsWait)
        {
            m_This.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));   
            renderCursorIsWait = true;
        }

        // initial rendering is complete - change the cursor back
        if ((renderCounter > (l.getModel().getSize() - 20)) && renderCursorIsWait)
        {
            m_This.setCursor(Cursor.getDefaultCursor());
            renderCursorIsWait = false;
        }

        renderCounter++;

        return retVal;
    }

    /**
     * Adds font tags (marks as red) around all values which match the search criteria
     * 
     * @param lineValue string to search in 
     * @param lineIndex line number being processed
     * @return string containing the markup
     */
    private String formatListBoxOutput(String lineValue, int lineIndex)
    {
        // Theoretically the count should never be zero or less, but this will avoid an exception just in case
        if (m_MaxLineCount <= 0)
            return lineValue;

        String formattedLineNumber = String.format("%" + Integer.toString(Integer.toString(m_MaxLineCount).length()) + "s", m_CurrentPage.getStartLineNumber() + lineIndex) + ": ";

        // We're not searching, simply return the line plus the added line number
        if((m_lastSearchText == null) || m_lastSearchText.isEmpty())
            return "<html><font color=\"#4682B4\">" + formattedLineNumber.replaceAll(" ", "&nbsp;") + "</font>" + lineValue + "</html>";

        // break up the search string by the search value in case there are multiple entries 
        String outText = "";    
        String[] listValues = lineValue.split(m_lastSearchText);

        if(listValues.length > 1)
        {
            // HTML gets rid of the preceding whitespace, so change it to the HTML code
            outText = "<html><font color=\"#4682B4\">" + formattedLineNumber.replaceAll(" ", "&nbsp;") + "</font>";

            for(int i = 0; i < listValues.length; i++)
            {
                outText += listValues[i];
                if(i + 1 < listValues.length)
                    outText += "<font color=\"red\">" + m_lastSearchText + "</font>";
            }

            return outText + "</html>";
        }

        return "<html><font color=\"#4682B4\">" + formattedLineNumber.replaceAll(" ", "&nbsp;") + "</font>" + lineValue + "</html>";
    }
}

我所做的就是填充模型:

// reset the rendering counter
this.renderCounter = 0;

// Reset the message history and add all new values
this.modelLogFileData.clear();
for (int i = 0; i < this.m_CurrentPage.getLines().size(); i++)
    this.modelLogFileData.addElement(this.m_CurrentPage.getLines().elementAt(i));

更多的代码被添加了,但我不确定它是否有助于解决问题。我正在寻找一种方法来确定组件是否完成了其初始渲染,而不需要实现计数器。 - JoshBramlett
永远不要改变呼叫列表在渲染器中的状态,所有在getXXRendererComponent中给定的参数都是严格只读的。将您的逻辑移动到其他地方,就像@trashgod已经建议的那样。 - kleopatra
2个回答

4

除了 @mKorbel 建议的外,使用 SwingWorker 批量填充列表模型。为工作线程添加属性更改监听器,并在完成后恢复光标。这里有一个相关的 Q&A,链接在此。

private static class TaskListener implements PropertyChangeListener {

    @Override
    public void propertyChange(PropertyChangeEvent e) {
        if (e.getNewValue() == SwingWorker.StateValue.DONE) {
            // restore cursor
        }
    }
}

抱歉出现了假启动;SwingWorker 在事件分派线程上通知 PropertyChangeListeners - trashgod
嘿,大家好,我只是想提醒一下,我并没有放弃这个项目...只是被分配到了一个更紧急的问题上,还没有机会去看你们的建议。再次感谢你们的帮助。 - JoshBramlett
谢谢大家,这正是我所寻找的,更重要的是,我对发生的事情有了更好的理解。 - JoshBramlett

3

1) 你需要将 Cursor 添加到 JList 中,而不是在 Renderer 中。示例请参见此处此处

2) 默认情况下,Renderer 返回 JLabel,没有特殊的定义原因。

3) Renderer 可以为(在本例中)JLabel 设置背景、字体等方法。

编辑:

4) 移除 retVal.setText(formatListBoxOutput((String)value, index));,创建 DefaulListModel 并将准备好的项移动到 Model#addItem 中。

5) int index, 可以与 JList#getModel().getSize() -1; 进行比较;从 Renderer 中移除该内容。

6) 更好的做法是通过后台任务添加项目(@trashgod 您的建议是正确的:-),并分批填充(谢谢),如果您实现了 SwingWorker,则可以创建一个包含 50 个项目的批次,并按照 50 个添加... 直到完成。

7) Renderer 仅用于格式化输出,您的 HTML 格式可以是:

  • 删除,其余请参见第 3 点。
  • 在后台任务中准备而不是在 Renderer 和加载 + Html 格式化 + 向后交互之间构建构造函数,在这种情况下,Renderer 无用,因为您可以通过使用 Html 对项目进行漂亮的预格式化,然后删除 Renderer。

谢谢,但我认为你误解了问题。光标是在父窗口上设置的(通过全局变量m_This)。正如我在帖子中所说,我的实现是有效的,我只是好奇是否有一种优雅的方法来确定JList的初始渲染何时完成。 - JoshBramlett
我同意;尽可能地,试着将任何加载/格式化工作从渲染器中移出,并移到工作线程中。 - trashgod
@Josh:赞同你接受这个全面的回答。我也建议你给它点赞。 - trashgod

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