【问题标题】:javafx predicate fires only first timejavafx 谓词仅第一次触发
【发布时间】:2016-08-05 07:54:49
【问题描述】:

我有一个奇怪的问题,我确定这是我的代码问题,但无法找出。

问题: 我有一个 javafx TableView,它由一个过滤列表支持,而过滤列表又由一个可观察列表支持。我的要求是根据用户在文本字段中输入的输入过滤表格视图数据。所以我在文本字段的 textProperty 上附加了一个失效侦听器,并且我正在尝试根据我的业务条件设置过滤列表的谓词,这很好。

见以下代码:

externalTradeTableViewDataFilterTextField.textProperty().addListener((obs) -> { 
//externalTradesFilteredList.setPredicate(somePredicate);
String filterText = externalTradeTableViewDataFilterTextField.getText().trim().toLowerCase();
externalTradesFilteredList.setPredicate((ExternalTrade anExternalTrade) -> {
if(filterText == null || filterText.isEmpty() || filterText.equals(""))
return true;
if(anExternalTrade.getOid().toString().contains(filterText))
return true;
else 
if(anExternalTrade.getExternalTradeSourceOid().getExternalTradeSrcName().toLowerCase().contains(filterText))
return true;
else 
if(anExternalTrade.getExternalTradeStatusOid().getExternalTradeStatusName().toLowerCase().contains(filterText))
return true;
else 
if(anExternalTrade.getExternalTradeStateOid().getExternalTradeStateName().toLowerCase().contains(filterText))
return true;
return false;
});
});

自从我实现了我的要求后,我开始专注于代码重构。因此计划将谓词逻辑移动到单独的谓词并将谓词移动到另一个类,以便我可以重用它。 问题从这里开始。

见以下代码:

externalTradeTableViewDataFilterTextField.textProperty().addListener((obs) -> { 
externalTradesFilteredList.setPredicate(somePredicate);
});

private Predicate<ExternalTrade> somePredicate = (ExternalTrade anExternalTrade) -> {
String filterText = externalTradeTableViewDataFilterTextField.getText().trim().toLowerCase();

if(filterText.isEmpty() || filterText == null || filterText.equals(""))
            return true;
if(anExternalTrade.getOid().toString().contains(filterText))
       return true;
else if(anExternalTrade.getExternalTradeSourceOid().getExternalTradeSrcName().toLowerCase().contains(filterText))
            return true;
else if(anExternalTrade.getExternalTradeStatusOid().getExternalTradeStatusName().toLowerCase().contains(filterText))
            return true;
        else if(anExternalTrade.getExternalTradeStateOid().getExternalTradeStateName().toLowerCase().contains(filterText))
            return true;
return false;
};

现在,每次我在文本字段中输入新文本时,我的侦听器都会被执行,但谓词逻辑只是第一次被调用。从第二次开始,谓词 (somePredicate) 没有被调用。

请帮助我。还建议我的代码是否良好或任何其他更好的方式来实现这一点并具有更好的性能。 bcoz 我确实看到一些使用绑定的代码,说过滤列表的过滤属性直接附加到文本字段的文本属性。

还有一件事。谓词中的逻辑是检查文本是否包含在任何列中,如果是则返回该行。我有 20 列。所以我需要为所有 20 列或任何其他方式编写 if 条件。是for循环是唯一的方法吗?或者我可以使用 .foreach 做点什么。

提前致谢。

