3

実装したい動作は、デフォルトで 1:1 の縮尺でビューを表示することです。コンテナーが (ユーザーが親 JFrame を動的にサイズ変更することによって) 大きくなる場合、ビューはより大きな領域に収まるようにスケーリングする必要があります。小さくする場合は、小さい領域に合わせてビューを拡大縮小する必要があります (上限まで)。最小サイズよりも小さい場合、スクロール バーはビューポートと共に表示され、最小縮尺で表示されるビュー内の移動をサポートする必要があります。

サイズ変更がいつ発生したかを判断するために JScrollPane と ComponentListener を使用して、機能が不十分な実装が現在あります。新しいサイズに基づいて、ビューが収まるように描画するための新しいスケール係数が設定され (最小スケールまで)、ビューの preferredSize が設定され、revalidate() が呼び出されます。問題は、これにより「ジッター」表示が発生することです。スクロールバーの表示を避けるために新しいスケールが適用されるポイントを超えてサイズ変更すると、スクロールバーが表示されてから消えます。これは、私のアプローチが予測的ではなく事後対応的であるためだと思います。つまり、LayoutManager はビューの preferredSize のみを見て、それに基づいてレイアウトを実行します。私' m JScrollPane のサイズ変更をリッスンし、サイズが小さすぎる場合にのみ、ビューの preferredSize を変更し、revalidate() の呼び出しでコンテナーを再度レイアウトします。少なくとも私はそう理解しています。

JScrollPane、JViewport、およびそれぞれの LayoutManager のソースを確認してきました。この時点で、私は 1 つ以上をサブクラス化して、より良い (予測的で、よりスムーズなサイジングを実現する) 実装を検討しています。これは、他の人が以前に実装したに違いない動作のようです。既存の Containers/LayoutManagers/methods を使用して行う別の方法はありますか?異なる LnF またはプラットフォーム間で意図しない動作をサブクラス化して危険にさらすことなく、これを行うことはできますか?

編集: ScrollPaneLayout のプロトタイプ サブクラスをハッキングしました。スクロールバーを追加する前に、ビューの最小サイズをチェックします。これは機能しています (ビューポートがビューの優先サイズよりも小さい場合にスクロールバーを表示する元の動作ではなく、ビューポートがビューの最小サイズよりも小さくなるまでスクロールバーが表示されないという点で) が、スクロールバーが表示されると、ビューはこれは最小サイズではなく推奨サイズです。ViewportLayout クラスもハックする必要があるかもしれませんが、これはすぐに堅牢になるとは思えません。

編集:

私は基本に戻って提案を再試行し、成功しました。ScrollPaneLayout と ViewportLayout のデフォルトの動作をオーバーロードしようとした私の試みと、それらの間の相互作用は正しい方向ではありませんでした。最初の試行の後、LayoutManagers が処理を行った後に不適切なサイズ設定を修正するための「リアクティブ」アプローチのちらつきと不安定性を回避する方法はないと確信しました。幸いなことに、どちらの LayoutManager もサブクラス化せずにこれを機能させる方法があります。Scrollableインターフェイス(以前は正しく行っていましたが、動作させるために他の変更を加えていませんでした)。実際には、ビューポートのサイズに応じて true/false を返すように getScrollableTracksViewport() を実装するのがコツです。私が行っていなかったのは、私が行っていた他の計算に加えて、ビューの preferredSize を更新することでした。これは重要なステップでした。(戻り値を適切に設定するために必要な計算をトリガーするために、ComponentListener.componentResized() 通知をリッスンすることにも依存していることに注意してください) 機能するコードは次のとおりです。

@SuppressWarnings("serial")
class ScalingScreenPanel extends JScrollPane {

private static final Dimension              PREFERRED_SIZE = new Dimension(800,200);

private static ScalingScreen                sScreen;

public ScalingScreenPanel() {
    setPreferredSize(PREFERRED_SIZE);
    getViewport().addComponentListener(new ComponentAdapter() {
        public void componentResized(ComponentEvent event) {
            sScreen.calculateSizes(event.getComponent().getSize());
        }
    });     
    setViewportView(sScreen=new ScalingScreen());       
}
public void setShow(Show pShow) {
    sScreen.setShow(pShow);
}
} // class PreviewScreenPanel

