如何摆脱 paint 和 paintImmediately 的不同位置

how to get rid of different locations from paint and paintImmediately

我正在研究任意形状的按钮。第一阶段成功,因为按钮按预期显示。 第二阶段失败,因为从 doClick 调用的 paintImmediately 绘制到一个完全不同的位置。 我尝试了几种使用 AffineTransforms 的方法,但无法计算出正确的位置。

如有任何提示,我们将不胜感激。

我试过的是这个 - 按钮class:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;

import javax.swing.JButton;
import javax.swing.SwingUtilities;


public class MyCircleButton extends JButton implements MouseListener {
   public MyCircleButton(String text, double xCenter, double yCenter, double rOuter, double rInner,
         double start, double extend) {
      super(text);
      this.text     = text;
      this.xCenter  = xCenter;
      this.yCenter  = yCenter;
      this.xOffset  = xCenter - rOuter;
      this.yOffset  = yCenter - rOuter;
      this.rOuter   = rOuter;
      this.rInner   = rInner;
      this.angStart = start;
      this.angSize  = extend;
      int fontSize = (int) (rOuter * 0.15);

      this.font = new Font("Arial", Font.BOLD, fontSize);
      this.addMouseListener(this);
      calcShape();
      calcText();
   }


   @Override
   public boolean contains(int x, int y) {
      return shape.contains(x, y);
   }


   @Override
   public void doClick(int pressTime) {
      model.setPressed(true);
      Component c = SwingUtilities.getRoot(this);
      Graphics  g = c.getGraphics();
      Rectangle r = shape.getBounds();

      if (g != null) {
         Graphics2D      g2 = (Graphics2D) g;
         Point           p  = SwingUtilities.convertPoint(this, new Point(0, 0), getParent());
         AffineTransform t  = new AffineTransform();

         t.setToTranslation(p.getX(), p.getY());
         //         t.setToTranslation(xText, yText);
         //         t.setToTranslation(xCenter, yCenter);
         //         g2.getTransform().concatenate(t);
         g2.setTransform(t);
         paint(g);
      }
      try {
         Thread.currentThread().sleep(pressTime);
      } catch (InterruptedException ie) {
      }
      model.setPressed(false);
   }


   @Override
   public int getHeight() {
      return shape.getBounds().height;
   }


   @Override
   public int getWidth() {
      return shape.getBounds().width;
   }


   @Override
   public int getX() {
      return shape.getBounds().x;
   }


   @Override
   public int getY() {
      return shape.getBounds().y;
   }


   @Override
   public void mouseClicked(MouseEvent me) {
      System.out.println("mouse clicked CIRCLE-BUTTON<" + text + "> at " + me.getX() + "/" + me.getY());
      doClick(50);
   }


   @Override
   public void mouseEntered(MouseEvent me) {
   }


   @Override
   public void mouseExited(MouseEvent me) {
   }


   @Override
   public void mousePressed(MouseEvent me) {
   }


   @Override
   public void mouseReleased(MouseEvent me) {
   }


   @Override
   public void paint(Graphics g) {
      Graphics2D  g2        = (Graphics2D) g;
      Shape       oldClip   = g2.getClip();
      Paint       oldPaint  = g2.getPaint();
      Stroke      oldStroke = g2.getStroke();
      Font        oldFont   = g2.getFont();
      FontMetrics fm        = g2.getFontMetrics(font);
      Rectangle2D tb        = fm.getStringBounds(text, g2);

      g2.setClip(this.shape);
      if (getModel().isArmed() || getModel().isPressed())
         g2.setPaint(Color.RED);
      else
         g2.setPaint(Color.GREEN);
      g2.fill(shape);
      g2.setStroke(new BasicStroke(4));
      g2.setPaint(Color.WHITE);
      g2.draw(shape);

      g2.setPaint(Color.BLACK);
      g2.setFont(font);
      g2.drawString(text, (int) (xText + 2 - tb.getWidth() / 2), (int) (yText + 2 + fm.getAscent() * 0.35));
      g2.setPaint(Color.WHITE);
      g2.drawString(text, (int) (xText - tb.getWidth() / 2), (int) (yText + fm.getAscent() * 0.35));

      g2.setFont(oldFont);
      g2.setStroke(oldStroke);
      g2.setPaint(oldPaint);
      g2.setClip(oldClip);
   }