【问题讨论】:

    标签: java-8 javafx-8


    【解决方案1】:

    过滤器不会重新计算的原因是,从FilteredList 的角度来看,它不会改变。

    在伪代码中,FilteredList 可能看起来像这样:

    public class FilteredList<T> {
    
        private ObjectProperty<Predicate<T>> predicate = new SimpleObjectProperty<>();
    
        private ObservableList<T> source ;
    
        public FilteredList<T>(ObservableList<T> source, Predicate<T> predicate) {
    
            this.source = source ;
    
            this.predicate.addListener((obs, oldPredicate, newPredicate) ->
                redoFilter());
    
            this.predicate.set(predicate);
        }
    
        // ...
    }
    

    即它使用谓词属性注册一个ChangeListener,并在它发生变化时重新计算过滤器。 (在现实生活中,它可能比这复杂得多,但这个想法会存在。)

    同时,SimpleObjectProperty 执行以下操作(同样,这是伪代码):

    public class SimpleObjectProperty<T> implements Property<T> {
    
        private T value ;
    
        public void set(T value) {
            if (! Objects.equals(this.value, value)) {
                T oldValue = this.value ;
                this.value = value ; 
                notifyChangeListeners(oldValue, this.value);
            }
        }
    }
    

    换句话说,ChangeListeners 只有在值实际发生变化时才会收到通知。

    在您的示例(第二个代码块)中,只要搜索字段中的文本发生更改,您就会调用

    externalTradesFilteredList.setPredicate(somePredicate);
    

    每次使用完全相同的参考somePredicate。因此,当过滤列表中包含该谓词的属性检查时,它没有看到任何更改(完全相同的对象...),因此不会触发更改侦听器,因此过滤列表不知道它必须更新。

    实际上,您没有更改谓词,您只是更改了现有谓词的内部状态。

    要修复,您可以这样做:

    private Predicate<ExternalTrade> createPredicate() {
        return (ExternalTrade anExternalTrade) -> {
            String filterText = externalTradeTableViewDataFilterTextField.getText().trim().toLowerCase();
    
            if(filterText.isEmpty() || filterText == null || filterText.equals(""))
                        return true;
            if(anExternalTrade.getOid().toString().contains(filterText))
                   return true;
            else if(anExternalTrade.getExternalTradeSourceOid().getExternalTradeSrcName().toLowerCase().contains(filterText))
                        return true;
            else if(anExternalTrade.getExternalTradeStatusOid().getExternalTradeStatusName().toLowerCase().contains(filterText))
                        return true;
                    else if(anExternalTrade.getExternalTradeStateOid().getExternalTradeStateName().toLowerCase().contains(filterText))
                        return true;
            return false;
        };
    
    }
    

    externalTradeTableViewDataFilterTextField.textProperty().addListener((obs) ->  
        externalTradesFilteredList.setPredicate(createPredicate()));
    

    或者等效地,创建一个类:

    private static class TradeTableFilter implements Predicate<ExternalTrade> {
    
        private final String filterText ;
    
        TradeTableFilter(String filterText) {
            this.filterText = filterText ;
        }
    
        @Override
        public boolean test(ExternalTrade anExternalTrade) {
    
            if(filterText.isEmpty() || filterText == null || filterText.equals(""))
                        return true;
            if(anExternalTrade.getOid().toString().contains(filterText))
                   return true;
            else if(anExternalTrade.getExternalTradeSourceOid().getExternalTradeSrcName().toLowerCase().contains(filterText))
                        return true;
            else if(anExternalTrade.getExternalTradeStatusOid().getExternalTradeStatusName().toLowerCase().contains(filterText))
                        return true;
                    else if(anExternalTrade.getExternalTradeStateOid().getExternalTradeStateName().toLowerCase().contains(filterText))
                        return true;
            return false;
        }
    
    }
    

    然后当然做

    externalTradeTableViewDataFilterTextField.textProperty().addListener((obs) ->  
        externalTradesFilteredList.setPredicate(new TradeTableFilter(externalTradeTableViewDataFilterTextField.getText().trim().toLowerCase()));
    

    对于您的其他问题(顺便说一句,您不应该在此论坛上真正将多个问题合并为一个问题:其他用户很难(或不可能)找到相同问题的现有答案),没有真正容易解决的问题。

    您可以将模型中的属性列表创建为Function&lt;ExternalTrade, String&gt;

    private final List<Function<ExternalTrade, String>> tradeProperties = Arrays.asList(
        t -> t.getOid().toString(),
        t -> t.getExternalTradeSourceOid().getExternalTradeSrcName().toLowerCase(),
        t -> t.getExternalTradeStatusOid().getExternalTradeStatusName().toLowerCase(),
        t -> t.getExternalTradeStateOid().getExternalTradeStateName().toLowerCase()
    );
    

    然后

    private Predicate<ExternalTrade> createPredicate() {
        return (ExternalTrade anExternalTrade) -> {
            String filterText = externalTradeTableViewDataFilterTextField.getText().trim().toLowerCase();
            return filterText == null ||
                   filterText.isEmpty() ||
                   tradeProperties().stream().anyMatch(p -> p.apply(anExternalTrade).contains(filterText));
        };
    }
    

    根据您的模型类和表的设置方式,您可能能够映射到函数列表中的 ObservableValues 而不是 Strings,然后您可能会重用该列表来创建循环中的列。但我对您的设置了解得不够多,不知道这是否可行。

    【讨论】:

    • 太棒了,我没有应用 n 检查您的解决方案,但它是有道理的。让我试试。也很抱歉提出多个问题。从现在开始我会改正自己。
    • 嘿,我正在尝试您的解决方案。第一个问题解决了。第二个问题。循环遍历所有列,我无法说 tradeProperties().stream().anyMatch(p -> p.contains(filterText));其中 p.contains 不起作用。我无法在作为谓词的 p 上调用 contains 方法。
    • 谢谢詹姆斯。有效。非常感谢。你介意看看stackoverflow.com/questions/38801302/…
    猜你喜欢
    • 2018-09-27
    • 2014-10-22
    • 2020-10-08
    • 1970-01-01
    • 2021-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多