Does anyone know how you would get the screen width in java? I read something about some toolkit method but I'm not quite sure what that is.
Thanks,
Andrew
java.awt.Toolkit.getDefaultToolkit().getScreenSize()
Here are the two methods I use, which account for multiple monitors and task-bar insets. If you don't need the two methods separately, you can, of course, avoid getting the graphics config twice.
static public Rectangle getScreenBounds(Window wnd) {
Rectangle sb;
Insets si=getScreenInsets(wnd);
if(wnd==null) {
sb=GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration()
.getBounds();
}
else {
sb=wnd
.getGraphicsConfiguration()
.getBounds();
}
sb.x +=si.left;
sb.y +=si.top;
sb.width -=si.left+si.right;
sb.height-=si.top+si.bottom;
return sb;
}
static public Insets getScreenInsets(Window wnd) {
Insets si;
if(wnd==null) {
si=Toolkit.getDefaultToolkit().getScreenInsets(GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration());
}
else {
si=wnd.getToolkit().getScreenInsets(wnd.getGraphicsConfiguration());
}
return si;
}
The working area is the desktop area of the display, excluding taskbars, docked windows, and docked tool bars.
If what you want is the "working area" of the screen, use this:
public static int GetScreenWorkingWidth() {
return java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().width;
}
public static int GetScreenWorkingHeight() {
return java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().height;
}
Toolkit has a number of classes that would help:
getScreenSize - raw screen size
getScreenInsets - gets size of toolbar, dock
getScreenResolution - dpi
We end up using 1 and 2, to compute usable maximum window size. To get the relevant GraphicsConfiguration, we use
GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[0].getDefaultConfiguration();
but there may be smarter multiple-monitor solutions.
The following code should do it (haven't tried it):
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
gd.getDefaultConfiguration().getBounds().getWidth();
edit:
For multiple monitors you should use the following code (taken from the javadoc of java.awt.GraphicsConfiguration:
Rectangle virtualBounds = new Rectangle();
GraphicsEnvironment ge = GraphicsEnvironment.
getLocalGraphicsEnvironment();
GraphicsDevice[] gs =
ge.getScreenDevices();
for (int j = 0; j < gs.length; j++) {
GraphicsDevice gd = gs[j];
GraphicsConfiguration[] gc =
gd.getConfigurations();
for (int i=0; i < gc.length; i++) {
virtualBounds =
virtualBounds.union(gc[i].getBounds());
}
}
The OP probably wanted something like this:
Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
Toolkit.getDefaultToolkit().getScreenSize().getWidth()
You can get it by using the AWT Toolkit.
Toolkit.getScreenSize().
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
If you need the resolution of the screen that a certain component is currently assigned to (something like most part of the root window is visible on that screen), you can use this answer.
A good way of detecting whether or not something is within visual bounds, is using
Screen.getScreensForRectangle(x, y, width, height).isEmpty();
This is an improvement to the multi-monitor solution posted (above) by Lawrence Dol. As in his solution, this code accounts for multiple monitors and task-bar insets. The included functions are: getScreenInsets(), getScreenWorkingArea(), and getScreenTotalArea().
Changes from the Lawrence Dol version:
This avoids getting the graphics configuration twice.
Added a function for getting the total screen area.
Renamed the variables for clarity.
Added Javadocs.
Code:
/**
* getScreenInsets, This returns the insets of the screen, which are defined by any task bars
* that have been set up by the user. This function accounts for multi-monitor setups. If a
* window is supplied, then the the monitor that contains the window will be used. If a window
* is not supplied, then the primary monitor will be used.
*/
static public Insets getScreenInsets(Window windowOrNull) {
Insets insets;
if (windowOrNull == null) {
insets = Toolkit.getDefaultToolkit().getScreenInsets(GraphicsEnvironment
.getLocalGraphicsEnvironment().getDefaultScreenDevice()
.getDefaultConfiguration());
} else {
insets = windowOrNull.getToolkit().getScreenInsets(
windowOrNull.getGraphicsConfiguration());
}
return insets;
}
/**
* getScreenWorkingArea, This returns the working area of the screen. (The working area excludes
* any task bars.) This function accounts for multi-monitor setups. If a window is supplied,
* then the the monitor that contains the window will be used. If a window is not supplied, then
* the primary monitor will be used.
*/
static public Rectangle getScreenWorkingArea(Window windowOrNull) {
Insets insets;
Rectangle bounds;
if (windowOrNull == null) {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
insets = Toolkit.getDefaultToolkit().getScreenInsets(ge.getDefaultScreenDevice()
.getDefaultConfiguration());
bounds = ge.getDefaultScreenDevice().getDefaultConfiguration().getBounds();
} else {
GraphicsConfiguration gc = windowOrNull.getGraphicsConfiguration();
insets = windowOrNull.getToolkit().getScreenInsets(gc);
bounds = gc.getBounds();
}
bounds.x += insets.left;
bounds.y += insets.top;
bounds.width -= (insets.left + insets.right);
bounds.height -= (insets.top + insets.bottom);
return bounds;
}
/**
* getScreenTotalArea, This returns the total area of the screen. (The total area includes any
* task bars.) This function accounts for multi-monitor setups. If a window is supplied, then
* the the monitor that contains the window will be used. If a window is not supplied, then the
* primary monitor will be used.
*/
static public Rectangle getScreenTotalArea(Window windowOrNull) {
Rectangle bounds;
if (windowOrNull == null) {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
bounds = ge.getDefaultScreenDevice().getDefaultConfiguration().getBounds();
} else {
GraphicsConfiguration gc = windowOrNull.getGraphicsConfiguration();
bounds = gc.getBounds();
}
return bounds;
}
Related
tl;dr
Under Windows 10, if I put my secondary display to the right of the primary one, and apply a scaling (e.g. 150%) to the secondary, then the display coordinates (as returned by the Java API) overlap instead of letting the display bounds sit side by side. In other words, if I slowly move my mouse from the left edge of the primary to the right edge of the secondary, Java's API MouseInfo.getPointerInfo().getLocation() returns an increasing X-position from 0 to 1920, then once the cursor enters the second screen, the value jumps back down to 1280 and then increases again to 2560. So the 1280-1920 range is returned twice, for different areas.
At the end of the post, I have included an (updated) demo that makes the issue obvious. Don't hesitate to try it and report back.
The long version:
This text gives (too) much context but is also meant to share the things I learned while searching on the topic.
First, why bother ? Because I am building a screen capture application in Java that requires a correct handling of multi-display configurations, including displays where Windows' scaling feature is applied.
Using the Java API (GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()), as long as the scaling is 100%, one can observe that the primary display has its top left corner at the origin (0,0), with the other displays having coordinates "next" to the main one.
The following pictures were made using the code at the end of the post.
E.g. if we have 2 full-hd displays, the main one has its top left corner is at (0,0), while...
if the secondary is positioned at its right, at the same level, its top left corner is (1920,0):
if the secondary is positioned at its left, at the same level, its top left corner is (-1920,0):
if the secondary is positioned below, aligned horizontally, its top left corner is (0,1080):
if the secondary is positioned above, aligned horizontally, its top left corner is (0,-1080):
and so on if the displays are not aligned:
or with different resolutions:
However, if the secondary display is scaled, things go awry: it seems the scaling factor is applied not only to its dimensions, but also its origin, which gets closer to (0,0).
If the secondary is on the left, it makes sense. For example, when the secondary 1920x1080 is scaled at 150%, it makes a logical 1280x720 positioned at (-1280,0):
But if the secondary is on the right, the origin is also scaled to (1280,0), getting closer to the origin and causing it to "overlap" the primary one:
In other words, if the mouse is at (1800,0) - see red dot above - I see no way of knowing if it actually is positioned on the right of the first display (at 120px from the right edge) or on the left of the secondary one (at 520px of the left edge). When moving the mouse from the primary to the secondary display in this case, the X position of the mouse "jumps back" when it reaches the border of the primary display.
The same is true for positioning a window on the screens. If I set the X-position of a dialog to 1800, I have no way to know where it will open.
After much browsing, some answers like this one indicate that the only way to query Windows scaling is by using native calls. Indeed, using JNA, one can get the physical size of the displays (although the answer seems to indicate that call should return the logical size). I.e the JNA calls ignore the scaling factor, and behaves exactly like the Java API when scaling is at 100%:
So am I missing something ?
Not knowing the scaling factor is a small issue, but not being able to tell which display the mouse is over, or not being able to position a window on the display I want looks like a real problem to me. Is it a Java Bug ?
Note: Here is the code for the app used above, run on with OpenJDK14 on Windows 10 64b. It shows a scaled down version of your display setup and mouse position as perceived by Java. It can also place and move a small dialog across the real screens if you click and drag inside the small rectangles. Credit: The UI is inspired by the WheresMyMouse code posted here.
As is, the code uses only the Java API.
If you want to compare with JNA, search for the 4 blocks marked "JNA_ONLY", uncomment them, and add the jna libs. The demo will then toggle between JNA and Java API for displaying screen bounds and mouse cursor at each right-click. The dialog positioning never uses JNA in this version.
// JNA_ONLY
//import com.sun.jna.platform.win32.User32;
//import com.sun.jna.platform.win32.WinDef;
//import com.sun.jna.platform.win32.WinUser;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
/**
* Java multi-display detection and analysis.
* UI idea based on WheresMyMouse - https://stackoverflow.com/a/21592711/13551878
*/
public class ShowDisplays {
private static boolean useJna = false;
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new JFrame("Display Configuration");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
public static class TestPane extends JPanel {
private List<Rectangle> screenBounds;
JDialog dlg;
public TestPane() {
screenBounds = getScreenBounds();
// refresh screen details every second to reflect changes in Windows Preferences in "real time"
new Timer(1000, e -> screenBounds = getScreenBounds()).start();
// Refresh mouse position at 25fps
new Timer(40, e -> repaint()).start();
MouseAdapter mouseAdapter = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
useJna = !useJna;
repaint();
}
}
#Override
public void mousePressed(MouseEvent e) {
System.out.println(e.getButton());
if (e.getButton() == MouseEvent.BUTTON1) {
if (!dlg.isVisible()) {
dlg.setVisible(true);
}
moveDialogTo(e.getPoint());
}
}
#Override
public void mouseDragged(MouseEvent e) {
moveDialogTo(e.getPoint());
}
private void moveDialogTo(Point mouseLocation) {
final Rectangle surroundingRectangle = getSurroundingRectangle(screenBounds);
double scaleFactor = Math.min((double) getWidth() / surroundingRectangle.width, (double) getHeight() / surroundingRectangle.height);
int xOffset = (getWidth() - (int) (surroundingRectangle.width * scaleFactor)) / 2;
int yOffset = (getHeight() - (int) (surroundingRectangle.height * scaleFactor)) / 2;
int screenX = surroundingRectangle.x + (int) ((mouseLocation.x - xOffset) / scaleFactor);
int screenY = surroundingRectangle.y + (int) ((mouseLocation.y - yOffset) / scaleFactor);
dlg.setLocation(screenX - dlg.getWidth() / 2, screenY - dlg.getHeight() / 2);
}
};
addMouseListener(mouseAdapter);
addMouseMotionListener(mouseAdapter);
// Prepare the test dialog
dlg = new JDialog();
dlg.setTitle("Here");
dlg.setSize(50, 50);
dlg.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
// Mouse position
Point mousePoint = getMouseLocation();
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
final Rectangle surroundingRectangle = getSurroundingRectangle(screenBounds);
double scaleFactor = Math.min((double) getWidth() / surroundingRectangle.width, (double) getHeight() / surroundingRectangle.height);
int xOffset = (getWidth() - (int) (surroundingRectangle.width * scaleFactor)) / 2;
int yOffset = (getHeight() - (int) (surroundingRectangle.height * scaleFactor)) / 2;
g2d.setColor(Color.BLUE);
g2d.fillRect(xOffset, yOffset, (int) (surroundingRectangle.width * scaleFactor), (int) (surroundingRectangle.height * scaleFactor));
Font defaultFont = g2d.getFont();
for (int screenIndex = 0; screenIndex < screenBounds.size(); screenIndex++) {
Rectangle screen = screenBounds.get(screenIndex);
Rectangle scaledRectangle = new Rectangle(
xOffset + (int) ((screen.x - surroundingRectangle.x) * scaleFactor),
yOffset + (int) ((screen.y - surroundingRectangle.y) * scaleFactor),
(int) (screen.width * scaleFactor),
(int) (screen.height * scaleFactor));
// System.out.println(screen + " x " + scaleFactor + " -> " + scaledRectangle);
g2d.setColor(Color.DARK_GRAY);
g2d.fill(scaledRectangle);
g2d.setColor(Color.GRAY);
g2d.draw(scaledRectangle);
// Screen text details
g2d.setColor(Color.WHITE);
// Display number
final Font largeFont = new Font(defaultFont.getName(), defaultFont.getStyle(), (int) (screen.height * scaleFactor) / 2);
g2d.setFont(largeFont);
String label = String.valueOf(screenIndex + 1);
FontRenderContext frc = g2d.getFontRenderContext();
TextLayout layout = new TextLayout(label, largeFont, frc);
Rectangle2D bounds = layout.getBounds();
g2d.setColor(Color.WHITE);
g2d.drawString(
label,
(int) (scaledRectangle.x + (scaledRectangle.width - bounds.getWidth()) / 2),
(int) (scaledRectangle.y + (scaledRectangle.height + bounds.getHeight()) / 2)
);
// Resolution + corner
final Font smallFont = new Font(defaultFont.getName(), defaultFont.getStyle(), (int) (screen.height * scaleFactor) / 10);
g2d.setFont(smallFont);
// Resolution
String resolution = screen.width + "x" + screen.height;
layout = new TextLayout(resolution, smallFont, frc);
bounds = layout.getBounds();
g2d.drawString(
resolution,
(int) (scaledRectangle.x + (scaledRectangle.width - bounds.getWidth()) / 2),
(int) (scaledRectangle.y + scaledRectangle.height - bounds.getHeight())
);
// Corner
String corner = "(" + screen.x + "," + screen.y + ")";
g2d.drawString(
corner,
scaledRectangle.x,
(int) (scaledRectangle.y + bounds.getHeight() * 1.5)
);
}
g2d.setFont(defaultFont);
FontMetrics fm = g2d.getFontMetrics();
if (mousePoint != null) {
g2d.fillOval(xOffset + (int) ((mousePoint.x - surroundingRectangle.x) * scaleFactor) - 2,
yOffset + (int) ((mousePoint.y - surroundingRectangle.y) * scaleFactor) - 2,
4,
4
);
g2d.drawString("Mouse pointer is at (" + mousePoint.x + "," + mousePoint.y + ")", 4, fm.getHeight());
}
g2d.drawString("Click and drag in this area to move a dialog on the actual screens", 4, fm.getHeight() * 2);
// JNA_ONLY
// g2d.drawString("Now using " + (useJna ? "JNA" : "Java API") + ". Right-click to toggle", 4, fm.getHeight() * 3);
g2d.dispose();
}
}
public static Rectangle getSurroundingRectangle(List<Rectangle> screenRectangles) {
Rectangle surroundingBounds = null;
for (Rectangle screenBound : screenRectangles) {
if (surroundingBounds == null) {
surroundingBounds = new Rectangle(screenRectangles.get(0));
}
else {
surroundingBounds.add(screenBound);
}
}
return surroundingBounds;
}
private static Point getMouseLocation() {
// JNA_ONLY
// if (useJna) {
// final WinDef.POINT point = new WinDef.POINT();
// if (User32.INSTANCE.GetCursorPos(point)) {
// return new Point(point.x, point.y);
// }
// else {
// return null;
// }
// }
return MouseInfo.getPointerInfo().getLocation();
}
public static List<Rectangle> getScreenBounds() {
List<Rectangle> screenBounds;
// JNA_ONLY
// if (useJna) {
// screenBounds = new ArrayList<>();
// // Enumerate all monitors, and call a code block for each of them
// // See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors
// // See http://www.pinvoke.net/default.aspx/user32/EnumDisplayMonitors.html
// User32.INSTANCE.EnumDisplayMonitors(
// null, // => the virtual screen that encompasses all the displays on the desktop.
// null, // => don't clip the region
// (hmonitor, hdc, rect, lparam) -> {
// // For each found monitor, get more information
// // See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfoa
// // See http://www.pinvoke.net/default.aspx/user32/GetMonitorInfo.html
// WinUser.MONITORINFOEX monitorInfoEx = new WinUser.MONITORINFOEX();
// User32.INSTANCE.GetMonitorInfo(hmonitor, monitorInfoEx);
// // Retrieve its coordinates
// final WinDef.RECT rcMonitor = monitorInfoEx.rcMonitor;
// // And convert them to a Java rectangle, to be added to the list of monitors
// screenBounds.add(new Rectangle(rcMonitor.left, rcMonitor.top, rcMonitor.right - rcMonitor.left, rcMonitor.bottom - rcMonitor.top));
// // Then return "true" to continue enumeration
// return 1;
// },
// null // => No additional info to pass as lparam to the callback
// );
// return screenBounds;
// }
GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] screenDevices = graphicsEnvironment.getScreenDevices();
screenBounds = new ArrayList<>(screenDevices.length);
for (GraphicsDevice screenDevice : screenDevices) {
GraphicsConfiguration configuration = screenDevice.getDefaultConfiguration();
screenBounds.add(configuration.getBounds());
}
return screenBounds;
}
}
This looks like you've run into a manifestation of bug JDK-8211999:
In a multi-monitor setting involving one HiDPI screen placed to the right of one regular monitor, on Windows 10, the bounds returned by GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[x].getDefaultConfiguration().getBounds() are overlapping. This causes various secondary bugs...
Comments note that:
The same bug exists on Linux as well, macOS is not affected.
There does not seem to be a simple pure Java workaround.
A fix has been proposed which works for Windows, by not even trying to do the coordinate math in Java, and delegating the solution to native code.
Since it appears that using the JNA (native) implementation appears to work, this seems the best approach for JDK versions 9 to 15. The bug was fixed in JDK16.
According to the bug report, it affects JDK 9+, so it is possible that reverting to JDK 8 may fix the issue, although I saw conflicting accounts on that.
On most desktop environments, you have one ore more action bars and sometimes docked windows.
A maximized window will used all the space that is not used by these different things on the side of the screen. That's what I'm calling the "available space"
Is there a java API to detect the available space, and if possible to listen to the changes that may occur ?
The basic idea is to get a reference to the screen device and subtract the screen insets from the screen bounds as follows
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
Rectangle bounds = new Rectangle(0, 0, 0, 0);
if (gd != null) {
GraphicsConfiguration gc = gd.getDefaultConfiguration();
bounds = gc.getBounds();
Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
bounds.x += insets.left;
bounds.y += insets.top;
bounds.width -= (insets.left + insets.right);
bounds.height -= (insets.top + insets.bottom);
}
Now, your next problem is going to be to determine which screen device you actually want, this example simple uses the default screen ;)
Yes some questions get close :)
There is a Bug in Java ( been around and reported since 2011, seems like there is no effort being made to fix it either - should be handled on the native side of the VM)
That is when you maximize an "undecorated" window, or a window drawn wihth a PLAF look and feel, it will cover the windows taskbar. Fine - desirable when you want it, but when you do want the taskbar maximized windows cover it. setting the "always on top" proerty doesn make any difference.
Yes one can resize a window BUT one has to know where the task bar is, or the size of the screen minus the taskbar - know how to do that?
and one needs to know you are maximizing on a screen without a taskbar if that is being done. and if on a multimonitor virtual desktop ...
Any ideas :)
Yes one can resize a window BUT one has to know where the task bar is,
or the size of the screen minus the taskbar - know how to do that?
Yes:
1.Look up the graphic device you are on (assuming p is a Point of the Screen you are looking for):
GraphicsConfiguration graphicsConfiguration = null;
for (GraphicsDevice gd : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) {
if (gd.getDefaultConfiguration().getBounds().contains(p)) {
graphicsConfiguration = gd.getDefaultConfiguration();
break;
}
}
2.Look at the screen bounds (watch out that some bounds location are negative with multiple screens - for example, if you have a secondary screen which is on the left of your main screen), the screen size and the "Insets" of the screen which are usually the taskbar and/or other graphical artifacts:
Rectangle screenBounds = graphicsConfiguration.getBounds();
Dimension screenSize = screenBounds.getSize();
Insets screenInsets = Toolkit.getDefaultToolkit()
.getScreenInsets(graphicsConfiguration);
Thanks
Here is the code made from above which is calls immediately after the window is maximized by the system. It checks for the taskbars and resizes the window accordinly.
note that the setBounds will "un-maximize" the window as far as Java is concerned so the "getExtendedState()" will return un maximized and I need to maintain my own flag. I also have to cache the last pre-maximized window size so I know where to restore the window to later - all way too messy but it works.
Rectangle bounds;
Rectangle fbounds = frame.getBounds();
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
// as system maximized this at this point we test the center of the window
// as it should be on the proper screen.
Point p = new Point(fbounds.x + (fbounds.width/2),fbounds.y + (fbounds.height/2));
GraphicsConfiguration graphicsConfiguration = null;
for (GraphicsDevice gd : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices())
{
if (gd.getDefaultConfiguration().getBounds().contains(p)) {
graphicsConfiguration = gd.getDefaultConfiguration();
break;
}
}
if(graphicsConfiguration != null)
{
bounds = graphicsConfiguration.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(graphicsConfiguration);
bounds.x += screenInsets.left;
bounds.y += screenInsets.top;
bounds.height -= screenInsets.bottom;
bounds.width -= screenInsets.right;
} else {
bounds = env.getMaximumWindowBounds();
}
if(fbounds.equals(bounds)) {
bounds.height -= 1;
}
frame.setBounds(bounds);
sometime ago I read this article that shows a way to implement mouse resizable components in Swing.
The author uses a null LayoutManager in order to allow absolute component positioning.
I know that a null layout should never be used, so my question is:
is there any already implemented LayoutManager that allow component's absolute positioning, or I have to implement it my own?
As alternatives, also consider
How to Use Internal Frames.
Resizing Components in conjunction with Moving Windows.
An exisiting framework, such as JGraph or JUNG.
A layout manager really does 3 things:
Set the location of a component. Since you need the ability to drag the component around, you would not want your layout manager to do this.
Set the size of a component. Since you need the ability to resize the component then you would not want to do this. However, you might want to give the component a default size based on the components preferred size. This way you don't need to specify the size when you create the component.
Determine the preferred size of the parent panel based on the components added to it. This will allow scroll panes to function properly as scrollbars can be added/removed as required. So you need to determine the behaviour of how dragging should work. That is, are you allowed to drag the component outside the current bounds of the panel. If so the the preferred size of the panel should automatically increase.
is there any already implemented LayoutManager that allow component's absolute positioning
I've been playing around with a layout manager that is close to your needs. It was designed to be used with the ComponentMover class from the Moving Windows link provided by trashgod.
Here is my test code for this class:
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
/**
*/
public class DragLayout implements LayoutManager, java.io.Serializable
{
public DragLayout()
{
}
/**
* Adds the specified component with the specified name to the layout.
* #param name the name of the component
* #param comp the component to be added
*/
#Override
public void addLayoutComponent(String name, Component comp) {}
/**
* Removes the specified component from the layout.
*
* #param comp the component to be removed
*/
#Override
public void removeLayoutComponent(Component component)
{
}
/**
* Determine the minimum size on the Container
*
* #param target the container in which to do the layout
* #return the minimum dimensions needed to lay out the
* subcomponents of the specified container
*/
#Override
public Dimension minimumLayoutSize(Container parent)
{
synchronized (parent.getTreeLock())
{
return preferredLayoutSize(parent);
}
}
/**
* Determine the preferred size on the Container
*
* #param parent the container in which to do the layout
* #return the preferred dimensions to lay out the
* subcomponents of the specified container
*/
#Override
public Dimension preferredLayoutSize(Container parent)
{
synchronized (parent.getTreeLock())
{
return getLayoutSize(parent);
}
}
/*
* The calculation for minimum/preferred size it the same. The only
* difference is the need to use the minimum or preferred size of the
* component in the calculation.
*
* #param parent the container in which to do the layout
*/
private Dimension getLayoutSize(Container parent)
{
Insets parentInsets = parent.getInsets();
int x = parentInsets.left;
int y = parentInsets.top;
int width = 0;
int height = 0;
// Get extreme values of the components on the container
for (Component component: parent.getComponents())
{
if (component.isVisible())
{
Point p = component.getLocation();
Dimension d = component.getPreferredSize();
x = Math.min(x, p.x);
y = Math.min(y, p.y);
width = Math.max(width, p.x + d.width);
height = Math.max(height, p.y + d.height);
}
}
// Width/Height is adjusted if any component is outside left/top edge
if (x < parentInsets.left)
width += parentInsets.left - x;
if (y < parentInsets.top)
height += parentInsets.top - y;
// Adjust for insets
width += parentInsets.right;
height += parentInsets.bottom;
Dimension d = new Dimension(width, height);
return d;
// return new Dimension(width, height);
}
/**
* Lays out the specified container using this layout.
*
* #param target the container in which to do the layout
*/
#Override
public void layoutContainer(Container parent)
{
synchronized (parent.getTreeLock())
{
Insets parentInsets = parent.getInsets();
int x = parentInsets.left;
int y = parentInsets.top;
// Get X/Y location outside the bounds of the panel
for (Component component: parent.getComponents())
{
if (component.isVisible())
{
Point location = component.getLocation();
x = Math.min(x, location.x);
y = Math.min(y, location.y);
}
}
x = (x < parentInsets.left) ? parentInsets.left - x : 0;
y = (y < parentInsets.top) ? parentInsets.top - y : 0;
// Set bounds of each component
for (Component component: parent.getComponents())
{
if (component.isVisible())
{
Point p = component.getLocation();
Dimension d = component.getPreferredSize();
component.setBounds(p.x + x, p.y + y, d.width, d.height);
}
}
}}
/**
* Returns the string representation of this column layout's values.
* #return a string representation of this layout
*/
public String toString()
{
return "["
+ getClass().getName()
+ "]";
}
public static void main( String[] args )
{
ComponentMover cm = new ComponentMover();
cm.setEdgeInsets( new Insets(-100, -100, -100, -100) );
// cm.setEdgeInsets( new Insets(10, 10, 10, 10) );
cm.setAutoLayout(true);
JPanel panel = new JPanel( new DragLayout() );
panel.setBorder( new MatteBorder(10, 10, 10, 10, Color.YELLOW) );
createLabel(cm, panel, "North", 150, 0);
createLabel(cm, panel, "West", 0, 100);
createLabel(cm, panel, "East", 300, 100);
createLabel(cm, panel, "South", 150, 200);
createLabel(cm, panel, "Center", 150, 100);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new JScrollPane(panel) );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
public static void createLabel(ComponentMover cm, JPanel panel, String text, int x, int y)
{
JLabel label = new JLabel( text );
label.setOpaque(true);
label.setBackground( Color.ORANGE );
label.setLocation(x, y);
panel.add( label );
cm.registerComponent( label );
}
}
For this layout the size is always assumed to be the preferred size. You would need to change this. Maybe set the size to be the preferred size when the size is (0, 0). You will also need to use the size of the component (not its preferred size) when determining the preferred size of the parent container.
The ComponentMover class can be configured to allow you to drag comopnents outside the bounds of the parent container or to keep the component inside the bounds. If you allow components to be moved outside the bounds, then the preferred size is automatically adjusted to take into account the new location of the component.
If you drag a component outside the top or left bounds, then all the components are shifted (right or down) do make sure no component has a negative location.
I guess it would depend on the specifics of how you wanted it to behave.
The main reason the null layout manager is discouraged is because of the fact that interfaces built using that can only be used in the size they were designed - You can't resize the UI. If this is fine for you, use it.
Another option I know of is the AbsoluteLayout that Netbeans is distributed with. You can get more info here:
http://www.java-tips.org/other-api-tips/netbeans/can-i-distribute-absolutelayout-with-my-applica.html.
I think this might be exactly what you are looking for, but as you can see from that link, they recommend rather using a Null layout... I don't think it makes much of a difference either way.
If you need to be able to allow users to define how the components will resize as well, you'll end up building something like the Netbeans Matisse form designer, which is probably overkill and does not strike me as much fun :)
The question is somewhat vague, so I might be missing the point completely. I assume that you are looking for a layout that will allow you to use absolute positioning, but will still allow you to resize the component and use all available space.
If you are handcoding it, I've had success with MIGLayout (http://www.miglayout.com/) and TableLayout (Which is less absolute but very easy to use - http://java.sun.com/products/jfc/tsc/articles/tablelayout/)
If you are using some Form designer, using GroupLayout might be a good choice, but you do not want to hand-code it. See this question :
GroupLayout: Is it worth learning?
I'm using setUndecorated(true); and getRootPane().setWindowDecorationStyle(JRootPane.FRAME); in my jFrame. This works great but now when I maximized my frame it spreads all over the window even taskbar is not visible. What can I do to make frame not to hide taskbar?
Also when I maximize minimize my frame multiple times the cursor is changed to this <-> which is generally used change size of frame when cursor is on the border of frame. Is there anything I can do for this?
A small code then can reproduce the thing:
import javax.swing.JFrame;
import javax.swing.JRootPane;
public class Demo extends JFrame {
public Demo() {
setSize(250,125);
setUndecorated(true);
getRootPane().setWindowDecorationStyle(JRootPane.FRAME);
setVisible(true);
}
public static void main(String[] args) {
new Demo();
}
}
This is a known bug: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4737788
Quote from this link:
A workaround is to subclass JFrame and
override the setExtendedState method,
catching any maximize events before
they happen and setting the maximum
bounds of the frame appropriately
before calling the superclass's
setExtendedState method.
import java.awt.*;
import javax.swing.*;
public class PFrame extends JFrame
{
private Rectangle maxBounds;
public PFrame()
{
super();
maxBounds = null;
}
//Full implementation has other JFrame constructors
public Rectangle getMaximizedBounds()
{
return(maxBounds);
}
public synchronized void setMaximizedBounds(Rectangle maxBounds)
{
this.maxBounds = maxBounds;
super.setMaximizedBounds(maxBounds);
}
public synchronized void setExtendedState(int state)
{
if (maxBounds == null &&
(state & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH)
{
Insets screenInsets = getToolkit().getScreenInsets(getGraphicsConfiguration());
Rectangle screenSize = getGraphicsConfiguration().getBounds();
Rectangle maxBounds = new Rectangle(screenInsets.left + screenSize.x,
screenInsets.top + screenSize.y,
screenSize.x + screenSize.width - screenInsets.right - screenInsets.left,
screenSize.y + screenSize.height - screenInsets.bottom - screenInsets.top);
super.setMaximizedBounds(maxBounds);
}
super.setExtendedState(state);
}
}
Fortega answer worked however, some part is not needed (or no longer needed with Java 8):
The Rectangle does not need to be saved.
The code does not take into account dual screen configuration. In particular, the GraphicsConfiguration will change if the window change screen.
As far as I tested, the only required override is setExtendedState.
When factoring dual screen configuration, at least on Windows, the below code does not work as intended:
Rectangle maxBounds = new Rectangle(screenInsets.left + screenSize.x,
screenInsets.top + screenSize.y,
screenSize.x + screenSize.width - screenInsets.right - screenInsets.left,
screenSize.y + screenSize.height - screenInsets.bottom - screenInsets.top);
On the following dual screen set up:
Left screen 1920x1080 (not primary), position: -1920, 0
Right screen 1920x1080 (primary), position: 0, 0
The maxBounds will contains negative x (-1920) but the setMaximizedBounds is somehow expecting a coordinate in the screen space (where (x,y) starts at (0,0)) , not the virtual screen:
It will set to setMaximizedBounds(x=-1920,y=0,width=1920,height=1050)
Windows will see the window on the left screen (because I have one taskbar per screen showing only window on that screen) however the window won't be shown on the screen because it is off bounds.
If the resolution of the screen, or worse, its scale factor (with a laptop, Windows 10 will apply a scale factor, ex: 25%, making the screen "not so" 1920x1080), then the above code does not adapt. For example, if my configuration have 3 screens with the right most being the primary, the window will badly display on the left and middle screen. I don't think I fixed this in the below code.
The following code work on Windows, with dual screen:
#Override
public synchronized void setExtendedState(final int state) {
if ((state & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH) {
final GraphicsConfiguration cfg = getGraphicsConfiguration();
final Insets screenInsets = getToolkit().getScreenInsets(cfg);
final Rectangle screenBounds = cfg.getBounds();
final int x = screenInsets.left + screenBounds.x * 0;
final int y = screenInsets.top + screenBounds.y * 0;
final int w = screenBounds.width - screenInsets.right - screenInsets.left;
final int h = screenBounds.height - screenInsets.bottom - screenInsets.top;
final Rectangle maximizedBounds = new Rectangle(x, y, w, h);
System.out.println("cfg (" + cfg + ") screen.{bounds: " + screenBounds + ", insets: " + screenInsets + ", maxBounds: " + maximizedBounds);
super.setMaximizedBounds(maximizedBounds);
}
super.setExtendedState(state);
}
On a simple JFrame:
Maximizing on the left screen ("screen=0") will print cfg (D3DGraphicsConfig[dev=D3DGraphicsDevice[screen=0],pixfmt=0]) screen.{bounds: java.awt.Rectangle[x=-1920,y=0,width=1920,height=1080], insets: java.awt.Insets[top=0,left=0,bottom=30,right=0], maxBounds: java.awt.Rectangle[x=0,y=0,width=1920,height=1050]
Maximizing on the right screen ("screen=1") will print cfg (D3DGraphicsConfig[dev=D3DGraphicsDevice[screen=1],pixfmt=0]) screen.{bounds: java.awt.Rectangle[x=0,y=0,width=1920,height=1080], insets: java.awt.Insets[top=0,left=0,bottom=30,right=0], maxBounds: java.awt.Rectangle[x=0,y=0,width=1920,height=1050]
Maybe you can set the maximum size of the jFrame and restrict it according to the screen size.
EDIT
Also check out setExtendedState
Starting from Fortega answer, you can make it work even with 125% screen sizi adding
Rectangle screenSize = getGraphicsConfiguration().getBounds();
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
screenSize.setSize(new Dimension(gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight()));
......