0

I've made a gui for my application. The JFrame has 2 JPanels, panel1 & panel2. panel1 is just that, a JPanel with a custom painting that repaints itself every 5 ms.

panel2 is my first attempt at a CardLayout implementation: it contains JPanels subPanel1 & subPanel2. subPanel1 contains a JComboBox and is added to panel2: panel2.add(subPanel1);.

subPanel2 has the .setLayout(new CardLayout()); command, and I add 3 new JPanels into it, with appropiate itemListener and all. Of course I also add it: panel2.add(subPanel2);

Now to the problem: focusing components in Java. I have knowledge of methods setFocusable(boolean) and requestFocus(). But I can't make them behave in any logical way.

First, the root problem of them all: When the combobox gets the focus, I can't unfocus it at all (tried clicking everywhere with cursor).

Following are experiments I've conducted:

1) without any code speaking to focus throughout the application the combobox starts with the focus, no matter which order panel1 and panel2 are added to the JFrame.

2) if I set panel1.setFocusable(true); (in its constructor) it will start with the focus

3) if I set panel1.setFocusable(false); and also request focus to it, it doesn't get it. (only thing that works as expected)

4) if I set panel2, subPanel1, or subPanel2 unfocusable individually or in any combination, they can still receive focus (the combobox, that is, which is the only component able to register focus).

5) if I set the combobox unfocusable, I'm still able to scroll between the cards in the CardLayout with the box' itemListener, but the focus doesn't stick to it. In fact panel1 still registers keyboard-inputs

So really I am very confused about the whole 'focus' thing. Maybe it is not what I assume it is? What I am trying to do is entirely block all interaction with panel2 untill a flag (which is evaluated every 5 ms) is true. Am I correct to assume that unlike JPanels, the JComboBox automatically has a mousebuttonListener to gain focus when clicked? if no, then how do I completely disable the JComboBox and all components the current card is displaying? Is it normal behavior that components within an unfocusable component are still focusable?

user2651804
  • 1,464
  • 4
  • 22
  • 45
  • It sounds like what you really want to use is [`.setEnabled(false)`](http://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#setEnabled(boolean))but note that you can't just set the panel disabled, you need to do it for each component in the panel as well. – Java Devil Nov 05 '13 at 21:54
  • @JavaDevil it does indeed look like that is what I want, thank you :) – user2651804 Nov 05 '13 at 22:08

2 Answers2

4

I've tried many different approaches to solve similar problems.

The problem with walking the component hierarchy and disabling each component is that you destroy any context and circumvent the management of these components.

Basically, if some child components need to remain disabled when you re-enable the container, you suddenly land in this problem of having multiple levels of intertwined responsibilities...

The best solution I've found to date is to simply use JXLayer, which provides a "lockable" layer concept, which allows you to "lock" an individual container, preventing user interactions with it, for example...

enter image description here

This was taken directly from the JXLayer demo code...

/**
 * Copyright (c) 2006-2008, Alexander Potochkin
 * All rights reserved.
 */

package org.jdesktop.jxlayer.demo;

import com.jhlabs.image.BlurFilter;
import com.jhlabs.image.EmbossFilter;
import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.demo.util.LafMenu;
import org.jdesktop.jxlayer.plaf.effect.BufferedImageOpEffect;
import org.jdesktop.jxlayer.plaf.effect.LayerEffect;
import org.jdesktop.jxlayer.plaf.ext.LockableUI;
import org.jdesktop.swingx.painter.BusyPainter;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Ellipse2D;

/**
 * A demo to show the abilities of the {@link LockableUI}.
 * 
 * @see BusyPainterUI  
 */
public class LockableDemo extends JFrame {
    private JXLayer<JComponent> layer;
    private LockableUI blurUI = 
            new LockableUI(new BufferedImageOpEffect(new BlurFilter()));
    private EnhancedLockableUI embossUI = 
            new EnhancedLockableUI(new BufferedImageOpEffect(new EmbossFilter()));
    private LockableUI busyPainterUI = new BusyPainterUI();

    private JCheckBoxMenuItem disablingItem =
            new JCheckBoxMenuItem(new AbstractAction("Lock the layer") {

                public void actionPerformed(ActionEvent e) {
                    blurItem.setEnabled(!disablingItem.isSelected());
                    embossItem.setEnabled(!disablingItem.isSelected());
                    busyPainterItem.setEnabled(!disablingItem.isSelected());

                    blurUI.setLocked(disablingItem.isSelected());
                    embossUI.setLocked(disablingItem.isSelected());
                    busyPainterUI.setLocked(disablingItem.isSelected());
                }
            });

