删除 Java Swing 中等待执行的 MouseWheelEvent/MouseWheelListener

问题描述 投票:0回答:1

我有一个实现,我想在每滚动 100 条记录后滚动 JList 时重新加载数据。我有一个很大的数据库,加载 JList 大约需要 7 秒。因此,当数据在后台加载时,将向用户显示等待对话框。

我遇到以下代码的两个问题:

  1. 对于每一次鼠标滚动,都会滚动三个记录,而不是滚动单个记录。
  2. 如果我在重新加载过程中继续滚动(即如果我继续移动鼠标滚轮),滚动操作事件将存储在某处,一旦重新加载完成,存储的操作事件将被执行。例如,如果我在重新加载记录时继续滚动六次,滚动条此时不会移动,因为存在等待对话框,但一旦重新加载完成,它将滚动六条记录。

我的目标是在加载数据甚至调用重新加载数据后删除任何等待执行的鼠标滚动/滚轮移动事件。

    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也会重置。

java swing jscrollpane jlist mousewheel
1个回答
0
投票

让我们从一些明显的问题开始

  1. 用户可以(并且将会)使用键盘和鼠标滚动列表,因此,
    MouseWheelListener
    (或任何类型的基于鼠标的侦听器)是一个不好的起点
  2. Swing 是单线程的并且不是线程安全的,这意味着您不应该在事件调度线程的上下文中执行长时间运行或阻塞操作,并且您不应该从外部更新 UI 或 UI 依赖的任何内容事件调度线程上下文。请参阅Swing 中的并发了解更多详细信息

那么,答案是什么?

嗯,对我来说,我从

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
设置为小于我的触发行的大小。

您也许可以更改它,以便如果最后一行是触发行的

>=
,您将开始加载更多页面,但是,您可能需要某种方法来确定加载是否正在进行中,并且可能修改确定下一个触发点的逻辑,以便在更动态的环境中更有效。

我还考虑将其中的很多内容整合到某种“分页”工作流程中,因此也许您可以将滚动窗格和列表传递到一个专用类中,并让它管理所有核心工作流程,并可能触发观察者加载数据,但这就是我。

© www.soinside.com 2019 - 2024. All rights reserved.