重写 JButton 中的 paintComponent() 会产生意想不到的结果
Overriding paintComponent() in JButton produces unexpected results
除了基本的 GUI 之外,我从未编写过任何程序,所以我已经开始了一个个人项目来编写国际象棋应用程序。
我在这个项目中的一个目标是让电路板重新缩放以适应 window,我成功地做到了这一点,没有遇到太多麻烦。然而,在这个过程中,我 运行 发现我的部分(在 JButtons 上表示为图标)没有与板的其余部分一起重新缩放。
我决定用图像 class 来表示它们,并制作了一个名为 ScalingJButton 的自定义 class,它覆盖了 paintComponent。这实际上非常有效...... the last piece to be drawn。其余的棋子没有画出来,结果程序就坏了。这是我的 ScalingJButton class:
public class ScalingJButton extends JButton{
private Image image;
ScalingJButton (){
this.image = null;
}
ScalingJButton (Image image){
this.image = image;
}
public void setImage(Image image){
this.image = image;
}
public Image getImage(){
return image;
}
@Override
public void paintComponent(Graphics g){
//super.paintComponent(g);
int x = getX();
int y = getY();
int width = getWidth();
int height = getHeight();
if (image != null) {
g.drawImage(image, x, y, width, height, this);
}
}}
此外,这里是负责实例化 ScalingJButton 的代码(VisualBoard 是 class 扩展 JPanel,这是它的构造函数)。
public VisualBoard (){
white = Color.WHITE;
black = Color.GRAY;
loadPieceImages();
setLayout(new GridLayout(8, 8));
for (int i = 0; i < 8; i++){
for (int j = 0; j < 8; j++){
squares[i][j] = new ScalingJButton();
if ((i + j) % 2 != 0) {
squares[i][j].setBackground(white);
}
else{
squares[i][j].setBackground(black);
}
add(squares[i][j]);
}
}
initializeStandardBoard();
}
最后,由于布局可能相关,这里是使板自动缩放的代码:
public class Chess {
public static void main (String[] args){
final VisualBoard board = new VisualBoard();
final JPanel container = new JPanel(new GridBagLayout());
container.add(board);
container.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
drawResizedBoard(board, container);
}
});
JFrame frame = new JFrame("Chess");
frame.setSize(1000,1000);
frame.add(container);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private static void drawResizedBoard(JPanel innerPanel, JPanel container) {
int w = (int)Math.round(container.getWidth()*0.9);
int h = (int)Math.round(container.getHeight()*0.9);
int size = Math.min(w, h);
innerPanel.setPreferredSize(new Dimension(size, size));
container.revalidate();
}}
我进行了广泛的调试,但大部分都无济于事。需要注意的一件事是,当调用 drawImage 时,ScalingJButton 中的“图像”变量保存正确的图片,所以这不是问题。另一个有趣的一点是,里面有棋子的方块是最后绘制的方块,根据我在棋盘上添加方块的顺序,将绘制不同的棋子(因此加载棋子没有问题)。
St运行gely,如果我取消 paintComponent 中的 super,将鼠标滑过绘制的部分会导致其他方块在我将鼠标滑过它们时填充该部分。
我完全迷路了,不知道该怎么做,所以任何帮助将不胜感激!
However, in the process I ran into the issue that my pieces (which were represented as Icons on JButtons) did not rescale
Stretch Icon 可能是更简单的解决方案。您可以在任何可以显示图标的组件上使用此图标。图像将动态更改大小以填充图标可用的 space。
您的代码中有两行可疑。第一个是
frame.add(container);
此行将您的 Swing 元素添加到 AWT 容器中。相反,您应该将其替换为
frame.setContentPane(container);
第二行是方法
public void paintComponent(Graphics g) {
尝试将其替换为
public void paint(Graphics g) {
希望对您有所帮助。
编辑:
也改变这个
g.drawImage(image, x, y, width, height, this);
到
public void paint(Graphics g) {
super.paint(g);
int width = getWidth();
int height = getHeight();
if (image != null) {
g.drawImage(image, 0, 0, width, height, this);
}
}
您的图像只是在网格之外。
首先,您需要保留 super.paintComponent(g);
行。如果不这样做,伪像将在看似随机的时间出现。
其次,getX() 和 getY() return 获取组件在其父级中的位置。绘制时,系统会为您提供一个坐标系,其中 0,0 是组件的左上角。所以你应该忽略 getX() 和 getY()。
最简单的替代方法是使用按钮的左上角:
int x = 0;
int y = 0;
您可能需要考虑按钮的边框:
if (image != null) {
Rectangle inner = SwingUtilities.calculateInnerArea(this, null);
g.drawImage(image, inner.x, inner.y, inner.width, inner.height, this);
}
如果你想让按钮看起来真的像一个普通按钮,你还可以考虑它的边距:
if (image != null) {
Rectangle inner = SwingUtilities.calculateInnerArea(this, null);
Insets margin = getMargin();
inner.x += margin.left;
inner.y += margin.top;
inner.width -= (margin.left + margin.right);
inner.height -= (margin.top + margin.bottom);
g.drawImage(image, inner.x, inner.y, inner.width, inner.height, this);
}
除了基本的 GUI 之外,我从未编写过任何程序,所以我已经开始了一个个人项目来编写国际象棋应用程序。
我在这个项目中的一个目标是让电路板重新缩放以适应 window,我成功地做到了这一点,没有遇到太多麻烦。然而,在这个过程中,我 运行 发现我的部分(在 JButtons 上表示为图标)没有与板的其余部分一起重新缩放。
我决定用图像 class 来表示它们,并制作了一个名为 ScalingJButton 的自定义 class,它覆盖了 paintComponent。这实际上非常有效...... the last piece to be drawn。其余的棋子没有画出来,结果程序就坏了。这是我的 ScalingJButton class:
public class ScalingJButton extends JButton{
private Image image;
ScalingJButton (){
this.image = null;
}
ScalingJButton (Image image){
this.image = image;
}
public void setImage(Image image){
this.image = image;
}
public Image getImage(){
return image;
}
@Override
public void paintComponent(Graphics g){
//super.paintComponent(g);
int x = getX();
int y = getY();
int width = getWidth();
int height = getHeight();
if (image != null) {
g.drawImage(image, x, y, width, height, this);
}
}}
此外,这里是负责实例化 ScalingJButton 的代码(VisualBoard 是 class 扩展 JPanel,这是它的构造函数)。
public VisualBoard (){
white = Color.WHITE;
black = Color.GRAY;
loadPieceImages();
setLayout(new GridLayout(8, 8));
for (int i = 0; i < 8; i++){
for (int j = 0; j < 8; j++){
squares[i][j] = new ScalingJButton();
if ((i + j) % 2 != 0) {
squares[i][j].setBackground(white);
}
else{
squares[i][j].setBackground(black);
}
add(squares[i][j]);
}
}
initializeStandardBoard();
}
最后,由于布局可能相关,这里是使板自动缩放的代码:
public class Chess {
public static void main (String[] args){
final VisualBoard board = new VisualBoard();
final JPanel container = new JPanel(new GridBagLayout());
container.add(board);
container.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
drawResizedBoard(board, container);
}
});
JFrame frame = new JFrame("Chess");
frame.setSize(1000,1000);
frame.add(container);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private static void drawResizedBoard(JPanel innerPanel, JPanel container) {
int w = (int)Math.round(container.getWidth()*0.9);
int h = (int)Math.round(container.getHeight()*0.9);
int size = Math.min(w, h);
innerPanel.setPreferredSize(new Dimension(size, size));
container.revalidate();
}}
我进行了广泛的调试,但大部分都无济于事。需要注意的一件事是,当调用 drawImage 时,ScalingJButton 中的“图像”变量保存正确的图片,所以这不是问题。另一个有趣的一点是,里面有棋子的方块是最后绘制的方块,根据我在棋盘上添加方块的顺序,将绘制不同的棋子(因此加载棋子没有问题)。
St运行gely,如果我取消 paintComponent 中的 super,将鼠标滑过绘制的部分会导致其他方块在我将鼠标滑过它们时填充该部分。
我完全迷路了,不知道该怎么做,所以任何帮助将不胜感激!
However, in the process I ran into the issue that my pieces (which were represented as Icons on JButtons) did not rescale
Stretch Icon 可能是更简单的解决方案。您可以在任何可以显示图标的组件上使用此图标。图像将动态更改大小以填充图标可用的 space。
您的代码中有两行可疑。第一个是
frame.add(container);
此行将您的 Swing 元素添加到 AWT 容器中。相反,您应该将其替换为
frame.setContentPane(container);
第二行是方法
public void paintComponent(Graphics g) {
尝试将其替换为
public void paint(Graphics g) {
希望对您有所帮助。
编辑:
也改变这个
g.drawImage(image, x, y, width, height, this);
到
public void paint(Graphics g) {
super.paint(g);
int width = getWidth();
int height = getHeight();
if (image != null) {
g.drawImage(image, 0, 0, width, height, this);
}
}
您的图像只是在网格之外。
首先,您需要保留 super.paintComponent(g);
行。如果不这样做,伪像将在看似随机的时间出现。
其次,getX() 和 getY() return 获取组件在其父级中的位置。绘制时,系统会为您提供一个坐标系,其中 0,0 是组件的左上角。所以你应该忽略 getX() 和 getY()。
最简单的替代方法是使用按钮的左上角:
int x = 0;
int y = 0;
您可能需要考虑按钮的边框:
if (image != null) {
Rectangle inner = SwingUtilities.calculateInnerArea(this, null);
g.drawImage(image, inner.x, inner.y, inner.width, inner.height, this);
}
如果你想让按钮看起来真的像一个普通按钮,你还可以考虑它的边距:
if (image != null) {
Rectangle inner = SwingUtilities.calculateInnerArea(this, null);
Insets margin = getMargin();
inner.x += margin.left;
inner.y += margin.top;
inner.width -= (margin.left + margin.right);
inner.height -= (margin.top + margin.bottom);
g.drawImage(image, inner.x, inner.y, inner.width, inner.height, this);
}