I'm working on a Swing UI in which I want to center multiple components (JDialogs and JFrames). I know that the following code will calculate the user's screen size, and from there, I can easily center a component:
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
For efficiency's sake, I should only calculate this once and store it in some kind of constant so it can be reused in any part of the project. What is the best practice for storing this for later reuse so it is accessible across multiple classes?
(Furthermore, if there is a better way of calculating screen size for centering, I'd be open to hearing that as well)
java.awt.Window.setLocationRelativeTo(null) will center it on the screen whilesetLocationRelativeTo(someComponent) will center it relative to the java.awt.Component, someComponent.
One thing to consider with the alternate of storing the center, is that if a user adjusts their resolution while the program is running than the stored constants will no longer be valid. Is recalling the getScreenSize function actually expensive? (I do not know whether or not it is)
This puts the upper left corner of the component on center, but not the whole component
This means the size of the dialog/frame is (0, 0), Your basic code should be:
frame.add( .... );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
For centering the object you should try:
The frame position in X = (half frame width) - (half screen size width)
Almost the same for Y = (half frame height) - (half screen size height)
You can easily stores the values in the main class with public access, so you don't need to read them several times
Also, if you do it yourself, you need to factor in the screen's insets using Toolkit.getScreenInsets to account for things like the task bar, which might be on any screen edge and be of any size.
Before I could target Java 1.4, I used:
static public void centerWindow(Window wnd, Component relcom) {
Rectangle scrbnd=getScreenBounds(wnd);
Dimension wndsiz=wnd.getSize();
Container root=null;
int px,py;
if(relcom!=null) {
if(relcom instanceof Window || relcom instanceof java.applet.Applet) {
root=(Container)relcom;
}
else {
Container parent;
for(parent=relcom.getParent(); parent!=null; parent=parent.getParent()) {
if(parent instanceof Window || parent instanceof java.applet.Applet) {
root=parent;
break;
}
}
}
}
if(relcom==null || !relcom.isShowing() || root==null || !root.isShowing()) {
px=(scrbnd.x+((scrbnd.width -wndsiz.width )/2));
py=(scrbnd.y+((scrbnd.height-wndsiz.height)/2));
}
else {
Point relloc=relcom.getLocationOnScreen();
Dimension relsiz=relcom.getSize();
px=(relloc.x+((relsiz.width -wndsiz.width )/2));
py=(relloc.y+((relsiz.height-wndsiz.height)/2));
}
if((px+wndsiz.width )>(scrbnd.x+scrbnd.width )) { px=((scrbnd.x+scrbnd.width )-wndsiz.width ); }
if((py+wndsiz.height)>(scrbnd.y+scrbnd.height)) { py=((scrbnd.y+scrbnd.height)-wndsiz.height); }
if(px<scrbnd.x) { px=scrbnd.x; }
if(py<scrbnd.y) { py=scrbnd.y; }
wnd.setLocation(px,py);
}
Related
Basically, I'm trying to make a button that has the text aligned to the left (so I'm using setHorizontalAlignment(SwingConstants.LEFT)) and the image on the right border of the button, far from the text.
I already tried setHorizontalTextAlignment(SwingConstants.LEFT), but that just makes the text go relativity to the left of the icon, which is not exactly what I want, since I needed the icon to be secluded from it.
Also, I can't make any fixed spacing because it's a series of buttons with different texts with different sizes.
I can't make any fixed spacing because it's a series of buttons with different texts with different sizes.
You can dynamically change the spacing with code like:
JButton button = new JButton("Text on left:")
{
#Override
public void doLayout()
{
super.doLayout();
int preferredWidth = getPreferredSize().width;
int actualWidth = getSize().width;
if (actualWidth != preferredWidth)
{
int gap = getIconTextGap() + actualWidth - preferredWidth;
gap = Math.max(gap, UIManager.getInt("Button.iconTextGap"));
setIconTextGap(gap);
}
}
};
button.setIcon( new ImageIcon("copy16.gif") );
button.setHorizontalTextPosition(SwingConstants.LEADING);
This is a derivative of camickr's answer to allow editing in a GUI builder as well as placing it in a dynamic layout. I also removed the UIManager.getInt("Button.iconTextGap") so the gap will shrink to 0 if necessary.
I called it a 'Justified' button in analogy with justified text alignment (stretches a paragraph to left & right by growing width of space characters).
public class JustifiedButton extends JButton {
#Override
public void doLayout() {
super.doLayout();
setIconTextGap(0);
if (getHorizontalTextPosition() != CENTER) {
int newGap = getSize().width - getMinimumSize().width;
if (newGap > 0)
setIconTextGap(newGap);
}
}
#Override
public Dimension getMinimumSize() {
Dimension minimumSize = super.getMinimumSize();
if (getHorizontalTextPosition() != CENTER)
minimumSize.width -= getIconTextGap();
return minimumSize;
}
#Override
public Dimension getPreferredSize() {
Dimension preferredSize = super.getPreferredSize();
if (getHorizontalTextPosition() != CENTER)
preferredSize.width -= getIconTextGap();
return preferredSize;
}
}
This is not exactly production-ready and needs some field-testing. If I find anything, I'll edit the code.
[edit] Now works for vertical text alignments. Also simplified a bit.
[edit2] Also manipulate getPreferredSize to play nice with scroll pane (otherwise it keeps growing and never shrinks again)
You can add a layout manager to your button.
JButton btn = new JButton();
btn.add(new JLabel(text));
btn.add(new JLabel(img));
btn.setLayout(/*best layout choice here*/);
btn.setPreferredSize(new Dimension(x,y));
btn.setMaximumSize(new Dimension(maxX, minY));
btn.setMinimumSize(new Dimension(minX, minY)); //this one is most important when it comes to layoutmanagers
Sorry I can't be much help when it comes to picking out a good layout - But this will eventually get you what you want. Maybe someone else can comment on which one to use.
I'm having an issue creating an empty JTabbedPane where the only portion to be seen on the GUI are the row of tabs.
Everytime I add a new tab with an "empty" component, the height of the JTabbedPane increases, but why?
The current workaround is to override getPreferredSize(), but it seems kludgy to me. Comment out the overridden method to see what I mean.
Am I missing something obvious?
Background:
We need a JTabbedPane where the tabbed pane starts off with 2 tabs, but the user can add more tabs as needed, up to 10. In addition, each tab contains the same components, but with different data. The decision was made to fake the look of a JTabbedPane, by implementing an empty JTabbedPane solely for the look, and to use a single fixed JPanel whose contents will be refreshed based on the tab clicked.
(Normally, I could just recreate the JPanel n-times, but that would nightmarish for the presenter classes who control the UI, which is beyond the scope of my question.)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CustomTabbedPane implements Runnable
{
static final int MAX_TABS = 11; // includes the "add" tab
JPanel pnlTabs;
JTabbedPane tabbedPane;
public static void main(String[] args)
{
SwingUtilities.invokeLater(new CustomTabbedPane());
}
public void run()
{
JPanel p = buildPanel();
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(p);
frame.setSize(800,400);
frame.setVisible(true);
}
private JPanel buildPanel()
{
tabbedPane = new JTabbedPane()
{
#Override
public Dimension getPreferredSize()
{
Dimension dim = super.getPreferredSize();
dim.height = getUI().getTabBounds(this, 0).height + 1;
return dim;
}
};
tabbedPane.addTab("Tab 1", getEmptyComp());
tabbedPane.addTab("Tab 2", getEmptyComp());
tabbedPane.addTab("+", new TabCreator());
tabbedPane.addMouseListener(new MouseAdapter()
{
#Override
public void mouseClicked(MouseEvent e)
{
addTab();
}
});
JScrollPane scroll = new JScrollPane(new JTable(5,10));
JPanel p = new JPanel(new BorderLayout());
p.add(tabbedPane, BorderLayout.NORTH);
p.add(scroll, BorderLayout.CENTER);
p.setBorder(BorderFactory.createLineBorder(Color.BLUE.darker(), 1));
return p;
}
private void addTab()
{
if (tabbedPane.getSelectedComponent() instanceof TabCreator)
{
int selIndex = tabbedPane.getSelectedIndex();
if (tabbedPane.getComponentCount() < MAX_TABS)
{
if (selIndex == tabbedPane.getComponentCount()-1)
{
String title = "Tab " + (selIndex + 1);
tabbedPane.insertTab(title, null, getEmptyComp(), "", selIndex);
tabbedPane.setSelectedIndex(selIndex);
if (tabbedPane.getComponentCount() == MAX_TABS)
{
tabbedPane.setEnabledAt(MAX_TABS-1, false);
}
}
}
}
}
private Component getEmptyComp()
{
return Box.createVerticalStrut(1);
}
class TabCreator extends JLabel {}
}
Great question! But it's fairly straightforward to get a hint on what's happening.
The problem is that your content does not have a minimum width, preferred size is not set, tab placement is top/bottom and the UI is default.
Since preferred size is not set, then when the layout is revalidated the calculations of space required go into the BasicTabbedPaneUI method Dimension calculateSize(false).
That reads:
int height = 0;
int width = 0;
<other vars>
// Determine minimum size required to display largest
// child in each dimension
<actual method>
Here it calculates the minimum size to accommodate any child and stores it into height/width. In your case this yields something like 10,10 (because of the single Label tab creator I think, I didn't follow that one).
Then happens the magic:
switch(tabPlacement) {
case LEFT:
case RIGHT:
height = Math.max(height, calculateMaxTabHeight(tabPlacement));
tabExtent = preferredTabAreaWidth(tabPlacement, height - tabAreaInsets.top - tabAreaInsets.bottom);
width += tabExtent;
break;
case TOP:
case BOTTOM:
default:
width = Math.max(width, calculateMaxTabWidth(tabPlacement));
tabExtent = preferredTabAreaHeight(tabPlacement, width - tabAreaInsets.left - tabAreaInsets.right);
height += tabExtent;
}
What happens here is it sets the preferred width to be the maximum of the largest tab width and the largest child width. In your case it's around 44 for the tab text. The tabExtent is then calculated to see just how many rows of tabs are needed to support this preferred width. In your case - it's 1 extra row of tabs for each tab. That's where the extra height in preferredSize().height comes from. Essentially because for horizontal tab placement it cares about width first, then height.
How to fix:
Set a preferred size :) I know a lot of people say don't set the preferred size, but in this case this will just work. Since a preferred size is set (via actually setting it, not overriding getPreferredSize()), the code will never get to counting tabs.
Give at least one of your children a size (via setPreferredSize or overriding getPreferredSize). If one of the childrens width is that of the frame, or, say, the table at the bottom the TabbedPane will not be allocating an extra row for each tab, since a single row will fit everything.
Make your own UI for the tabbed pane. It may be easier to make your own tabbed pane though really, I've never done this.
EDIT:
After thinking about this a bit more, I realized that solution number 1 AND your own solution suffer from the flaw that, if the tabbed pane actually does require multiple rows for the tabs (hello frame resizes), bad things will happen. Don't use it.
I looked around at all the other answers, but they all recommend to use GroupLayout, BoxLayout, or to wrap the panel that's using GridBagLayout with another panel that uses one of the layouts mentioned above.
I'm currently using GridBagLayout, and I'm wondering if there's a way to set the maximum height of a panel. I don't want a limit on width, only height, so setMaximumSize(Dimension) won't work.
Does GridBagLayout support this in any way? Sorry if my question doesn't contain any code, the only attempt I could possibly find is setMaximumSize(Dimension), or wrapping it in another panel (which I'm hoping to avoid)
If you want to limit only one dimension then just do not limit the other:
component.setMaximumSize( new Dimension(
Integer.MAX_VALUE,
requiredMaxHeight
) );
Well, I would say that almost always the right solution is to fix the layouting either by using a different layout manager, or by using a different hierarchy of containers (or both).
However, since it seems you won't be persuaded (I infer that from your question), I can suggest a solution to the specific question you ask (again, I would recommend to take a different path of fixing the layout, which probably is your real problem).
You can set the maximum height without affecting the maximum width, by overriding the setMaximumSize() method as follows:
#Override
public void setMaximumSize(Dimension size) {
Dimension currMaxSize = getMaximumSize();
super.setMaximumSize(currMaxSize.width, size.height);
}
Another approach can be to keep the "overridden" setting of the max height, and return it when returning the maximum height, like so:
private int overriddenMaximumHeight = -1;
public void setMaximumHeight(int height) {
overriddenMaximumHeight = height;
}
#Override
public Dimension getMaximumSize() {
Dimension size = super.getMaximumSize();
int height = (overriddenMaximumHeight >=0) ? overriddenMaximumHeight : size.height;
return new Dimension(size.width, height);
}
Again (lastly), I would recommend taking a more common approach, but if you insist ...
Hi I am writing a simple program to display a frame.
However the frame turns out real small when I type setLayout(null);
But if i ignore this command, the button is always at the top center
Can some one point out my error?
import java.io.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
class theframe {
private static void create() {
JFrame frame = new JFrame("FrameDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Insets insets = frame.getInsets();
frame.setSize(500 + insets.left + insets.right, 350 + insets.top + insets.bottom);
add(frame.getContentPane()); //my function add
frame.pack();
frame.setVisible(true);
}
public static void add(Container pane) {
pane.setLayout(null);
Insets insets = pane.getInsets();
JPanel p1 = new JPanel();
p1.setPreferredSize(new Dimension(500, 350));
JButton b1 = new JButton("one");
Dimension size = b1.getPreferredSize();
b1.setBounds(25 + insets.left, 5 + insets.top, size.width, size.height);
pane.add(p1);
p1.add(b1);
}
public static void main(String Args[]) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
create();
}
});
}
}
If you see the pack() function it is not in JFrame.java but Window.java
public void pack() {
Container parent = this.parent;
if (parent != null && parent.getPeer() == null) {
parent.addNotify();
}
if (peer == null) {
addNotify();
}
Dimension newSize = getPreferredSize();
if (peer != null) {
setClientSize(newSize.width, newSize.height);
}
if(beforeFirstShow) {
isPacked = true;
}
validateUnconditionally();
}
now if you see getPreferredSize() function in Container.java
#Deprecated
public Dimension preferredSize() {
/* Avoid grabbing the lock if a reasonable cached size value
* is available.
*/
Dimension dim = prefSize;
if (dim == null || !(isPreferredSizeSet() || isValid())) {
synchronized (getTreeLock()) {
prefSize = (layoutMgr != null) ?
layoutMgr.preferredLayoutSize(this) :
super.preferredSize();
dim = prefSize;
}
}
if (dim != null){
return new Dimension(dim);
}
else{
return dim;
}
}
Everything boils down to layoutMgr.preferredLayoutSize(this);
Since you are not setting the layout(or null) you are getting that issue. Either remove that statement or use some layout.
You're calling pack() method with absolute positioning (null layout), you should use LayoutManager instead of using setting the layout to null.
Just remove this line:
pane.setLayout(null);
NOTE: take my advice and learn yourself LayoutManagers, because if you refuse to learn, you'll probably go to null layout, yes,it is easier but highly recommended to not use it.
Some basic things you must know:
The default layout of JFrame is BorderLayout, which there only five locations you can put your components in as shown in the link.
FlowLayout is the default layout manager for every JPanel. It simply lays out components in a single row, starting a new row if its container is not sufficiently wide.
After all, read more about LayoutManagers before starting using swing, belive me it makes your work very easier, just try it, it's amazing.
Java GUIs might have to work on a number of platforms, on different screen resolutions & using different PLAFs. As such they are not conducive to exact placement of components.
To organize the components for a robust GUI, instead use layout managers, or combinations of them, along with layout padding & borders for white space.
See this answer for tips on how to:
Combine layouts.
Provide white space using layout constructor arguments & borders.
I had the same issue luckly, I solved with one line code. May this will also help you try to follow these steps:
1- Goto in your file code
2- In start of code find this line:
initComponents();
3- Just add this new code after this line:
this.setMinimumSize(new Dimension(300, 300));
The keyword "this" here representing your current top-level component, so new dimensions as (300, 300) is applied here. You may change this 300 values according to your required frame / component size.
This is my JFrame code:
public static int width = 800;
public static int height = 600;
public static void main(String[]args){
JFrame frame= new JFrame("RETRO");
frame.add(new Screen());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width,height);
frame.setVisible(true);
frame.setResizable(false);
}
Basically when I want something to move to the edge of the screen, I have to add extra pixels for it work (I'm guessing because it includes the frame itself instead of just the display size? However the origins work fine (x=0, y=0)). Example:
public double getX(){
if(x<0)
x=0;
if(x+getImage().getWidth(null)>Game.width-6)
x=Game.width-6-getImage().getWidth(null);
return x;
}
public double getY(){
if(y<0)
y=0;
if(y+getImage().getHeight(null)>Game.height-26)
y=Game.height-26-getImage().getHeight(null);
return y;
}
Is there a way around this? I don't think the JFrame would be the same size on everyone's computer, not to mention the guesswork. Rather have it much neater and flexible by using an exiting variable from the JFrame component. Does there exist something like a frame.getDisplayWidth and Height function?
Don't set the size of the frame, set the preferred size of the contents.
Pack the frame
Get co-ordinates according to the position in the content
How do you set a 'preferred size' for the contents? I'm using a Screen class (extended JPanel) for rendering.
screen.setPreferredSize(new Dimension (600,400));
frame.setContentPane(screen);
frame.pack();
// frame will now be the size it needs to display the contents
// and the frame's own decorations (title bar etc.)
// ..now add a nice tweak.
frame.setMinimumSize(frame.getSize());
It seems you want to do something like a full screen app. JFrame's setDecorated(false) would do away with title and borders. setBounds does the sizing.
Instead of explicitly setting the size of the frame, try to set the preferred size of the content (in your case, the Screen object).
Also, just in case you need to find out the actual size of the frame decorations (title bar and borders), there is no need for guess work -- you can get this information by calling JFrame.getInsets()