首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >JTable与RowSorter的变化--过滤器变化时的选择

JTable与RowSorter的变化--过滤器变化时的选择
EN

Stack Overflow用户
提问于 2021-09-03 14:51:24
回答 1查看 102关注 0票数 0

这是一个复杂的问题,不幸的是,一个小的SSCCE是不可能的。因此,我有一个很长的SCCE来演示这个问题。简化的示例程序使用简化的数据源时区.若要使用它,请在表中选择一个时区,然后更改顶部按钮的筛选器。请注意底部的文本更改,以显示应用程序选择。不想要的行为是,当切换到不包括所选值的筛选器时,模型中的选定值将被清除。令人惊讶的是,当选择被筛选出时,该值将被更新为未设置;但当筛选返回时,应用程序选择将被返回。

基于Swing的应用程序的设计是一个,该模型被认为是应用程序数据的权威来源,包括当前的选择。模型可以有多个ViewModel视图,显示模型中的数据。此当前选择可反映在多个视图中。用户应该能够从任何视图中更改所选内容。如果不适用于某些视图,所选内容可能在所有视图中可见,也可能不是可见的(一个真实世界的示例可能是一个视图,该视图显示车辆维护可能不显示车辆正在执行的行程)。

示例程序有一个JLabel作为应用程序所选内容的简化视图,它在应用程序的底部显示模型中的选择。

另一个更相关的视图是一个JTable,它显示每行一个时区(作为字符串)。它有一个自定义的ListSelectionModel作为ViewModel,它将更改请求转发给应用程序模型,并侦听来自应用程序模型的更改,并通过调用Super上的方法将它们应用于选择。这与预期的一样,至少在应用过滤之前是这样的。

过滤过程主要在JTable及其内部类(如JTable$SortManager )中完成。它似乎记住并清除所选内容,执行排序和筛选,如果所选值不在新筛选的集合中,则恢复所选内容。

不幸的是,在ListSelectionModel中,这些清除和选择操作正在改变应用程序模型中的底层选择。在我的实际应用中,选择加载了更多关于选择的信息来显示,并且是一个相对昂贵的操作,因此应该避免对这个值的虚假更改。

因此,问题是:是否有一种方法可以防止在更改表筛选器时更改应用程序模型的选择?--我想这个解决方案属于以下几种类型之一:

当筛选/排序正在进行时,可能有某种方式在happening

  • There中检测ListSelectionModel中的应用程序模型,而不是更新应用程序模型,而这可能是可以在某个地方重写以更改不希望的行为(

)的东西。

下面是示例代码:

代码语言:javascript
运行
复制
import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;

import javax.swing.Box;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.RowFilter;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;

