2013-09-17 3 views
3

Я пытаюсь получить зависание от графического материала (рисование линий, и т. Д.) В Swing. До сих пор все обучающие программы, которые я видел, объявляют класс , который переопределяет paintComponent, и все методы paintComponent выполняют некоторые настройки, например, рисуют красный квадрат (хотя, возможно, они рисуют его в другом месте каждый раз) , Или, может быть, они рисуют несколько строк и фигур, но метод paintComponent делает все сразу.Инкрементальная графика в Swing

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

import java.awt.*; 
import javax.swing.*; 
public class DrawTest { 

    private interface GraphicsAction { 
     public void action (Graphics g); 
    } 

    private static class TestPanel extends JPanel { 

     private GraphicsAction paintAction; 

     public void draw (GraphicsAction action) { 
      paintAction = action; 
      repaint(); 
     } 

     @Override 
     public void paintComponent (Graphics g) { 
      super.paintComponent (g); 
      if (paintAction != null) 
       paintAction.action(g); 
     } 
    } 

    private static void createAndShowGui() { 
     JFrame frame = new JFrame ("DrawTest"); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.setPreferredSize(new Dimension(500,500)); 

     TestPanel p = new TestPanel(); 
     frame.getContentPane().add(p); 
     frame.pack(); 
     frame.setVisible(true); 
     p.repaint(); 

     p.draw (new GraphicsAction() { 
      public void action (Graphics g) { 
       g.setColor(Color.RED); 
       g.drawLine(5, 30, 100, 50); 
      } 
     }); 

     // in real life, we would do some other stuff and then 
     // later something would want to add a blue line to the 
     // diagram 

     p.draw (new GraphicsAction() { 
      public void action (Graphics g) { 
       g.setColor(Color.BLUE); 
       g.drawLine(5, 30, 150, 40); 
      } 
     }); 

    } 

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

Это не работает. Появляется синяя линия, но нет красной линии. Я угадывание это потому, что repaint() в draw вызывает все , чтобы начать сначала, когда я рисую синюю линию, но я не уверен; во всяком случае, я не знаю, как еще получить paintComponent.

Также, если я положил Thread.sleep(1000) между двумя звонками p.draw, Я даже не вижу красную линию на секунду. Так что я совсем не понял о том, как заставить мою графику появляться, когда захочу.

Я провел несколько поисков по «инкрементной графике» в Swing, но ничего не помогает найти решение. Я нашел статью Oracle «Живопись в AWT и Swing», в которой обсуждается переопределение метода update() для выполнения инкрементной графики, но я не нашел фактических примеров этого.

Так как я могу заставить это делать то, что я хочу? Похоже, что достаточно общей задачи, чтобы был простой способ сделать это, но я не нашел . Я предполагаю, что это должно быть выполнимым, не вызывая getGraphics, который, в зависимости от других ответов StackOverflow, будет в лучшем случае, вид gauche.

ответ

6

Картина в Swing разрушительна. То есть, всякий раз, когда запускается новый цикл рисования, вы должны полностью перестроить вывод в соответствии с состоянием объекта, который вы рисуете.

Посмотрите на Painting in AWT and Swing

Итак, когда вы звоните

p.draw (new GraphicsAction() { 
    public void action (Graphics g) { 
     g.setColor(Color.RED); 
     g.drawLine(5, 30, 100, 50); 
    } 
}); 

Вслед за

p.draw (new GraphicsAction() { 
    public void action (Graphics g) { 
     g.setColor(Color.BLUE); 
     g.drawLine(5, 30, 150, 40); 
    } 
}); 

Вы в основном выбрасывая первое действие. Игнорирование того, как на данный момент запланированы рецензенты. В первом запросе говорится: «Нарисуйте красную линию», второй говорит: «Нарисуйте синюю линию», но до того, как эти действия будут выполнены, будет очищен контекст Graphics, подготовив его для обновления.

Это очень важно, так как контекст Graphics, предоставленный вами, является общим ресурсом.Все компоненты, окрашенные ранее, использовали один и тот же контекст, все компоненты, окрашенные после использования того же контекста. Это означает, что если вы не «очистите» контекст до рисования, вы можете получить нежелательные артефакты.

Но как вы можете обойти это?

Здесь есть несколько вариантов.

Вы могли бы привлечь к бэк-буфер (или BufferedImage), который имеет собственную Graphics контекст, который вы можете добавить и нужно только «краска» в методе paintComponent.

Это будет означать, что каждый раз, когда вы вызываете p.draw(...), вы на самом деле будете рисовать этот буфер сначала, а затем позвонить repaint.

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

Другим решением было бы разместить каждое действие в List, и при необходимости просто пропустить через List и повторно применять действие в случае необходимости.

Это, вероятно, самый простой подход, но по мере роста количества действий может снизить эффективность процесса обработки краски, замедляя процесс рисования.

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

Кроме того, если я поставлю Thread.sleep (1000) между двумя p.draw вызовов

Свинг представляет собой однопроволочный каркас. Это означает, что все обновления и модификации, как ожидается, будут выполнены в контексте потока диспетчеризации событий.

Точно так же все, что блокирует работу EDT, будет препятствовать процессу (между прочим) запросам краски.

Это означает, что когда вы sleep между p.draw вызовов, вы останавливая EDT от бега, то есть он не может обработать ваши запросы краски ...

Посмотрите на Concurrency in Swing для получения более подробной информации

Обновлены например

enter image description here

Я просто хочу отметить, что действительно неэффективен. Повторное создание буфера каждый раз, когда вызывается invalidate, создаст большое количество короткоживущих объектов и может значительно снизить производительность.

Как правило, я использовал бы javax.swing.Timer, который будет не повторяться, который будет перезагружаться каждый раз, когда вызывается invalidate.Это будет установлено на короткую задержку (где-то между 125-250 миллисекундами). Когда таймер запускается, я бы просто перестроил буфер в это время, но это только пример;)

