2009-11-04 15 views
8

Przez ostatnie dwa dni starałem się, aby zrozumieć jak Java obsługuje grafiki, ale nie udało się marnie właśnie. Moim głównym problemem jest zrozumienie, jak i kiedy należy wywołać paint() (lub nowszą funkcję paintComponent()).Dlaczego farba()/paintComponent() nigdy nie jest wywoływana?

W poniższym kodzie zrobiłem, aby zobaczyć, kiedy rzeczy są tworzone, paintComponent() nigdy nie jest wywoływany, chyba że ręcznie dodaję do niego wywołanie lub wywołania JFrame.paintAll()/JFrame.paintComponents().

Zmieniono nazwę metody paint() na paintComponent() w nadziei, że naprawię mój problem z tym, że nigdy nie zostanie wywołany (nawet przy odświeżaniu()), ale bez powodzenia.

package jpanelpaint; 

import java.awt.*; 
import javax.imageio.*; 
import javax.swing.*; 
import java.io.*; 
import java.util.ArrayList; 

public class ImageLoadTest extends JComponent { 
ArrayList<Image> list; 

public ImageLoadTest() { 
    list = new ArrayList<Image>(); 

    try { //create the images (a deck of 4 cards) 
    for(String name : createImageFileNames(4)){ 
    System.err.println(name); 
    list.add(ImageIO.read(new File(name))); 
    } 
    } catch (IOException e) { } 
} 

    protected void paintComponent(Graphics g) { 
    int yOffset=0; 
    System.err.println("ImageLoadTest.paintComponent()"); 
    for(Image img : list) { 
     g.drawImage(img, 0, yOffset, null); 
     yOffset+=20; 
    } 
    } 

public static void main(String args[]) throws InterruptedException { 
    JFrame frame = new JFrame("Empty JFrame"); 
    frame.setSize(new Dimension(1000, 500)); 
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

    frame.setVisible(true); 

    Thread.sleep(1000); 
    frame.setTitle("Loading images"); 
    ImageLoadTest ilt = new ImageLoadTest(); 
    frame.add(ilt); 
    //update the screen 
    //DOESN'T WORK. only works if I call frame.paintAll(frame.getGraphics()) 
    ilt.repaint(); 
    frame.repaint(); 

    Thread.sleep(1000); 
    frame.setTitle("Setting background"); 
    ilt.setBackground(Color.BLACK); 
    //update the screen - DOESN'T WORK even if I call paintAll .. 
    ilt.repaint(); 
    frame.repaint(); 

      //have to call one of these to get anything to display 
// ilt.paintComponent(frame.getGraphics()); //works 
    frame.paintComponents(frame.getGraphics()); //works 
} 

//PRIVATE HELPER FUNCTIONS 

private String[] createImageFileNames(int count){ 
    String[] fileNames = new String[count]; 
    for(int i=0; i < count; i++) 
    fileNames[i] = "Cards" + File.separator + (i+1) + ".bmp"; 
    return fileNames; 
} 
} 

Odpowiedz

3

Były to główne problemy z oryginalnego kodu, który spowodował, że nie będzie działać:

  1. nie c alling sprawdzania poprawności() po operacji add()
  2. nie ustawianie preferowanego rozmiaru komponentu.
  3. niestawienie super.paintComponent(), gdy przesłanianie go (tego dokonał setBackground() wywołanie nie działa)
  4. co potrzebne do dziedziczenia z JPanel, aby mogła ona dostać malowane. Ani Komponent ani JComponent było wystarczające dla setBackground() zadzwonić do pracy, nawet gdy ustalające punkt 3.

Uczyniwszy powyższe, to naprawdę nie ma znaczenia, czy wywołanie paintComponent metoda lub farby, zarówno wydawało się do pracy tak długo, jak pamiętałem, żeby na początku zadzwonić do super konstruktora.

Ta informacja została zebrana z tego, co napisali @jitter, @tackline i @camickr, tak wielkie rzeczy!

P.S. Nie mam pojęcia, czy odpowiedź na własne pytanie jest uważana za złą formę, ale ponieważ informacje, których potrzebowałem, zostały zebrane z kilku odpowiedzi, pomyślałem, że najlepszym sposobem jest poprawienie innych odpowiedzi i napisanie podsumowania w ten sposób.