public class TableProblem
    extends JFrame
{
    public static final class ApplicationModel
    {
        String[] data = TimeZone.getAvailableIDs();
        public String[] getData() { return data; }

        private String modelSelection;
        public String getModelSelection() { return modelSelection; }
        public void setModelSelection(String value) { modelSelection = value; fireModelSelectionChange(); }

        private void fireModelSelectionChange()
        { selectionListeners.forEach(l -> l.modelSelectionChanged(modelSelection, findModelIndex(modelSelection))); }

        private int findModelIndex(String value)
        {
            if (value != null)
                for (int i = 0; i < data.length; i++)
                    if (value.equals(data[i]))
                        return i;
            return -1;
        }
        private List<ApplicationModelSelectionListener> selectionListeners = new ArrayList<>();
        public void addSelectionListener(ApplicationModelSelectionListener l) { selectionListeners.add(l); }
    }

    public interface ApplicationModelSelectionListener
    {
        void modelSelectionChanged(String selection, int selectedModelIndex);
    }

    /** This class acts as the selection ViewModel.  The actual model is the
    * passed-in ApplicationModel.
    */
    public final class TimeZoneListSelectionModel
        extends DefaultListSelectionModel
        implements ApplicationModelSelectionListener
    {
        private final ApplicationModel appMdl;
        private static final long serialVersionUID = 1L;

        private TimeZoneListSelectionModel(ApplicationModel appMdl)
        {
            setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            this.appMdl = appMdl;
            appMdl.addSelectionListener(this);
        }

        // Requests to ListSelectionModel to modify the selection are passed
        // to the appMdl
        @Override
        public void clearSelection()
        {
            appMdl.setModelSelection(null);
        }

        @Override
        public void setSelectionInterval(int index0, int index1)
        {
            int modelIdx = tbl.convertRowIndexToModel(index0);
            String value = appMdl.getData()[modelIdx];
            appMdl.setModelSelection(value);
        }

        @Override
        public void addSelectionInterval(int index0, int index1)
        {
            int modelIdx = tbl.convertRowIndexToModel(index0);
            String value = appMdl.getData()[modelIdx];
            appMdl.setModelSelection(value);
        }

        // Notification from the app model about selection change gets
        // percolated back to the user interface
        @Override
        public void modelSelectionChanged(String selection, int selectedModelIndex)
        {
            if (selectedModelIndex == -1)
            {
                super.clearSelection();
                return;
            }
            int viewIndex = tbl.convertRowIndexToView(selectedModelIndex);
            if (viewIndex == -1)
                super.clearSelection();
            else
                super.setSelectionInterval(viewIndex, viewIndex);
        }
    }

    public static final class TimeZoneTableModel
        extends AbstractTableModel
    {
        private static final long serialVersionUID = 1L;
        private final String[] data;

        public TimeZoneTableModel(String[] data)
        {
            this.data = data;
        }

        @Override public int getRowCount() { return data.length; }

        @Override public int getColumnCount() { return 1; }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex)
        {
            if (columnIndex == 0)
                return data[rowIndex];
            throw new IllegalArgumentException("columnIndex="+columnIndex+" should be < 1");
        }

        @Override public String getColumnName(int column)
        { return "Time Zone"; }
    }

    private static final class StringRowFilter
        extends RowFilter<TableModel, Integer>
    {
        private String prefix;
        public void setPrefix(String value) { prefix = value; rowSorter.sort(); }

        private final TableRowSorter<TableModel> rowSorter;

        public StringRowFilter(TableRowSorter<TableModel> rowSorter)
        {
            this.rowSorter = rowSorter;
        }

        @Override
        public boolean include(
            Entry<? extends TableModel, ? extends Integer> entry)
        {
            if (prefix == null)
                return true;
            String lowerCase = entry.getStringValue(0).toLowerCase();
            return lowerCase.startsWith(prefix);
        }
    }

    private static final long serialVersionUID = 1L;

    public static void main(String[] args)
    {
        ApplicationModel appMdl = new ApplicationModel();
        SwingUtilities.invokeLater(() -> new TableProblem(appMdl).setVisible(true));
    }

    private final JTable tbl;

    public TableProblem(ApplicationModel appMdl)
    {
        super("View-ModelView-Model Test");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        TimeZoneTableModel mdl = new TimeZoneTableModel(appMdl.getData());

        tbl = new JTable(mdl);
        tbl.setAutoCreateRowSorter(true);
        TimeZoneListSelectionModel tzListSelectionModel = new TimeZoneListSelectionModel(appMdl);
        tbl.setSelectionModel(tzListSelectionModel);

        @SuppressWarnings("unchecked")
        TableRowSorter<TableModel> rowSorter = (TableRowSorter<TableModel>)tbl.getRowSorter();
        StringRowFilter filter = new StringRowFilter(rowSorter);
        rowSorter.setRowFilter(filter);

        Box filterButtons = createFilterButtons(filter);

        Box vbox = Box.createVerticalBox();
        vbox.add(filterButtons);
        vbox.add(new JScrollPane(tbl));

        JLabel mdlSelect = new JLabel("App Model selection: ");
        appMdl.addSelectionListener((selection, selectedModelIndex) ->
            mdlSelect.setText("App Model selection: " + selection + " (" +
                selectedModelIndex + ")"));
        vbox.add(mdlSelect);

        add(vbox, BorderLayout.CENTER);

        pack();
    }

    private static Box createFilterButtons(StringRowFilter filter)
    {
        Box filterButtons = Box.createHorizontalBox();
        filterButtons.add(new JLabel("Filter: "));
        for (String filterStr : "All,Africa,America,Antarctica,Asia,Australia,Canada,Europe,Pacific,Us".split(","))
            addFilterButton(filter, filterButtons, filterStr);
        return filterButtons;
    }

    private static void addFilterButton(StringRowFilter filter,
        Box filterButtons, String buttonName)
    {
        String filterPrefix = "All".equals(buttonName) ? null : buttonName.toLowerCase();
        JButton asiaButton = new JButton(buttonName);
        asiaButton.addActionListener(ae -> filter.setPrefix(filterPrefix));
        filterButtons.add(asiaButton);
    }
}
EN