import java.awt.Color; 
import java.awt.Dimension; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.image.BufferedImage; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 
import javax.swing.SwingUtilities; 

public class DrawTest { 

    private interface GraphicsAction { 

     public void action(Graphics g); 
    } 

    private static class TestPanel extends JPanel { 

     private GraphicsAction paintAction; 
     private BufferedImage buffer; 

     @Override 
     public void invalidate() { 
      BufferedImage img = new BufferedImage(
        Math.max(1, getWidth()), 
        Math.max(1, getHeight()), BufferedImage.TYPE_INT_ARGB); 
      Graphics2D g2d = img.createGraphics(); 
      g2d.setColor(getBackground()); 
      g2d.fillRect(0, 0, getWidth(), getHeight()); 
      if (buffer != null) { 
       g2d.drawImage(buffer, 0, 0, this); 
      } 
      g2d.dispose(); 
      buffer = img; 
      super.invalidate(); 
     } 

     protected BufferedImage getBuffer() { 
      if (buffer == null) { 
       buffer = new BufferedImage(
         Math.max(1, getWidth()), 
         Math.max(1, getHeight()), BufferedImage.TYPE_INT_ARGB); 
       Graphics2D g2d = buffer.createGraphics(); 
       g2d.setColor(getBackground()); 
       g2d.fillRect(0, 0, getWidth(), getHeight()); 
       g2d.dispose(); 
      } 
      return buffer; 
     } 

     public void draw(GraphicsAction action) { 
      BufferedImage buffer = getBuffer(); 
      Graphics2D g2d = buffer.createGraphics(); 
      action.action(g2d); 
      g2d.dispose(); 
      repaint(); 
     } 

     @Override 
     public void paintComponent(Graphics g) { 
      super.paintComponent(g); 
      g.drawImage(getBuffer(), 0, 0, this); 
     } 
    } 

    private static void createAndShowGui() { 
     JFrame frame = new JFrame("DrawTest"); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.setPreferredSize(new Dimension(500, 500)); 

     TestPanel p = new TestPanel(); 
     frame.getContentPane().add(p); 
     frame.pack(); 
     frame.setVisible(true); 
     p.repaint(); 

     p.draw(new GraphicsAction() { 
      public void action(Graphics g) { 
       g.setColor(Color.RED); 
       g.drawLine(5, 30, 100, 50); 
      } 
     }); 

     // in real life, we would do some other stuff and then 
     // later something would want to add a blue line to the 
     // diagram 
     p.draw(new GraphicsAction() { 
      public void action(Graphics g) { 
       g.setColor(Color.BLUE); 
       g.drawLine(5, 30, 150, 40); 
      } 
     }); 

    } 

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

Хорошо, я еще раз рассмотрю статью «Живопись»; Я уже посмотрел на его часть, но я постараюсь более внимательно рассмотреть ее. Bummer, что они испортили форматирование своих примеров кода и имеют ссылку на демоверсию «UpdateDemo», которая не существует. Существуют ли какие-либо ссылки на пример кода, который использует буфер резервного копирования или «BufferedImage» с собственным контекстом «Graphics» (я полагаю, что он не будет использоваться другими компонентами, если я его не допущу); и мне нужно создать экземпляр «Graphics», чтобы заставить его работать? – ajb

+0

Я добавил простой пример, основанный на прямом рисовании действия на буфер поддержки ... – MadProgrammer

+0

Большое спасибо за пример. Мне нужно изучить его дальше. Как часто вызывать недействительность? Из javadoc для 'java.AWT.Component', он будет вызываться всякий раз, когда изменяется информация о компоновке; но если вы не ожидаете изменения размера окна, кажется, что его можно было бы назвать почти ничем, и это может быть достаточно хорошим для моих целей. Или я должен ожидать, что его можно будет вызывать чаще по другим причинам, даже если компонент, содержащий графику, в значительной степени находится в фиксированном месте? – ajb

Смежные вопросы