pushMatrix to center of window - java

I'm looking to make a graphic in Processing that's centered in the middle of the window. I want to be able to change the size of the window and have the graphic remain centred no matter what, so I intend to do this through centering the matrix itself.
How would I go about doing this? Normally I would translate the matrix to the center of the window based on the size of the window itself, but if I'm changing the size then it won't work.
Suggestions?

here, I got this old code that kind of do this...
import processing.opengl.*;
int newCanvasWidth = MIN_WINDOW_WIDTH; // made global to use in draw
int newCanvasHeight = MIN_WINDOW_HEIGHT;
java.awt.Insets insets; //"An Insets object is a representation of the borders of a container"
//from http://docs.oracle.com/javase/1.4.2/docs/api/java/awt/Insets.html
void setup()
{
size(200, 200); // always first line
frame.pack(); //frame.pack() no need for setResizable... plus insets
insets = frame.getInsets();
frame.setResizable(true);
/// for debuging, system depende`nt, at least screen is...
print("MIN_WINDOW_WIDTH = " + MIN_WINDOW_WIDTH);
print(" MIN_WINDOW_HEIGHT = " + MIN_WINDOW_HEIGHT);
print(" screenWidth = " + displayWidth);
println(" screenHeight = " + displayHeight);
}
void draw()
{
background(255);
ellipse(width/2, height/2, width/2, height/2);
}

Related

Java Multi-Display Handling under Windows - Bug with scaled displays?

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.

How do I make a Vbox use the entire space

This is javafx8, I have to write that here since i do not have enough rep to use the tag.
Simple example:
public class Test extends Application
{
public static void main(String args[]) throws Exception { launch(args); }
private Canvas canvas;
#Override
public void start(Stage primaryStage)
{
VBox box = new VBox();
Button dummyButton = new Button("some text");
canvas = new Canvas();
canvas.heightProperty().addListener(observable -> draw());
canvas.widthProperty().addListener(observable -> draw());
VBox.setVgrow(canvas, Priority.ALWAYS);
box.getChildren().addAll(dummyButton, canvas);
Scene scene = new Scene(box, 1300, 800);
primaryStage.setScene(scene);
primaryStage.show();
}
private void draw()
{
final double width = canvas.getWidth();
final double height = canvas.getHeight();
System.out.println("w, h: " + width + "; " + height);
final GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.GREENYELLOW);
gc.fillRect(0, 0, width, height);
}
}
I probably misread the documentation of vbox, at the bottom it says that
For example, if a vbox needs the ListView to be allocated all extra space
and then does this VBox.grow thing, just as I did in my example. But the canvas is never changing his size. It always has a width and height of 0, while I expected the canvas to grow and shrink as I resize the window.
How can I get my canvas ( and I guess with that also my vbox ) to use the entire vertical space available.
Please do not suggest the use of the primary stage, in my real application I am so many layers away from that with many other elements in between. Primary stage is only here to have a runnable program.
Your draw method is a bit messed up.
private void draw() {
final double width = canvas.getWidth();
final double height = canvas.getHeight();
System.out.println("w, h: " + width + "; " + height);
final GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.GREENYELLOW);
gc.fillRect(0, 0, width, height);
}
If we look at your method, you are doing a few things that can cause problems.
But first, as you noticed, the statement:
VBox.setVgrow(canvas, Priority.ALWAYS);
is causing you trouble. So let's just get rid of it and use your current structure.
If we just change your listeners from canvas to box:
canvas.heightProperty().addListener(observable -> draw());
canvas.widthProperty().addListener(observable -> draw());
to
box.heightProperty().addListener(observable -> draw());
box.widthProperty().addListener(observable -> draw());
We will now dynamically update things as your Vbox size changes. This means that your method draw will not be called when we resize, it wasn't doing that before so that's good!
So now back to the draw method. First of all, you are asking for the canvas width each time, when really we care about getting the canvas to match the box, so let's change that from canvas to box (You'll declare your Vbox as a field now).
private void draw() {
final double width = box.getWidth();
final double height = box.getHeight();
System.out.println("w, h: " + width + "; " + height);
final GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.GREENYELLOW);
gc.fillRect(0, 0, width, height);
}
But this still won't work very well. That's because we never update the canvas size! So let's add a setWidth and a setHeight in
private void draw() {
final double width = box.getWidth();
final double height = box.getHeight();
canvas.setWidth(width);
canvas.setHeight(height);
System.out.println("w, h: " + width + "; " + height);
final GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.GREENYELLOW);
gc.fillRect(0, 0, width, height);
}
With that, we'll notice that everything seems to be looking pretty good, with one exception. The button is now blocking the top of the screen and will make it so that you can't actually fill that top part. That's because the button was drawn first onto the screen and gets priority (If we switch it to be added at index 1 via a different add method call, we see the button disappears). I can't think of a way to fix that right now, but hopefully that solves part of your problem!

graphics not showing on resized subpanel?

