如何通过Tab的text属性管理TabPane中的Tab?

写在前面

有时候会有这种需求:点击同一个标签或者按钮时打开同一个标签页(Tab),点击不同的标签或者按钮打开不同的标签页。例如编辑器中打开同一个文件,对应打开同一个标签页,点击不同的文件打开不同的标签页。
但是,javafx中的TabPane没有提供通过text来管理Tab的方法——依据属性text获得标签页;依据属性text删除标签页。

解决方案

那么如何解决上面的问题?我们可以在属性textTab之间建立起联系,所以理所当然的想到了哈希。

简易实现

新建一个HashMap变量用于存储textTab之间的映射,在向TabPane中加入Tab时,在哈希中增加textTab映射

package pre.huangjs.tabpane;

import javafx.application.Application;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.stage.Stage;

import java.util.HashMap;

/**
 * Created by huangjs on 2018/4/9.
 */
public class TabPaneTest extends Application {

    public void start(Stage primaryStage) throws Exception{

        TabPane tabPane = new TabPane();
        HashMap<String, Tab> tabsMap = new HashMap<>();

        // 向TabPane中加入Tab时,在哈希中建立属性text和Tab的联系
        Tab tab1 = new Tab("tab I");
        tabsMap.put(tab1.getText(), tab1);
        tabPane.getTabs().add(tab1);

        // 当想使用text的值取得相应的Tab时,就从哈希中取得
        Tab tab1Backup = tabsMap.get("tab I");
        System.out.println("tab1Backup == tab1: " + (tab1Backup == tab1));
    }

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

但是这样做的话,每次添加新的Tab时,就需要手动添加一个Tab,当然删除时也需要手动删除HashMap中的Tab。所以我们可以新建一个辅助类来完成这些工作。
TabPaneHelper.java

package pre.huangjs.tabpane;

import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;

import java.util.HashMap;

/**
 * Created by huangjs on 2018/4/10.
 */
public class TabPaneHelper {
    private TabPane tabPane;
    private HashMap<String, Tab> tabsMap;

    public TabPane getTabPane() {
        return tabPane;
    }

    public HashMap<String, Tab> getTabsMap() {
        return tabsMap;
    }

    public TabPaneHelper() {
        this.tabPane = new TabPane();
        this.tabsMap = new HashMap<>();
    }

    /**
     * 添加一个Tab到TabPane中
     * @param tab
     */
    public void addTab(Tab tab) {
        tabPane.getTabs().add(tab);
        tabsMap.put(tab.getText(), tab);
    }

    /**
     * 添加多个Tab到TabPane
     * @param tabs
     */
    public void addTabs(Tab... tabs) {
        boolean flag = tabPane.getTabs().addAll(tabs);
        if (flag == true) {
            for (Tab tab : tabs) {
                tabsMap.put(tab.getText(), tab);
            }
        }
    }

    /**
     * 删除一个TabPane
     * @param tab
     */
    public void removeTab(Tab tab) {
        tabPane.getTabs().remove(tab);
        tabsMap.remove(getTabByText(tab.getText()));
    }

    /**
     * 通过Tab的text获取对应的Tab
     * @param text
     * @return
     */
    public Tab getTabByText(String text) {
        return tabsMap.get(text);
    }
}

接下来测试一下,不能直接写一个main方法然后测试,会报错。需要新建一个类继承Application,然后测试。
测试类TabPaneTest

package pre.huangjs.tabpane;

import javafx.application.Application;
import javafx.scene.control.Tab;
import javafx.stage.Stage;

/**
 * Created by huangjs on 2018/4/10.
 */
public class TabPaneHelperTest extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        TabPaneHelper tp = new TabPaneHelper();
        Tab tab1 = new Tab("tab1");
        Tab tab2 = new Tab("tab2");
        Tab tab3 = new Tab("tab3");
        Tab tab4 = new Tab();

        // add tabs
        tp.addTabs(tab1, tab2);
        System.out.println("**************************华丽分割线1*****************************");
        System.out.println(tp.getTabPane().getTabs());

        // remove tab
        tp.removeTab(tab1);
        System.out.println("**************************华丽分割线2*****************************");
        System.out.println(tp.getTabPane().getTabs());

        tp.removeTab(tab3);
        System.out.println("**************************华丽分割线3*****************************");
        System.out.println(tp.getTabPane().getTabs());

        // add a tab which text is null
        tp.addTab(tab4);
        System.out.println(tp.getTabPane().getTabs());
        System.out.println("**************************华丽分割线4*****************************");
        System.out.println(tp.getTabsMap());
        System.out.println(tp.getTabByText(null));// 可以添加没有设定text值的Tab,也可以获得,但是这样没有什么意义~~原本想通过text管理Tab,都没有text...
    }

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

结果

