使用 headers 缩放 JScrollPane

Zoom on a JScrollPane with headers

我有一个包含 table 行和列 header 的 JFrame。 我的 table 是由 3 个面板(行 header、列 header 和网格)组成的自定义组件。 这些面板是常规的 JPanel,在 MigLayout 中包含 JButton 或 JLabel。 我在 JScrollPane 中显示此组件,以便同时滚动我的网格和我的 headers。 这部分工作正常。

现在,用户应该可以缩放我的组件了。

我尝试使用 pbjar JXLayer,但如果我将整个 JScrollPane 放入层中,所有内容都会缩放,触发滚动条。

我尝试使用 3 个 JXLayer,一个用于我的 JScrollPane 的每个视口。但是这个解决方案只是弄乱了我的布局,因为视口内的面板居中而不是 top-left 对齐。

import org.jdesktop.jxlayer.JXLayer;
import org.pbjar.jxlayer.demo.TransformUtils;
import org.pbjar.jxlayer.plaf.ext.transform.DefaultTransformModel;

public class Matrix extends JScrollPane {

    private Grid grid;
    private Header rowHeader; 
    private Header columnHeader; 

    private DefaultTransformModel zoomTransformModel;
    private double zoom = 1;

    public Matrix() {
        super(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

        this.zoomTransformModel1 = new DefaultTransformModel();
        this.zoomTransformModel1.setScaleToPreferredSize(true);
        this.zoomTransformModel1.setScale(1);
        this.zoomTransformModel2 = new DefaultTransformModel();
        this.zoomTransformModel2.setScaleToPreferredSize(true);
        this.zoomTransformModel2.setScale(1);
        this.zoomTransformModel3 = new DefaultTransformModel();
        this.zoomTransformModel3.setScaleToPreferredSize(true);
        this.zoomTransformModel3.setScale(1);

        this.grid = new Grid();

        this.setViewportView(TransformUtils.createTransformJXLayer(this.grid,
                zoomTransformModel1););
        this.matrixRowHeader = new Header(Orientation.VERTICAL);
        this.setRowHeader(new JViewport(
                TransformUtils.createTransformJXLayer(
                    this.rowHeader, zoomTransformModel2)));

        this.matrixColumnHeader = new Header(Orientation.HORIZONTAL);
        this.setColumnHeader(new JViewport(
                TransformUtils.createTransformJXLayer(
                    this.columnHeader, zoomTransformModel2)));
    }

    public void setScale(double scale) {
        this.zoomTransformModel1.setScale(scale);
        this.zoomTransformModel2.setScale(scale);
        this.zoomTransformModel3.setScale(scale);
    }
}

我如何处理 JScrollPane 的缩放而不缩放 scrollBars 并且不弄乱我的布局?

首先,MigLayout 似乎与JXLayer 不兼容。当同时使用两者时,使用 MigLayout 的面板中的组件具有不可预测的行为。

然后,原始的 pbjar JXLayer 只允许您将组件放在 Layer 窗格的中心。 可以下载 Pbjar 源 on github。请注意,这不是官方的 Piet Blok 存储库。

我找到的解决方案是修改TransformLayoutTransformUITranformModel 类:

Alignment enum 给出层中组件的可能对齐方式。

public enum Alignment {
    TOP,
    BOTTOM,
    LEFT, 
    RIGHT,
    CENTER
}

TransformLayout中:

@Override
public void layoutContainer(Container parent) {
    JXLayer<?> layer = (JXLayer<?>) parent;
    LayerUI<?> layerUI = layer.getUI();
    if (layerUI instanceof CustomTransformUI) {
        JComponent view = (JComponent) layer.getView();
        JComponent glassPane = layer.getGlassPane();
        if (view != null) {
        Rectangle innerArea = new Rectangle();
        SwingUtilities.calculateInnerArea(layer, innerArea);
        view.setSize(view.getPreferredSize());
        Rectangle viewRect = new Rectangle(0, 0, view.getWidth(), view
            .getHeight());
        int x;
        int y;
        Alignment alignX = ((CustomTransformUI) layerUI).getAlignX();
        Alignment alignY = ((CustomTransformUI) layerUI).getAlignY();
        if(alignX == Alignment.LEFT) {
            x = (int) (innerArea.getX() - viewRect.getX());
        } else if(alignX == Alignment.RIGHT) {
            x = (int) (innerArea.getX()+innerArea.getWidth()-viewRect.getWidth()-viewRect.getX());
        } else {
            x = (int) Math.round(innerArea.getCenterX()
                    - viewRect.getCenterX());
        }
        if(alignY == Alignment.TOP) {
            y = (int) (innerArea.getY() - viewRect.getY());
        } else if(alignY == Alignment.BOTTOM) {
            y = (int) (innerArea.getY()+innerArea.getHeight()-viewRect.getHeight()-viewRect.getY());
        } else {
            y = (int) Math.round(innerArea.getCenterY()
                    - viewRect.getCenterY());
        }
        viewRect.translate(x, y);
        view.setBounds(viewRect);

        }
        if (glassPane != null) {
        glassPane.setLocation(0, 0);
        glassPane.setSize(layer.getWidth(), layer.getHeight());
        }
        return;
    }
    super.layoutContainer(parent);
}

TransformUI中:

private Alignment alignX; // horizontal alignment
private Alignment alignY; // verticalalignment

public TransformUI(TransformModel model, Alignment alignX, Alignment alignY) {
    super();
    this.setModel(model);
    this.alignX = alignX;
    this.alignY = alignY;
}

public Alignment getAlignX() {
    return alignX;
}

public Alignment getAlignY() {
    return alignY;
}

TransformModel中:

private Alignment alignX = Alignment.CENTER;
private Alignment alignY = Alignment.CENTER;

public CustomTransformModel(Alignment alignX, Alignment alignY) {
    super();
    this.alignX = alignX;
    this.alignY = alignY;
}

@Override
public AffineTransform getTransform(JXLayer<? extends JComponent> layer) {
    JComponent view = (JComponent)layer.getView();
    /*
    * Set the current actual program values in addition to the user
    * options.
    */
    this.setValue(Type.LayerWidth, layer == null ? 0 : layer.getWidth());
    this.setValue(Type.LayerHeight, layer == null ? 0 : layer.getHeight());
    this.setValue(Type.ViewWidth, view == null ? 0 : view.getWidth());
    this.setValue(Type.ViewHeight, view == null ? 0 : view.getHeight());
    /*
    * If any change to previous values, recompute the transform.
    */
    if (!Arrays.equals(this.prevValues, this.values)) {
        System.arraycopy(this.values, 0, this.prevValues, 0, this.values.length);
        this.transform.setToIdentity();
        if (view != null) {
            double scaleX;
            double scaleY;
            double centerX;
            if(this.alignX == Alignment.LEFT) {
                centerX = 0.0;
            } else if (this.alignX == Alignment.RIGHT){
                centerX = layer == null ? 0.0 : (double)layer.getWidth();
            } else {
                centerX = layer == null ? 0.0 : (double)layer.getWidth() / 2.0;
            }
            double centerY;
            if(this.alignY == Alignment.TOP) {
                centerY = 0.0;
            } else if(this.alignY == Alignment.BOTTOM){
                centerY = layer == null ? 0.0 : (double)layer.getHeight();
            } else {
                centerY = layer == null ? 0.0 : (double)layer.getHeight() / 2.0;
            }
            AffineTransform nonScaledTransform = this.transformNoScale(centerX, centerY);
            if (((Boolean)this.getValue(Type.ScaleToPreferredSize)).booleanValue()) {
                scaleY = scaleX = ((Double)this.getValue(Type.PreferredScale)).doubleValue();
            } else {
                Area area = new Area(new Rectangle2D.Double(0.0, 0.0, view.getWidth(), view.getHeight()));
                area.transform(nonScaledTransform);
                Rectangle2D bounds = area.getBounds2D();
                scaleX = layer == null ? 0.0 : (double)layer.getWidth() / bounds.getWidth();
                scaleY = layer == null ? 0.0 : (double)layer.getHeight() / bounds.getHeight();
                if (((Boolean)this.getValue(Type.PreserveAspectRatio)).booleanValue()) {
                    scaleY = scaleX = Math.min(scaleX, scaleY);
                }
            }
            this.transform.translate(centerX, centerY);
            this.transform.scale((Boolean)this.getValue(Type.Mirror) != false ? - scaleX : scaleX, scaleY);
            this.transform.translate(- centerX, - centerY);
            this.transform.concatenate(nonScaledTransform);
        }
    }
    return this.transform;
}

您现在可以使用以下方式创建具有可配置对齐方式的可缩放面板:

TransformModel model = new TransformModel(Alignment.LEFT, Alignment.TOP);
TransformUI ui = new TransformUI(model, Alignment.LEFT, Alignment.TOP);
new JXLayer((Component)component, (LayerUI)ui)

请注意,这是一个快速修复。它可能可以改进。