我有一个实现,我想在每滚动 100 条记录后滚动 JList 时重新加载数据。我有一个很大的数据库,加载 JList 大约需要 7 秒。因此,当数据在后台加载时,将向用户显示等待对话框。
我遇到以下代码的两个问题:
我的目标是在加载数据甚至调用重新加载数据后删除任何等待执行的鼠标滚动/滚轮移动事件。
JScrollPane calculateScrollPane = new JScrollPane();
JList calculateList = new JList();
MouseWheelListener mouseListener = new MouseWheelListener() {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
int firstVisibleIndex = calculateList.getFirstVisibleIndex();
WaitDialog wait = new WaitDialog();
wait.start();
if(firstVisibleIndex % 100 == 0)
reloadData(e);
wait.stop();
}
};
calculateScrollPane.addMouseWheelListener(mouseListener);
我尝试存储firstIndexAfterReload = firstVisibleIndex;并使用calculateList.ensureIndexIsVisible(firstIndexAfterReload)重置calculateList第一个可见索引;这只会重置firstVisibleIndex一次。如果我严格滚动多次,那么firstIndexAfterReload也会重置。
让我们从一些明显的问题开始
MouseWheelListener
(或任何类型的基于鼠标的侦听器)是一个不好的起点那么,答案是什么?
嗯,对我来说,我从
veriticalScrollBar
的 JScrollPane
开始,并在其中添加了 AdjustmentListener
。当垂直滚动条位置发生变化时,这会通知我。这可能不是“最好”的选择,但对我来说这是显而易见的选择。这意味着我们不依赖于监视键盘和鼠标,而是监视滚动窗格本身的状态(或部分状态)。
接下来,我设置了一个“触发器”,在我的测试中,它比整页数据少了 5 行。也就是说,在我的示例中,一个页面有 20 行,因此我的触发器始终少 5 行(15/35/55/75/95),这意味着我们尝试稍微提前加载新页面。
当
AdjustmentListener
被触发时,我检查了 JList#getLastVisibleIndex
属性,如果值匹配,然后我使用 SwingWorker
将数据加载到不同的线程,但这使我可以通过一种简单的方法将该数据重新同步回 EDT。请参阅工作线程和 SwingWorker 了解更多详细信息
这个想法可能看起来像......
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingWorker;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
DefaultListModel<String> model = new DefaultListModel<>();
for (int index = 0; index < 20; index++) {
model.addElement(Integer.toString(index));
}
JList<String> list = new JList<>(model);
list.setVisibleRowCount(10);
JScrollPane scrollPane = new JScrollPane(list);
scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
private int triggerRow = 15;
@Override
public void adjustmentValueChanged(AdjustmentEvent e) {
int lastRow = list.getLastVisibleIndex();
if (lastRow == triggerRow) {
int startingValue = model.getSize();
triggerRow += 20;
SwingWorker<List<String>, List<String>> worker = new SwingWorker<>() {
@Override
protected List<String> doInBackground() throws Exception {
List<String> values = new ArrayList<>(20);
for (int index = 0; index < 20; index++) {
values.add(Integer.toString(startingValue + index));
}
// Artificial 7 second delay...
Thread.sleep(7000);
publish(values);
return values;
}
@Override
protected void process(List<List<String>> chunks) {
for (List<String> values : chunks) {
model.addAll(values);
}
}
};
worker.execute();
}
}
});
setLayout(new BorderLayout());
add(scrollPane);
}
}
}
现在,请理解。这是简化的解决方案。我特意将
setVisibleRowCount
设置为小于我的触发行的大小。
您也许可以更改它,以便如果最后一行是触发行的
>=
,您将开始加载更多页面,但是,您可能需要某种方法来确定加载是否正在进行中,并且可能修改确定下一个触发点的逻辑,以便在更动态的环境中更有效。
我还考虑将其中的很多内容整合到某种“分页”工作流程中,因此也许您可以将滚动窗格和列表传递到一个专用类中,并让它管理所有核心工作流程,并可能触发观察者加载数据,但这就是我。