E:\software\jdk1.8.0_121\bin\java -Didea.launcher.port=7546 -Didea.launcher.bin.path=E:\software\IDEA\bin -Dfile.encoding=UTF-8 -classpath E:\software\jdk1.8.0_121\jre\lib\charsets.jar;E:\software\jdk1.8.0_121\jre\lib\deploy.jar;E:\software\jdk1.8.0_121\jre\lib\ext\access-bridge-64.jar;E:\software\jdk1.8.0_121\jre\lib\ext\cldrdata.jar;E:\software\jdk1.8.0_121\jre\lib\ext\dnsns.jar;E:\software\jdk1.8.0_121\jre\lib\ext\jaccess.jar;E:\software\jdk1.8.0_121\jre\lib\ext\jfxrt.jar;E:\software\jdk1.8.0_121\jre\lib\ext\localedata.jar;E:\software\jdk1.8.0_121\jre\lib\ext\nashorn.jar;E:\software\jdk1.8.0_121\jre\lib\ext\sunec.jar;E:\software\jdk1.8.0_121\jre\lib\ext\sunjce_provider.jar;E:\software\jdk1.8.0_121\jre\lib\ext\sunmscapi.jar;E:\software\jdk1.8.0_121\jre\lib\ext\sunpkcs11.jar;E:\software\jdk1.8.0_121\jre\lib\ext\zipfs.jar;E:\software\jdk1.8.0_121\jre\lib\javaws.jar;E:\software\jdk1.8.0_121\jre\lib\jce.jar;E:\software\jdk1.8.0_121\jre\lib\jfr.jar;E:\software\jdk1.8.0_121\jre\lib\jfxswt.jar;E:\software\jdk1.8.0_121\jre\lib\jsse.jar;E:\software\jdk1.8.0_121\jre\lib\management-agent.jar;E:\software\jdk1.8.0_121\jre\lib\plugin.jar;E:\software\jdk1.8.0_121\jre\lib\resources.jar;E:\software\jdk1.8.0_121\jre\lib\rt.jar;D:\workspace\coding\java\javafx-in-action\target\test-classes;D:\workspace\coding\java\javafx-in-action\target\classes;E:\software\IDEA\lib\idea_rt.jar com.intellij.rt.execution.application.AppMain pre.huangjs.tabpane.TabPaneHelperTest
**************************华丽分割线1*****************************
[javafx.scene.control.Tab@269f4b07, javafx.scene.control.Tab@2a01737]
**************************华丽分割线2*****************************
[javafx.scene.control.Tab@2a01737]
**************************华丽分割线3*****************************
[javafx.scene.control.Tab@2a01737]
[javafx.scene.control.Tab@2a01737, javafx.scene.control.Tab@23de4bc6]
**************************华丽分割线4*****************************
{null=javafx.scene.control.Tab@23de4bc6, tab1=javafx.scene.control.Tab@269f4b07, tab2=javafx.scene.control.Tab@2a01737}
javafx.scene.control.Tab@23de4bc6

Yeah! we are successful!
但是这个辅助类需要完善:

  • 添加Tab时的方法addTab()addtabs()没有针对添加是否成功做出提示,其实就是想仿照List的add()方法
  • 这里开始我想在添加Tab时判断text是否为空,但是我觉得没有必要,因为这是一个通过text来管理Tab的类,text都为null,那还何谈通过text来管理?

其他的实现

上面是一种实现方法,这里在给出另一种实现方法:

package pre.huangjs.tabpane;

import javafx.collections.ListChangeListener;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;

import java.util.HashMap;
import java.util.List;

/**
 * Created by huangjs on 2018/4/9.
 */
public class TabPaneExpansion {
    private TabPane tabPane;
    private HashMap<String, Tab> tabsMap;

    public TabPane getTabPane() {
        return tabPane;
    }

    public void setTabPane(TabPane tabPane) {
        this.tabPane = tabPane;
    }

    public TabPaneExpansion() {
        this.tabPane = new TabPane();
        this.tabsMap = new HashMap<>();
        initial();
    }

    public TabPaneExpansion(TabPane tabPane) {
        this.tabPane = tabPane;
        this.tabsMap = new HashMap<>();
        initial();
    }

    private void initial() {
        tabPane.getTabs().addListener(new ListChangeListener<Tab>() {

            @Override
            public void onChanged(Change<? extends Tab> c) {
                while (c.next()) {

                    // if elements were added into list, the elements's text
                    // and the elements themselves need to be added into HashMap
                    if (c.wasAdded()) {
                        List<? extends Tab> addedTabs = c.getAddedSubList();
                        for (Tab tab : addedTabs) {
                            tabsMap.put(tab.getText(), tab);
                        }
                    }

                    // if elements were removed from list, the elements's text
                    // and the elements themselves need to be removed from HashMap
                    if(c.wasRemoved()){
                        List<? extends Tab> removedTabs = c.getRemoved();
                        for(Tab tab : removedTabs){
                            tabsMap.remove(tab.getText());
                        }
                    }
                }

            }
        });
    }

    public boolean addTab(Tab tab) {
        return this.tabPane.getTabs().add(tab);
    }

    public boolean addTabs(Tab... tabs) {
       return this.tabPane.getTabs().addAll(tabs);
    }

    public Tab getTabByText(String text) {
        return tabsMap.get(text);
    }

    public boolean removeTab(String text){
        return this.tabPane.getTabs().remove(getTabByText(text));
    }
}

TabPane中保存Tab是使用ObservableList,所以我们可以为这个可观察的列表添加一个监听器,监听它的改变——添加新元素的同时向tabsMap中添加;删除时在tabsMap中删除。

  • 这是TabPane源码中tabs的初始化
private ObservableList<Tab> tabs = FXCollections.observableArrayList();
  • 关于ObservableList其实它就是JDK中观察者模式的运用
  • 看上面代码时可以主要关注initialize()方法

总结

实现“通过text管理Tab”这一目的的三种实现,其原理都是利用HashMap来连接text和Tab,第三种使用TabPane本身方法的返回值来弥补了第二种的缺憾。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,928评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • 年终总结这种事总是不能少的。 2017年总体来说,我觉得是长大的一年。我不知道你们会不会有这样的感觉,就是明显能感...
    YokoYin阅读 456评论 0 2
  • 莫把功名说! 想诸君,从来好义,不嫌瓜葛! 杯酒浇成金兰诺,月下单衣履雪。 少年气,将军割发。 剑影寒时催孟浪,正...
    红花白鬓阅读 246评论 0 0
  • 今天我们换个角度,希望所有家长思考三个问题: 1、作为父母,你是否对孩子的教育倾注了足够的精力? 2、你希望自己的...
    幸福亲子会阅读 349评论 0 0