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

时间:2016-08-28 11:35:07

标签: java swing focus assistive-technology

我有一个覆盖JPanel的{​​{1}}。我想让我在这个面板上手动绘制的某些元素可以调整,以便使用辅助技术的人可以使用键盘来使用我的应用程序。

如果你能给我一些很棒的指示。

1 个答案:

答案 0 :(得分:4)

您可以执行以下操作:

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

    1. 制作一个具有标准行为的组件,如果它具有焦点,则显示该用户。在我的示例代码中,此类为FocusableComponent extends JComponent,如果它具有焦点,则会在组件周围绘制一个蓝色矩形(这在方法FocusableComponent.paintComponent(Graphics)内完成)。
    2. 然后,对于您绘制的每个不同的“元素”,子类FocusableComponent并覆盖其paintComponent(Graphics)方法以绘制元素。确保在其中调用“super.paintComponent(Graphics)”以绘制蓝色矩形(如果它具有焦点)。
    3. 代码:

      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);
          }
      }
      

      截图:

      Screenshot

      一些注意事项:

      1. 焦点转移的顺序与rapaint()mousePressed(...)内调用RandomLayout的顺序相关,确定哪个组件在其周围有蓝色矩形,哪个不具有蓝色矩形。
      2. 方法Component.getElementAt(Point)没有“透视”透明/非透明像素。
      3. 更新

        注意:此更新是可选扩展(但可能更多 java-contract-consistent - 让我把它放在一起)以上解决方案您可能只阅读以下两种实现中的一种(以下“更新”一个null,上述“更新前”一个LayoutManager LayoutManager

        按照上述代码的更新进行操作,该代码使用自定义null来布置容器中的组件,如用户“Andrew Thompson”在评论中所建议的那样。
        与上述代码的唯一区别在于,在构建LayoutManager时,不是设置为YourPanel LayoutManager,而是使用自定义LayoutManager的新实例,而不是设置每个组件的边界,您只需设置其大小。

        我已将自定义RandomLayout命名为Insets,并将容器的所有组件放在随机位置,同时考虑到组件的大小和容器的BorderYourPanel中添加的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); } } 证明了这一点。

        LayoutManager

        更新的“RandomLayout”:

        自定义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; } } 本身使用JavaDoc(可能很大,但可以重复使用):

        LayoutManager

        更新了屏幕截图:

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

        Updated screenshot

        更新说明:

        请注意,这是我的第一个自定义GridLayout,但我已经阅读了文档,并且还SpringLayoutLayoutManager作为示例(因为,在我看来,{{1}我的文档是不够的)当然我测试了它。由于现在是正确的,我找不到任何问题。当然,任何有关改进的建议或建议都将受到赞赏。