2012-12-25 38 views
11

Sự hiểu biết của tôi: Không giống như hầu hết các thành phần/hoạt động trong Swing gọi tới JComponent.repaint() là an toàn chỉ mặc dù yêu cầu được làm lại từ một chủ đề khác (tức là không phải từ EDT). Chỉ có EDT. Đoạn mã dưới đây minh họa điều này.JComponent.paintImmediately() hoạt động như thế nào trong Java Swing?

public class PaintingDemo { 

    public static void main(String[] args) { 
     final JFrame frame = new JFrame(); 
     final JPanel p = new MyPanel(); 
     SwingUtilities.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       frame.add(p, BorderLayout.CENTER); 
       frame.setSize(200, 200); 
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
       frame.setVisible(true); 
      } 
     }); 
     new Thread("MyThread") { 
      public void run() { 
       while (true) { 
       // Below statements are important to show the difference 
        p.repaint(); 
        p.paintImmediately(p.getBounds()); 
        try { 
         Thread.sleep(1000); 
        } catch(Exception e) {} 
       } 
      } 
     }.start(); 
    } 

} 

class MyPanel extends JPanel { 
    @Override 
    public void paint(Graphics g) { 
     System.out.println("paint() called in "+ Thread.currentThread().getName()); 
     super.paint(g); 
    } 
} 

Từ đầu ra, biết rằng bức tranh được thực hiện trong EDT khi repaint() được gọi không phân biệt từ chuỗi được gọi - vì vậy không có vấn đề gì. Nhưng, trong trường hợp paintImmediately() - bức tranh xảy ra trong cùng một luồng mà từ đó nó được gọi.

Hãy xem xét một trường hợp mà EDT đang thay đổi trạng thái của một thành phần và một chuỗi khác (mà từ đó paintImmediately() được gọi) đang vẽ cùng một thành phần.

Câu hỏi của tôi: Trong trường hợp paintImmediately(), cách đồng bộ hóa giữa Chủ đề sự kiện (EDT) và các chủ đề khác được xử lý như thế nào?

Trả lời

5

Để hiểu biết của tôi, khi bạn gọi paintImmediately, bạn gọi đoạn mã sau:

 Component c = this; 
     Component parent; 

     if(!isShowing()) { 
      return; 
     } 

     JComponent paintingOigin = SwingUtilities.getPaintingOrigin(this); 
     if (paintingOigin != null) { 
      Rectangle rectangle = SwingUtilities.convertRectangle(
        c, new Rectangle(x, y, w, h), paintingOigin); 
      paintingOigin.paintImmediately(rectangle.x, rectangle.y, rectangle.width, rectangle.height); 
      return; 
     } 

     while(!c.isOpaque()) { 
      parent = c.getParent(); 
      if(parent != null) { 
       x += c.getX(); 
       y += c.getY(); 
       c = parent; 
      } else { 
       break; 
      } 

      if(!(c instanceof JComponent)) { 
       break; 
      } 
    } 
    if(c instanceof JComponent) { 
     ((JComponent)c)._paintImmediately(x,y,w,h); 
    } else { 
     c.repaint(x,y,w,h); 
    } 

Vì vậy, trừ khi đây không phải là một JComponent, bạn kết thúc gọi _paintImmediately() mà kết thúc lên gọi paint(Graphics) như gợi ý stack trace bên dưới (chụp từ một đoạn mã tôi sẽ đăng ở phần cuối của bài này):

Thread [pool-1-thread-1] (Suspended)  
    TestPaint$1.paint(Graphics) line: 23  
    TestPaint$1(JComponent).paintToOffscreen(Graphics, int, int, int, int, int, int) line: 5221 
    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 
    TestPaint$1(JComponent)._paintImmediately(int, int, int, int) line: 5169  
    TestPaint$1(JComponent).paintImmediately(int, int, int, int) line: 4980 
    TestPaint$1(JComponent).paintImmediately(Rectangle) line: 4992 
    TestPaint$3.run() line: 50 
    ThreadPoolExecutor.runWorker(ThreadPoolExecutor$Worker) line: 1110 
    ThreadPoolExecutor$Worker.run() line: 603 
    Thread.run() line: 722 

Nhưng nếu bạn cố gắng cũng để gọi repaint() đồng thời (từ chủ đề khác), bạn sẽ thấy rằng cả hai chạy cùng một lúc (tôi đã thử bước vào mã với trình gỡ lỗi và vẽ không bao giờ ngừng xuất hiện trong Chủ đề khác) có vẻ như ở cấp mã Java, không có nhiều đồng bộ hóa (ít nhất tôi không thể phát hiện bất kỳ thứ gì). Vì vậy, nếu bạn kết thúc sửa đổi trạng thái thành phần trong EDT, tôi tin rằng kết quả là khá khó lường và bạn nên tránh tình huống như vậy bằng mọi cách.

