Как работает PaintComponent?

Это может быть очень нубский вопрос. Я только начинаю изучать Java

Я не понимаю работу метода paintComponent. Я знаю, что если я хочу что-то нарисовать, я должен переопределить метод paintComponent.

public void paintComponent(Graphics g)
{
   ...
}

Но когда он вызывается? Я никогда не видел ничего похожего на "object.paintComponent(g)", но он все равно рисуется во время работы программы.

И что такое параметр Graphics? Откуда это? Параметр должен быть указан при вызове метода. Но, как я уже говорил, кажется, что этот метод никогда не вызывается явно. Так кто предоставляет этот параметр? И почему мы должны приводить его к Graphics2D?

public void paintComponent(Graphics g)
{
    ...
    Graphics2D g2= (Graphics2D) g;
    ...
}

person Hải Phong    schedule 21.03.2013    source источник
comment
Начните с Рисование в AWT и Swing .   -  person trashgod    schedule 21.03.2013
comment
@trashgod всегда так любезен, чтобы опубликовать конкретную ссылку, на которую также ссылаются в вики тега Swing :-) Или в Другими словами: всегда сначала заглядывайте в вики...   -  person kleopatra    schedule 21.03.2013
comment
@kleopatra: +1 Также легче исправить следующее (неизбежное) исчезновение ссылки.   -  person trashgod    schedule 21.03.2013


Ответы (5)


(Очень) короткий ответ на ваш вопрос заключается в том, что paintComponent называется «когда это необходимо». Иногда легче думать о системе Java Swing GUI как о «черном ящике», в котором большая часть внутренностей обрабатывается без особой видимости.

Существует ряд факторов, определяющих необходимость перерисовки компонента: перемещение, изменение размера, изменение фокуса, скрытие другими кадрами и т. д. и т. п. Многие из этих событий обнаруживаются автоматически, и paintComponent вызывается внутренне, когда определяется, что эта операция необходима.

