在多显示器环境中将窗口完全放在屏幕上

时间:2013-10-13 17:22:37

标签: java swing

有时我必须显示相对于现有组件的弹出窗口或对话框(主要示例是日期输入控件,旁边有日历按钮)。

多年来它一直很漂亮,但总是有一个错误,即日历可能部分出现在屏幕之外(它被硬编码显示在该字段的右侧)。没有人注意到,因为在窗口的最右边没有日期控件。最近,随着新窗口的增加,情况发生了变化。

那么,我想,让我们把窗户位置(在我将它放在应该的位置之后)固定在屏幕上。我写了一个简单的实用方法来做到这一点:

public static void correctWindowLocationForScreen(Window window) {
    GraphicsConfiguration gc = window.getGraphicsConfiguration();
    Rectangle screenRect = gc.getBounds();
    Rectangle windowRect = window.getBounds();
    Rectangle newRect = new Rectangle(windowRect);
    if (windowRect.x + windowRect.width > screenRect.x + screenRect.width)
        newRect.x = screenRect.x + screenRect.width - windowRect.width;
    if (windowRect.y + windowRect.height > screenRect.y + screenRect.height)
        newRect.y = screenRect.y + screenRect.height - windowRect.height;
    if (windowRect.x < screenRect.x)
        newRect.x = screenRect.x; 
    if (windowRect.y < screenRect.y)
        newRect.y = screenRect.y;
    if (!newRect.equals(windowRect))
        window.setLocation(newRect.x, newRect.y);
}

问题解决了。或不。我使用触发组件的屏幕坐标(显示日历的按钮)定位我的窗口:

JComponent invoker = ... // passed in from the date field (a JButton)
Window owner = SwingUtilities.getWindowAncestor(invoker);
JDialog dialog = new JDialog(owner);
dialog.setLocation(invoker.getLocationOnScreen());
correctWindowLocationForScreen(dialog);
如果“调用者”组件位于跨越两个屏幕的窗口中,

Havoc会爆发。显然“window.getGraphicsConfiguration()”返回窗口左上角恰好所处的图形配置。这不一定是窗口中日期组件所在的屏幕。

那么在这种情况下如何正确定位对话框呢?

2 个答案:

答案 0 :(得分:1)

可以迭代所有设备,找到点所在的监视器。然后保持该矩形。

请参阅GraphicsEnvironment.getScreenDevices

这不会使用当前的Window,但您已经发现一个窗口可能会显示在多个监视器中。

有用可能是Component.getLocationOnScreen

答案 1 :(得分:0)

好的,这就是我最终得到的(处理奇数边缘情况的代码墙)。

correctWindowLocationForScreen()将重新定位一个窗口,如果它在可见的屏幕区域内完全(最简单的情况,它完全在一个屏幕上。硬盘,它跨越多个屏幕)。如果窗口仅按一个像素离开整个屏幕区域,则使用找到的第一个屏幕矩形重新定位。如果窗口不适合屏幕,则它位于左上方并在屏幕上方向右下方延伸(它由positionInsideRectangle()检查/改变坐标的顺序隐含)。

考虑到要求非常简单,它非常复杂。

/**
 * Check that window is completely on screen, if not correct position.
 * Will not ensure the window fits completely onto the screen.
 */
public static void correctWindowLocationForScreen(final Window window) {
    correctComponentLocation(window, getScreenRectangles());
}

/**
 * Set the component location so that it is completely inside the available
 * regions (if possible).
 * Although the method will make some effort to place the component
 * nicely, it may end up partially outside the regions (either because it
 * doesn't fit at all, or the regions are placed badly).
 */
public static void correctComponentLocation(final Component component, final Rectangle ... availableRegions) {
    // check the simple cases (component completely inside one region, no regions available)
    final Rectangle bounds = component.getBounds();
    if (availableRegions == null || availableRegions.length <= 0)
        return;
    final List<Rectangle> intersecting = new ArrayList<>(3);
    for (final Rectangle region : availableRegions) {
        if (region.contains(bounds)) {
            return;
        } else if (region.intersects(bounds)) {
            // partial overlap
            intersecting.add(region);
        }
    }
    switch (intersecting.size()) {
        case 0:
            // position component in the first available region
            positionInsideRectangle(component, availableRegions[0]);
            return;
        case 1:
            // position component in the only intersecting region
            positionInsideRectangle(component, intersecting.get(0));
            return;
        default:
            // uuuh oooh...
            break;
    }
    // build area containing all detected intersections
    // and check if the bounds fall completely into the intersection area
    final Area area = new Area();
    for (final Rectangle region : intersecting) {
        final Rectangle2D r2d = new Rectangle2D.Double(region.x, region.y, region.width, region.height);
        area.add(new Area(r2d));
    }
    final Rectangle2D boundsRect = new Rectangle2D.Double(bounds.x, bounds.y, bounds.width, bounds.height);
    if (area.contains(boundsRect))
        return;
    // bah, just place it in the first intersecting region...
    positionInsideRectangle(component, intersecting.get(0));
}   

/**
 * Position component so that its completely inside the rectangle.
 * If the component is larger than the rectangle, component will
 * exceed to rectangle bounds to the right and bottom, e.g.
 * the component is placed at the rectangles x respectively y.
 */
public static void positionInsideRectangle(final Component component, final Rectangle region) {
    final Rectangle bounds = component.getBounds();
    int x = bounds.x;
    int y = bounds.y;
    if (x + bounds.width > region.x + region.width)
        x = region.x + region.width - bounds.width;
    if (y + bounds.height > region.y + region.height)
        y = region.y + region.height - bounds.height;
    if (region.x < region.x)
        x = region.x; 
    if (y < region.y)
        y = region.y;
    if (x != bounds.x || y != bounds.y)
        component.setLocation(x, y);
}

/**
 * Gets the available display space as an arrays of rectangles
 * (there is one rectangle for each screen, if the environment is
 * headless the resulting array will be empty).
 */
public static Rectangle[] getScreenRectangles() {
    try {
        Rectangle[] result;
        final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        final GraphicsDevice[] devices = ge.getScreenDevices();
        result = new Rectangle[devices.length];
        for (int i=0; i<devices.length; ++i) {
            final GraphicsDevice gd = devices[i];
            result[i] = gd.getDefaultConfiguration().getBounds();
        }
        return result;
    } catch (final Exception e) {
        return new Rectangle[0];
    }
}