@SuppressWarnings("serial")
public abstract class ScalingScreen extends JPanel implements Scrollable {

private static final Dimension              PREFERRED_SIZE = new Dimension(1000,100);
private static final JLabel                 EMPTY_PANEL = new JLabel("Empty",SwingConstants.CENTER);

private static final int                    DEFAULT_SIZE = 7;
private static final int                    MINIMUM_SIZE = 5;
private static final int                    SPACING = 3;
private static final int                    MINIMUM_PITCH = MINIMUM_SIZE+SPACING; // Do not modify directly
private static final int                    DEFAULT_PITCH = DEFAULT_SIZE+SPACING; // Do not modify directly

protected int                               cSize;
protected int                               cPitch;
protected Dimension                         cOrigin;
private Dimension                           cMinimumScreenSize;
private Dimension                           cDefaultScreenSize;

protected Dimension                         cCurrentScreenSize;
private boolean                             cEnableVerticalScrollbar = false, cEnableHorizontalScrollbar = false;

protected Dimension                         cGridSize = null;

ScalingScreen() {
    cOrigin = new Dimension(0,0);
    add(EMPTY_PANEL);
}
public void setShow(Show pShow) {
    remove(EMPTY_PANEL);
    cGridSize = new Dimension(pShow.dimension());
    cMinimumScreenSize = new Dimension(cGridSize.width*MINIMUM_PITCH+SPACING,cGridSize.height*MINIMUM_PITCH+SPACING);
    cDefaultScreenSize = new Dimension(cGridSize.width*DEFAULT_PITCH+SPACING,cGridSize.height*DEFAULT_PITCH+SPACING);
    setMinimumSize(cMinimumScreenSize);
    setPreferredSize(cDefaultScreenSize);
    calculateSizes(getSize());
                repaint();
}
public void calculateSizes(Dimension pViewportSize) {
    if (cGridSize==null) return;
    cPitch = Math.max(MINIMUM_PITCH,Math.min((pViewportSize.width-SPACING)/cGridSize.width,(pViewportSize.height-SPACING)/cGridSize.height));
    cSize = cPitch - SPACING;
    cOrigin = new Dimension((pViewportSize.width-(cPitch*cGridSize.width))/2,(pViewportSize.height-(cPitch*cGridSize.height))/2);
    cCurrentScreenSize = new Dimension(Math.max(pViewportSize.width,cMinimumScreenSize.width),Math.max(pViewportSize.height,cMinimumScreenSize.height));
    Dimension preferredSize = new Dimension();
    if (pViewportSize.width<cMinimumScreenSize.width) {
        cOrigin.width = 0;
        cEnableHorizontalScrollbar = true;
        preferredSize.width = cMinimumScreenSize.width;
    } else {
        cOrigin.width = (pViewportSize.width-(cPitch*cGridSize.width))/2;
        cEnableHorizontalScrollbar = false;
        preferredSize.width = cDefaultScreenSize.width;         
    }
    if (pViewportSize.height<cMinimumScreenSize.height) {
        cOrigin.height = 0;
        cEnableVerticalScrollbar = true;
        preferredSize.height = cMinimumScreenSize.height;
    } else {
        cOrigin.height = (pViewportSize.height-(cPitch*cGridSize.height))/2;
        cEnableVerticalScrollbar = false;
        preferredSize.height = cDefaultScreenSize.height;
    }
    setPreferredSize(preferredSize);
                repaint();
}

// Methods to implement abstract Scrollable interface
@Override
public Dimension getPreferredScrollableViewportSize() {
    return getPreferredSize();
}
@Override
public boolean getScrollableTracksViewportHeight() {
    return !cEnableVerticalScrollbar;
}
@Override
public boolean getScrollableTracksViewportWidth() {
    return !cEnableHorizontalScrollbar;
}
@Override
public int getScrollableBlockIncrement(Rectangle pVisibleRect, int pOrientation, int pDirection) {
    switch (pOrientation) {
    case SwingConstants.VERTICAL:
        return pVisibleRect.height/2;
    case SwingConstants.HORIZONTAL:
        return pVisibleRect.width/2;
    default:
        return 0;
    }       
}
@Override
public int getScrollableUnitIncrement(Rectangle pVisibleRect,
        int pOrientation, int pDirection) {
    switch (pOrientation) {
    case SwingConstants.VERTICAL:
        return 1;
    case SwingConstants.HORIZONTAL:
        return 1;
    default:
        return 0;
    }
}
      @Override
      public void paintComponent(Graphcs g) {
          // custom drawing stuff
      }
} // class ScalingScreen
4

