Метод Java Paint не рисует?

Я работаю над простым небольшим компонентом качели и рву на себе волосы, пытаясь понять, почему мой метод рисования не работает.

Идея этого компонента заключается в том, что это небольшая панель JPanel с меткой. Фон (за этикеткой) должен быть белым, с цветным прямоугольником с левой стороны, указывающим соотношение двух измерений: «фактического» и «ожидаемого».

Если бы у вас была куча этих компонентов, выровненных по вертикали, они сформировали бы гистограмму, состоящую из горизонтальных полос.

Такие вещи должны быть супер-простыми.

Во всяком случае, вот код:

package com.mycompany.view;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;

import javax.swing.JLabel;
import javax.swing.JPanel;

public class BarGraphPanel extends JPanel {

   private static final Color BACKGROUND = Color.WHITE;
   private static final Color FOREGROUND = Color.BLACK;

   private static final Color BORDER_COLOR = new Color(229, 172, 0);
   private static final Color BAR_GRAPH_COLOR = new Color(255, 255, 165);

   private int actual = 0;
   private int expected = 1;

   private JLabel label;

   public BarGraphPanel() {
      super();
      label = new JLabel();
      label.setOpaque(false);
      label.setForeground(FOREGROUND);
      super.add(label);
      super.setOpaque(true);
   }

   public void setActualAndExpected(int actual, int expected) {
      this.actual = actual;
      this.expected = expected;
   }

   @Override
   public void paint(Graphics g) {

      double proportion = (expected == 0) ? 0 : ((double) actual) / expected;
      Rectangle bounds = super.getBounds();

      g.setColor(BACKGROUND);
      g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);

      g.setColor(BAR_GRAPH_COLOR);
      g.fillRect(bounds.x, bounds.y, (int) (bounds.width * proportion), bounds.height);

      g.setColor(BORDER_COLOR);
      g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);

      label.setText(String.format("%s of %s (%.1f%%)", actual, expected, proportion * 100));
      super.paint(g);
      g.dispose();
   }

}

А вот и простой тестовый жгут:

package com.mycompany.view;

import java.awt.Dimension;
import java.awt.GridLayout;

import javax.swing.JFrame;
import javax.swing.UIManager;

public class MyFrame extends JFrame {

   public MyFrame() {
      super();
      super.setLayout(new GridLayout(3, 1));
      super.setPreferredSize(new Dimension(300, 200));

      BarGraphPanel a = new BarGraphPanel();
      BarGraphPanel b = new BarGraphPanel();
      BarGraphPanel c = new BarGraphPanel();

      a.setActualAndExpected(75, 100);
      b.setActualAndExpected(85, 200);
      c.setActualAndExpected(20, 300);

      super.add(a);
      super.add(b);
      super.add(c);
   }

   public static void main(String[] args) {
      javax.swing.SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGUI();
         }
      });
   }

   public static void createAndShowGUI() {

      try {
         UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
      } catch (Throwable t) { }

      MyFrame frame = new MyFrame();
      frame.pack();
      frame.setVisible(true);
   }

}

Тестовая система создает простую рамку, а затем добавляет три таких элемента управления.

Все метки отображаются правильно, что указывает мне на то, что метод paint() действительно вызывается, но прямоугольники не рисуются в объекте Graphics.

Что я делаю не так?

И почему Swing-программирование так отстойно?


Вот мой окончательный код. Спасибо всем за вашу помощь!

public void paintComponent(Graphics g) {

   double proportion = (expected == 0) ? 0 : ((double) actual) / expected;

   Rectangle bounds = super.getBounds();

   g.setColor(BACKGROUND);
   g.fillRect(0, 0, bounds.width, bounds.height);

   g.setColor(BAR_GRAPH_COLOR);
   g.fillRect(0, 0, (int) (bounds.width * proportion), bounds.height);

   g.setColor(BORDER_COLOR);
   g.drawRect(0, 0, bounds.width - 1, bounds.height - 1);

   FontMetrics metrics = g.getFontMetrics();
   String label = String.format("%s of %s (%.1f%%)", actual, expected, proportion * 100);
   Rectangle2D textBounds = metrics.getStringBounds(label, g);

   g.setColor(FOREGROUND);
   g.drawString(label, 5, (int) ((bounds.height + textBounds.getHeight()) / 2));
}