   protected void calcShape() {
      Arc2D     a        = new Arc2D.Double(xOffset, yOffset, 2 * rOuter, 2 * rOuter, angStart, angSize,
            Arc2D.PIE);
      Ellipse2D e        = new Ellipse2D.Double(xOffset + rOuter - rInner, yOffset + rOuter - rInner,
            2 * rInner, 2 * rInner);
      Area      resShape = new Area(a);

      resShape.subtract(new Area(e));
      this.shape = resShape;
   }


   protected void calcText() {
      double angle = angStart + angSize / 2;
      double r     = rInner + (rOuter - rInner) / 2;
      double xOff  = r * Math.sin(Math.toRadians(90 + angle));
      double yOff  = r * Math.cos(Math.toRadians(90 + angle));

      xText = xCenter + (Math.abs(xOff) > 0.1 ? xOff : 0);
      yText = yCenter + (Math.abs(yOff) > 0.1 ? yOff : 0);
   }


   private double xCenter;
   private double yCenter;
   private double xOffset;
   private double yOffset;
   private double rOuter;
   private double rInner;
   private double angStart;
   private double angSize;
   private double xText;
   private double yText;
   private String text;
   private Shape  shape;
   private Font   font;
}

和窗格 class:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


public class TestCirclePane extends JPanel {
   public TestCirclePane(int w, int h) {
      diameter = (int) (0.9 * (w < h ? w : h));
      Dimension s = new Dimension(w, h);

      xCenter = w / 2;
      yCenter = h / 2;
      setMinimumSize(s);
      setPreferredSize(s);
      setLayout(null);
      createComponents(this);
   }


   @Override
   public void paintChildren(Graphics g) {
      for (Component c : getComponents()) {
         if (c instanceof MyCircleButton)
            ((MyCircleButton) c).paint(g);
      }
   }


   protected void createComponents(JPanel p) {
      double r   = diameter / 2;
      double w   = r * 0.66;
      double r3o = r;
      double r3i = r - w * 0.5;

      add(new MyCircleButton("B0", xCenter, yCenter, r3o, r3i, 135, 90));
      add(new MyCircleButton("B1", xCenter, yCenter, r3o, r3i, 45, 90));
      add(new MyCircleButton("B2", xCenter, yCenter, r3o, r3i, -45, 90));
      add(new MyCircleButton("B3", xCenter, yCenter, r3o, r3i, 225, 90));
   }


   @Override
   protected void paintComponent(Graphics arg0) {
      super.paintComponent(arg0);
      Graphics2D g2       = (Graphics2D) arg0;
      Dimension  d        = getSize();
      Paint      oldPaint = g2.getPaint();

      g2.setPaint(Color.WHITE);
      g2.fill3DRect(0, 0, d.width, d.height, true);
      g2.setPaint(Color.LIGHT_GRAY);
      g2.drawLine(0, yCenter, d.width, yCenter);
      g2.drawLine(xCenter, 0, xCenter, d.height);
      g2.setPaint(oldPaint);
   }


   public static void createWindow() {
      JFrame frame  = new JFrame("Test - CircleButton");
      JPanel client = new JPanel();

      client.setLayout(new BorderLayout());
      client.add(placeHolder("top"), BorderLayout.NORTH);
      client.add(placeHolder("bottom"), BorderLayout.SOUTH);
      client.add(placeHolder("left"), BorderLayout.WEST);
      client.add(placeHolder("right"), BorderLayout.EAST);
      client.add(new TestCirclePane(800, 600), BorderLayout.CENTER);

      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.add(client);
      frame.pack();
      frame.setVisible(true);
   }


   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         @Override
         public void run() {
            createWindow();
         }
      });
   }


   public static JComponent placeHolder(String s) {
      JLabel l = new JLabel(s);

      l.setPreferredSize(new Dimension(300, 300));

      return l;
   }


   private int               xCenter;
   private int               yCenter;
   private int               diameter;
   private static final long serialVersionUID = 1L;
}