Chỉ để minh họa quan điểm của mình, tôi đã thử sửa đổi trạng thái của biến trong phương thức paint, thêm sleep để tăng nguy cơ va chạm giữa 2 chủ đề (EDT và khác) và có vẻ như không có đồng bộ hóa giữa hai Chủ đề (System.err.println() được xuất ra null theo thời gian).

Bây giờ tôi tự hỏi tại sao bạn cần phải thực hiện một paintImmediately. Trừ khi bạn đang chặn các EDT, không có quá nhiều lý do hợp lệ để thực hiện điều đó.

Dưới đây là mã tôi đã sử dụng để kiểm tra những thứ đó (khá gần với mã được đăng trong câu hỏi). Mã này chỉ có nghĩa là để cố gắng hiểu những gì đang xảy ra, không phải để cho thấy làm thế nào để thực hiện bức tranh thích hợp cũng không thực hành Swing tốt.

import java.awt.Color; 
import java.awt.Graphics; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.util.Random; 
import java.util.concurrent.Executors; 

import javax.swing.JFrame; 
import javax.swing.JPanel; 
import javax.swing.SwingUtilities; 
import javax.swing.Timer; 

public class TestPaint { 

    protected void initUI() { 
     JFrame frame = new JFrame(); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.setTitle(TestPaint.class.getSimpleName()); 
     final Random rand = new Random(); 
     final JPanel comp = new JPanel() { 
      private String value; 

      @Override 
      public void paint(Graphics g) { 
       value = "hello"; 
       super.paint(g); 
       try { 
        Thread.sleep(20); 
       } catch (InterruptedException e) { 
        // TODO Auto-generated catch block 
        e.printStackTrace(); 
       } 
       g.setColor(new Color(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256))); 
       g.fillRect(0, 0, getWidth(), getHeight()); 
       if (SwingUtilities.isEventDispatchThread()) { 
        System.err.println("Painting in the EDT " + getValue()); 
       } else { 
        System.err.println("Not painting in EDT " + getValue()); 
       } 
       value = null; 
      } 

      public String getValue() { 
       return value; 
      } 
     }; 
     frame.add(comp); 
     frame.setSize(400, 400); 
     frame.setLocationRelativeTo(null); 
     frame.setVisible(true); 
     Timer t = new Timer(1, new ActionListener() { 

      @Override 
      public void actionPerformed(ActionEvent e) { 
       comp.repaint(); 
      } 
     }); 
     t.start(); 
     Executors.newSingleThreadExecutor().execute(new Runnable() { 

      @Override 
      public void run() { 
       while (true) { 
        comp.paintImmediately(comp.getBounds()); 
       } 
      } 
     }); 
    } 

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

} 
+0

+1 bạn đã đóng đinh nó. Tôi chỉ tự hỏi tại sao tài liệu 'JComponent' tham chiếu' paintImmediately() 'và' repaint (..) 'khi nguồn là khác nhau. Tôi phải yêu cầu mặc dù trong đoạn của bạn, chúng tôi sẽ không bao giờ được ghi đè 'sơn (..)' của một "JComponent' vậy tại sao bạn không sử dụng' paintComponent' (nó vẫn có vẻ để tái sản xuất kết quả)? Và một câu hỏi khác tại sao việc tăng 'giấc ngủ (int milis) 'không làm tăng va chạm? Và tại sao bạn gọi 'paintImmediately' từ một chủ đề khác với EDT, (bởi vì tôi thấy một lần gọi là EDT no null sẽ xảy ra)? –

+0

Cảm ơn @Guillaume vì đã tự mình thử và chia sẻ nghiên cứu của bạn.Tôi đồng ý rằng không có nhiều lý do để sử dụng paintImmediately() nhưng tôi chỉ cố gắng để hiểu "nó hoạt động như thế nào?" Bởi vì javadoc nói ... "Phương pháp này rất hữu ích nếu người dùng cần cập nhật màn hình trong khi sự kiện hiện tại đang được gửi đi." Ngoài ra, phương pháp này là công khai và hoàn toàn không có cảnh báo được đề cập ở bất cứ nơi nào về sự cẩn trọng của nó; ít nhất javadoc là gây hiểu lầm. Bây giờ tôi hiểu paintImmediately() cần phải được gọi trong vòng EDT và không an toàn chủ đề của nó không giống như repaint(). – Learner

+0

@DavidKroukamp Tuyệt đối không có lý do gì để ghi đè 'paint' thay vì' paintComponent', tôi chỉ nghĩ rằng trong nỗ lực để xem cách 'paintImmediately' cuối cùng gọi' paint', thêm lệnh gọi 'paintComponent' vào cuộc gọi ngăn xếp là không cần thiết và kết quả quan sát sẽ vẫn như cũ. Vì vậy, nó đã được nhiều hơn để chỉ cần loại bỏ bất kỳ "tiếng ồn" từ các thông tin, và như tôi đã nói trước khi đoạn của tôi nó không minh họa cách thích hợp để sử dụng Swing. Chúc mừng ;-) –

Các vấn đề liên quan