回答 1

Stack Overflow用户

发布于 2021-09-07 15:16:47

这个问题的一个令人失望和不满意的解决方案是,在告诉JTableRowSorter之前,在请求修改过滤器值之前,清除模型中当前选定的项。

这是对行为的一种更改,其中选择被清除,但当单击筛选值时防止虚假清除和重置值。

对示例代码的更改将涉及将选择模型传递给filter按钮的操作。将发生变化的三种方法如下。

代码语言:javascript
运行
复制
public TableProblem(ApplicationModel appMdl)
{
    super("View-ModelView-Model Test");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    TimeZoneTableModel mdl = new TimeZoneTableModel(appMdl.getData());

    tbl = new JTable(mdl);
    tbl.setAutoCreateRowSorter(true);
    TimeZoneListSelectionModel tzListSelectionModel = new TimeZoneListSelectionModel(appMdl);
    tbl.setSelectionModel(tzListSelectionModel);

    @SuppressWarnings("unchecked")
    TableRowSorter<TableModel> rowSorter = (TableRowSorter<TableModel>)tbl.getRowSorter();
    StringRowFilter filter = new StringRowFilter(rowSorter);
    rowSorter.setRowFilter(filter);

    Box filterButtons = createFilterButtons(filter, tzListSelectionModel);

    Box vbox = Box.createVerticalBox();
    vbox.add(filterButtons);
    vbox.add(new JScrollPane(tbl));

    JLabel mdlSelect = new JLabel("App Model selection: ");
    appMdl.addSelectionListener((selection, selectedModelIndex) ->
        mdlSelect.setText("App Model selection: " + selection + " (" +
            selectedModelIndex + ")"));
    vbox.add(mdlSelect);

    add(vbox, BorderLayout.CENTER);

    pack();
}

private static Box createFilterButtons(StringRowFilter filter,
    TimeZoneListSelectionModel tzListSelectionModel)
{
    Box filterButtons = Box.createHorizontalBox();
    filterButtons.add(new JLabel("Filter: "));
    for (String filterStr : "All,Africa,America,Antarctica,Asia,Australia,Canada,Europe,Pacific,Us".split(","))
        addFilterButton(filter, filterButtons, filterStr, tzListSelectionModel);
    return filterButtons;
}

private static void addFilterButton(StringRowFilter filter,
    Box filterButtons, String buttonName,
    TableProblem.TimeZoneListSelectionModel tzListSelectionModel)
{
    String filterPrefix = "All".equals(buttonName) ? null : buttonName.toLowerCase();
    JButton asiaButton = new JButton(buttonName);
    asiaButton.addActionListener(ae -> {
        tzListSelectionModel.clearSelection();
        filter.setPrefix(filterPrefix);
    });
    filterButtons.add(asiaButton);
}

请注意,我不会将这个答案标记为解决方案,因为它更多地是一个解决方案,并且更倾向于实际解决问题的方案。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69046874

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档