我的组件比屏幕大,其中一部分没有显示出来(我会使用滚动条)。
在paintComponent(g)
中收到呼叫时,如何知道应该绘制哪个区域?
我不确定这是否是您的意思,但问题在于每次在JPanel
的paintComponent(Graphics g)
中接到电话时,您将不得不在JScrollPane
上调用repaint()
,否则JPanel
上的更新将无法在JScrollPane
中显示。
此外,我看到您想使用JScrollBar
(或者可能您混淆了术语)?我建议使用JScrollPane
我制作了一个小例子,其中包含一个网格的JPanel
,每2秒钟更改一次颜色(从红色到黑色,反之亦然)。JPanel
/Grid比JScrollPane
大;无论如何,我们都必须在JScrollPane
实例上调用repaint()
,否则网格颜色不会改变:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) throws Exception {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Test().createAndShowUI();
}
});
}
private void createAndShowUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initComponents(frame);
frame.setPreferredSize(new Dimension(400, 400));
frame.pack();
frame.setVisible(true);
}
private void initComponents(JFrame frame) {
JScrollPane jsp = new JScrollPane();
jsp.setViewportView(new Panel(800, 800, jsp));
frame.getContentPane().add(jsp);
}
}
class Panel extends JPanel {
private int across, down;
private Panel.Tile[][] tiles;
private Color color = Color.black;
private final JScrollPane jScrollPane;
public Panel(int width, int height, JScrollPane jScrollPane) {
this.setPreferredSize(new Dimension(width, height));
this.jScrollPane = jScrollPane;
createTiles();
changePanelColorTimer();//just something to do to check if its repaints fine
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < across; i++) {
for (int j = 0; j < down; j++) {
g.setColor(color);
for (int k = 0; k < 5; k++) {
g.drawRect(tiles[i][j].x + k, tiles[i][j].y + k, tiles[i][j].side - k * 2, tiles[i][j].side - 2 * k);
}
}
}
updateScrollPane();//refresh the pane after every paint
}
//calls repaint on the scrollPane instance
private void updateScrollPane() {
jScrollPane.repaint();
}
private void createTiles() {
across = 13;
down = 9;
tiles = new Panel.Tile[across][down];
for (int i = 0; i < across; i++) {
for (int j = 0; j < down; j++) {
tiles[i][j] = new Panel.Tile((i * 50), (j * 50), 50);
}
}
}
//change the color of the grid lines from black to red and vice versa every 2s
private void changePanelColorTimer() {
Timer timer = new Timer(2000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (color == Color.black) {
color = Color.red;
} else {
color = Color.black;
}
}
});
timer.setInitialDelay(2000);
timer.start();
}
private class Tile {
int x, y, side;
public Tile(int inX, int inY, int inSide) {
x = inX;
y = inY;
side = inSide;
}
}
}
Panel
类中,如果我们在paintComponent(Graphics g)
中注释掉updateScrollPane();
这一行,那么我们就看不到网格改变颜色了。Graphics
对象的剪辑边界来找到实际需要绘制的区域。null
剪辑。然而,这显然从未发生过(其他Swing类也依赖于剪辑永远不是null
!)。它包含一个大小为800x800的带有滚动条的JPanel
。面板绘制了一组矩形,并打印出已绘制的矩形数量。
可以使用“使用剪辑边界”复选框启用和禁用剪辑。当使用剪辑时,仅重新绘制可见区域的面板,矩形的数量要少得多。(请注意,这里判断是否必须绘制矩形的测试相当简单:它只执行矩形与可见区域的交集测试。对于实际应用程序,将直接使用剪辑边界找出必须绘制哪些矩形)。
此示例还显示了滚动窗格的一些棘手内部情况:当关闭闪烁并移动滚动条时,可以看到尽管整个可见区域发生了变化,但实际上只需重新绘制很小的区域(即由于滚动而变为可见的区域)。另一部分只是通过拷贝前面的内容来移动它。可以使用JViewport.html#setScrollMode修改此行为。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class PaintRegionTest
{
public static void main(String[] args) throws Exception
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final PaintRegionPanel paintRegionPanel = new PaintRegionPanel();
paintRegionPanel.setPreferredSize(new Dimension(800, 800));
final Timer timer = new Timer(1000, new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
paintRegionPanel.changeColor();
}
});
timer.setInitialDelay(1000);
timer.start();
JScrollPane scrollPane = new JScrollPane(paintRegionPanel);
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
JPanel controlPanel = new JPanel(new FlowLayout());
final JCheckBox blinkCheckbox = new JCheckBox("Blink", true);
blinkCheckbox.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
if (blinkCheckbox.isSelected())
{
timer.start();
}
else
{
timer.stop();
}
}
});
controlPanel.add(blinkCheckbox);
final JCheckBox useClipCheckbox = new JCheckBox("Use clip bounds");
useClipCheckbox.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
paintRegionPanel.setUseClipBounds(
useClipCheckbox.isSelected());
}
});
controlPanel.add(useClipCheckbox);
frame.getContentPane().add(controlPanel, BorderLayout.SOUTH);
frame.setPreferredSize(new Dimension(400, 400));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class PaintRegionPanel extends JPanel
{
private Color color = Color.BLACK;
private boolean useClipBounds = false;
void setUseClipBounds(boolean useClipBounds)
{
this.useClipBounds = useClipBounds;
}
void changeColor()
{
if (color == Color.BLACK)
{
color = Color.RED;
}
else
{
color = Color.BLACK;
}
repaint();
}
@Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(color);
Rectangle clipBounds = g.getClipBounds();
Rectangle ownBounds = new Rectangle(0,0,getWidth(),getHeight());
System.out.println("clipBounds: " + clipBounds);
System.out.println(" ownBounds: " + ownBounds);
Rectangle paintedRegion = null;
if (useClipBounds)
{
System.out.println("Using clipBounds");
paintedRegion = clipBounds;
}
else
{
System.out.println("Using ownBounds");
paintedRegion = ownBounds;
}
int counter = 0;
// This loop performs a a simple test see whether the objects
// have to be painted. In a real application, one would
// probably use the clip information to ONLY create the
// rectangles that actually have to be painted:
for (int x = 0; x < getWidth(); x += 20)
{
for (int y = 0; y < getHeight(); y += 20)
{
Rectangle r = new Rectangle(x + 5, y + 5, 10, 10);
if (r.intersects(paintedRegion))
{
g.fill(r);
counter++;
}
}
}
System.out.println("Painted "+counter+" rectangles ");
}
}
Rectangle
实例,但可能存在更复杂的模式)。但在这些情况下,可能还有更优雅和更简单的解决方案,而不是手动检查剪辑边界。所有的答案都是错误的。所以我决定回答这个问题,尽管这个问题已经两年了。
我认为正确的答案是在paintComponent(Graphics g)
方法中调用g.getClipBounds()
。它将返回控件坐标系中无效并必须重新绘制的区域的矩形。