I'm writing a GUI in Java using Swing. At the moment, I'm trying to create a "Module" (a yellow block) that has a widget holder (black bar) on the left and right edges. Each holder will hold several small blocks that I'd like to display in a vertical line. Here is a picture:
Example module :
I want to be able to space the magenta/cyan blocks evenly along the widget holders.
I've looked at several tutorials for Swing and have tried implementing the layout of the widget holders as GridLayout and BoxLayout, but both without luck. A single column GridLayout seems to be the natural choice here, but I can't seem to make it work, even though I've written small test programs that properly use a grid.
The fact that the layout managers work in simple examples but not in this slightly more complex program has left me perplexed.
In my program,
The module is a JPanel
The widget holders / black bars are also JPanels
The widgets themselves (cyan/magenta blocks) are currently JPanels, but I've tried having them as JLabels and JButtons as well. I just want them to be able to listen to mouse events and to have an area and color.
On a side note, I was also having trouble with the layout of the Module itself to position the widget holders on the left and right. I tried using a horizontal BoxLayout (holder, horizontal glue, holder), and another time I tried using a BorderLayout (using EAST/WEST for either holder), but no matter what I did the holders would not budge - so as much as I didn't want to, I used setBounds() to position them.
Module Class (widget holders are inputLine and outputLine):
public class Module extends JPanel
{
private static final int MOD_WIDTH = 86;
private static final int MOD_HEIGHT = 60;
private int screenX, screenY, myX, myY;
private boolean moving = false;
// figure out the layout !
private JPanel inputLine, outputLine;
public Module()
{
//super(new BorderLayout());
initPanel();
initWidgets();
initMouse();
setLayout(null);
list();
}
private final void initPanel()
{
this.setSize(new Dimension(MOD_WIDTH, MOD_HEIGHT));
this.setBackground(Color.ORANGE);
}
private void initWidgets()
{
inputLine = new JPanel(new GridLayout(0, 1, 5, 5));
outputLine = new JPanel(new GridLayout(0, 1, 5, 5));
inputLine.setBounds (0, 0, 18, 60);
outputLine.setBounds(68, 0, 18, 60);
this.add(inputLine);
this.add(outputLine);
/* adding IOWidgets to test */
inputLine.add(new InputWidget());
inputLine.add(new InputWidget());
inputLine.add(new InputWidget());
outputLine.add(new OutputWidget());
outputLine.add(new OutputWidget());
outputLine.add(new OutputWidget());
outputLine.add(new OutputWidget());
inputLine.setBackground(Color.BLACK);
outputLine.setBackground(Color.BLACK);
}
Here is the abstract IOWidget class that both type of widgets (input[magenta], output[cyan]) derive from. It will have added functionality later.
public abstract class IOWidget extends JLabel
{
private static final int EDGE_SIZE = 8;
public IOWidget()
{
this.setSize(new Dimension(EDGE_SIZE, EDGE_SIZE));
}
}
Here is the InputWidget class. At the moment, it's identical to OutputWidget until I add the extra functionality, so I'll only post this one :
public class InputWidget extends IOWidget
{
public InputWidget()
{
this.setBackground(Color.MAGENTA);
}
}
In my application, modules are added to a larger JPanel. I would hope that the layout of the module is independent of how it's added to another JComponent, so I will omit the rest of the code.
Here is what the program looks like when run :
For completeness, here's the output of calling list on a single module :
rhopkins.honors.Dataflow.Module[,0,0,86x60,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,preferredSize=]
javax.swing.JPanel[,0,0,18x60,invalid,layout=java.awt.GridLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,preferredSize=]
rhopkins.honors.Dataflow.InputWidget[,0,0,8x8,invalid,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=8,height=8]]
rhopkins.honors.Dataflow.InputWidget[,0,0,8x8,invalid,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=8,height=8]]
rhopkins.honors.Dataflow.InputWidget[,0,0,8x8,invalid,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=8,height=8]]
javax.swing.JPanel[,68,0,18x60,invalid,layout=java.awt.GridLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,preferredSize=]
rhopkins.honors.Dataflow.OutputWidget[,0,0,8x8,invalid,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=8,height=8]]
rhopkins.honors.Dataflow.OutputWidget[,0,0,8x8,invalid,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=8,height=8]]
rhopkins.honors.Dataflow.OutputWidget[,0,0,8x8,invalid,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=8,height=8]]
rhopkins.honors.Dataflow.OutputWidget[,0,0,8x8,invalid,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=8,height=8]]
I'd very much like to know what I'm doing wrong. Also, I'm aware I'm new to Swing and GUI development in general, so any criticism about style/conventions/anything is welcome.
Dont use a null layout.
Your main panel should probably be a BorderLayout. You can add your widget holders to the WEST and EAST of your main panel.
The widget panel should be able to use a vertical BoxLayout. You will need to add glue before/after every widget you add to the panel. Since the BoxLayout respects the sizes of the component, you will need to override the getPreferredSize(), getMinimumSize() and getMaximumSize() methods to all return the same value. This way any extra space in the panel should be divided equally among the glue that you add.
I have a very simple Java program (see below). The GridLayout has 20 rows and 4 columns. As you know the elements are supposed to be added horizontally by (GridLayout) definition. However, I get the two elements (labels) placed one above the other, vertically.
I colored them and realised the labels take up the whole row, hence the vertical effect. But then I also used setSize(5,5) with each to make them smaller, however they still take up the whole row. Any advice as to why this happens and how to fix/set smaller size/etc?
public class Sam extends JFrame {
public JButton btn_arr;
public Container c;
public JLabel[] lbl = new JLabel[20];
public Sam()
{
c = getContentPane();
c.setLayout(new GridLayout(20,4));
lbl[1] = new JLabel("Column1");
c.add(lbl[1]);
lbl[2] = new JLabel("Column2");
c.add(lbl[2]);
show();
}
public static void main(String[] args)
{
Sam x = new Sam();
x.setVisible(true);
x.setSize(7500,4500);
}
}
You're only adding two components to the grid so they will fill it up. You need to add more components to the grid as placeholders so that it can place the original JLabels in their proper place, perhaps empty JLabels or JPanels.
As an aside, you should avoid setting the size of any Swing component. Your current size of 7500, 4500 is a bit on the large size.
As a second aside, perhaps you want to use a JTable instead here.
Edit: if you want a GridLayout with 4 columns and variable number of rows, use 0 for your GridLayout row constant:
c.setLayout(new GridLayout(0, 4));
e.g.,
import java.awt.*;
import javax.swing.*;
public class Sam extends JFrame {
public static final int COLUMN_COUNT = 4;
public JButton btn_arr;
public Container c;
public JLabel[] lbl = new JLabel[COLUMN_COUNT];
public Sam() {
c = getContentPane();
c.setLayout(new GridLayout(0, COLUMN_COUNT));
for (int i = 0; i < lbl.length; i++) {
lbl[i] = new JLabel("Column " + (i + 1));
c.add(lbl[i]);
}
}
public static void main(String[] args) {
Sam x = new Sam();
x.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
x.pack();
x.setLocationRelativeTo(null);
x.setVisible(true);
// x.setSize(7500,4500);
}
}
But still I wonder if a JTable wouldn't work better here.
One thing to keep in mind with the GridLayout is it that it is designed to cover the entire containing panel sizing the cells as equally as possible, and elements added to the cells will be expanded to fill the entire cell. So as the cell sizes change, the labels will also change in size. Effectively grid cells force an expansion/contraction in both X and Y direction of all contained elements.
One way to prevent that from happening if you must use the GridLayout is to not add the labels directly to the container that uses the GridLayout, but instead put each label inside a JPanel that uses a FlowLayout (the default) that you can set alignment of either Left, Middle or Right, then add that JPanel to the Grid container. The JPanel will be resized but it will not change the size of the Label.
Or use the GridBagLayout manager. More complex, but once you understand it, it makes life easier. But as Hovercraft mentioned, if what you are trying to do is create a grid with column headers, a JTable might be a better option.
I am making a pig latin translater using JFrame in Java. Here's my problem; I have a "quit" button that closes the program; that doesn't matter, but what does is I have no control over its alignment (or any other component). I tried using quit.setAlignmentY(BOTTOM_ALIGNMENT); in the hopes that that would align it to the bottom of the page, but nothing changed. Some help here, please?
In case anyone needs it, here's the code;
public class Main extends JFrame{
private static JLabel label, result;
private static JTextField english;
private static JButton quit;
private static String originalResult = "Translated to pig latin: ";
private static ArrayList<String> beginningSymbols = new ArrayList<>();
private static ArrayList<String> endingSymbols = new ArrayList<>();
//prompt for string to translate, display final result
public Main(){
super("Pig Latin Translator");
setLayout(new FlowLayout());
setVisible(true);
setSize(600, 300);
setDefaultCloseOperation(EXIT_ON_CLOSE);
translatingHandler th = new translatingHandler();
label = new JLabel("Enter a phrase to translate into pig latin, then press enter:");
english = new JTextField(15);
result = new JLabel(originalResult);
quit = new JButton("Quit program");
english.addActionListener(th);
quit.addActionListener(th);
quit.setAlignmentY(BOTTOM_ALIGNMENT);
add(label);
add(english);
add(quit);
add(result);
english.requestFocus();
}
public static void main(String[] args){
new Main();
}
...
}
The JButton quit is the one I'm trying to align to the bottom of the page. Thanks!
Actually you are using FlowLayout. If you take a look at FlowLayout tutorials it is mentioned that
The FlowLayout class puts components in a row, sized at their
preferred size. If the horizontal space in the container is too small
to put all the components in one row, the FlowLayout class uses
multiple rows. If the container is wider than necessary for a row of
components, the row is, by default, centered horizontally within the
container.
If you insist on using FlowLayout align your components.
Anyways take a look at Using Layout Managers. For your task appropriate layout managers will be BorderLayout.
But if you need something very flexible use GridBagLayout or MigLayout but they are a little complex to use.
So as #HovercraftFullOfEels suggested try avoiding them.
Welcome to the confusing world of Java Swing. You probably want to look into layout managers. Specifically, BorderLayout might be of interest.
I'm developing a Vaadin application and am having extreme difficulty getting some aspects of the layout as I want. The major problem right now is that I can't seem to get a vertical scroll in my layout no matter how big the size of the content is or how small the browser window is..
I have read up on the subject, I know that the hLayout and the vLayout doesn't support scrollbars but the Panel do. I've tried in many different combinations to make it work but I've only managed to get a horizontal scrollbar to generate but never a vertical one.
Another problem is that I'm building the application inside an existing "template" provided by the company. This template contains a footer containing some copyright information. This footer doesn't seem to occupy any space in the browser window with regards to the content I'm adding, which causes when viewing on smaller screens the horizontal scrollbar to appear "underneath" the footer, non-accessible... I'll provide some of the code of how it looks now.
public class InventorySimCardTable extends M2MViewBase { //M2MViewBase extends VerticalLayout
private final SPanel mainContent = Cf.panel("");
private final SPanel tabPanel = Cf.panel("");
private final SVerticalLayout tabcontent = Cf.vLayout();
protected InventoryFilterPanel inventoryFilterPanel;
#Override
protected void initComponent() {
setSizeFull();
tabPanel.setSizeFull();
tabPanel.getContent().setSizeUndefined();
Table simCardTable = new Table();
simCardTable.setWidth("1898px");
simCardTable.setPageLength(15);
tableContainer.setSizeUndefined();
tableContainer.addComponent(simCardTable);
mainContent.setWidth("99%");
mainContent.setHeight("100%");
mainContent.setContent(tableContainer);
mainContent.setScrollable(true);
centeringlayout.setSizeFull();
centeringlayout.addComponent(mainContent);
centeringlayout.setComponentAlignment(mainContent, Alignment.MIDDLE_CENTER);
tabPanel.addComponent(centeringlayout);
addComponent(tabPanel);
}
}
I would love to know if anyone sees any obvious errors in my code. And if anyone knows what property I can set on the footer CSS to have it occupy space in the content view so that the horizontal scroll doesn't appear underneath it. Thank you!
What I did to solve this issue was to structure the code as follows. This will create a vertical and horizontal scroll bar for the Panel holding my filter component and the table. Hopefully this can help someone with a similar problem.
#Override
protected void initComponent() {
super.initComponent();
if(!tableCreated) {
createSimCardsTable();
tableCreated = true;
}
mainWindow = this.getWindow();
Panel basePanel = new Panel("");
basePanel.addComponent(inventoryFilterPanel);
AbstractComponent separatorLine = Cf.horizontalLine(); //Of no signficance
separatorLine.addStyleName("m2m-horizontal-line-list-separator");
separatorLine.setWidth("99%");
basePanel.addComponent(separatorLine);
basePanel.addComponent(simCardTable);
basePanel.setSizeFull();
basePanel.getContent().setSizeUndefined(); // <-- This is the important part
addComponent(basePanel);
setExpandRatio(basePanel, 1);
}
All Vaadin components have size undefined by default, so usually there is no need to call method setSizeUndefined(). Also there is no need to call setScrollable(true), because it enables only programmatic scrolling possibility.
When I was trying to make a sense of scrolling appearance I wrote a simple skeleton of layout. Try this out as a content of the main window:
import com.vaadin.ui.HorizontalSplitPanel;
import com.vaadin.ui.Label;
import com.vaadin.ui.Panel;
import com.vaadin.ui.VerticalLayout;
public class Skeleton extends VerticalLayout {
public Skeleton() {
setSizeFull();
addComponent(new Label("Header component"));
HorizontalSplitPanel splitPanel = new HorizontalSplitPanel();
Panel leftComponent = new Panel();
Panel rightComponent = new Panel();
splitPanel.setFirstComponent(leftComponent);
splitPanel.setSecondComponent(rightComponent);
for (int i = 0 ; i < 200 ; i ++) {
leftComponent.addComponent(new Label("left"));
rightComponent.addComponent(new Label("right"));
}
leftComponent.setSizeFull();
rightComponent.setSizeFull();
addComponent(splitPanel);
setExpandRatio(splitPanel, 1);
addComponent(new Label("Footer component"));
}
}
You should see scrollbars inside the nested panels. But if setSizeFull() is removed from Skeleton layout, then it is not limited in size (by default) and grows downwards - then only the scrollbar of the whole window appears.
Add this to your styles.css
.v-verticallayout > div {
overflow-y: auto ! important;
}
First of all try to make your panel scrollable by calling setScrollable(true) method, but this will not work if you set some custom layout with setSizeFull() as this panel new layout.
If you exactly know that you application will be opened in device with small screen resolution, you simple can set for your "primary"/"main" layout some fixed width and height, or add some CSS style with params like min-width: {some value} px, min-height: {some value} px.
Based on this post, I added vertical.setSizeUndefined(); and started seeing vertical scrollbars.
setMainWindow(new Window(title ));
vertical.setSizeFull();
vertical.setHeight("100%");
toolbar = createToolbar();
vertical.addComponent(toolbar);
vertical.setExpandRatio(toolbar, 0.03f);
Component tree = buildTree();
vertical.addComponent(tree);
vertical.setExpandRatio(tree, 0.97f);
vertical.setSizeUndefined();
getMainWindow().setContent(vertical);>
The only way I could fix this now (v6.8.14) is to specify the height in px values in stead of %
Use CustomLayout, always. It's faster, more efficient and by controlling html and css easily you can acieve a graphically consistent result
I have a JTable inside of a JScrollPane. I am creating a custom cell editor for one of the columns of the table, and I want this editor to pop up a scrolling JList. I've done this by using a Popup to show a new JScrollPane containing the JList.
Everything is working, except for the position of the Popup. My custom component for the editor looks basically like this:
public class CustomPanel extends JPanel {
JTextField text = new JTextField();
JList list = new JList();
JScrollPane scroll = new JScrollPane(list);
Component owner = null;
public CustomPanel(Component owner) {
this.owner = owner;
add(text);
}
public void showPopup() {
Popup p = PopupFactory.getPopup(owner, scroll, getX(), getY()+getHeight());
p.show();
}
}
What is happening is that getX() and getY() are returning the position of the table cell relative to the JScrollPane holding it, and Popup is wanting absolute screen position. Even if I pass in owner the JScrollPane that they are relative to, it doesn't work. I get the same problem if I use text.getX() / text.getY().
How can I position my Popup directly below the TextBox?
Just a bit more background: The end goal is a multiple-select combobox that displays all of the selected items as a comma-separated list. If something else like this already exists, please don't hesitate to point me to it.
Edit: owner.getLocationOnScreen().y + getY() doesn't work when the scroll pane is anywhere but scrolled all the way up. However, just plain getLocationOnScreen().y DOES work. Problem solved, thank you.
You can query the absolute screen position with Component.getLocationOnScreen(). Is that what you're looking for?