繁体   English   中英

如何使绘制的JPanel上的元素可聚焦

[英]How to make an element on a painted JPanel focusable

我有一个JPanel带有重写的paintComponent 我想使在此面板上手动绘制的某些元素具有焦点,以便使用辅助技术的人可以使用键盘来使用我的应用程序。

如果您能给我一些建议,那就太好了。

您可以执行以下操作:

  1. 将您的元素转换为JComponent
  2. 将面板的LayoutManager设置为null 然后,将所有组件/元素添加到此面板中,然后可以使用Component.setBounds(...)方法自由移动它们。
  3. 在面板中添加一个MouseListener ,它将在每次按下鼠标时将焦点转移到所选组件上。
  4. 您可以通过在面板的MouseListener中调用方法Component.getComponentAt(Point)来确定按下了哪个组件。

简单的例子:

  1. 使组件具有显示或不显示焦点的标准行为。 在我的此类下面的示例代码中, FocusableComponent extends JComponent ,如果有焦点,它将在组件周围绘制一个蓝色矩形(这是在FocusableComponent.paintComponent(Graphics)方法内部完成的)。
  2. 然后,为每个不同的“ 元素 ”绘制子类FocusableComponent并重写其paintComponent(Graphics)方法以绘制该元素。 确保在其中调用“ super.paintComponent(Graphics) ”以绘制蓝色矩形(如果它具有焦点)。

码:

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

public class FocusablePaintComps {
    private static abstract class FocusableComponent extends JComponent {
        @Override protected void paintComponent(final Graphics g) {
            super.paintComponent(g);
            if (hasFocus()) {
                final Color prevColor = g.getColor();
                g.setColor(Color.BLUE);
                g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
                g.setColor(prevColor);
            }
        }
    }

    private static class FocusableComponent1 extends FocusableComponent {
        @Override protected void paintComponent(final Graphics g) {
            super.paintComponent(g);
            g.fillOval(0, 0, getWidth() - 1, getHeight() - 1);
        }
    }

    private static class FocusableComponent2 extends FocusableComponent {
        @Override protected void paintComponent(final Graphics g) {
            super.paintComponent(g);
            final int w = getWidth(), h = getHeight();
            g.fillRect(20, 20, w - 40, h - 40);
            g.fillArc(10, 10, w - 1, h - 1, 60, 150);
        }
    }

    private static class YourPanel extends JPanel {
        private Component previousFocusedComponent = null;

        private YourPanel() {
            super(null); //Null LayoutManager. This is important to be able to
            //move added components around freelly (with the method setBounds(...)).

            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(final MouseEvent evt) {
                    final Component src = getComponentAt(evt.getPoint());
                    if (src instanceof FocusableComponent) {
                        final FocusableComponent fc = (FocusableComponent) src;
                        fc.requestFocusInWindow(); //Transfer focus to the pressed component.
                        if (previousFocusedComponent != null)
                            previousFocusedComponent.repaint(); //Repaint the last (without focus now).
                        setComponentZOrder(fc, 0); //Update: To make fc paint over all others as  
                        //the user http://stackoverflow.com/users/131872/camickr commented.  
                        fc.repaint(); //Repaint the new (with focus now).
                        previousFocusedComponent = fc;
                    }
                    else { //If clicked on empty space, or a non-FocusableComponent:
                        requestFocusInWindow(); //Tranfer focus to somewhere else (e.g. the panel itself).
                        if (previousFocusedComponent != null) {
                            previousFocusedComponent.repaint(); //Repaint the last (without focus now).
                            previousFocusedComponent = null;
                        }
                    }
                }
            });

            setPreferredSize(new Dimension(250, 250));

            add(new FocusableComponent1(), Color.RED, new Rectangle(10, 10, 200, 20));
            add(new FocusableComponent1(), Color.GREEN, new Rectangle(40, 150, 50, 70));
            add(new FocusableComponent2(), Color.GRAY, new Rectangle(60, 125, 90, 100));
            add(new FocusableComponent2(), Color.MAGENTA, new Rectangle(150, 60, 80, 150));
        }

        private void add(final FocusableComponent fc, final Color fgColor, final Rectangle bounds) {
            fc.setForeground(fgColor);
            add(fc);
            fc.setBounds(bounds);
        }
    }

    public static void main(final String[] args) {
        final JFrame frame = new JFrame("Focused Paint Comps");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new YourPanel());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

截图:

截图

一些注意事项:

  1. 相对于在mousePressed(...)内部调用rapaint()的顺序转移焦点的顺序,确定哪个组件周围具有蓝色矩形,而哪个组件则没有。
  2. 方法Component.getElementAt(Point)不能“透视”透明/非透明像素。

