在 JavaFX 中的窗格之间切换
Posted
技术标签:
【中文标题】在 JavaFX 中的窗格之间切换【英文标题】:Switch between panes in JavaFX 【发布时间】:2013-04-17 02:26:23 【问题描述】:我正在尝试使用 FXML 在 JavaFX 中制作 Java 程序。但是我在布局管理方面遇到了麻烦。我想在窗格之间切换,就像我习惯使用 CardLayout 一样,但我似乎无法获得它。
我搜索了一下,没有找到任何答案。
JavaFX 中是否有任何等效的 CardLayout?如果是这样,你能给我举个例子吗?这对我的夜晚很有帮助!
这是我的 FXML 代码
<AnchorPane id="anchorPane" prefHeight="324.0" prefWidth="530.0" xmlns:fx="http://javafx.com/fxml" fx:controller="javafxapplication2.SampleController">
<children>
<Pane fx:id="mainScreen" layoutX="6.0" prefHeight="324.0" prefWidth="518.0">
<children>
<Button layoutX="254.0" layoutY="37.0" mnemonicParsing="false" text="Button" />
</children>
</Pane>
<Pane fx:id="loginScreen" prefHeight="324.0" prefWidth="530.0">
<children>
<TextField id="password" fx:id="username" layoutX="142.0" layoutY="106.0" prefWidth="200.0" />
<TextField fx:id="password" layoutX="142.0" layoutY="140.0" prefWidth="200.0" />
<Label fx:id="label" layoutX="126.0" layoutY="120.0" minHeight="16.0" minWidth="69.0" />
<Button fx:id="button" layoutX="213.0" layoutY="196.0" onAction="#handleButtonAction" onKeyPressed="#handleButtonAction" text="Login" />
</children>
</Pane>
</children>
</AnchorPane>
【问题讨论】:
【参考方案1】:JRebirth Application Framework 使用其专用模式 wB-CSMvc 提供自定义“CardLayout”。
StackModel 类将完成这项工作(由 org.jrebirth.af:component artifact 提供),您可以找到 here 和 here 的 2 个用法。
每个“卡片”模型都可以使用 enum|modelKey 标识符来调用,并且每个堆栈都有一个唯一的名称。
第一个示例用于JRebirth Demo Application,它是一个非常简单的应用程序,可以显示作为 JRebirth 模块动态加载的其他 JRebirth 展示应用程序(来自单独的独立 jar)。
public final class JRebirthDemo extends DefaultApplication<StackPane>
public static void main(final String... args)
Application.launch(JRebirthDemo.class, args);
@Override
public Class<? extends Model> firstModelClass()
return MainModel.class;
@Override
protected String applicationTitle()
return "JRebirth Demo Application";
@Override
protected void customizeScene(final Scene scene)
super.customizeScene(scene);
addCSS(scene, DemoStyles.DEFAULT);
addCSS(scene, WorkbenchStyles.DEFAULT);
@Override
protected void customizeStage(final Stage stage)
// Center the stage
stage.centerOnScreen();
@Override
protected List<? extends ResourceItem<?, ?, ?>> getResourceToPreload()
return Collections.emptyList();
此应用程序将加载其第一个模型(MainModel)并将其根节点放入场景根节点(StakPane,自动构建)。
MainModel 将列出所有应用程序的子模块,以在其左侧菜单中添加一个按钮条目,以及一个将显示每个模块内容的 StackModel。 StackModel 是使用其唯一的 String 键使用特殊注释加载的。
public final class MainModel extends DefaultModel<MainModel, MainView>
private final List<ModuleModel> modules = new ArrayList<>();
@Link("DemoStack")
private StackModel stackModel;
@Override
protected void initModel()
for (final ModuleModel mm : getModels(ModuleModel.class))
this.modules.add(mm);
@Override
protected void showView()
view().node().setCenter(this.stackModel.node());
@Override
protected void hideView()
// Nothing to do yet
List<ModuleModel> getModules()
return this.modules;
MainView 将负责创建模块菜单:
public final class MainView extends DefaultView<MainModel, BorderPane, MainController>
private final List<Button> buttonList = new ArrayList<>();
public MainView(final MainModel model) throws CoreException
super(model);
@Override
protected void initView()
node().setPrefSize(800, 600);
node().setLeft(createMenu());
@Override
public void start()
this.buttonList.stream().findFirst().ifPresent(button -> button.fire());
private Node createMenu()
final VBox box = new VBox();
for (final ModuleModel mm : model().getModules())
final Node n = createModuleButton(mm);
VBox.setMargin(n, new Insets(4, 4, 4, 4));
box.getChildren().add(n);
return box;
private Node createModuleButton(final ModuleModel mm)
final Button b = new Button(mm.moduleName());
b.getStyleClass().add("menuButton");
b.setPrefSize(100, 50);
b.setOnAction(controller()::onButtonFired);
b.setUserData(Key.create(mm.getClass()));
this.buttonList.add(b);
return b;
当触发任何菜单按钮时,MainController 会加载 Module 内容:
public final class MainController extends DefaultController<MainModel, MainView> implements ActionAdapter
public MainController(final MainView view) throws CoreException
super(view);
public void onButtonFired(final ActionEvent event)
final Button b = (Button) event.getSource();
final UniqueKey<? extends Model> data = (UniqueKey<? extends Model>) b.getUserData();
model().sendWave(StackWaves.SHOW_PAGE_MODEL,
WBuilder.waveData(StackWaves.PAGE_MODEL_KEY, data),
WBuilder.waveData(StackWaves.STACK_NAME, "DemoStack"));
第二个例子将 StackModel 作为一个 innerComponent 加载,每张卡片将由一个枚举条目标识(存储在 FXMLPage 中),让我们看看 FXMLShowCaseModel :
final InnerComponent<StackModel> stack = CBuilder.innerComponent(StackModel.class, FXMLPage.class);
this.stackModel = findInnerComponent(stack);
将枚举条目与模型链接的枚举:
public enum FXMLPage implements PageEnum
StandaloneFxml,
IncludedFxml,
ViewEmbeddedFxml,
HybridFxml;
@Override
public UniqueKey<? extends Model> getModelKey()
UniqueKey<? extends Model> modelKey;
switch (this)
default:
case ViewEmbeddedFxml:
modelKey = Key.create(EmbeddedModel.class);
break;
case StandaloneFxml:
modelKey = Key.create(StandaloneModel.class);
break;
case HybridFxml:
modelKey = Key.create(HybridModel.class, FXMLModel.KEYPART_FXML_PREFIX + "org.jrebirth.af.showcase.fxml.ui.hybrid.Hybrid");
break;
case IncludedFxml:
modelKey = Key.create(IncludedModel.class, new LoremIpsum());
break;
return modelKey;
由于卡片列表是已知的,工具栏项被静态创建到 FXMLShowCaseView 中,事件处理也使用另一种技术静态定义到 FXMLShowCaseController 中:
public final class FXMLShowCaseController extends DefaultController<FXMLShowCaseModel, FXMLShowCaseView>
private static final Logger LOGGER = LoggerFactory.getLogger(FXMLShowCaseController.class);
public FXMLShowCaseController(final FXMLShowCaseView view) throws CoreException
super(view);
@Override
protected void initEventAdapters() throws CoreException
// WaveData<Class<? extends PageEnum>> stackName = Builders.waveData(StackWaves.STACK_PAGES, FXMLShowCaseModel.STACK_PAGES);
// Manage Ui Command Button
linkWave(view().getShowIncluded(), ActionEvent.ACTION, StackWaves.SHOW_PAGE_ENUM,
WBuilder.waveData(StackWaves.PAGE_ENUM, FXMLPage.IncludedFxml));
linkWave(view().getShowEmbedded(), ActionEvent.ACTION, StackWaves.SHOW_PAGE_ENUM,
WBuilder.waveData(StackWaves.PAGE_ENUM, FXMLPage.ViewEmbeddedFxml));
linkWave(view().getShowStandalone(), ActionEvent.ACTION, StackWaves.SHOW_PAGE_ENUM,
WBuilder.waveData(StackWaves.PAGE_ENUM, FXMLPage.StandaloneFxml));
linkWave(view().getShowHybrid(), ActionEvent.ACTION, StackWaves.SHOW_PAGE_ENUM,
WBuilder.waveData(StackWaves.PAGE_ENUM, FXMLPage.HybridFxml));
如果您有任何问题,请告诉我
【讨论】:
【参考方案2】:非动画过渡
如果您不需要窗格之间的动画过渡,那么您可以:
-
通过创建新场景和set that scene on your Stage 或来替换整个场景
仅替换父布局中的特定窗格,方法是从其父窗格中删除旧窗格并添加新窗格(通过操作父窗格的 children list)或
将所有窗格放在
StackPane
中,然后将要显示的窗格移动到stack's child list 的顶部。
动画过渡
如果您想在窗格之间进行动画转换,请参阅 Angela Caicedo 的两部分系列文章,介绍在 JavaFX 中管理多个屏幕:
Part I Part IIAngela 的解决方案是使用 StackPane 和一个单独的自定义 ScreenController 类来管理堆栈中的窗格之间的 Transitions 或 animations。
框架
JFXFlow 和 WebFX 等框架还可以为您的应用提供浏览器风格的界面,允许用户使用后退和前进按钮以及历史列表在屏幕之间来回切换。
2017 年更新
我认为上面提到的两个框架的开发现在都已经失效了。其他正在开发的框架有:
TornadoFX JRebirth afterburner.fx还有许多其他(我不会在这里提供完整的列表)。
相关
Loading new fxml in the same scene【讨论】:
【参考方案3】:我是这样做的: (在本例中,我创建了两个 FXML 文档及其对应的控制器。它们分别称为 FXMLLogin.fxml 和 Home.fxml。
所以,从 FXMLLogin 到 Home,
在这个例子中,我在 FXMLLoginController 中创建了一个方法,它响应被按下的表单上的“登录”按钮:
@FXML
private void login(javafx.event.ActionEvent event) throws IOException
if(pwf1.getText().equals("alphabetathetagamma"))
Parent blah = FXMLLoader.load(getClass().getResource("Home.fxml"));
Scene scene = new Scene(blah);
Stage appStage = (Stage) ((Node) event.getSource()).getScene().getWindow();
appStage.setScene(scene);
appStage.show();
else
label1.setText("Password is incorrect. Please Try Again");
注意@FXML 非常重要。
如果我正确理解了您的问题,那么这应该可以解决问题。
窗格之间的切换一点也不明显,并且在我发现的网络上的任何教程中都没有明确概述。在我第一次弄清楚之前,我不得不自己广泛地用谷歌搜索。幸运的是,一旦你掌握了窍门,它实际上非常简单。
希望我没有误解你的问题?如果这是您需要的,请告诉我:)
【讨论】:
在我的情况下,两者之间的转换很慢(大约需要一秒钟),我不知道这是否正常?以上是关于在 JavaFX 中的窗格之间切换的主要内容,如果未能解决你的问题,请参考以下文章