4

Jednym z głównych problemów jest to, że nie aktualizuje się elementów zamachowych na Event Dispatch Thread (EDT). Spróbuj zawijania cały kod w swoim głównym sposobem w następujących przypadkach:

SwingUtilities.invokeLater(new Runnable() { 
     public void run() { 
      // swing code here...    
     } 
    }); 

też: dodać ImageLoadTest do ramy przed ustawieniem rama widoczna. Jest to oparte na szybkim pobieżnym przeczytaniu kodu - przeczytam go dalej i zobaczę, co jeszcze mogę znaleźć.

EDIT:

Śledź moje oryginalne porady powyżej i uprościć główną metodę wyglądać następująco i swojej paintComponent() zostanie wywołana:

public static void main(String args[]) throws InterruptedException { 
    SwingUtilities.invokeLater(new Runnable() { 
     public void run() { 
      JFrame frame = new JFrame("Empty JFrame"); 
      frame.setSize(new Dimension(1000, 500)); 
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
      PaintComponentTest ilt = new PaintComponentTest(); 
      frame.add(ilt); 
      frame.setVisible(true); 
      ilt.setBackground(Color.BLACK); 
     } 
    }); 
} 

Również chciałbym przeczytać na używanie timerów do wykonywania animacji, a także generowanie ogólnych zdarzeń Swing i jak/kiedy nadpisywać różne metody malowania.

http://java.sun.com/products/jfc/tsc/articles/painting/

http://java.sun.com/docs/books/tutorial/uiswing/misc/timer.html

http://java.sun.com/docs/books/tutorial/uiswing/concurrency/dispatch.html

+0

Dzięki, spróbuję. Ale czy naprawdę muszę używać setVisible na końcu, aby działał? Punktem wczesnego wywołania było zobaczenie, jak powinienem obsłużyć dodanie dodatkowych elementów graficznych w późniejszym czasie. Ale cała Runiczna sprawa była dla mnie nowa; Nie widziałem tego w żadnym z tutoriali, które widziałem (np. this one). – oligofren

+0

Używanie ['java.awt.EventQueue.'] 'InvokeLater' sprawia, że ​​działa on dla mnie. –

+1

(Chociaż to prawdopodobnie tylko pokrywa błąd. Powinieneś 'revalidate' po' add', jak opisano w dokumentacji API dla 'add'.) –

2

Polecam przeczytaniu kilku pierwszych rozdziałów "Filthy Rich klientów". Używałem Swinga od lat, ale dopiero po przeczytaniu tej książki w końcu w pełni zrozumiałem, jak działa mechanizm malowania Java.

+0

To była dobra wskazówka. Właściwie to przeczytałem później. Fantastyczna książka, chociaż prace GUI wydają się być rzeczą z końca lat dziewięćdziesiątych w tym świecie internetu. – oligofren

11

Jednym z powodów, dla których farba paintbroska() nie jest wywoływana w oryginalnym kodzie jest to, że komponent ma "zerowy rozmiar", a RepaintManger jest wystarczająco inteligentny, aby nie próbować malować czegoś bez rozmiaru.

Przyczyną zmiany kolejności kodu jest to, że po dodaniu komponentu do ramki, a następnie uczynieniu ramki widoczną, menedżer układu jest wywoływany w celu rozmieszczenia komponentu. Domyślnie ramka korzysta z BorderLayout i domyślnie komponent jest dodawany do środka BorderLayout, co daje całą przestrzeń dostępną dla komponentu, aby został pomalowany.

Jednak zmiana menedżera układu panelu zawartości na FlowLayout nadal będzie miała problem, ponieważ FlowLayout respektuje preferowany rozmiar komponentu, który wynosi zero.

To, co naprawdę musisz zrobić, to przypisać preferowanemu rozmiarowi komponent, aby administratorzy układu mogli wykonywać swoją pracę.

4

Aby dokonać Tom Hawtin - tackline zadowolony.I przepisał ponownie