更新:

注意 :此更新是上述解决方案的可选扩展 (但也许是更具java-contract-consistent的功能 -让我说吧)。 您可能只阅读以下两种实现之一(以下两种“更新”之一使用RandomLayout ,或者上述“预更新”一种使用null LayoutManager )。

遵循上述代码的更新,该更新使用自定义LayoutManager来布局容器中的组件,如用户“ Andrew Thompson”在评论中所建议的那样。
与上述代码的唯一区别是,无需使用构建YourPanel时将LayoutManager设置为null的方式,而是使用自定义LayoutManager的新实例,并且无需设置每个组件的边界,只需设置其大小即可。

我已将自定义LayoutManager命名为RandomLayout并且考虑到组件的Insets和容器的Insets ,将容器的所有组件放置在随机位置(这由YourPanel添加的Border YourPanel )。

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.LineBorder;

public class FocusablePaintComps {
    private static abstract class FocusableComponent extends JComponent {
        @Override protected void paintComponent(final Graphics g) {
            super.paintComponent(g);
            if (hasFocus()) {
                final Color prevColor = g.getColor();
                g.setColor(Color.BLUE);
                g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
                g.setColor(prevColor);
            }
        }
    }

    private static class FocusableComponent1 extends FocusableComponent {
        @Override protected void paintComponent(final Graphics g) {
            super.paintComponent(g);
            g.fillOval(0, 0, getWidth() - 1, getHeight() - 1);
        }
    }

    private static class FocusableComponent2 extends FocusableComponent {
        @Override protected void paintComponent(final Graphics g) {
            super.paintComponent(g);
            final int w = getWidth(), h = getHeight();
            g.fillRect(20, 20, w - 40, h - 40);
            g.fillArc(10, 10, w - 1, h - 1, 60, 150);
        }
    }

    private static class YourPanel extends JPanel {
        private Component previousFocusedComponent = null;

        private YourPanel() {
            super(new RandomLayout()); //RandomLayout: custom LayoutManager which lays
            //out the components in random positions (takes Insets into account).

            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(final MouseEvent evt) {
                    final Component src = getComponentAt(evt.getPoint());
                    if (src instanceof FocusableComponent) {
                        final FocusableComponent fc = (FocusableComponent) src;
                        fc.requestFocusInWindow(); //Transfer focus to the pressed component.
                        if (previousFocusedComponent != null)
                            previousFocusedComponent.repaint(); //Repaint the last (without focus now).
                        setComponentZOrder(fc, 0); //Update: To make fc paint over all others as  
                        //the user http://stackoverflow.com/users/131872/camickr commented.  
                        fc.repaint(); //Repaint the new (with focus now).
                        previousFocusedComponent = fc;
                    }
                    else { //If clicked on empty space, or a non-FocusableComponent:
                        requestFocusInWindow(); //Tranfer focus to somewhere else (e.g. the panel itself).
                        if (previousFocusedComponent != null) {
                            previousFocusedComponent.repaint(); //Repaint the last (without focus now).
                            previousFocusedComponent = null;
                        }
                    }
                }
            });

            setBorder(new LineBorder(Color.LIGHT_GRAY, 20));
            setPreferredSize(new Dimension(300, 250));

            add(new FocusableComponent1(), Color.RED, new Dimension(200, 20));
            add(new FocusableComponent1(), Color.GREEN, new Dimension(50, 70));
            add(new FocusableComponent2(), Color.GRAY, new Dimension(90, 100));
            add(new FocusableComponent2(), Color.MAGENTA, new Dimension(80, 150));
        }

        private void add(final FocusableComponent fc, final Color fgColor, final Dimension size) {
            add(fc);
            fc.setForeground(fgColor);
            fc.setSize(size);
        }
    }

    public static void main(final String[] args) {
        final JFrame frame = new JFrame("Focused Paint Comps");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new YourPanel());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

更新的“ RandomLayout”:

以及带有JavaDoc的自定义LayoutManager本身(可能很大,但希望可以重用):

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.util.Random;

/**
 * A {@link java.awt.LayoutManager} which lays out randomly all the {@link java.awt.Component}s
 * of its parent, taking into consideration the parent's {@link java.awt.Insets}.
 * <p>
 * Use {@link #setRandomizeOnce(boolean)} method to determine if the lastly laid-out parent will
 * be only laid-out randomly once and not for each {@link #layoutContainer(java.awt.Container)}
 * subsequent call for the same parent, or the opposite.
 * </p>
 */
