JScrollPaneの可視領域のみ描画したい
現在ペイント用のコンポーネントを作成しているのですが、
スクロールを高速にするために現在見えている部分だけを描画するようにしたいと思っています。
クリップ領域や JViewport#getViewRect などを使用して
描画する範囲を計算しているのですが、スクロール時に端が見切れてしまいます
これを修正する方法はありますか?
スクロールした状態:
(プログラムの方でこの縦線を描画しているわけではありません)
下記が現在のソースです
package gridtest;
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import javax.swing.Box;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
/**
* グリッドのスクロールをテストします.
*
* @author twagniws
*/
public class GridTest {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
JFrame window = new JFrame();
//テーブルの作成
JCheckBox fastPaintCheckBox = new JCheckBox("Fast Paint", true);
GridView grid = new GridView(1024, 1024);
Box checkHBox = Box.createHorizontalBox();
{
checkHBox.add(Box.createHorizontalGlue());
checkHBox.add(fastPaintCheckBox);
}
Box rootVBox = Box.createVerticalBox();
{
rootVBox.add(checkHBox);
}
//イベントの作成
fastPaintCheckBox.addActionListener((e) -> grid.setFastPaint(fastPaintCheckBox.isSelected()));
//ウィンドウ表示
window.add(rootVBox, BorderLayout.NORTH);
window.add(new JScrollPane(grid), BorderLayout.CENTER);
window.setSize(256, 256);
window.setLocationRelativeTo(null);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
SwingUtilities.invokeLater(() -> {
window.setVisible(true);
});
}
/**
* グリッドを描画するコンポーネント.
*/
public static class GridView extends JPanel implements Scrollable {
private int rowCount;
private int columnCount;
private int cellWidth;
private int cellHeight;
private boolean fastPaint;
private Color[][] data;
public GridView(int rowCount, int columnCount) {
this.rowCount = rowCount;
this.columnCount = columnCount;
setCellWidth(8);
setCellHeight(8);
setFastPaint(true);
this.data = new Color[rowCount][columnCount];
//真っ黒に埋める
for (int i = 0; i < rowCount; i++) {
Arrays.fill(data[i], 0, columnCount, Color.BLACK);
}
//マウスイベントを受信
enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(
columnCount * getCellWidth(),
rowCount * getCellHeight()
);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int startRow = 0;
int endRow = rowCount;
int startCol = 0;
int endCol = columnCount;
if(isFastPaint()) {
//クリップ領域から計算する場合
//殆どまともに描画されない
/*
Rectangle clipRect = g.getClipBounds();
if (clipRect != null) {
startRow = clipRect.x / getCellWidth();
endRow = startRow + (clipRect.width / getCellWidth());
startCol = clipRect.y / getCellHeight();
endRow = startCol + (clipRect.height / getCellHeight());
}
//*/
//スクロール領域から計算する場合
//端が切れる
//*
Container parent = getParent();
if (parent instanceof JViewport) {
JViewport port = (JViewport) parent;
Rectangle visibleRect = port.getViewRect();
//セルの描画範囲を計算
startRow = visibleRect.y / getCellHeight();
endRow = startRow + (visibleRect.height / getCellHeight());
startCol = visibleRect.x / getCellWidth();
endCol = startCol + (visibleRect.width / getCellWidth());
}
//*/
}
//どちらも使用しない場合、
//描画は問題ないが、
//クリックでの描画やスクロールが重い
//一応修正
startRow = clampRow(startRow);
endRow = clampRow(endRow);
startCol = clampCol(startCol);
endCol = clampCol(endCol);
//描画
Graphics2D g2 = (Graphics2D) g;
Color saveColor = g2.getColor();
Rectangle cellRect = new Rectangle(0, 0, getCellWidth(), getCellHeight());
for (int i = startRow; i < endRow; i++) {
cellRect.y = i * getCellHeight();
for (int j = startCol; j < endCol; j++) {
cellRect.x = j * getCellWidth();
Color dataAt = data[i][j];
g2.setColor(dataAt);
g2.fill(cellRect);
}
}
g2.setColor(saveColor);
}
@Override
protected void processMouseEvent(MouseEvent e) {
super.processMouseEvent(e);
paintCell(e);
}
@Override
protected void processMouseMotionEvent(MouseEvent e) {
super.processMouseMotionEvent(e);
paintCell(e);
}
private void paintCell(MouseEvent e) {
if (e.getID() != MouseEvent.MOUSE_PRESSED
&& e.getID() != MouseEvent.MOUSE_DRAGGED) {
return;
}
Point modelPoint = viewToModel(e.getPoint());
//左クリック=黄色 右クリック=白
Color color = Color.YELLOW;
if (SwingUtilities.isRightMouseButton(e)) {
color = Color.WHITE;
}
data[modelPoint.y][modelPoint.x] = color;
//Rectangle rect = new Rectangle();
//rect.setLocation(modelPoint);
//rect.width = getCellWidth();
//rect.height = getCellHeight();
//repaint(rect);
repaint();
}
private Point viewToModel(Point pt) {
Point ret = new Point();
ret.x = pt.x / getCellWidth();
ret.y = pt.y / getCellHeight();
ret.x = Math.min(columnCount - 1, Math.max(0, ret.x));
ret.y = Math.min(rowCount - 1, Math.max(0, ret.y));
return ret;
}
private int clampRow(int row) {
return Math.min(rowCount - 1, Math.max(0, row));
}
private int clampCol(int col) {
return Math.min(columnCount - 1, Math.max(0, col));
}
//
//プロパティ
//
public void setCellWidth(int cellWidth) {
this.cellWidth = cellWidth;
repaint();
revalidate();
}
public int getCellWidth() {
return cellWidth;
}
public void setCellHeight(int cellHeight) {
this.cellHeight = cellHeight;
repaint();
revalidate();
}
public int getCellHeight() {
return cellHeight;
}
public void setFastPaint(boolean fastPaint) {
this.fastPaint = fastPaint;
}
public boolean isFastPaint() {
return fastPaint;
}
//
//Scrollableの実装
//
@Override
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
@Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
switch (orientation) {
case SwingConstants.VERTICAL:
return getCellHeight();
case SwingConstants.HORIZONTAL:
return getCellWidth();
default:
throw new IllegalArgumentException();
}
}
@Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
switch (orientation) {
case SwingConstants.VERTICAL:
return visibleRect.height;
case SwingConstants.HORIZONTAL:
return visibleRect.width;
default:
throw new IllegalArgumentException();
}
}
@Override
public boolean getScrollableTracksViewportWidth() {
Container parent = SwingUtilities.getUnwrappedParent(this);
if (parent instanceof JViewport) {
return parent.getWidth() > (getCellWidth() * columnCount);
}
return false;
}
@Override
public boolean getScrollableTracksViewportHeight() {
Container parent = SwingUtilities.getUnwrappedParent(this);
if (parent instanceof JViewport) {
return parent.getHeight() > (getCellHeight() * rowCount);
}
return false;
}
}
}