Istnieje kilka rzeczy, które zmieniły (Sprawdź linie z komentarzem //new)

przepisał całkowicie

  • podzielony na czystą nowego pliku składnik (ImageLoadTest.java) oraz plik do przetestowania (Tester.java)

Ulepszenia oryginalnego kodu pocztowego

zadzwoń do setPreferredSize() składnika w konstruktorze:
  • konstruktor wezwanie rodzica w ImageLoadTest konstruktora (super())
  • przewidzianego drugiego konstruktora do listy obrazów, który składnik powinien wyświetlić
  • WAŻNE ustawiony. Jeśli rozmiar nie jest ustawiony, oczywiście nie pomaluje twojego komponentu. preferowany rozmiar jest oparty na max. szerokość wszystkich obrazów i sumy wszystkich wysokościach graficznych
  • wywołanie super.paintComponent(g) w nadpisane paintComponent()
  • zmienił paintComponent automatycznie oprzeć yOffset na wysokość obrazu są rysowane inicjalizacji

  • GUI wykonanej na EDT

  • jak oryginalny kod oparty na użyciu sleep() do zilustrowania ładowania i ładowania zdjęć może zająć dużo czasu SwingWorker są używane
  • worker czeka, a następnie ustawia nowy tytuł, a następnie ładuje obrazy
  • po zakończeniu worker w done() w końcu dodaje komponent do JFrame i wyświetla go. Dodano komponent do panelu treści o numerze JFrame, jak opisano w apelu JFrame. I jak opisano w javadoc, zadzwoniłem pod numer validate() pod numerem JFrame po wywołaniu add(), ponieważ JFrame jest już widocznym kontem, który zmieniły dzieci.

javdoc cytat z validate()

Sposób sprawdzania poprawności służy do spowodować pojemnik położyć swoje podskładniki ponownie. Należy go wywołać, gdy podskładniki kontenera zostaną zmodyfikowane: (dodane lub usunięte z kontenera lub związane z layoutem zmienione informacje ) po wyświetleniu kontenera .

  • 2-te pracownik po prostu robi trochę więcej czeka następnie ustawia kolor tła na czarny
  • wykorzystywane JPanel jako klasy bazowej dla ImageLoadTest naprawić setBackground() którego nie mogłem dostać się do pracy z JComponent.

więc gdzie główne problemy, które nie zostały wymienione preferowany rozmiar komponentu oraz że nie zadzwonił validate() na JFrame po dodaniu czegoś do już widocznych pojemniku.

To powinno działać

jpanelpaint/ImageLoadTest.java

package jpanelpaint; 

import java.awt.Dimension; 
import java.awt.Graphics; 
import java.awt.Image; 
import javax.swing.JPanel; 
import java.util.List; 

public class ImageLoadTest extends JPanel { 
    private List<Image> list; 

    public ImageLoadTest() { 
    super(); 
    } 

    public ImageLoadTest(List<Image> list) { 
    this(); 
    this.list = list; 
    int height = 0; 
    int width = 0; 
    for (Image img : list) { 
     height += img.getHeight(this); 
     width = img.getWidth(this) > width ? img.getWidth(this) : width; 
     setPreferredSize(new Dimension(width, height)); 
    } 
    } 

    @Override 
    protected void paintComponent(Graphics g) { 
    int yOffset=0; 
    super.paintComponent(g); 
    System.err.println("ImageLoadTest.paintComponent()"); 
    for(Image img : list) { 
     g.drawImage(img, 0, yOffset, null); 
     yOffset+=img.getHeight(this); 
    } 
    } 
} 

Tester.java

import java.awt.Dimension; 
import java.awt.Color; 
import java.awt.Image; 
import java.io.File; 
import java.io.IOException; 
import javax.imageio.ImageIO; 
import javax.swing.JFrame; 
import javax.swing.SwingWorker; 
import javax.swing.SwingUtilities; 
import java.util.List; 
import java.util.ArrayList; 
import java.util.concurrent.ExecutionException; 
import jpanelpaint.ImageLoadTest; 

public class Tester { 

    private JFrame frame; 
    private ImageLoadTest ilt; 
    private final int NUMBEROFFILES = 4; 
    private List<Image> list; 

    //will load the images 
    SwingWorker worker = new SwingWorker<List<Image>, Void>() { 
    @Override 
    public List<Image> doInBackground() throws InterruptedException { 
     //sleep at start so user is able to see empty jframe 
     Thread.sleep(1000); 
     //let Event-Dispatch-Thread (EDT) handle this 
     SwingUtilities.invokeLater(new Runnable() { 
     public void run() { 
      frame.setTitle("Loading images"); 
     } 
     }); 
     //sleep again so user is able to see loading has started 
     Thread.sleep(1000); 
     //loads the images and returns list<image> 
     return loadImages(); 
    } 

    @Override 
    public void done() { 
     //this is run on the EDT anyway 
     try { 
     //get result from doInBackground 
     list = get(); 
     frame.setTitle("Done loading images"); 
     ilt = new ImageLoadTest(list); 
     frame.getContentPane().add(ilt); 
     frame.getContentPane().validate(); 
     //start second worker of background stuff 
     worker2.execute(); 
     } catch (InterruptedException ignore) {} 
     catch (ExecutionException e) { 
     String why = null; 
     Throwable cause = e.getCause(); 
     if (cause != null) { 
      why = cause.getMessage(); 
     } else { 
      why = e.getMessage(); 
     } 
     System.err.println("Error retrieving file: " + why); 
     } 
    } 
    }; 

    //just delay a little then set background 
    SwingWorker worker2 = new SwingWorker<Object, Void>() { 
    @Override 
    public List<Image> doInBackground() throws InterruptedException { 
     Thread.sleep(1000); 
     SwingUtilities.invokeLater(new Runnable() { 
     public void run() { 
      frame.setTitle("Setting background"); 
     } 
     }); 
     Thread.sleep(1000); 
     return null; 
    } 

    @Override 
    public void done() { 
     ilt.setBackground(Color.BLACK); 
     frame.setTitle("Done!"); 
    } 
    }; 

    public static void main(String args[]) { 
    new Tester(); 
    } 

    public Tester() { 
    //setupGUI 
    SwingUtilities.invokeLater(new Runnable() { 
     public void run() { 
     frame = new JFrame("Empty JFrame"); 
     frame.setSize(new Dimension(1000, 500)); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.setVisible(true); 
     } 
    }); 

    //start the swingworker which loads the images 
    worker.execute(); 
    } 

    //create image names 
    private String[] createImageFileNames(int count){ 
    String[] fileNames = new String[count]; 
    for(int i=0; i < count; i++) 
     fileNames[i] = "Cards" + File.separator + (i+1) + ".bmp"; 
    return fileNames; 
    } 

    //load images 
    private List<Image> loadImages() { 
    List<Image> tmpA = new ArrayList<Image>(); 
    try { 
     for(String name : createImageFileNames(NUMBEROFFILES)){ 
     System.err.println(name); 
     tmpA.add(ImageIO.read(new File(name))); 
     } 
    } catch (IOException e) { } 

    return tmpA; 
    } 
} 
+0

Wciąż robisz rzeczy Swing z EDT! –

+0

Dzięki za wskazanie oczywistych.8 | Zamiast zajmować się mną, ponieważ rozwiązałem faktyczne problemy z plakatu z pytaniami (obrazy + tło ​​nie są wyświetlane), głosujesz na mnie za coś innego, co już wyjaśniłem. Pomyślałem, co najmniej, że odrobina oligofrenu potrafi/zintegrować się z moim rozwiązaniem przez siebie – jitter

+0

Wow, jitter, to dużo pracy dla naprawienia jedynie kodu proof-of-concept! Może trochę przesada, ale i tak wielkie DZIĘKUJĘ. Użyłem twojego kodu, aby naprawić główne problemy w oryginalnym kodzie: 1) nie wywoływanie validate() po operacji add() 2) nie ustawianie preferowanego rozmiaru komponentu. 3) nie wywoływanie super.paintComponent() podczas jej przesłonięcia (to spowodowało, że wywołanie setBackground() nie zadziałało Po wykonaniu tej czynności wszystko działa zgodnie z przeznaczeniem edytując oryginalny wpis, aby to odzwierciedlić. – oligofren

Powiązane problemy