public class RandomLayout implements LayoutManager {
    /**
     * The {@link java.awt.Container} which was lastly laid-out.
     */
    private Container lastParent;

    /**
     * The {@link java.awt.Insets} of {@code lastParent} the last time it was laid-out.
     */
    private Insets lastInsets;

    /**
     * If {@code true} then this {@link java.awt.LayoutManager} keeps track of the
     * {@link java.awt.Container}s laid-out to make sure that {@code lastParent} is
     * only laid-out once. If the another {@link java.awt.Container} is laid-out, other
     * than {@code lastParent}, then its components are laid-out randomly and the
     * {@link java.awt.Container} becomes the {@code lastParent}.
     */
    private boolean randomizeOnce;

    /**
     * Normal constructor of {@code RandomLayout} with explicit value for {@code randomizeOnce}.
     * 
     * @param randomizeOnce {@code true} if the lastly laid-out parent will be only laid-out
     * randomly once and not for each {@link #layoutContainer(java.awt.Container)} subsequent call
     * for the same parent, otherwise {@code false} and each call to
     * {@link #layoutContainer(java.awt.Container)} will lay out randomly the {@link java.awt.Container}.
     */
    public RandomLayout(final boolean randomizeOnce) {
        this.randomizeOnce = randomizeOnce;
    }

    /**
     * Default constructor of {@code RandomLayout} with {@code randomizeOnce} set to {@code true}.
     */
    public RandomLayout() {
        this(true);
    }

    /**
     * If {@code true} then this {@link java.awt.LayoutManager} keeps track of the
     * {@link java.awt.Container}s laid-out to make sure that {@code lastParent} is
     * only laid-out once. If the another {@link java.awt.Container} is laid-out, other
     * than {@code lastParent}, then its components are laid-out randomly and the
     * {@link java.awt.Container} becomes the {@code lastParent}.
     * 
     * @param randomizeOnce {@code true} if the lastly laid-out parent will be only laid-out
     * randomly once and not for each {@link #layoutContainer(java.awt.Container)} subsequent call
     * for the same parent, otherwise {@code false}.
     */
    public void setRandomizeOnce(final boolean randomizeOnce) {
        this.randomizeOnce = randomizeOnce;
    }

    /**
     * Tells if the lastly laid-out parent will be only laid-out randomly once and not for each
     * {@link #layoutContainer(java.awt.Container)} subsequent call for the same parent, or the
     * opposite.
     * 
     * @return {@code true} if the lastly laid-out parent will be only laid-out randomly once and
     * not for each {@link #layoutContainer(java.awt.Container)} subsequent call for the same
     * parent, otherwise {@code false}.
     */
    public boolean isRandomizeOnce() {
        return randomizeOnce;
    }

    /**
     * @return The {@link java.awt.Container} which was lastly laid-out.
     */
    protected Container getLastParent() {
        return lastParent;
    }

    /**
     * @return The {@link java.awt.Insets} of {@code lastParent} the last time it was laid-out.
     * @see #getLastParent()
     */
    protected Insets getLastInsets() {
        return lastInsets;
    }

    /**
     * Adds the specified component with the specified name to the layout.
     * @param name The name of the component.
     * @param comp The {@link java.awt.Component} to be added.
     */
    public void addLayoutComponent(final String name,
                                   final Component comp) {
    }

    /**
     * Removes the specified component from the layout.
     * @param comp The {@link java.awt.Component} to be removed.
     */
    public void removeLayoutComponent(final Component comp) {
    }

    /** 
     * {@inheritDoc}
     * @return The preferred size dimensions for the specified {@link java.awt.Container}.
     */
    @Override
    public Dimension preferredLayoutSize(final Container parent) {
        final Dimension prefDim = minimumLayoutSize(parent);
        prefDim.width += 2; //+2 to spare.
        prefDim.height += 2; //+2 to spare.
        return prefDim;
    }

    /**
     * {@inheritDoc}
     * @return The minimum size dimensions for the specified {@link java.awt.Container}.
     */
    @Override
    public Dimension minimumLayoutSize(final Container parent) {
        final Dimension minDim = new Dimension();

        final int childCnt = parent.getComponentCount();
        for (int i=0; i<childCnt; ++i)
            applyBigger(minDim, getPreferredSize(parent, parent.getComponent(i)));

        final Insets parInsets = parent.getInsets();
        minDim.width += (parInsets.left + parInsets.right);
        minDim.height += (parInsets.top + parInsets.bottom);

        return minDim;
    }