不确定这是否有帮助,但这是我的两分钱:

  1. 不要覆盖 paintChildren(…)。 JPanel 将自动重新绘制添加到面板的任何组件。

  2. 不要覆盖 paint(…)。自定义绘画是通过覆盖 paintComponent(…) 完成的,你需要调用 super.paintComponent(…) 否则你可能会有绘画工件。

  3. 不要覆盖getX(getY()这些方法用于控制组件在面板上的位置。

  4. 我猜你也不应该覆盖 getWidth() 和 getHeight()。我猜你应该覆盖 getPreferredSize() 方法来控制组件的大小。

  5. 如果您打算使用空布局,那么您的代码负责设置面板上每个组件的size/location。

  6. 不要在你的画法上翻译。组件的绘制应始终相对于 (0, 0) 完成。位置决定组件在面板上的位置。

  7. 不要在 doClick() 方法中调用 Thread.sleep()。 Thread.sleep() 将导致 EDT 休眠,这意味着 GUI 无法自行重绘。不确定为什么你甚至需要覆盖该方法。

所以这是有效的变体。再次首先是 Button-class:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;

import javax.swing.JButton;
import javax.swing.SwingUtilities;


public class MyCircleButton extends JButton implements MouseListener {
   public MyCircleButton(String text, double xCenter, double yCenter, double rOuter, double rInner,
         double start, double extend) {
      super(text);
      this.xCenter  = xCenter;
      this.yCenter  = yCenter;
      this.xOffset  = xCenter - rOuter;
      this.yOffset  = yCenter - rOuter;
      this.rOuter   = rOuter;
      this.rInner   = rInner;
      this.angStart = start;
      this.angSize  = extend;
      int fontSize = (int) ((rOuter - rInner) * 0.5);

      setFont(new Font("Arial", Font.BOLD, fontSize));
      this.addMouseListener(this);
      setBorderPainted(false);
      setContentAreaFilled(false);
      calcShape();
      calcText();
   }


   @Override
   public boolean contains(int x, int y) {
      return shape.contains(x, y);
   }


   @Override
   public int getHeight() {
      return shape.getBounds().height;
   }


   @Override
   public int getWidth() {
      return shape.getBounds().width;
   }


   @Override
   public int getX() {
      return shape.getBounds().x;
   }


   @Override
   public int getY() {
      return shape.getBounds().y;
   }


   @Override
   public void mouseClicked(MouseEvent me) {
   }


   @Override
   public void mouseEntered(MouseEvent me) {
   }


   @Override
   public void mouseExited(MouseEvent me) {
   }


   @Override
   public void mousePressed(MouseEvent me) {
      model.setArmed(true);
      model.setPressed(true);
      paintImmediately();
   }


   @Override
   public void mouseReleased(MouseEvent me) {
      model.setArmed(false);
      model.setPressed(false);
      paintImmediately();
   }


   @Override
   public void paintComponent(Graphics g) {
      Graphics2D      g2        = (Graphics2D) g;
      Paint           oldPaint  = g2.getPaint();
      Stroke          oldStroke = g2.getStroke();
      Font            oldFont   = g2.getFont();
      AffineTransform oldTrans  = g2.getTransform();
      FontMetrics     fm        = g2.getFontMetrics(getFont());
      Rectangle2D     tb        = fm.getStringBounds(getText(), g2);
      AffineTransform t         = new AffineTransform();

      t.setToTranslation(-1 * shape.getBounds().getX(), -1 * shape.getBounds().getY());
      t.concatenate(oldTrans);
      g2.setTransform(t);
      if (getModel().isArmed() || getModel().isPressed())
         g2.setPaint(Color.RED);
      else
         g2.setPaint(Color.GREEN);
      g2.fill(shape);
      g2.setStroke(new BasicStroke(4));
      g2.setPaint(Color.WHITE);
      g2.draw(shape);

      g2.setPaint(Color.BLACK);
      g2.setFont(getFont());
      g2.drawString(getText(), (int) (xText + 2 - tb.getWidth() / 2),
            (int) (yText + 2 + fm.getAscent() * 0.35));
      g2.setPaint(Color.WHITE);
      g2.drawString(getText(), (int) (xText - tb.getWidth() / 2), (int) (yText + fm.getAscent() * 0.35));
      g2.setFont(oldFont);
      g2.setStroke(oldStroke);
      g2.setPaint(oldPaint);
   }


   protected void calcShape() {
      Arc2D     a        = new Arc2D.Double(xOffset, yOffset, 2 * rOuter, 2 * rOuter, angStart, angSize,
            Arc2D.PIE);
      Ellipse2D e        = new Ellipse2D.Double(xOffset + rOuter - rInner, yOffset + rOuter - rInner,
            2 * rInner, 2 * rInner);
      Area      resShape = new Area(a);

      resShape.subtract(new Area(e));
      this.shape = resShape;
   }


   protected void calcText() {
      double angle = angStart + angSize / 2;
      double r     = rInner + (rOuter - rInner) / 2;
      double xOff  = r * Math.sin(Math.toRadians(90 + angle));
      double yOff  = r * Math.cos(Math.toRadians(90 + angle));

      xText = xCenter + (Math.abs(xOff) > 0.1 ? xOff : 0);
      yText = yCenter + (Math.abs(yOff) > 0.1 ? yOff : 0);
   }


   protected void paintImmediately() {
      Component       c  = SwingUtilities.getRoot(this);
      Graphics        g  = c.getGraphics();
      Graphics2D      g2 = (Graphics2D) g;
      Point           p  = SwingUtilities.convertPoint(this, new Point(0, 0), c);
      AffineTransform oT = g2.getTransform();
      AffineTransform t  = new AffineTransform();

      t.setToTranslation(p.getX(), p.getY());
      g2.setTransform(t);
      paintComponent(g);
      g2.setTransform(oT);
   }


   private double xCenter;
   private double yCenter;
   private double xOffset;
   private double yOffset;
   private double rOuter;
   private double rInner;
   private double angStart;
   private double angSize;
   private double xText;
   private double yText;
   private Shape  shape;
}

现在是窗格 class:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


public class TestCirclePane extends JPanel {
   public TestCirclePane(int w, int h) {
      diameter = (int) (0.9 * (w < h ? w : h));
      Dimension s = new Dimension(w, h);

      xCenter = w / 2;
      yCenter = h / 2;
      setMinimumSize(s);
      setPreferredSize(s);
      setLayout(null);
      createComponents(this);
   }


   protected void createComponents(JPanel p) {
      double r   = diameter / 2;
      double w   = r * 0.66;
      double r3o = r;
      double r3i = r - w * 0.5;

      add(new MyCircleButton("B0", xCenter, yCenter, r3o, r3i, 135, 90));
      add(new MyCircleButton("B1", xCenter, yCenter, r3o, r3i, 45, 90));
      add(new MyCircleButton("B2", xCenter, yCenter, r3o, r3i, -45, 90));
      add(new MyCircleButton("B3", xCenter, yCenter, r3o, r3i, 225, 90));
   }


   @Override
   protected void paintComponent(Graphics arg0) {
      super.paintComponent(arg0);
      Graphics2D g2       = (Graphics2D) arg0;
      Dimension  d        = getSize();
      Paint      oldPaint = g2.getPaint();

      g2.setPaint(Color.WHITE);
      g2.fill3DRect(0, 0, d.width, d.height, true);
      g2.setPaint(Color.LIGHT_GRAY);
      g2.drawLine(0, yCenter, d.width, yCenter);
      g2.drawLine(xCenter, 0, xCenter, d.height);
      g2.setPaint(oldPaint);
   }


   public static void createWindow() {
      JFrame frame  = new JFrame("Test - CircleButton");
      JPanel client = new JPanel();

      client.setLayout(new BorderLayout());
      client.add(placeHolder("top"), BorderLayout.NORTH);
      client.add(placeHolder("bottom"), BorderLayout.SOUTH);
      client.add(placeHolder("left"), BorderLayout.WEST);
      client.add(placeHolder("right"), BorderLayout.EAST);
      client.add(new TestCirclePane(800, 600), BorderLayout.CENTER);

      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.add(client);
      frame.pack();
      frame.setVisible(true);
   }


   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         @Override
         public void run() {
            createWindow();
         }
      });
   }


   public static JComponent placeHolder(String s) {
      JLabel l = new JLabel(s);

      l.setPreferredSize(new Dimension(300, 300));

      return l;
   }


   private int               xCenter;
   private int               yCenter;
   private int               diameter;
   private static final long serialVersionUID = 1L;
}

由于具有该形状的按钮的行为不像 "normal" 按钮,我需要添加一个鼠标侦听器。为了给点击提供视觉反馈,我使用了 mousePressed()mouseReleased()。 但是当我的 paintComponent() 从绘画链外部调用时,我意识到它不知道它的(按钮)来源。

所以我想,我的 paintImmediately 是在按钮 paint 概念之外的。因此我准备了环境,就像 superclass 会在正常的绘画操作上做的那样。好吧,我认为 superclass 会做的。不知道 - 只是猜测。

因此,如果您认为我的编码有误,请向我解释,如何做得更好。

我终于学会了=:O

我将形状创建从面板移动到 Layoutmanager 并设置按钮的大小和位置,现在可以按预期工作。 没有重载位置或大小吸气剂,并且没有 paintImmediately :)

感谢您的帮助。