I am having issues with a small project im working on. I am trying to create a Moveable message panel when holding down the mouse button but i am stuck on one part.
I want to place the A small panel with a size of 50x30 pixels that contains the message "java" in it and have this small panel in a larger panel and place that panel into my JFrame.
However, when i do so the message "java" disappears and only the the small panel in the larger panel appears. I added borders to my panels to make sure that my panels were actually visible. Please help and here is my code:
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.event.*;
public class MovingPanel extends JFrame {
private String message;
private int x = 100;
private int y = 100;
public MovingPanel() {
JPanel panel = new JPanel();
MessagePanel p1 = new MessagePanel("Java");
panel.setBorder(new LineBorder(Color.RED, 2));
panel.setLayout(null);
p1.setLocation(x, y);
p1.setSize(50, 30);
p1.setBorder(new LineBorder(Color.BLACK, 2));
p1.setLayout(new BorderLayout());
panel.add(p1);
add(panel);
}
public static void main(String[] args) {
MovingPanel frame = new MovingPanel();
frame.setSize(500, 500);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setTitle("Test Message Panel");
frame.setVisible(true);
}
class MessagePanel extends JPanel {
public MessagePanel(String s) {
message = s;
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawString(message, x + 20, y + 10);
}
}
}
Maybe you can try to use a simple JLabel component instead of your "MessagePanel".
First thing you need to understand is this.
The second and third arguments of this g.drawString(message, x + 20, y + 10); method are the x and y location of the panel.
With the above being said, you have to remember that it is the x and y location of the containing panel, which is MessagePanel.
You have the size of your MessagePanel object set at 50, 30, yet you are trying to access a point 120 (x + 20) and 110 (100 + 10), which does not exist since you size the size of the panel.
So now that's understood, let's say you want to paint the message at the very left corner of the MessagePanel, so you try and do this g.drawString(message, 0, 0);. This still would show anything as the point starts from the bottom left corner of the message, so the message would actually be riding just above the visible area.
When drawing strings, you need to consider the FontMetrics, which allows you to get the size of the string you are trying to draw, so you can position the message exactly on the screen where you want it.
A simple fix would be just set an x and y a little above 0, 0, like 15, 15. Though this might get your message to draw, it wouldn't be centered. You can keep on changing and getting different numbers to check if it is aligned in the middle, but the proper way is to use FontMetrics
As a said a simple (but maybe not desired) fix is to just change this
g.drawString(message, x + 20, y + 10);
To
g.drawString(message, 15, 15);
And you will see the message.
Instead of what you are doing though, this is how I would do it.
Instead of using two panels, I would just use one - the one that's doing the painting.
Don't set the size of it, instead override getPrefferedSize inside that class, to whatever size you want the main panel to me.
When you draw, just draw a rectangle the size you want at the specified coordinates.
Also draw the message in the same paintComponent method.
call pack() on the JFrame.
If you do the above, there's no need to try and move the location of the MessagePanel. Instead move the x and y coordinates when you call repaint, You can have offsets for the message. Like
int boxX = 100;
int boxY = 100;
int messageOffset = 15;
Then you can paint like this
protected void paintComponent(Graphics g){
super.paintComponent(g);
g.drawRect(boxX, boxY, 50, 30);
g.drawString(message, boxX + messageOffset, boxY + messageOffset);
}
Now in your action methods, just alter the boxX and/or boxY and call repaint.
Also, if you want a thicker line, look into Graphics2D API, you can setStroke.

Why jframe hides taskbar when maximized?

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()));
......

How come JFrame window size in Java does not produce the size of window specified?

I am just messing around trying to make a game right now, but I have had this problem before too. When I specify a specific window size (1024 x 768 for instance) the window produced is just a little larger than what I specified. Very annoying. Is there a reason for this? How do I correct it so the window created is actually the size I want instead of being just a little bit bigger? Up till now I have always just gone back and manually adjusted the size a few pixels at a time until I got the result I wanted, but that is getting old. If there was even a formula I could use that would tell me how many pixels I needed to add/subtract from my my variable that would be excellent!
P.S. I don't know if my OS could be a factor in this, but I am using W7X64.
private int windowWidth = 1024;
private int windowHeight = 768;
public SomeWindow() {
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(windowWidth, windowHeight);
this.setResizable(false);
this.setLocation(0,0);
this.setVisible(true);
}
I want the total frame that Windows
creates to be the exact size I specify
I don't understand your problem. Post your SSCCE that shows the problem.
If I run code like:
frame.setResizable(false);
frame.setSize(1024, 768);
frame.setVisible(true);
System.out.println(frame.getSize());
It displays java.awt.Dimension[width=1024,height=768], is that not what you expect?
If there was even a formula I could
use that would tell me how many pixels
I needed to add/subtract from my my
variable that would be excellent!
Maybe you are referring to the space occupied by the title bar and borders?
import java.awt.*;
import javax.swing.*;
public class FrameInfo
{
public static void main(String[] args)
{
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
Rectangle bounds = env.getMaximumWindowBounds();
System.out.println("Screen Bounds: " + bounds );
GraphicsDevice screen = env.getDefaultScreenDevice();
GraphicsConfiguration config = screen.getDefaultConfiguration();
System.out.println("Screen Size : " + config.getBounds());
JFrame frame = new JFrame("Frame Info");
System.out.println("Frame Insets : " + frame.getInsets() );
frame.setSize(200, 200);
System.out.println("Frame Insets : " + frame.getInsets() );
frame.setVisible( true );
System.out.println("Frame Size : " + frame.getSize() );
System.out.println("Frame Insets : " + frame.getInsets() );
System.out.println("Content Size : " + frame.getContentPane().getSize() );
}
}
When you say the obtained window size is not the asked one, are you talking about the window, with its decorations ?
Indeed, window size is defined without OS specific window decoration.
Try to add
this.setUndecorated(true);
before the
this.setVisible(true);

Categories