    /**
     * {@inheritDoc}. If the another {@link java.awt.Container} is laid-out, other
     * than {@code lastParent}, then its components are laid-out randomly and the
     * {@link java.awt.Container} becomes the {@code lastParent}.
     */
    @Override
    public void layoutContainer(final Container parent) {
        if (parent == null)
            throw new IllegalArgumentException("Cannot lay out null.");
        if (isRandomizeOnce() && lastParent == parent) { //At least take care of insets (if they have changed).
            final Insets parentInsets = parent.getInsets();
            if (!lastInsets.equals(parentInsets)) {
                final int offx = parentInsets.left - lastInsets.left,
                          offy = parentInsets.top - lastInsets.top;

                final int childCnt = parent.getComponentCount();
                for (int i=0; i<childCnt; ++i) {
                    final Component child = parent.getComponent(i);
                    final Point childLoca = child.getLocation();
                    childLoca.x += offx;
                    childLoca.y += offy;
                    child.setLocation(childLoca);
                }

                lastInsets = parentInsets;
            }
        }
        else
            layoutContainerRandomly(parent);
    }

    /**
     * Explicitly lays out randomly the specified container.
     * <p>
     * This is equivalent of calling:
     * <pre>
     * boolean isRand1 = randomLayout.isRandomizeOnce();
     * randomLayout.setRandomizeOnce(false);
     * randomLayout.layoutContainer(parent);
     * randomLayout.setRandomizeOnce(isRand1);
     * </pre>
     * {@code parent} becomes {@code lastParent}.
     * </p>
     * @param parent The container to be laid out.
     */
    public void layoutContainerRandomly(final Container parent) { //Place each child at a random location for the "new" parent (lastParent != parent).
        if (parent == null)
            throw new IllegalArgumentException("Cannot lay out null.");

        reset();

        final Dimension parentSize = parent.getSize();
        final Insets parentInsets = parent.getInsets();
        final Dimension childSize = new Dimension();
        final Point childLoca = new Point();
        final Random rand = new Random();

        final int childCnt = parent.getComponentCount();
        for (int i=0; i<childCnt; ++i) {
            final Component child = parent.getComponent(i);

            child.getSize(childSize);

            childLoca.x = parentInsets.left + 1;
            childLoca.y = parentInsets.top + 1;

            final int xBound = parentSize.width - parentInsets.left - parentInsets.right - childSize.width,
                      yBound = parentSize.height - parentInsets.top - parentInsets.bottom - childSize.height;

            if (xBound > 0)
                childLoca.x += rand.nextInt(xBound);
            if (yBound > 0)
                childLoca.y += rand.nextInt(yBound);

            child.setLocation(childLoca);
        }

        lastParent = parent;
        lastInsets = parentInsets;
    }

    /**
     * Invalidates the tracking of the lastly laid-out {@link java.awt.Container} and its last
     * {@link java.awt.Insets}.
     * @see #getLastParent()
     * @see #getLastInsets()
     */
    protected void reset() {
        lastParent = null;
        lastInsets = null;
    }

    private static void applyBigger(final Dimension inputOutput,
                                    final Dimension input) {
        if (inputOutput != null && input != null) {
            inputOutput.width = (int) Math.max(inputOutput.width, input.width);
            inputOutput.height = (int) Math.max(inputOutput.height, input.height);
        }
    }

    private static void applyIfBetter(final Dimension inputOutput,
                                      final Dimension input) {
        if (inputOutput != null && input != null
            && (input.width > inputOutput.width
                || input.height > inputOutput.height)) {
            inputOutput.width = input.width;
            inputOutput.height = input.height;
        }
    }

    /**
     * Tries to determine the best size for {@code child}.
     * @param parnt The parent {@link java.awt.Container} being laid-out.
     * @param child The child {@link java.awt.Component} of {@code parnt} being laid-out.
     * @return A preferred size for the {@code child} to be laid-out.
     */
    protected static Dimension getPreferredSize(final Container parnt,
                                                final Component child) {
        final Dimension minDim = new Dimension();
        if (child != null) {
            applyIfBetter(minDim, child.getMinimumSize());
            applyIfBetter(minDim, child.getSize());
            applyIfBetter(minDim, child.getPreferredSize());
        }
        return minDim;
    }
}

更新的屏幕截图:

这是新的屏幕截图(没有太大的视觉差异):

更新截图

更新注意事项:

请注意,这是我的第一个自定义LayoutManager ,但是我已经阅读了文档,还以GridLayoutSpringLayout为例(因为在我看来, LayoutManager的文档还不够),当然我已经对其进行了测试。 由于现在很不错,我找不到任何问题。 任何改进的建议或提议当然将受到赞赏。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM