如何使绘制的 JPanel 上的元素可聚焦
How to make an element on a painted JPanel focusable
我有一个 JPanel
被覆盖 paintComponent
。我想让我在此面板上手动绘制的某些元素可聚焦,以便使用辅助技术的人可以通过键盘使用我的应用程序。
如果你能给我一些建议那就太棒了。
您可以执行以下操作:
- 将您的 元素 转换为
JComponent
。
- 将面板的
LayoutManager
设置为 null
。然后,您将所有 components/elements 添加到此面板中,您可以使用方法 Component.setBounds(...)
自由移动它们。
- 在您的面板中添加一个
MouseListener
,每次按下鼠标都会将焦点转移到选定的组件。
- 您可以通过调用面板
MouseListener
中的方法 Component.getComponentAt(Point)
来确定按下了哪个组件。
简单示例:
- 制作一个具有向用户显示其是否具有焦点的标准行为的组件。在我下面的示例代码中,这个 class 是
FocusableComponent extends JComponent
,如果它有焦点,它会在组件周围绘制一个蓝色矩形(这是在方法 FocusableComponent.paintComponent(Graphics)
中完成的)。
- 然后,对于您绘制的每个不同的“element”,subclass
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://whosebug.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);
}
}
截图:
一些注意事项:
- 相对于
rapaint()
在 mousePressed(...)
中调用的顺序,焦点转移的顺序决定了哪个组件周围有蓝色矩形,哪个没有。
- 方法
Component.getElementAt(Point)
没有"see through"transparent/non-opaque像素。
更新:
注意:此更新是一个可选扩展(但可能是一个更多的java合同-consistent - 让我说一下)上述解决方案。您可能只阅读了两种实现中的一种(下面的 "update" 和 RandomLayout
,或者上面的 "pre-update" 和 null
LayoutManager
)。
根据用户 "Andrew Thompson" 在评论中的建议,对上述代码进行了更新,它使用自定义 LayoutManager
来布置容器中的组件。
与上述代码的唯一区别是,在构造 YourPanel
时,不是将 null
设置为 LayoutManager
,而是使用自定义 LayoutManager
的新实例,而不是为每个组件设置边界,你只需要设置它的大小。
我已将自定义 LayoutManager
命名为 RandomLayout
并将容器的所有组件放置在随机位置,同时考虑到组件的大小和 Insets
容器(这由 YourPanel
中添加的 Border
证明)。
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://whosebug.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":
自定义 LayoutManager
本身带有 JavaDoc(可能很大,但希望可以重用):
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
,但我已经阅读了文档,还阅读了 GridLayout
和 SpringLayout
作为示例(因为,在我看来,LayoutManager
的文档是不够的)当然我测试了它。现在是wright,我找不到任何问题。当然,我们将不胜感激任何改进建议或建议。
我有一个 JPanel
被覆盖 paintComponent
。我想让我在此面板上手动绘制的某些元素可聚焦,以便使用辅助技术的人可以通过键盘使用我的应用程序。
如果你能给我一些建议那就太棒了。
您可以执行以下操作:
- 将您的 元素 转换为
JComponent
。 - 将面板的
LayoutManager
设置为null
。然后,您将所有 components/elements 添加到此面板中,您可以使用方法Component.setBounds(...)
自由移动它们。 - 在您的面板中添加一个
MouseListener
,每次按下鼠标都会将焦点转移到选定的组件。 - 您可以通过调用面板
MouseListener
中的方法Component.getComponentAt(Point)
来确定按下了哪个组件。
简单示例:
- 制作一个具有向用户显示其是否具有焦点的标准行为的组件。在我下面的示例代码中,这个 class 是
FocusableComponent extends JComponent
,如果它有焦点,它会在组件周围绘制一个蓝色矩形(这是在方法FocusableComponent.paintComponent(Graphics)
中完成的)。 - 然后,对于您绘制的每个不同的“element”,subclass
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://whosebug.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);
}
}
截图:
一些注意事项:
- 相对于
rapaint()
在mousePressed(...)
中调用的顺序,焦点转移的顺序决定了哪个组件周围有蓝色矩形,哪个没有。 - 方法
Component.getElementAt(Point)
没有"see through"transparent/non-opaque像素。
更新:
注意:此更新是一个可选扩展(但可能是一个更多的java合同-consistent - 让我说一下)上述解决方案。您可能只阅读了两种实现中的一种(下面的 "update" 和 RandomLayout
,或者上面的 "pre-update" 和 null
LayoutManager
)。
根据用户 "Andrew Thompson" 在评论中的建议,对上述代码进行了更新,它使用自定义 LayoutManager
来布置容器中的组件。
与上述代码的唯一区别是,在构造 YourPanel
时,不是将 null
设置为 LayoutManager
,而是使用自定义 LayoutManager
的新实例,而不是为每个组件设置边界,你只需要设置它的大小。
我已将自定义 LayoutManager
命名为 RandomLayout
并将容器的所有组件放置在随机位置,同时考虑到组件的大小和 Insets
容器(这由 YourPanel
中添加的 Border
证明)。
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://whosebug.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":
自定义 LayoutManager
本身带有 JavaDoc(可能很大,但希望可以重用):
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
,但我已经阅读了文档,还阅读了 GridLayout
和 SpringLayout
作为示例(因为,在我看来,LayoutManager
的文档是不够的)当然我测试了它。现在是wright,我找不到任何问题。当然,我们将不胜感激任何改进建议或建议。