提问人:slow_learner 提问时间:10/27/2023 更新时间:10/27/2023 访问量:33
删除 Java Swing 中等待执行的 MouseWheelEvent/MouseWheelListener
Remove MouseWheelEvent/MouseWheelListener awaiting execution in Java Swing
问:
我有一个实现,我想在每 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
- Swing 是单线程的,不是线程安全的,这意味着您不应该在事件调度线程的上下文中执行长时间运行或阻塞操作,也不应该从 Event Dispatching Thread 上下文之外更新 UI 或 UI 所依赖的任何内容。有关更多详细信息,请参阅 Swing 中的并发性
那么,答案是什么?
好吧,对我来说,我从 的开始,并在其中添加了一个。当垂直滚动条位置发生变化时,这将通知我。这可能不是“最佳”选择,但对我来说是显而易见的选择。这意味着我们不依赖于监视键盘和鼠标,而是监视滚动窗格本身的状态(或部分状态)。veriticalScrollBar
JScrollPane
AdjustmentListener
接下来,我设置了一个“触发器”,在我的测试中,它比一整页数据少 5 行。也就是说,在我的示例中,一个页面是 20 行,所以我的触发器总是少 5 行 (15/35/55/75/95),这意味着我们尝试稍微早一点加载一个新页面。
当触发时,我检查了属性,如果值被数学化,我然后使用 a 将数据加载卸载到不同的线程,但这允许我轻松将该数据重新同步回 EDT。有关更多详细信息,请参阅 Worker Threads 和 SwingWorkerAdjustmentListener
JList#getLastVisibleIndex
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
您可以更改它,以便如果最后一行是触发行,您将开始加载更多页面,但是,您可能需要某种方法来确定是否首先正在加载,并可能修改确定下一个触发点的逻辑,以便在更动态的环境中更有效率。>=
我还会考虑将其中很多内容滚动到某种“分页”工作流中,所以也许你可以将滚动窗格和列表传递到一个专用类中,让它管理所有的核心工作流,并可能触发观察者加载数据,但这就是我。
哦,还可以考虑利用它来提供某种视觉反馈,有关更多详细信息,请参阅如何使用 JLayer 类装饰组件JLayer
评论