    private JRadioButtonMenuItem blurItem = new JRadioButtonMenuItem("Blur effect");
    private JRadioButtonMenuItem embossItem = new JRadioButtonMenuItem("Unlock button");
    private JRadioButtonMenuItem busyPainterItem = new JRadioButtonMenuItem("BusyPainter");

    public LockableDemo() {
        super("Lockable layer demo");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JComponent view = createLayerPanel();
        layer = new JXLayer<JComponent>(view);

        layer.setUI(blurUI);
        add(layer);
        add(createToolPanel(), BorderLayout.EAST);
        setJMenuBar(createMenuBar());
        setSize(380, 300);
        setLocationRelativeTo(null);
    }

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

    private JMenuBar createMenuBar() {
        JMenu menu = new JMenu("Menu");

        disablingItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.ALT_MASK));

        menu.add(disablingItem);
        menu.addSeparator();

        blurItem.setSelected(true);
        blurItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1, InputEvent.ALT_MASK));
        menu.add(blurItem);

        embossItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_2, InputEvent.ALT_MASK));
        menu.add(embossItem);

        busyPainterItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_3, InputEvent.ALT_MASK));
        menu.add(busyPainterItem);

        ButtonGroup group = new ButtonGroup();
        group.add(blurItem);
        group.add(embossItem);
        group.add(busyPainterItem);

        ItemListener menuListener = new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                if (blurItem.isSelected()) {
                    layer.setUI(blurUI);
                } else if (embossItem.isSelected()) {
                    layer.setUI(embossUI);
                } else if (busyPainterItem.isSelected()) {
                    layer.setUI(busyPainterUI);
                }
            }
        };

        blurItem.addItemListener(menuListener);
        embossItem.addItemListener(menuListener);
        busyPainterItem.addItemListener(menuListener);

        embossUI.getUnlockButton().addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                disablingItem.doClick();
            }
        });

        JMenuBar bar = new JMenuBar();
        bar.add(menu);

        bar.add(new LafMenu());
        return bar;
    }

    private JComponent createLayerPanel() {
        JComponent panel = new JPanel();
        panel.add(new JCheckBox("JCheckBox"));
        panel.add(new JRadioButton("JRadioButton"));
        panel.add(new JTextField(15));
        JButton button = new JButton("Have a nice day");
        button.setMnemonic('H');
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(LockableDemo.this,
                        "actionPerformed");
            }
        });
        panel.add(button);
        panel.add(new JTextField(15));
        panel.add(new JCheckBox("JCheckBox"));
        panel.add(new JRadioButton("JRadioButton"));
        panel.add(new JTextField(15));
        panel.add(new JCheckBox("JCheckBox"));
        panel.add(new JRadioButton("JRadioButton"));
        panel.setBorder(BorderFactory.createEtchedBorder());
        return panel;
    }

    private JComponent createToolPanel() {
        JComponent box = Box.createVerticalBox();
        JCheckBox button = new JCheckBox(disablingItem.getText());
        button.setModel(disablingItem.getModel());
        box.add(Box.createGlue());
        box.add(button);
        box.add(Box.createGlue());
        JRadioButton blur = new JRadioButton(blurItem.getText());
        blur.setModel(blurItem.getModel());
        box.add(blur);
        JRadioButton emboss = new JRadioButton(embossItem.getText());
        emboss.setModel(embossItem.getModel());
        box.add(emboss);
        JRadioButton translucent = new JRadioButton(busyPainterItem.getText());
        translucent.setModel(busyPainterItem.getModel());
        box.add(translucent);
        box.add(Box.createGlue());
        return box;
    }


    /**
     * Subclass of the {@link LockableUI} which shows a button
     * that allows to unlock the {@link JXLayer} when it is locked  
     */
    public static class EnhancedLockableUI extends LockableUI {
        private JButton unlockButton = new JButton("Unlock");

        public EnhancedLockableUI(LayerEffect... lockedEffects) {
            super(lockedEffects);
            unlockButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    setLocked(false);
                }
            });
            unlockButton.setVisible(false);
        }

        public AbstractButton getUnlockButton() {
            return unlockButton;
        }

        @Override
        @SuppressWarnings("unchecked")
        public void installUI(JComponent c) {
            super.installUI(c);
            JXLayer<JComponent> l = (JXLayer<JComponent>) c;
            l.getGlassPane().setLayout(new GridBagLayout());
            l.getGlassPane().add(unlockButton);
            unlockButton.setCursor(Cursor.getDefaultCursor());
        }

        @Override
        @SuppressWarnings("unchecked")
        public void uninstallUI(JComponent c) {
            super.uninstallUI(c);
            JXLayer<JComponent> l = (JXLayer<JComponent>) c;
            l.getGlassPane().setLayout(new FlowLayout());
            l.getGlassPane().remove(unlockButton);
        }

        public void setLocked(boolean isLocked) {
            super.setLocked(isLocked);
            unlockButton.setVisible(isLocked);
        }
    }

    /**
     * Subclass of the {@link LockableUI} which uses the {@link BusyPainterUI}
     * from the SwingX project to implement the "busy effect" 
     * when {@link JXLayer} is locked.  
     */
    public static class BusyPainterUI extends LockableUI 
            implements ActionListener {
        private BusyPainter busyPainter;
        private Timer timer;
        private int frameNumber;

        public BusyPainterUI() {
            busyPainter = new BusyPainter() {
                protected void doPaint(Graphics2D g, JComponent object,
                                       int width, int height) {
                    // centralize the effect
                    Rectangle r = getTrajectory().getBounds();
                    int tw = width - r.width - 2 * r.x;
                    int th = height - r.height - 2 * r.y;
                    g.translate(tw / 2, th / 2);
                    super.doPaint(g, object, width, height);
                }
            };
            busyPainter.setPointShape(new Ellipse2D.Double(0, 0, 20, 20));
            busyPainter.setTrajectory(new Ellipse2D.Double(0, 0, 100, 100));
            timer = new Timer(100, this);
        }

        @Override
        protected void paintLayer(Graphics2D g2, JXLayer<? extends JComponent> l) {
            super.paintLayer(g2, l);
            if (isLocked()) {
                busyPainter.paint(g2, l, l.getWidth(), l.getHeight());
            }
        }

        @Override
        public void setLocked(boolean isLocked) {
            super.setLocked(isLocked);
            if (isLocked) {
                timer.start();
            } else {
                timer.stop();
            }
        }

        // Change the frame for the busyPainter
        // and mark BusyPainterUI as dirty 
        public void actionPerformed(ActionEvent e) {
            frameNumber = (frameNumber + 1) % 8;
            busyPainter.setFrame(frameNumber);
            // this will repaint the layer
            setDirty(true);
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
2

It sounds like what you really want to use is .setEnabled(false)

If you need to set all the components in a panel to be disabled then you can use a method like this to do it: (probably not the best method for JComponents but can be easily modified if required, but this does work)

public static void setContainerAndChildrenEnabled(Container c, boolean b)
{
    Component[] allComps = c.getComponents();
    for(Component com : allComps)
    {
        com.setEnabled(b);
        if(com instanceof Container)
            setContainerAndChildrenEnabled((Container) com, b);
    }
}

Then call it with the panel you want to set for and true or false to enable/disable. This will also recursively call setEnabled() for every Component within a Container

From the docs two points to note:

  1. Note: Disabling a lightweight component does not prevent it from receiving MouseEvents.

  2. Note: Disabling a heavyweight container prevents all components in this container from receiving any input events. But disabling a lightweight container affects only this container.

See isLightweight()

Community
  • 1
  • 1
Java Devil
  • 10,629
  • 7
  • 33
  • 48
  • This is a dangerous approach as it throws away all the context. What happens if one or more children of the container need to be disabled, but you enable the entire container? – MadProgrammer Nov 05 '13 at 22:44
  • @MadProgrammer Yes in that case this wouldn't be very good/useful, this is only intended to be an all or nothing implementation and thought their could be implications that I wasn't aware of. I've never encountered them before however, but have found it useful when I have panels within panels within panels and want to disable/enable everything within the parent panel. – Java Devil Nov 05 '13 at 23:14
  • Spent two weeks hunting down why certain components weren't enabled/disabled when I thought they should be ... because of this exact example (we had a number of background loading process going on which would disable and re-enable the fields independently) – MadProgrammer Nov 05 '13 at 23:18
  • @MadProgrammer 2 weeks sounds painful, thanks for the heads up. Might have to look into `JXLayer`. – Java Devil Nov 05 '13 at 23:23
  • I need only disable everything or nothing, so this works perfect for me! Thanks =] – user2651804 Nov 06 '13 at 00:26