Я работал со Swing много лет, и я не думаю, что когда-либо вызывал paintComponent напрямую или даже видел, чтобы он вызывался напрямую из чего-то другого. Самое близкое, что я нашел, это использование методов repaint() для программного запуска перерисовки определенных компонентов (которые, как я предполагаю, вызывают правильные методы paintComponent ниже по течению.

По моему опыту, paintComponent редко переопределяется напрямую. Я признаю, что существуют пользовательские задачи рендеринга, требующие такой детализации, но Java Swing предлагает (достаточно) надежный набор JComponents и Layouts, которые можно использовать для выполнения большей части тяжелой работы без необходимости прямого переопределения paintComponent. Я предполагаю, что моя цель здесь состоит в том, чтобы убедиться, что вы не можете что-то сделать с собственными JComponents и Layouts, прежде чем пытаться свернуть свои собственные компоненты, отрисованные пользователем.

person SeKa    schedule 21.03.2013
comment
Спасибо @GuillaumePolet за это. Мои термины были технически неверными, как вы и заявили. Я отредактировал сообщение, чтобы уточнить. - person SeKa; 21.03.2013
comment
SeKa, поскольку вы много лет работали со свингом, не возражаете, если я спрошу, стоит ли изучать свинг/javafx у потенциального студента/ищущего работу? свинг/javafx часто используется? Большое спасибо за любые предложения. - person Thor; 30.06.2016

Две вещи, которые вы можете сделать здесь:

  1. Прочитайте Рисование в AWT и Swing
  2. Используйте отладчик и поставьте точку останова в методе paintComponent. Затем пройдите вверх по трассировке стека и посмотрите, как предоставляется параметр Graphics.

Просто для информации, вот трассировка стека, которую я получил из примера кода, который я разместил в конце:

Thread [AWT-EventQueue-0] (Suspended (breakpoint at line 15 in TestPaint))  
    TestPaint.paintComponent(Graphics) line: 15 
    TestPaint(JComponent).paint(Graphics) line: 1054    
    JPanel(JComponent).paintChildren(Graphics) line: 887    
    JPanel(JComponent).paint(Graphics) line: 1063   
    JLayeredPane(JComponent).paintChildren(Graphics) line: 887  
    JLayeredPane(JComponent).paint(Graphics) line: 1063 
    JLayeredPane.paint(Graphics) line: 585  
    JRootPane(JComponent).paintChildren(Graphics) line: 887 
    JRootPane(JComponent).paintToOffscreen(Graphics, int, int, int, int, int, int) line: 5228   
    RepaintManager$PaintManager.paintDoubleBuffered(JComponent, Image, Graphics, int, int, int, int) line: 1482 
    RepaintManager$PaintManager.paint(JComponent, JComponent, Graphics, int, int, int, int) line: 1413  
    RepaintManager.paint(JComponent, JComponent, Graphics, int, int, int, int) line: 1206   
    JRootPane(JComponent).paint(Graphics) line: 1040    
    GraphicsCallback$PaintCallback.run(Component, Graphics) line: 39    
    GraphicsCallback$PaintCallback(SunGraphicsCallback).runOneComponent(Component, Rectangle, Graphics, Shape, int) line: 78    
    GraphicsCallback$PaintCallback(SunGraphicsCallback).runComponents(Component[], Graphics, int) line: 115 
    JFrame(Container).paint(Graphics) line: 1967    
    JFrame(Window).paint(Graphics) line: 3867   
    RepaintManager.paintDirtyRegions(Map<Component,Rectangle>) line: 781    
    RepaintManager.paintDirtyRegions() line: 728    
    RepaintManager.prePaintDirtyRegions() line: 677 
    RepaintManager.access$700(RepaintManager) line: 59  
    RepaintManager$ProcessingRunnable.run() line: 1621  
    InvocationEvent.dispatch() line: 251    
    EventQueue.dispatchEventImpl(AWTEvent, Object) line: 705    
    EventQueue.access$000(EventQueue, AWTEvent, Object) line: 101   
    EventQueue$3.run() line: 666    
    EventQueue$3.run() line: 664    
    AccessController.doPrivileged(PrivilegedAction<T>, AccessControlContext) line: not available [native method]    
    ProtectionDomain$1.doIntersectionPrivilege(PrivilegedAction<T>, AccessControlContext, AccessControlContext) line: 76    
    EventQueue.dispatchEvent(AWTEvent) line: 675    
    EventDispatchThread.pumpOneEventForFilters(int) line: 211   
    EventDispatchThread.pumpEventsForFilter(int, Conditional, EventFilter) line: 128    
    EventDispatchThread.pumpEventsForHierarchy(int, Conditional, Component) line: 117   
    EventDispatchThread.pumpEvents(int, Conditional) line: 113  
    EventDispatchThread.pumpEvents(Conditional) line: 105   
    EventDispatchThread.run() line: 90  

Параметр Graphics взят отсюда:

RepaintManager.paintDirtyRegions(Map) line: 781 

Используемый фрагмент выглядит следующим образом:

Graphics g = JComponent.safelyGetGraphics(
                        dirtyComponent, dirtyComponent);
                // If the Graphics goes away, it means someone disposed of
                // the window, don't do anything.
                if (g != null) {
                    g.setClip(rect.x, rect.y, rect.width, rect.height);
                    try {
                        dirtyComponent.paint(g); // This will eventually call paintComponent()
                    } finally {
                        g.dispose();
                    }
                }

Если вы посмотрите на него, вы увидите, что он извлекает графику из самого JComponent (косвенно с помощью javax.swing.JComponent.safelyGetGraphics(Component, Component)), который сам в конечном итоге берет его из своего первого «тяжеловесного родителя» (обрезанного до границ компонента), из которого он сам берет его. соответствующий родной ресурс.

Что касается того факта, что вы должны преобразовать Graphics в Graphics2D, так получилось, что при работе с Window Toolkit Graphics фактически расширяет Graphics2D, но вы можете использовать другие Graphics, которые "не должны" расширять Graphics2D (это не случается очень часто, но AWT/Swing позволяет вам это делать).

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

import javax.swing.JFrame;
import javax.swing.JPanel;

class TestPaint extends JPanel {

    public TestPaint() {
        setBackground(Color.WHITE);
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawOval(0, 0, getWidth(), getHeight());
    }

    public static void main(String[] args) {
        JFrame jFrame = new JFrame();
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jFrame.setSize(300, 300);
        jFrame.add(new TestPaint());
        jFrame.setVisible(true);
    }
}
person Guillaume Polet    schedule 21.03.2013
comment
Как бы вы рисовали в JPanel, если бы TestPaint не расширял класс JPanel? - person Doug Hauf; 06.05.2014

Внутренности системы GUI вызывают этот метод и передают параметр Graphics в качестве графического контекста, в котором вы можете рисовать.

person Gian    schedule 21.03.2013
comment
@ nubhihi219 не имеет значения :-) В любом случае вы не можете контролировать, когда происходит рисование, внутренние элементы — это... ну... внутренние, в разработке приложений не о чем беспокоиться, за исключением очень редкие угловые случаи - person kleopatra; 21.03.2013

Вызов object.paintComponent(g) является ошибкой.

Вместо этого этот метод вызывается автоматически при создании панели. Метод paintComponent() также может быть явно вызван методом repaint(), определенным в классе Component.

Результатом вызова repaint() является то, что Swing автоматически очищает графику на панели и выполняет метод paintComponent для перерисовки графики на этой панели.

person Saumya Sharma    schedule 23.10.2014

Возможно, вам придется переопределить метод void paintComponent(Graphics g){}, если вы хотите, чтобы любой предыдущий рисунок был постоянным для компонента. Вам нужно сделать это, явно вызвав метод восходящего класса, например super.painComponent();. Таким образом, в любое время, когда java потребуется использовать этот метод paintComponent, вы сохраняете внесенные изменения.

Это объясняется тем фактом, что если вы этого не сделаете, суперкласс отменит все, что вы сделали, просто вызвав свой собственный метод, полностью игнорируя любые изменения.

person jfo420    schedule 26.03.2021