8 Replies Latest reply: Apr 5, 2012 2:33 PM by David Grieve RSS

    Use different "fx-border-image-source" for first tab and remaining tabs

    ytw
      Hi,

      I'm using something like this
      .tab {
      -fx-padding: 0px 5px -2px 5px;
      -fx-background-insets: 0 -20 0 0;
      -fx-background-color: transparent;
      -fx-text-fill: #c4d8de;
      -fx-border-image-source: url("images/tab5.png");
      -fx-border-image-slice: 20 20 20 20 fill;
      -fx-border-image-width: 20 20 20 20;
      -fx-border-image-repeat: stretch;
      -fx-font-size: 22px;
      }
      .tab:selected {
      -fx-border-image-source: url("images/tab-selected5.png");
      -fx-text-fill: #333333;
           /*
           -fx-background-color: red;*/
      }

      to customize the tab appearance of a TabPane.

      That worked well. But I need to use a different set of images for just the first tab. Does anyone know a way to accomplish that?

      Thanks.
        • 1. Re: Use different "fx-border-image-source" for first tab and remaining tabs
          David Grieve
          At some point in the future, there may be structural pseudoclasses which would help tremendously for something like this. Meanwhile, you can use either an id or an additional style. For example:

          .tab.first { .... }

          or

          .tab#first { .... }
          • 2. Re: Use different "fx-border-image-source" for first tab and remaining tabs
            jsmith
            After you have added the tab pane to an active scene, lookup the first tab in the scene, set it's id to first-tab and style the first-tab via css.
            import javafx.application.Application;
            import javafx.scene.*;
            import javafx.scene.control.*;
            import javafx.scene.layout.*;
            import javafx.scene.paint.Color;
            import javafx.scene.shape.Rectangle;
            import javafx.stage.*;
            
            public class ThumbTabPane extends Application {
              public static void main(String[] args) { launch(args); }
              @Override public void start(Stage stage) {
                // create some tabs.
                TabPane tabPane = new TabPane();
                Tab tab1 = new Tab("Sites"); tab1.setContent(new Rectangle(400, 200, Color.LIGHTGREEN));
                Tab tab2 = new Tab("Favorites"); tab2.setContent(new Rectangle(400, 200, Color.LIGHTSTEELBLUE));
                Tab tab3 = new Tab("Loves"); tab3.setContent(new Rectangle(400, 200, Color.PINK));
                tabPane.getTabs().addAll(tab1, tab2, tab3);
                tabPane.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
            
                // layout the stage.
                StackPane layout = new StackPane();
                layout.getChildren().add(tabPane);
                layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10;");
                Scene scene = new Scene(layout);
                scene.getStylesheets().add(ThumbTabPane.class.getResource("stylishtabs.css").toExternalForm());
                stage.setScene(scene);
                stage.show();
                
                Node node = scene.getRoot().lookup(".tab");
                node.setId("first-tab");
              }
            }
            // file stylishtabs.css
            .tab {
              -fx-border-image-source: url("http://th33.photobucket.com/albums/d73/siewsammuk/th_icon_cool.gif");
              -fx-border-image-slice: 20 20 20 20 fill;
              -fx-border-image-width: 20 20 20 20;
              -fx-border-image-repeat: stretch;  
            }
            .tab:selected { -fx-border-image-source: url("http://www.panic.com/blog/wp-content/themes/panic/images/icon_smile.png"); }  
            
            #first-tab.tab { -fx-border-image-source: url("http://roi.ltcdn.com/roidsn/en/main/icon_6.jpg"); }
            #first-tab.tab:selected { -fx-border-image-source: url("http://pensacolatodo.com/icon_images/icon_wordpress.gif"); }
            • 3. Re: Use different "fx-border-image-source" for first tab and remaining tabs
              ytw
              Thanks, jsmith. Your solution worked.

              I have a related question that I'm hoping you'll have a solution for as well.
              How can I "fix up" the first tab of tab panes that are created after I "fixed up" the first tab of the initial tab pane?
              My app allows user to create new tab panes at any moment during program execution.

              Thanks very much!
              • 4. Re: Use different "fx-border-image-source" for first tab and remaining tabs
                jsmith
                How can I "fix up" the first tab of tab panes that are created after I "fixed up" the first tab of the initial tab pane?
                My app allows user to create new tab panes at any moment during program execution.
                Not easy to answer this one.
                The best answer would be to use structural pseudoclasses, but (as David points out), they are not yet implemented.
                The trick here is how to identify the first tab of each tab pane so that it can be styled separately from the other panes.
                Doing the styling without a dynamic lookup is preferrable to using a dynamic lookup (i.e. when the first tab is created give it a specific style, e.g. tab0).
                This is how the charts work, where they set style classes based on series of data, e.g. series0, series1 - this allows you to independently style each series of data.
                However the chart stuff has all of that built into the implementation, whereas the tabs don't. To achieve that you would likely need to go into the TabSkin code (http://openjdk.java.net/projects/openjfx/) find out where and how it generates the Tab nodes and write a custom tab skin or extension of the existing one which assigns a numeric style class to each new tab in a pane (e.g tab0, tab1, etc). In other words, not particularly easy if you are unfamilar with the tab skin implementation. You could log a javafx jira feature request to have those style classes set on tabs - file it here => http://javafx-jira.kenai.com.

                In the meantime a simple alternative is to use the dynamic lookup method in my previous post and a hack such that whenever you add a new tab pane to the scene you do something like the following:
                new Timeline(
                  new KeyFrame(
                    Duration.millis(50),
                    new EventHandler<ActionEvent>() {
                      @Override public void handle(ActionEvent arg0) {
                        Node tab = newTabPane.lookup(".tab");
                        if (tab != null) tab.getStyleClass().add("first-tab");
                      }
                    }
                  )
                ).play();
                The reason for the Timeline is that I don't really know at what stage the css layout pass is executed. I know that when you initially show the stage and then do a lookup, the css pass seems to have already been done and the lookup will work. But for something that is dynamically added or modified after the scene is displayed - I have no idea when the css layout pass occurs, other than it's some time in the future and not at the time that you add the tabPane to the scene. So, the Timeline introduces a short delay to (hopefully) give the css layout pass time to execute and allow the lookup to work (not return null). Not the best or most efficient solution, but should work for you.
                • 5. Re: Use different "fx-border-image-source" for first tab and remaining tabs
                  ytw
                  Hi dgrieve,

                  I would like to try your suggested solution, but I'm not sure which "object" I should assign the id or additional style.
                  Is it the Tab object of something else?

                  Thanks.
                  • 6. Re: Use different "fx-border-image-source" for first tab and remaining tabs
                    David Grieve
                     
                    .tab.first { -fx-font: 16 "Comic Sans MS"; }
                            final TabPane tabPane = new TabPane();
                            tabPane.setPrefSize(400, 400);
                            tabPane.setSide(Side.TOP);
                            tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
                            final Tab tab1 = new Tab();
                            tab1.setText("Tab 1");
                            tab1.getStyleClass().add("first"); // <--- Here is the magic
                            final Tab tab2 = new Tab();
                            tab2.setText("Tab 2");
                            final Tab tab3 = new Tab();
                            tab3.setText("Tab 3");
                            final Tab tab4 = new Tab();
                            tab4.setText("Tab 4");
                            tabPane.getTabs().addAll(tab1, tab2, tab3, tab4);
                    • 7. Re: Use different "fx-border-image-source" for first tab and remaining tabs
                      jsmith
                      Oh that's great, it's really simple. Thanks David!

                      I'd mistakenly assumed because tab wasn't a node that you couldn't adjust styles on it - but of course you can :-)
                      • 8. Re: Use different "fx-border-image-source" for first tab and remaining tabs
                        David Grieve
                        @jsmith - CSS is processed before layout. In some cases, it might be processed during layout (for example, during layout a ScrollPane determines it needs a ScrollBar), but in general it comes at the beginning of a pulse before layout.