我如何支持缩放JScrollPane中显示的视图以避免显示最大尺寸的JScrollBar? [英] How can I support scaling a view displayed in a JScrollPane to avoid displaying JScrollBars up to a minimum size?

查看:108
本文介绍了我如何支持缩放JScrollPane中显示的视图以避免显示最大尺寸的JScrollBar?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我要实现的行为是默认情况下以1:1比例显示视图.如果将其容器做大(由用户动态调整父JFrame的大小),则应缩放视图以适应更大的区域;否则,视图将被缩放.如果缩小,则视图应缩放以适合较小的区域-上限.当小于最小尺寸时,滚动条应带有视口,以支持以最小比例显示的视图中的导航.

The behavior I want to implement is to display a view at 1:1 scale by default. If its container is made larger (by the user dynamically resizing the parent JFrame) the view should be scaled to fit the larger area; if made smaller, the view should be scaled to fit the smaller area - up to a limit. When smaller than a minimum size, scroll bars should appear with a viewport to support navigating around the view which is displayed at its minimum scale.

我现在使用的是JScrollPane和ComponentListener来确定何时发生调整大小,但现在实现的功能却很差.基于新的大小,设置新的比例因子以绘制适合的视图(最大比例),设置视图的preferredSize并调用revalidate().问题在于,这会导致显示抖动";当调整大小超过应用新比例的点以避免显示滚动条时,滚动条出现,然后消失.我相信这是因为我的方法是被动的,不是预测性的.也就是说,LayoutManager仅查看视图的preferredSize并基于该视图执行布局.我正在监听JScrollPane的调整大小,当调整大小过小时,仅更改视图的preferredSize,然后通过调用revalidate()再次使容器布局.至少我是这样理解的.

I have a poorly-functioning implementation now, using a JScrollPane and a ComponentListener to determine when a resize has occurred. Based on the new size a new scale factor is set for painting the view to fit (up to the minimum scale), the preferredSize of the view is set and revalidate() is called. The problem is that this results in "jittery" display; when sizing past the points where a new scale is applied to avoid displaying scrollbars, the scrollbars appear, then disappear. I believe this is because my approach is reactive, not predictive. That is, the LayoutManager is only looking at the view's preferredSize and performing layout based on that. I'm listening for the resize of the JScrollPane and when it is too small only then changing the preferredSize of the view and then causing the container to be laid out again with the call to revalidate(). At least that's how I understand it.

我一直在审查JScrollPane,JViewport及其各自的LayoutManagers的源代码.在这一点上,我正在考虑将一个或多个子类化以具有更好的(可预测的,从而导致更平滑的大小调整)实现.这似乎是其他人之前必须实现的行为.是否有另一种方法可以使用现有的Containers/LayoutManagers/method来做这不会产生子类,也不会冒着跨不同LnF或平台的意外行为的风险?

I've been reviewing the source for JScrollPane, JViewport and their respecive LayoutManagers. At this point I"m considering subclassing one or more to have a better (predictive, resulting in smoother sizing) implementation. This seems like behavior that others must have implemented before. Is there another way to use existing Containers/LayoutManagers/methods to do this without subclassing and risking unintended behavior across different LnF or platforms?

我已经破解了ScrollPaneLayout的原型子类.在添加滚动条之前,它将检查视图的最小大小.这是可行的(因为直到视口小于视图的最小大小时才会显示滚动条,而不是在视口小于视图的首选大小时显示滚动条的原始行为),但是当显示滚动条时,视图会认为仍然是首选大小,而不是最小大小.我可能也必须要攻克ViewportLayout类,但这正在迅速发展,我怀疑它会很健壮.

I've hacked a prototype subclass of ScrollPaneLayout. It checks for the view's minimum size before adding scrollbars. This is working (in that the scrollbars do not appear until the viewport is smaller than the view's minimum size, instead of the original behavior which displayed scrollbars when the viewport was smaller than the view's preferred size) but when the scrollbars are displayed the view thinks it is still the preferred size, not the minimum size. I may have to hack at the ViewportLayout class as well, but this is quickly becoming something that I doubt will be robust.

我回到基础上,再次尝试了建议,并获得了成功.我试图重载ScrollPaneLayout和ViewportLayout的默认行为,并且它们之间的交互不是正确的方向.第一次尝试后,我确信没有办法避免在LayoutManagers完成工作后,我的反应性"方法来解决错误的大小调整所带来的闪烁和不稳定性.幸运的是,有一种方法可以在不继承LayoutManager的情况下进行工作-如前所述,这是通过实现Scrollable接口(我之前已经做过正确的操作,但是还没有进行其他更改来使其正常工作).诀窍的确是实现getScrollableTracksViewport(),以使其根据视口大小返回true/false.除了我正在做的其他计算之外,我还没有做的就是更新视图的preferredSize.这是关键的一步. (请注意,我还依靠侦听ComponentListener.componentResized()通知来触发正确设置返回值所需的计算).

I went back to basics and tried the suggestions again, and have been succcessful. My attempts to overload the default behavior of ScrollPaneLayout and ViewportLayout, and the interactions between them was not the right direction. After my first attempt I was convinced that there was no way to avoid the flickering and instability of my "reactive" approach to fixing incorrect sizing after the LayoutManagers had done their thing. Fortunately, there is a way to make this work without subclassing either LayoutManager - and as stated, it's by implementing the Scrollable interface (which I had done correctly before, but had not made other changes to make it work). The trick is indeed to implement getScrollableTracksViewport() such that it returns true/false depending on the viewport size. What I hadn't done was update the view's preferredSize in addition to the other calculuations I was doing. This was a critical step. (Note that I'm also relying on listening to ComponentListener.componentResized() notifications to trigger the calculations needed to properly set the return values) The functioning code follows, thatnks for the help.

@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

推荐答案

基本方法是让您的自定义组件实现Scrollable并根据需要对getTracksViewportWidth/-Height进行编码.

The basic approach is to let your custom component implement Scrollable and code the getTracksViewportWidth/-Height as needed.

修改

实现它们返回true(从而禁用滚动条),直到我达到最小比例,然后返回false?

implement them to return true (thus disabling scrollbars) until I reach my minimum scale, then return false?

确实是这个主意-但并没有达到我的预期:在到达最小点的转折点",即使使用自我调整的getPrefScrollable(不会似乎有效果,一开始就被调用了

exactly, that was the idea - but didn't work out as I expected: at the "turning point" when reaching the min the image is scaled to its preferred, even with an self-adjusting getPrefScrollable (which doesn't seem to have any effect, it's called once at the start)

编辑2

在OP的帮助下:

更新视图的preferredSize

update the view's preferredSize

最终得到它:(我的初次尝试是将其倒置;-)将prefScrollable保持为真实" pref,并根据滚动条是否可见,让pref返回最小值或pref.

finally got it: (my initial attempt had it upside down ;-) keep the prefScrollable to the "real" pref and let pref return either minimum or pref, depending on whether or not the scrollbar should be visible.

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;
    }

}

这篇关于我如何支持缩放JScrollPane中显示的视图以避免显示最大尺寸的JScrollBar?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