6 Replies Latest reply: Nov 13, 2013 6:06 AM by JamesLee83 RSS

    How can we prevent JTabbedPanes from transferring focus to components outside of the tabs during tab traversal?

    JamesLee83

      Hi,

       

      I noticed a strange focus traversal behavior of JTabbedPane.

       

      During tab traversal (when the user's intention is just to switch between tabs), the focus is transferred to a component outside of the tabs (if there is a component after/below the JTabbedPane component), if using Java 6. For example, if using the SSCCE below...

      import java.awt.BorderLayout;
      import java.awt.event.FocusAdapter;
      import java.awt.event.FocusEvent;
      import java.awt.event.KeyEvent;
      
      import javax.swing.Box;
      import javax.swing.BoxLayout;
      import javax.swing.JButton;
      import javax.swing.JFrame;
      import javax.swing.JPanel;
      import javax.swing.JScrollPane;
      import javax.swing.JTabbedPane;
      import javax.swing.JTextField;
      import javax.swing.SwingUtilities;
      
      public class TabbedPaneTest extends JPanel {
      
          public TabbedPaneTest() {
              super(new BorderLayout());
      
              JTabbedPane tabbedPane = new JTabbedPane();
      
              tabbedPane.addTab("Tab 1", buildPanelWithChildComponents());
              tabbedPane.setMnemonicAt(0, KeyEvent.VK_1);
      
              tabbedPane.addTab("Tab 2", buildPanelWithChildComponents());
              tabbedPane.setMnemonicAt(1, KeyEvent.VK_2);
      
              tabbedPane.addTab("Tab 3", buildPanelWithChildComponents());
              tabbedPane.setMnemonicAt(2, KeyEvent.VK_3);
      
              tabbedPane.addTab("Tab 4", buildPanelWithChildComponents());
              tabbedPane.setMnemonicAt(3, KeyEvent.VK_4);
      
              JPanel panel = new JPanel(new BorderLayout());
              panel.add(tabbedPane);
      
              JButton button = new JButton("Dummy component that gains focus when switching tabs");
              panel.add(button, BorderLayout.SOUTH);
      
              /*
               * To replicate the focus traversal issue, please follow these steps -
               * 1) Run this program in Java 6; and then
               * 2) Click on a child component inside any tab; and then
               * 3) Click on any other tab (or use the mnemonic keys ALT + 1 to ALT 4).
               */
              button.addFocusListener(new FocusAdapter() {
                  @Override
                  public void focusGained(FocusEvent e) {
                      System.err.println("Gained focus (not supposed to when just switching tabs).");
                  }
              });
      
              add(new JScrollPane(panel));
          }
      
          private JPanel buildPanelWithChildComponents() {
              JPanel panel = new JPanel();
      
      
              BoxLayout boxlayout = new BoxLayout(panel, BoxLayout.PAGE_AXIS);
              panel.setLayout(boxlayout);
      
              panel.add(Box.createVerticalStrut(3));
      
              for (int i = 0; i < 4; i++) {
                  panel.add(new JTextField(10));
                  panel.add(Box.createVerticalStrut(3));
              }
      
              return panel;
          }
      
          public static void main(String[] args) {
              SwingUtilities.invokeLater(new Runnable() {
                  public void run() {
                      JFrame frame = new JFrame("Test for Java 6");
                      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      
                      frame.add(new TabbedPaneTest());
      
                      frame.pack();
                      frame.setVisible(true);
                  }
              });
          }
      
      }
      
      

       

       

      ... Then we can replicate this behavior by following these steps:

      1) Run the program in Java 6; and then

      2) Click on a child component in any of the tabs; and then

      3) Click on any other tab (or use the mnemonic keys 'ALT + 1' to 'ALT + 4').

       

      At step 3 (upon selecting any other tab), the focus would go to the component below the JTabbedPane first (hence the printed message in the console), before actually going to the selected tab.

       

      This does not occur in Java 7, so I'm assuming it is a bug that is fixed. And I know that Oracle suggests that we should use Java 7 nowadays.

       

      The problem is: We need to stick to Java 6 for a certain application. So I'm looking for a way to fix this issue for all our JTabbedPane components while using Java 6.

       

      So, is there a way to prevent JTabbedPanes from passing the focus to components outside of the tabs during tab traversal (e.g. when users are just switching between tabs), in Java 6?

       

      Note: I've read the release notes between Java 6u45 to Java 7u15, but I was unable to find any changes related to the JTabbedPane component. So any pointers on this would be deeply appreciated.

       

       

      Regards,

      James

        • 1. Re: How can we prevent JTabbedPanes from transferring focus to components outside of the tabs during tab traversal?
          kleopatra-JavaNet

          Curious: why is it important that no child components gets focused before the selected tab?

          • 2. Re: How can we prevent JTabbedPanes from transferring focus to components outside of the tabs during tab traversal?
            JamesLee83

            Hi Kleopatra,

             

            Thanks for the reply.

             

            Please allow me to clarify first: Actually the problem is not that the child components (inside tabs) get focused before the selected tab. The problem is: the component outside of the tabs gets focused before the selected tab. For example, the JButton in the SSCCE posted above gets focused when users switch between tabs, despite the fact that the JButton is not a child component of the JTabbedPane.

             

            It is important for me to prevent this behavior because it causes a usability issue for forms with 'auto-scrolling' features.

             

            What I mean by 'auto-scrolling' here is: a feature where the form automatically scrolls down to show the current focused component (if the component is not already visible). This is a usability improvement for long forms with scroll bars (which saves the users' effort of manually scrolling down just to see the focused component).

             

            To see this feature in action, please run the SSCCE below, and keep pressing the 'Tab' key (the scroll pane will follow the focused component automatically):

            import java.awt.BorderLayout;
            import java.awt.Component;
            import java.awt.GridBagConstraints;
            import java.awt.GridBagLayout;
            import java.awt.Insets;
            import java.awt.event.FocusAdapter;
            import java.awt.event.FocusEvent;
            import java.awt.event.KeyEvent;
            
            
            import javax.swing.JButton;
            import javax.swing.JComponent;
            import javax.swing.JFrame;
            import javax.swing.JLabel;
            import javax.swing.JPanel;
            import javax.swing.JScrollPane;
            import javax.swing.JTabbedPane;
            import javax.swing.JTextField;
            import javax.swing.JViewport;
            import javax.swing.SwingUtilities;
            
            
            public class TabbedPaneAutoScrollTest extends JPanel {
            
            
                private AutoScrollFocusHandler autoScrollFocusHandler;
            
            
                public TabbedPaneAutoScrollTest() {
                    super(new BorderLayout());
            
            
                    autoScrollFocusHandler = new AutoScrollFocusHandler();
            
            
                    JTabbedPane tabbedPane = new JTabbedPane();
                    tabbedPane.addTab("Tab 1", buildPanelWithChildComponents(20));
                    tabbedPane.setMnemonicAt(0, KeyEvent.VK_1);
            
            
                    tabbedPane.addTab("Tab 2", buildPanelWithChildComponents(20));
                    tabbedPane.setMnemonicAt(1, KeyEvent.VK_2);
            
            
                    tabbedPane.addTab("Tab 3", buildPanelWithChildComponents(20));
                    tabbedPane.setMnemonicAt(2, KeyEvent.VK_3);
            
            
                    tabbedPane.addTab("Tab 4", buildPanelWithChildComponents(20));
                    tabbedPane.setMnemonicAt(3, KeyEvent.VK_4);
            
            
                    JPanel panel = new JPanel(new BorderLayout());
                    panel.add(tabbedPane);
            
            
                    JButton button = new JButton("Dummy component that gains focus when switching tabs");
                    panel.add(button, BorderLayout.SOUTH);
            
            
                    /* 
                     * To replicate the focus traversal issue, please follow these steps - 
                     * 1) Run this program in Java 6; and then 
                     * 2) Click on a child component inside any tab; and then 
                     * 3) Click on any other tab (or use the mnemonic keys ALT + 1 to ALT 4). 
                     */
                    button.addFocusListener(new FocusAdapter() {
                        @Override
                        public void focusGained(FocusEvent e) {
                            System.err.println("Gained focus (not supposed to when just switching tabs).");
                        }
                    });
            
            
                    button.addFocusListener(autoScrollFocusHandler);
            
            
                    JScrollPane scrollPane = new JScrollPane(panel);
                    add(scrollPane);
            
            
                    autoScrollFocusHandler.setScrollPane(scrollPane);
                }
            
            
                private JPanel buildPanelWithChildComponents(int numberOfChildComponents) {
                    final JPanel panel = new JPanel(new GridBagLayout());
            
            
                    final String labelPrefix = "Dummy Field ";
                    final Insets labelInsets = new Insets(5, 5, 5, 5);
                    final Insets textFieldInsets = new Insets(5, 0, 5, 0);
                    final GridBagConstraints gridBagConstraints = new GridBagConstraints();
                    JTextField textField;
                    for (int i = 0; i < numberOfChildComponents; i++) {
                        gridBagConstraints.insets = labelInsets;
                        gridBagConstraints.gridx = 1;
                        gridBagConstraints.gridy = i;
                        panel.add(new JLabel(labelPrefix + (i + 1)), gridBagConstraints);
            
            
                        gridBagConstraints.insets = textFieldInsets;
                        gridBagConstraints.gridx = 2;
                        textField = new JTextField(22);
                        panel.add(textField, gridBagConstraints);
            
            
                        textField.addFocusListener(autoScrollFocusHandler);
                    }
            
            
                    return panel;
                }
            
            
                public static void main(String[] args) {
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            JFrame frame = new JFrame("Test for Java 6 with auto-scrolling");
                            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                            frame.add(new TabbedPaneAutoScrollTest());
                            frame.setSize(400, 300);
                            frame.setVisible(true);
                        }
                    });
                }
            
            
            }
            
            
            /**
             * Crude but simple example for auto-scrolling to focused components.
             * 
             * Note: We don't actually use FocusListeners for this feature, 
             *       but this is short enough to demonstrate how it behaves.
             */
            class AutoScrollFocusHandler extends FocusAdapter {
            
            
                private JViewport viewport;
                private JComponent view;
            
            
                public void setScrollPane(JScrollPane scrollPane) {
                    viewport = scrollPane.getViewport();
                    view = (JComponent) viewport.getView();
                }
            
            
                @Override
                public void focusGained(FocusEvent event) {
                    Component component = (Component) event.getSource();
                    view.scrollRectToVisible(SwingUtilities.convertRectangle(component.getParent(),
                            component.getBounds(), view));
                }
            
            
            }
            

             

            Now, while the focus is still within the tab contents, try to switch to any other tab (e.g. by clicking on the tab headers, or by using the mnemonic keys 'ALT + 1' to 'ALT + 4')...

             

            ... then you'll notice the following usability issue:

            1) JRE 1.6 causes the focus to transfer to the JButton (which is outside of the tabs entirely) first; then

            2) In response to the JButton gaining focus, the 'auto-scrolling' feature scrolls down to the bottom of the form, to show the JButton. At this point, the tab headers are hidden from view since there are many child components; then

            3) JRE 1.6 transfers the focus to the tab contents; then

            4) The 'auto-scrolling' feature scrolls up to the selected tab's contents, but the tab header itself is still hidden from view (as a side effect of the behavior above); then

            5) Users are forced to manually scroll up to see the tab headers whenever they are just switching between tabs.

             

            In short, the tab headers will be hidden when users switch tabs, due to the Java 6 behavior posted above.

             

            That is why it is important for me to prevent the behavior in my first post above (so that it won't cause usability issues when we apply the 'auto-scrolling' feature to our forms).

             

             

            Best Regards,

            James


            • 3. Re: How can we prevent JTabbedPanes from transferring focus to components outside of the tabs during tab traversal?
              kleopatra-JavaNet

              thanks for the clarification, obviously didn't read your question carefully enough ;-)

               

              Just: when comparing 6/7 behaviour (fully agree that 6 passing temporary focus onto the button is weird), it's not soo far away - the difference is that in 6 the tabs aren't visible always on switching, in 7 only if they are not visible on the current tab. For when I have textField 18 focused, the tabs are scrolled of, then alt to change to another tab moves the focus to textField 1 on that other tab with the tabs not visible.

               

              So a way out might be to improve the auto-scroller functionality such that it does its best to keep the tabs visible, something like:

              [code]

               

                      Component component = (Component) event.getSource();
                      Rectangle visibleRect = view.getVisibleRect();
                      Rectangle componentRectInView = SwingUtilities.convertRectangle(
                              component.getParent(), component.getBounds(), view);
                      if (visibleRect.contains(componentRectInView)) return;
                      visibleRect.y = Math.max(0, 
                              componentRectInView.y + componentRectInView.height - visibleRect.height);
                      view.scrollRectToVisible(visibleRect);

               

               

              [/code]

               

              (darn, how to format code here?)

              Cheers

              Jeanette

              • 4. Re: How can we prevent JTabbedPanes from transferring focus to components outside of the tabs during tab traversal?
                JamesLee83

                hahah, I'm just glad that someone has the patience to read my long winded questions ;-)

                 

                Your code works great! It does keep my tabs visible, although it seems to flicker a little in some rare scenarios, but I'm sure that is a minor problem that can be fixed later.

                 

                If we can't prevent the issue in Java 6 from happening in the first place, then I guess we can just handle it as you've suggested.

                 

                Accepted your reply as a useful answer. Thanks.

                 

                PS - I'm also not sure how to format code here. The forum removes the empty lines from my code when I choose the "Insert > Syntax Highlighting" option in the advanced editor. Not sure how everyone else deals with it.

                 

                Cheers,

                James

                • 5. Re: How can we prevent JTabbedPanes from transferring focus to components outside of the tabs during tab traversal?
                  JamesLee83

                  I wasn't able to fix the flickering problem. Although we can now keep the tabs visible (thanks to the approach suggested by Jeanette), the JScrollPane will still scroll down and up for a split second, due to the focus being transferred to the component below the JTabbedPane in the process. Most of the time, this happens so quickly that most users won’t notice it. But on my machine, the flickering is pretty obvious around 1 out of 5 times (maybe more often on slower machines).

                   

                  So instead of using the approach above, I’m now moving back to finding a way to prevent the focus issue from happening in the first place.


                  What we know so far is: the intermediate focus component is different between JDK 1.6 and JDK 1.7.


                  For example, in the scenario posted in the original post (where users are switching between tabs), the behavior is:

                  JDKFocus Traversal BehaviorIntermediate Focus Component
                  1.6

                  The focus transfers from:

                  1) The 1st Tab's child component; to

                  2) The component below the JTabbedPane; to

                  3) The 2nd Tab's child component.

                  The next component outside of the JTabbedPane (e.g. the JButton in the SSCCE above), which is wrong.

                   

                  This causes several side effects (e.g. hiding tabs when auto-scrolling is applied, or some flickering that occurs when we show the tabs by force).

                  1.7

                  The focus transfers from:

                  1) The 1st Tab's child component; to

                  2) The JTabbedPane; to

                  3) The 2nd Tab's child component.


                  The JTabbedPane itself, which is correct.


                  So I guess I'll have to continue tracing the code differences between 1.6 and 1.7 in JTabbedPane and its super classes (e.g. Container & Component), to find the root cause.


                  But if anyone knows the root cause or fix for this, or any better ideas, then please do let me know.

                   

                   

                  Cheers,

                  James

                  • 6. Re: How can we prevent JTabbedPanes from transferring focus to components outside of the tabs during tab traversal?
                    JamesLee83

                    Just realized this bug was reported here:

                    Bug ID: JDK-4625667 Strange FOCUS_GAINED event is sent to application

                     

                    Strangely, the status of the bug is still 'open', even though it was fixed in JDK 1.7. So I'm not sure if it was fixed intentionally or as a side effect of another fix/enhancement.

                     

                    So I've posted my questions above to the OpenJDK community, here:

                    OpenJDK Swing Development - Seems like JDK-4625667 can be closed now. But can we replicate the fix using Java 6?

                     

                    Any replies in either threads will be deeply appreciated by me and my balding head.

                     

                     

                    Cheers,

                    James