NatTable单列中多数据类型标记单元格的排序实现方案咨询
我在NatTable中所有值都以字符串形式存储,已经通过IConfigLabelAccumulator为单元格添加了DataTypeStringLabel(值为"String")和DataTypeNumberLabel(值为"Number")标签,某一列中同时存在带这两种标签的单元格。目前所有值都按字符串规则排序(符合预期),但我需要让带有Number标签的值按数字规则排序。
现在有两个疑问:
- 哪种方案更优:是将数据转换为数字类型,还是使用自定义Comparator?如果选择数据转换,是否仅为排序目的进行转换?我需要在排序后仍保留数据的字符串格式。
- 若选择自定义Comparator,不确定如何注册标签,是否应该使用已创建的
IConfigLabelAccumulator cellLabelAccumulator?我尝试过一段代码但报错:The method registerOverride(int, String) is undefined for the type IConfigLabelAccumulator。
附相关实现代码:
public BodyLayerStack(List<TableLine> values, int columnCount, Integer[] columnIndicesForRowHeaders) { EventList<TableLine> eventList = GlazedLists.eventList(values); TransformedList<TableLine, TableLine> rowObjectsGlazedList = GlazedLists.threadSafeList(eventList); // this.filterList = new FilterList<>(rowObjectsGlazedList); //修改为: // 使用Comparator为null的SortedList构造方法,因为Comparator将通过配置设置 sortedList = new SortedList<>(rowObjectsGlazedList, null); // 用FilterList包装SortedList filterList = new FilterList<>(sortedList); bodyDataProvider = new ListDataProvider<TableLine>(filterList, getColumnAccessor(columnCount)); bodyDataLayer = new DataLayer(bodyDataProvider); IConfigLabelAccumulator cellLabelAccumulator = new IConfigLabelAccumulator() { @Override public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) { int columnIndex = bodyDataLayer.getColumnIndexByPosition(columnPosition); int rowIndex = bodyDataLayer.getRowIndexByPosition(rowPosition); if( isRowHeader(columnIndicesForRowHeaders, columnIndex) ) { configLabels.addLabel(NatTableFactory.RowHeaderLabel); } else { // 获取当前位置的数据类型并添加/刷新标签 configLabels.addLabel(filterList.get(rowIndex).getObjectTypeByColumn(columnIndex)); if(configLabels.getLabels().get(0) != null && configLabels.getLabels().get(0)=="Number") { System.out.println("Number"); } } } }; bodyDataLayer.setConfigLabelAccumulator(cellLabelAccumulator); GlazedListsEventLayer<TableLine> glazedListsEventLayer = new GlazedListsEventLayer<>(bodyDataLayer, filterList); selectionLayer = new SelectionLayer(glazedListsEventLayer, false); selectionLayer.setSelectionModel(getSelectionModel()); selectionLayer.addConfiguration(getSelectionConfiguration()); setUnderlyingLayer(new ViewportLayer(selectionLayer)); }
尝试的自定义Comparator配置代码:
public static IConfiguration getCustomComparatorConfiguration(final AbstractLayer columnHeaderDataLayer) { return new AbstractRegistryConfiguration() { @Override public void configureRegistry(IConfigRegistry configRegistry) { // 注册标签 BodyLayerStack.super.getConfigLabelAccumulator().registerOverride( //ERROR 4, //此处应填入排序列的索引? NatTableFactory.DataTypeNumberLabel); // 注册自定义比较器 configRegistry.registerConfigAttribute( SortConfigAttributes.SORT_COMPARATOR, NatTableLayerConfigurations.getCustomComparator(), DisplayMode.NORMAL, NatTableFactory.DataTypeNumberLabel); } }; }
先给你明确结论:自定义Comparator是最优方案
完全没必要为了排序去转换数据类型——毕竟你明确要求排序后还要保留字符串格式。如果转成数字,你不仅要处理转换失败的异常(比如有些字符串根本不是合法数字),排序完还得转回去,平白增加了复杂度,完全没必要。
而自定义Comparator的方式,既能在排序时区分两种类型的单元格,按对应的规则排序,又不会改动原始数据的存储格式,完美匹配你的需求。
关于自定义Comparator的正确实现与注册
你之前的代码报错,是因为IConfigLabelAccumulator根本没有registerOverride这个方法,这是对API的误解。其实你已经通过cellLabelAccumulator在BodyLayerStack里给单元格正确打上了DataTypeNumberLabel和DataTypeStringLabel标签,这些标签已经生效了,不需要再在配置类里重复注册,直接针对标签绑定对应的比较器就行。
第一步:写一个能区分数字和字符串的Comparator
这个比较器的逻辑很简单:先尝试把字符串转成数字,能转成数字的就按数字规则比,转不了的就按字符串规则比。代码示例如下:
public class MixedTypeComparator implements Comparator<Object> { @Override public int compare(Object o1, Object o2) { String val1 = (String) o1; String val2 = (String) o2; // 先尝试按数字比较 try { Double num1 = Double.parseDouble(val1); Double num2 = Double.parseDouble(val2); return num1.compareTo(num2); } catch (NumberFormatException e) { // 转不成数字,就按字符串默认规则比较 return val1.compareTo(val2); } } }
如果你想严格对应标签的判断逻辑,也可以结合单元格的标签来处理,但上面的实现已经足够灵活,能自动适配两种类型的排序需求。
第二步:正确注册比较器到配置注册表
修改你之前的配置方法,去掉错误的标签注册代码,直接绑定比较器和标签:
public static IConfiguration getCustomComparatorConfiguration() { return new AbstractRegistryConfiguration() { @Override public void configureRegistry(IConfigRegistry configRegistry) { // 给带Number标签的单元格绑定数字比较器 configRegistry.registerConfigAttribute( SortConfigAttributes.SORT_COMPARATOR, new MixedTypeComparator(), DisplayMode.NORMAL, NatTableFactory.DataTypeNumberLabel ); // 给带String标签的单元格绑定默认字符串比较器(这步可选,因为默认就是按字符串排序) configRegistry.registerConfigAttribute( SortConfigAttributes.SORT_COMPARATOR, Comparator.naturalOrder(), DisplayMode.NORMAL, NatTableFactory.DataTypeStringLabel ); } }; }
第三步:把配置添加到你的NatTable实例里
别忘了在创建NatTable的时候,把这个配置加进去,不然不会生效:
NatTable natTable = new NatTable(parent, bodyLayerStack, false); natTable.addConfiguration(getCustomComparatorConfiguration()); natTable.configure();
最后给你提个小bug
你在cellLabelAccumulator里判断标签的时候用了==比较字符串,这在Java里是错的!应该用equals()方法:
// 原来的错误代码 if(configLabels.getLabels().get(0) != null && configLabels.getLabels().get(0)=="Number") // 修改成这样 if(configLabels.getLabels().get(0) != null && "Number".equals(configLabels.getLabels().get(0)))
==比较的是字符串对象的引用地址,而equals()才是比较字符串的实际内容,这个小问题很容易导致标签判断失效,得赶紧改过来。
这样整个流程就通了,排序的时候带Number标签的单元格会按数字规则排序,其他的按字符串规则,而且原始数据的字符串格式完全保留。
内容的提问来源于stack exchange,提问作者Michal Kotus

