【发布时间】:2019-07-16 23:14:18
【问题描述】:
我想在 JavaFX 菜单中添加一个选项列表。显示菜单时应修改选项的顺序(我将使用模糊匹配,但该方法与我的问题无关)。我可以用分别包含TextField 和ListView 的CustomMenuItems 来模仿这种行为:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.ListView;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuButton;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MWE extends Application {
@Override
public void start(Stage primaryStage) {
final Menu menu = new Menu("MENU");
final List<String> options = Arrays.asList(
"AbC",
"dfjksdljf",
"skdlfj",
"stackoverflow");
final StringProperty currentSelection = new SimpleStringProperty(null);
final TextField fuzzySearchField = new TextField(null);
final CustomMenuItem fuzzySearchItem = new CustomMenuItem(fuzzySearchField, false);
// TODO unfortunately we seem to have to grab focus like this!
fuzzySearchField.addEventFilter(MouseEvent.MOUSE_MOVED, e->{fuzzySearchField.requestFocus(); fuzzySearchField.selectEnd();});
final ObservableList<String> currentMatches = FXCollections.observableArrayList();
// just some dummy matching here
fuzzySearchField.textProperty().addListener((obs, oldv, newv) -> currentMatches.setAll(options.stream().filter(s -> s.toLowerCase().contains(newv)).collect(Collectors.toList())));
final ListView<String> lv = new ListView<>(currentMatches);
lv.addEventFilter(MouseEvent.MOUSE_MOVED, e -> lv.requestFocus());
final CustomMenuItem lvItem = new CustomMenuItem(lv, false);
menu.getItems().setAll(fuzzySearchItem, lvItem);
fuzzySearchField.textProperty().addListener((obs, oldv, newv) -> currentSelection.setValue(currentMatches.size() > 0 ? currentMatches.get(0) : null));
fuzzySearchField.setText("");
menu.setOnShown(e -> fuzzySearchField.requestFocus());
final MenuButton button = new MenuButton("menu");
button.getItems().setAll(menu);
Platform.runLater(() -> {
final Scene scene = new Scene(button);
primaryStage.setScene(scene);
primaryStage.show();
});
}
}
但是,在菜单结构中包含ListView 感觉很奇怪。这就是为什么我尝试使用MenuItems 而不是ListView:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.ListView;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MWE2 extends Application {
@Override
public void start(Stage primaryStage) {
final Menu menu = new Menu("MENU");
final List<String> options = Arrays.asList(
"AbC",
"dfjksdljf",
"skdlfj",
"stackoverflow");
final StringProperty currentSelection = new SimpleStringProperty(null);
final TextField fuzzySearchField = new TextField(null);
final CustomMenuItem fuzzySearchItem = new CustomMenuItem(fuzzySearchField, false);
// TODO unfortunately we seem to have to grab focus like this!
fuzzySearchField.addEventFilter(MouseEvent.MOUSE_MOVED, e->{fuzzySearchField.requestFocus(); fuzzySearchField.selectEnd();});
final ObservableList<String> currentMatches = FXCollections.observableArrayList();
// just some dummy matching here
fuzzySearchField.textProperty().addListener((obs, oldv, newv) -> currentMatches.setAll(options.stream().filter(s -> s.toLowerCase().contains(newv)).collect(Collectors.toList())));
currentMatches.addListener((ListChangeListener<String>)change -> {
List<MenuItem> items = new ArrayList<>();
items.add(fuzzySearchItem);
currentMatches.stream().map(MenuItem::new).forEach(items::add);
System.out.println("Updating menu items!");
menu.getItems().setAll(items);
});
fuzzySearchField.textProperty().addListener((obs, oldv, newv) -> currentSelection.setValue(currentMatches.size() > 0 ? currentMatches.get(0) : null));
fuzzySearchField.setText("");
menu.setOnShown(e -> fuzzySearchField.requestFocus());
final MenuButton button = new MenuButton("menu");
button.getItems().setAll(menu);
Platform.runLater(() -> {
final Scene scene = new Scene(button);
primaryStage.setScene(scene);
primaryStage.show();
});
}
}
在那个例子中,菜单在显示时没有更新,即使我可以看到"Updating menu items!" 打印到控制台,所以menu 的项目正在更新。但是,屏幕上的菜单不会改变。
有没有办法请求重绘菜单?
相关问题:
-
JavaFX: Update menu sub-items everytime menu shown解决方法好像不对,
Menu好像没有方法setOnMouseEntered - Adding dynamic entries to Menu in JavaFX 在显示菜单时不会更新菜单(如果我理解正确的话)
【问题讨论】:
-
我有一个非常老套的“解决方案”,我不想用作答案:首先,通过反射获取私有方法
Menu.setShowing(boolean)。然后,当更新列表并显示菜单时,首先使用false调用该方法,然后使用true作为参数调用该方法。同样,非常老套,不是我想要的答案,但它有效(但不知道任何副作用)。 -
您不需要反射,
menu.hide和menu.show也可以。我可以建议对您的第一个解决方案进行改进 - 您可以设置ListView的样式,使其看起来有点像菜单(有点像,不是 100% 像菜单) -
谢谢@Enigo 我试过
menu.hide和menu.show但延迟比通过Menu.setShowing要长得多。其原因对我来说并不完全清楚。我可能会设计ListView。我希望菜单是动态的,即使可见。如果不是这样,我想知道这背后的基本原理是什么。 -
对我来说看起来像一个错误:如果您直接在 contextMenu 中执行相同操作(不通过子菜单间接),列表将按预期更新。
-
报告了the bug,初步挖掘了一个肮脏(!)hackaround的原因和轮廓
标签: java listview javafx menu javafx-8