1 に答える 1

3

基本的なアプローチは、カスタム コンポーネントに Scrollable を実装させ、必要に応じて getTracksViewportWidth/-Height をコーディングすることです。

編集

最小スケールに達するまでtrueを返すように実装し(スクロールバーを無効にする)、その後falseを返しますか?

まさに、それがアイデアでしたが、期待どおりに機能しませんでした。最小値に到達したときの「ターニングポイント」で、自己調整 getPrefScrollable を使用しても、画像は好みに合わせてスケーリングされます (これは持っていないようです)。任意の効果、最初に一度呼び出されます)

編集 2

OPの助けを借りて:

ビューのpreferredSizeを更新します

最終的にそれを手に入れました:(私の最初の試みはそれを逆さまにしました;-) prefScrollableを「実際の」設定に保ち、スクロールバーが表示されるかどうかに応じて、prefが最小値またはprefを返すようにします。

public static class JImagePanel extends JPanel implements Scrollable {

    private BufferedImage image;

    public JImagePanel(BufferedImage image) {
        this.image = image;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        BufferedImage scaled = //new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
                GraphicsUtilities.createCompatibleImage(getWidth(), getHeight());
        Graphics2D g2 = scaled.createGraphics();
        g2.drawImage(image, 0, 0, getWidth(), getHeight(), null);
        g.drawImage(scaled, 0, 0, this);
        g2.dispose();
    }

    /**
     * This method is used for laying out this container
     * inside the Viewport: let it return the "real" pref
     * or min, depending on whether or not the scrollbars
     * are showing.  
     */
    @Override
    public Dimension getPreferredSize() {
        Dimension size = getImageSize();
        if (!getScrollableTracksViewportWidth()) {
            size.width = getMinimumSize().width;
        }
        if (!getScrollableTracksViewportHeight()) {
            size.height = getMinimumSize().height;
        }
        return size;
    }

    @Override
    public Dimension getMinimumSize() {
        Dimension min = getImageSize();
        min.height /= 2;
        min.width /= 2;
        return min;
    }

    /**
     * This is used for laying out the scrollPane. Keep 
     * it fixed to "real" pref size.
     */
    @Override
    public Dimension getPreferredScrollableViewportSize() {
        return getImageSize();
    }

    /**
     * The unscaled image size (aka: "real" pref)
     */
    protected Dimension getImageSize() {
        return new Dimension(image.getWidth(), image.getHeight());
    }

    @Override
    public boolean getScrollableTracksViewportWidth() {
        return getParent() instanceof JViewport
                && getParent().getWidth() >= getMinimumSize().width;
    }

    @Override
    public boolean getScrollableTracksViewportWidth() {
        return getParent() instanceof JViewport
                && getParent().getWidth() >= getMinimumSize().width;
    }

    @Override
    public boolean getScrollableTracksViewportHeight() {
        return getParent() instanceof JViewport
              && getParent().getHeight() >= getMinimumSize().height;        }


    @Override
    public int getScrollableUnitIncrement(Rectangle visibleRect,
            int orientation, int direction) {
        return 10;
    }

    @Override
    public int getScrollableBlockIncrement(Rectangle visibleRect,
            int orientation, int direction) {
        return 100;
    }

}
于 2012-10-07T15:09:18.637 に答える