person benjismith    schedule 02.02.2009    source источник
comment
Попробуйте удалить super.paint(g) и g.dispose() и посмотрите, что произойдет.   -  person Zach Scrivena    schedule 02.02.2009
comment
Интересно. Если я удаляю super.paint(g) и g.dispose(), то получаю правильные прямоугольники фона, но теряю рендеринг метки на переднем плане.   -  person benjismith    schedule 02.02.2009
comment
Хм... как насчет того, чтобы поставить super.paint(g) перед тем, как рисовать полосы? В качестве альтернативы вы можете использовать g.drawString() для рендеринга текста.   -  person Zach Scrivena    schedule 02.02.2009


Ответы (4)


Я думаю, вы почти ответили на свой вопрос в комментариях вместе с ответом Дэвида. Измените paint(Graphics g) на paintComponent(Graphics g) и удалите последние две строки метода, и все будет в порядке.

EDIT: Как ни странно, это работает только для первого такта из трех. Продолжаются испытания...

Кстати, у вас ошибка в коде отрисовки границ. Так должно быть:

g.setColor(BORDER_COLOR);
g.drawRect(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1);

EDIT2: Хорошо, понятно. Ваш полный метод paintComponent должен выглядеть следующим образом:

@Override
public void paintComponent(Graphics g) {
    double proportion = (expected == 0) ? 0 : ((double) actual) / expected;
    Rectangle bounds = super.getBounds();
    g.setColor(BACKGROUND);
    g.fillRect(0, 0, bounds.width, bounds.height);
    g.setColor(BAR_GRAPH_COLOR);
    g.fillRect(0, 0, (int) (bounds.width * proportion), bounds.height);
    g.setColor(BORDER_COLOR);
    g.drawRect(0, 0, bounds.width-1, bounds.height-1);
    label.setText(String.format("%s of %s (%.1f%%)", actual, expected, proportion * 100));
}

Обратите внимание, что координаты, заданные для g.fillRect() и g.drawRect(), относятся к компоненту, поэтому они должны начинаться с (0,0).

И нет, я не могу помочь вам с вашим последним вопросом, хотя я чувствую вашу боль. :)

person Michael Myers    schedule 02.02.2009

В вашей JPanel вы вызвали super.setOpaque(true). JPanel полностью заполнит фон, когда вы вызовете super.paint() и перезапишете свои ретанглы.

person basszero    schedule 02.02.2009

Не уверен, что это источник вашей проблемы, но в Swing вместо этого вы должны переопределить paintComponent(Graphics2D)...

person David Z    schedule 02.02.2009
comment
Да, в разных версиях этого кода я использовал paint() и paintComponent(). Это не имеет никакого значения. - person benjismith; 02.02.2009
comment
Если вы переопределяете paintComponent(), вам не нужно беспокоиться о рисовании дочерних элементов. - person Michael Myers; 02.02.2009
comment
Кроме того, вам не нужно беспокоиться о покраске границы. делегаты paint в paintComponent, paintBorder и paintChildren. - person Dan Dyer; 02.02.2009

Во всяком случае, я думаю, вам следует вызывать super.paint(g); в ТОПе вашего метода, а не в самом низу. Возможно, суперкласс рисует поверх ваших вещей.

person Outlaw Programmer    schedule 02.02.2009
comment
Я пробовал вызывать его вверху и внизу, и это не имело никакого значения. Но это могло быть из-за одной из других ошибок. - person benjismith; 02.02.2009