CButton to request focus for component not working on first click

Hi!

I’m trying to install a CButton in the title bar of a DefaultSingleCDockable. The purpose of the button is to put focus on a JTextField in the dockable.

On the first click after the applications starts, this doesn’t work: the button itself gets the focus, not the textfield. On a second click, the textfield gets the focus.

How can I reliable perform a focus request on first click to a CButton? I’m using dockingframes version 1.1.2p17b.

Here is code that shows the problem:

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

import com.jcop.jta.ui.MainWindow;

import bibliothek.gui.dock.action.DockAction;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.action.CAction;
import bibliothek.gui.dock.common.action.CButton;
import bibliothek.gui.dock.common.intern.action.CDecorateableAction;
import bibliothek.gui.dock.common.intern.action.CDecorateableActionListener;


public class TestDockButton {
    
    static void setupGUI() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(500, 500);
        CControl dockControl = new CControl( frame );
        
        DefaultSingleCDockable frame1 = new DefaultSingleCDockable("frame1", "Frame1");
        dockControl.addDockable(frame1);

        DefaultSingleCDockable frame2 = new DefaultSingleCDockable("frame2", "Frame2");
        Container content2 = frame2.getContentPane();
        content2.setLayout(new FlowLayout());
        final JTextField field1 = new JTextField(20);
        content2.add(field1);
        final JTextField field2 = new JTextField(20);
        content2.add(field2);
        
        CButton selectField1 = new CButton("Select field1", null);
        selectField1.setShowTextOnButtons(true);
        selectField1.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                field1.requestFocusInWindow();
            }
        });
        frame2.addAction(selectField1);
        CButton selectField2 = new CButton("Select field2", null);
        selectField2.setShowTextOnButtons(true);
        selectField2.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                field2.requestFocusInWindow();
            }
        });
        frame2.addAction(selectField2);
        dockControl.addDockable(frame2);

        frame.getContentPane().add(dockControl.getContentArea(), BorderLayout.CENTER);
        CGrid grid = new CGrid(dockControl);
        grid.add(0, 0, 100, 100, frame1);
        grid.add(0, 0, 100, 100, frame2);
        dockControl.getContentArea().deploy(grid);

        frame.setVisible(true);
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                setupGUI();
            }
        });
    }
}

Screenshot immediately after applications startup:

After first click (note the small black dots around the button, denoting focus):

After second click: the text field finally gets the focus:

Thanks for your help !
Alex

Interestingly, when clicking the button “slowly” (i.e. waiting a second between the press and the release), then it seems to work.

With a “fast” click, the text field receives the focus, but immediately loses it due to a “CLEAR_GLOBAL_FOCUS_OWNER” event, which SEEMS to be only possible due to https://github.com/Benoker/DockingFrames/blob/0aeb1207760083dd7f1152f5ee331bb18825405b/docking-frames-core/src/bibliothek/gui/dock/control/focus/RepeatingFocusRequest.java#L114 - Beni will probably tell you more about the details…

I’ve worked around this issue by doing this:

    @Override
    public void actionPerformed(ActionEvent e) {
        Timer timer = new Timer(300, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                field1.requestFocusInWindow();
            }
        });
        timer.setRepeats(false);
        timer.start();
    }
});```

However, this is fragile: in my application, 300ms works, but 100ms doesn't...

Alex

Yeah, I tried wrapping the field1.requestFocusInWindow(); call into a SwingUtilities.invokeLater. This should actually place the command at the end of the queue, and make sure that it is executed after other events (that might affect the focus) have been processed. At least, this “occasionally” helps to (albeit hackily) resolve glitches like this. But in this case, it didn’t work. (I read class names like “RepeatingFocusRequest”, … there seems to be some machinery running that I don’t fully understand yet…)

First of all, the solution is to use the “FocusController”, which usually is used by DockingFrames to transfer focus. There is no easier API available because I never anticipated that someone wants to manually transfer the focus the way you are doing right now.

            @Override
            public void actionPerformed(ActionEvent e) {
            	FocusController focusController = dockControl.getController().getFocusController();
            	focusController.focus( new DefaultFocusRequest(
            			frame2.intern(),
            			field1,
            			true ) );
            }
        });```

A short explanation: Focus in Swing works most of the times ... until more than one Window is involved, and not if Components are added and removed. These are two features that DockingFrames is doing often, hence DockingFrames really kills the default focus management of Swing. The "FocusController" makes sure that focus really, really is granted to those Components where DockingFrames thinks it is correct. In your case, unfortunately, that is the button you pressed. By telling the FocusController what you actually want to be focused, you stop it from selecting your button.


[Edit]
I'll add a method "AbstractCDockable.toFront( Component focus )" in the next release, that method basically is what is written in the code snippet above.

Ein Beitrag wurde in ein neues Thema verschoben: Issue with setting the focus on click