java - 在 JavaFX 中制作适当的选项卡式界面?

标签 java user-interface javafx tabs user-experience

我正在使用将使用选项卡的 JavaFX 编写应用程序。用户可能希望同时打开彼此相邻的选项卡(而不是一次只打开一个选项卡)。理想情况下,这可以通过将选项卡拖到屏幕的一侧来实现。

最初我认为制作一个只有一个网格的网格面板可能是个好主意,然后在用户拖动选项卡时添加网格。我认为那行不通。有没有好的方法来做到这一点? JavaFX 的布局和面板控件真的就这么缺吗?

在 C# 中,我可能会使用类似这样的东西来实现我想要的预期行为:http://avalondock.codeplex.com/

问题:如何在 JavaFX 中使用选项卡实现适当的用户体验,允许用户拖动选项卡以具有“拆分”布局?

编辑:目前我正在考虑尝试复制我的上述想法,但使用 HBox。有谁知道如何在 HBox 中实现选项卡拖动到另一个象限?

最佳答案

所以你的问题有点含糊,我可能不应该回答它,因为老实说我真的不知道“适当的选项卡式界面”的真正含义。

这是一个允许标签页并排打开的系统。右键单击选项卡以调出上下文菜单以选择垂直或水平拆分。选项卡内容将在所需方向的 SplitPane 内的新 TabPane 中复制。

这绝对不是一个完整的对接解决方案,甚至可能不接近您想要的,但也许您会发现它有用(也许不是 ;-)。该解决方案仅完成拆分,而不是在拆分区域之间拖放选项卡(如果您自己无法弄清楚,可能 get in contact with Tom Schindl 有关拖放部分的信息)。

下面的解决方案只是将拆分选项卡的内容复制到新拆分区域中的新选项卡。进一步的增强可以实现类似于通用同步文档模型的东西,它允许并排编辑在不同选项卡中复制的文件(类似于 Intellij Idea 的编辑器实现,从中派生了这种拆分系统概念)。

sample

import javafx.application.Application;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

import java.util.HashMap;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class SplitTabs extends Application {
    final Lorem lorem = new Lorem();

    final HashMap<Node, SplitPane> splitPanes = new HashMap<>();

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        TabPane tabPane = new TabPane();
        //noinspection ResultOfMethodCallIgnored
        IntStream.range(0, 5)
                .mapToObj(i -> createTab(lorem.nextString(1), lorem.nextString(200)))
                .collect(Collectors.toCollection(tabPane::getTabs));

        stage.setScene(new Scene(new StackPane(tabPane)));
        stage.show();
    }

    private Tab createTab(String title, String text) {
        TextArea textArea = new TextArea(text);
        textArea.setWrapText(true);

        Tab tab = new Tab(title);
        tab.setContent(textArea);
        tab.setOnCloseRequest(event -> {
            TabPane tabPane = tab.getTabPane();
            if (tabPane.getTabs().size() <= 1) {
                SplitPane splitPane = splitPanes.get(tabPane);
                if (splitPane == null) {
                    // don't allow the last tab to be closed.
                    event.consume();
                    return;
                }

                int siblingIdx = (splitPane.getItems().indexOf(tabPane) + 1) % 2;
                Node siblingItem = splitPane.getItems().get(siblingIdx);


                Optional<SplitPane> optionalParentSplitPane =
                        splitPanes.values().stream()
                                .filter(searchPane -> searchPane.getItems().contains(splitPane))
                                .findFirst();

                // make the last TabPane the root.
                if (!optionalParentSplitPane.isPresent()) {
                    StackPane stackPane = (StackPane) splitPane.getParent() ;
                    stackPane.getChildren().setAll(siblingItem);
                    splitPanes.clear();
                    return;
                }

                // graft sibling under parent.
                SplitPane parentSplitPane = optionalParentSplitPane.get();

                int idx = parentSplitPane.getItems().indexOf(splitPane);
                parentSplitPane.getItems().set(idx, siblingItem);
                splitPane.getItems().forEach(splitPanes::remove);
                splitPanes.put(siblingItem, parentSplitPane);
            }
        });

        MenuItem splitVertically = new MenuItem("Split Vertically");
        splitVertically.setOnAction(event -> split(title, text, tab, Orientation.HORIZONTAL));
        MenuItem splitHorizontally = new MenuItem("Split Horizontally");
        splitHorizontally.setOnAction(event -> {
            split(title, text, tab, Orientation.VERTICAL);
        });

        tab.setContextMenu(new ContextMenu(
                splitVertically,
                splitHorizontally
        ));

        return tab;
    }

    private void split(String title, String text, Tab tab, Orientation orientation) {
        TabPane tabPane = tab.getTabPane();
        Tab tabCopy = createTab(title, text);
        TabPane newTabPane = new TabPane(tabCopy);
        SplitPane splitPane = new SplitPane(tabPane, newTabPane);
        splitPane.setOrientation(orientation);

        if (splitPanes.isEmpty()) {
            StackPane stackPane = (StackPane) tabPane.getParent();
            stackPane.getChildren().setAll(splitPane);
            splitPanes.put(tabPane, splitPane);
            splitPanes.put(newTabPane, splitPane);
        } else {
            SplitPane parentSplit = splitPanes.get(tabPane);
            int idx = parentSplit.getItems().indexOf(tabPane);
            parentSplit.getItems().set(idx, splitPane);
            splitPanes.remove(tabPane);
            splitPanes.put(splitPane, parentSplit);
            splitPanes.put(tabPane, splitPane);
            splitPanes.put(newTabPane, splitPane);
        }
    }
}

class Lorem {
    private static final String[] IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque hendrerit imperdiet mi quis convallis. Pellentesque fringilla imperdiet libero, quis hendrerit lacus mollis et. Maecenas porttitor id urna id mollis. Suspendisse potenti. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras lacus tellus, semper hendrerit arcu quis, auctor suscipit ipsum. Vestibulum venenatis ante et nulla commodo, ac ultricies purus fringilla. Aliquam lectus urna, commodo eu quam a, dapibus bibendum nisl. Aliquam blandit a nibh tincidunt aliquam. In tellus lorem, rhoncus eu magna id, ullamcorper dictum tellus. Curabitur luctus, justo a sodales gravida, purus sem iaculis est, eu ornare turpis urna vitae dolor. Nulla facilisi. Proin mattis dignissim diam, id pellentesque sem bibendum sed. Donec venenatis dolor neque, ut luctus odio elementum eget. Nunc sed orci ligula. Aliquam erat volutpat.".split(" ");
    private int idx = 0;

    public String nextString(int nWords) {
        int end = Math.min(idx + nWords, IPSUM.length);

        StringBuilder result = new StringBuilder();
        for (int i = idx; i < end; i++) {
            result.append(IPSUM[i]).append(" ");
        }

        idx += nWords;
        idx = idx % IPSUM.length;

        return result.toString();
    }
}

关于java - 在 JavaFX 中制作适当的选项卡式界面?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43005279/

相关文章:

java - 如何按值从 HashMap 中删除条目?

java - 为什么这个javafx fxml不调整大小?

java - 如何从 fxml 文件中的 xml 文件读取值?

JavaFX:如何将 TextArea 的操作限制在最后一行?

java - 从文件中确定最小和最大 double 值

java - JPA:在空的多边上将获取结果加入到 NULL

java - 使用 MVC 和 DAO 模式在 JSP 页面中的 HTML 中显示 JDBC 结果集

PHP基于时间的上传脚本

java - 重新启动扫雷 GUI 程序

c++ - 如何避免 QDial 控件中的回绕?