0

Ive got an Application that should display tiles of different "sizes" (better different spans) in different positions on a 8x6 Grid. These positions change and new tiles are added at runtime. I tried different Layouts to accomplish this but without success. BoxLayout: No Chance to span Components over multiple Cells GridLayout: No Chance to span Components over multiple Cells GridBagLayout: I used this Layout until I realized that when I try to merge two cells the layout just expands the cell where the component is in but not merges the cells. This is also described in the documentation

GridBagLayout does not allow components to span multiple rows.

Example:

How it looks when i add dummy panels to my GridBagLayout:

Grid with faint white lines

How it actually look when I add my tiles:

Tiles grid

How it should look like:

Actual grid

Is there a Layout that can accomplish this task or is there another possibility to get this thing to work (JTable?!?)? Would be great if you could provide me with some new ideas (when possible with some tutorials). I use Netbeans 7.3 without any GUI Builder extensions.

Code EDIT:

Layout (created by Netbeans)

java.awt.GridBagLayout contentPaneLayout = new java.awt.GridBagLayout();
contentPaneLayout.columnWidths = new int[] {240};
contentPaneLayout.rowHeights = new int[] {160};
contentPane.setLayout(contentPaneLayout);

Adding a Panel (called for every Panel)

GridBagConstraints c = new GridBagConstraints();
c.gridx = positionx;
c.gridy = positiony;
c.insets = new Insets(2, 2, 2, 2);
c.weightx = width == 0 ? 2 : width;
c.weighty = height == 0 ? 2 : height;
c.anchor = GridBagConstraints.FIRST_LINE_START;
contentPane.add(myPanel, c);
user2346363
  • 31
  • 1
  • 8
  • `GridBagLayout` can do it (it supports row span and col span). I think `MigLayout` should be able to do it too. – Guillaume Polet May 24 '13 at 09:53
  • but how to do it with `GridBagLayout`? I used `GridBagConstraints weightx` and `weighty` but this produces to output as shown above. I had a look onto MigLayout too but it seems that it just adds the components one after the other, but in my case the components should be added to a "random" position within the grid. – user2346363 May 24 '13 at 10:05
  • Post your code and I'll take a look at it. Just explaining your code in comments is unefficient. – Guillaume Polet May 24 '13 at 11:13
  • yes GBL can support row and col span – Oliver Watkins May 24 '13 at 11:44
  • using c.weightx = width == 0 ? 2 : width; c.weighty is wrong. Weight is not Span – Oliver Watkins May 24 '13 at 11:45
  • I've got code that I think illustrates my own lack of understanding about this, but I'm not the original poster. Is there a standard SO way of posting that code to this question? It won't go in a comment, and it isn't an answer, it's a continuation of the question. But I don't know anything to do except click on the 'answer' button to post code. – arcy May 24 '13 at 12:36
  • not sure. I find i have the same problem. Just state 'continuation of question' in the answer. thats should be pretty safge – Oliver Watkins May 24 '13 at 12:45
  • What's the base class of your tiles? Do they extend JPanel? Do these tiles have a fixed size, or can GridBaglayout adjust the sizes? – Gilbert Le Blanc May 25 '13 at 14:11
  • See if this question gives you any ideas. http://stackoverflow.com/questions/16316593/change-jframe-shape – Gilbert Le Blanc May 25 '13 at 14:15

4 Answers4

1

Don't mix up weightX with gridWidth. It should be like this :

GridBagConstraints c = new GridBagConstraints();
c.gridx = positionx;
c.gridy = positiony;
c.insets = new Insets(2, 2, 2, 2);
c.gridWidth= width == 0 ? 2 : width; //should be
c.gridHeight = height == 0 ? 2 : height; //should be
c.anchor = GridBagConstraints.FIRST_LINE_START;
contentPane.add(myPanel, c);
Oliver Watkins
  • 12,575
  • 33
  • 119
  • 225
1

I'm sorry, I still don't get it. This is a continuation of the question, not an attempt at an answer.

I have never been able to figure out GridBagLayout; it is, to me, a sterling example of a component that is not explained. I've never seen an explanation of many of its constraints, etc., just examples. But what I want to do never looks like the examples, and I hate futzing with it by trial and error, hoping to happen on a combination that does what I want. What a way to program.

EDIT: Ok, now it's an answer, at least to the continuation question. Through typical trial-and-error I believe I have discovered the missing principle, and provide it here so it can get lost while someone looks in the official documentation for it.

The Oracle "How to use GridBagLayout" contains the following: "...GridBagLayout places components in rectangles (cells) in a grid, and then uses the components' preferred sizes to determine how big the cells should be"

It is easy to miss the implication of this: gridbag determines the width of a column and the height of a row by using the preferred sizes of all the components in that column/row. Think of GBL, at time of realization, as going through all the components that are going to appear in a column, determining the largest preferred-size width, and making that the width of the first column, etc.

The code below attempts to treat the GridBagLayout panel as a grid with columns and rows determined by the 'x','y','width', and 'height' attributes of the components within the panel, and GBL evidently ignores that.

So that's why my program below doesn't do what I expected; I could fiddle with setting the preferred sizes of the components, raising the ire of those who claim programs should not do that. If I did have this problem to solve, I wouldn't, in fact, do that.

I can't suggest anything definitive to the OP; after this exercise, I still prefer GroupLayout (with some additions of my own) for laying out components. To choose a good way to solve the original problem, we would need to know more about how the sizes of the components are chosen and what flexibility the OP needed.

Here's what I've gotdoesn't work:

import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.border.Border;

/**
 * trying to do this:
 * <pre>
 * +----------------+----------------+--------+
 * |                |                |        |
 * |     tile 1     |     tile 3     |        |
 * |                |                |        |
 * +----------------+----------------+ tile 5 |
 * |                |                |        |
 * |                |                |        |
 * |                |                |        |
 * |     tile 2     +----------------+--------+-------+
 * |                |                |                |
 * |                |     tile 4     |     tile 6     |
 * |                |                |                |
 * |----------------+----------------+----------------+
 * 
 * </pre>
 */
public class Tiles extends JFrame
{
  private static final long serialVersionUID = 1L;
  private GridBagConstraints constraints = new GridBagConstraints();
  private Border lineBorder = BorderFactory.createLineBorder(Color.black);
  JPanel gblPanel = new JPanel(new GridBagLayout());

  public static void main(String[] args)
  {
    Tiles tiles = new Tiles();
    tiles.go();
  }

  public void go()
  {
    createUI();
    setVisible(true);
  }

  private void createUI()
  {
    setDefaultCloseOperation(DISPOSE_ON_CLOSE);
    addLabelToGblPanel("1", 2, 1, 0, 0);  // label, width, height, x position, y position
    addLabelToGblPanel("3", 2, 1, 2, 0);
    addLabelToGblPanel("5", 1, 2, 4, 0);
    addLabelToGblPanel("2", 2, 2, 0, 1);
    addLabelToGblPanel("4", 2, 1, 2, 2);
    addLabelToGblPanel("6", 2, 1, 4, 2);
    getContentPane().add(gblPanel);
    pack();
  }

  private void addLabelToGblPanel(String num, int width, int height, int x, int y)
  {
    String numString = "Tile " + num;
    JLabel tile = new JLabel(numString, SwingConstants.CENTER);
    tile.setBorder(lineBorder);
    constraints.ipadx = 25;
    constraints.ipady = 25;
    constraints.gridwidth = width;
    constraints.gridheight = height;
    constraints.gridx = x;
    constraints.gridy = y;
//    constraints.fill = GridBagConstraints.BOTH;
//    constraints.weightx = 0.5;
//    constraints.weighty = 0.5;
    gblPanel.add(tile, constraints);
  }
}

Obviously I've got something wrong; what I've got shows up as a 3x2 grid. The sizes and positions I've put onto them make no difference whatsoever. Is there anyone who can explain how the constraints work to make it do this?

This is what drove me to learn how to do GroupLayout by hand...

arcy
  • 12,845
  • 12
  • 58
  • 103
  • +1 for bothering, all coordinates are accesible from 1st row, becasue GBC is based 1st column, you can to put invisible, whatever JComponent, JLabel for example, then this JLabel creates coordinates in 1st column for rest of rows – mKorbel May 25 '13 at 09:45
  • I'm afraid I don't understand your comment. Is there some adjustment of my code you can suggest that will make the layout look like the target? – arcy May 25 '13 at 11:20
1

With the help of the answer of Oliver I managed to get the layout to work. Oliver is right, in my orginal code I mixed up weightX and gridWidth (due to various tries). But when I just exchange weigthX/Y with gridWidth/Height then no tiles are shown. So i needed to define a weight of 1 as well.

So the code to add a panel looks like this:

GridBagConstraints c = new GridBagConstraints();
c.gridx = positionx;
c.gridy = positiony;
c.insets = new Insets(2, 2, 2, 2);
c.gridwidth = width == 0 ? 2 : width;
c.gridheight = height == 0 ? 2 : height;
c.weightx = 1;
c.weighty = 1;
c.anchor = GridBagConstraints.FIRST_LINE_START;
contentPane.add(myPanel, c);

But GridBagLayout still merged my rows and columns I had to add some dummy panels as described in the question GridBagLayout in Java Column Issue as well (before adding my tiles)

for (int i = 0; i < columnCount; i++) {
        for (int j = 0; j < rowCount; j++) {
            GridBagConstraints c = new GridBagConstraints();
            c.gridx = i;
            c.gridy = j;
            c.insets = new Insets(2, 2, 2, 2);
            c.weightx = 1;
            c.weighty = 1;
            c.gridheight = 1;
            c.gridwidth = 1;
            c.anchor = GridBagConstraints.FIRST_LINE_START;
            NewJPanel p = new NewJPanel(); //A JPanel I created with the same background, width and height of a cell. May work with a normal JPanel as well.
            //p.setVisible(false);
            p.setOpaque(false);
            contentPanel.add(p, c);
        }
    }

My tiles extend JPanel and are created like this (don't know if needed):

Size size = new Dimension(cellWidth * gridbagconstraint.gridwidth, cellHeight * gridbagconstraint.gridheight);
setSize(size);
setMaximumSize(size);
setMinimumSize(size);
setPreferredSize(size);

Thank you guys for your support!

Community
  • 1
  • 1
user2346363
  • 31
  • 1
  • 8
0

Turns out that a null layout works the best.

Here's the original Widget organizer I created.

Original Widget Organizer

Here's the Widget organizer after I moved a couple of widgets.

Moved Widgit Organizer

I put this together as one source module so it would be easy to copy and paste. In an actual project, these classes should be in separate source modules.

Here's the code.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;

public class WidgetOrganizer implements Runnable {

    @Override
    public void run() {
        createFrame();
    }

    private void createFrame() {
        JFrame frame = new JFrame("Widget Organizer");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        DragAndDrop dragAndDrop = new DragAndDrop();

        WidgetFactory widgetFactory = new WidgetFactory();
        DrawingPanel drawingPanel = new DrawingPanel(widgetFactory.getWidgets());
        drawingPanel.setDragAndDrop(dragAndDrop);
        drawingPanel.drawWidgets();

        dragAndDrop.setDragAndDrop(drawingPanel);

        frame.add(drawingPanel.getPanel());
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new WidgetOrganizer());
    }

    public class DrawingPanel {

        private JPanel          panel;

        private List<Widget>    widgets;

        public DrawingPanel(List<Widget> widgets) {
            super();
            this.widgets = widgets;
            createDrawingPanel();
        }

        private void createDrawingPanel() {
            panel = new JPanel();
            panel.setLayout(null);
            panel.setPreferredSize(new Dimension(600, 500));
        }

        public void drawWidgets() {
            panel.removeAll();
            for (Widget widget : widgets) {
                JPanel widgetPanel = widget.getPanel();
                widgetPanel.setBounds(widget.getWidgetBounds());
                panel.add(widgetPanel);
            }
            panel.validate();
            panel.repaint();
        }

        public JPanel getPanel() {
            return panel;
        }

        public List<Widget> getWidgets() {
            return widgets;
        }

        public void setDragAndDrop(DragAndDrop dragAndDrop) {
            panel.addMouseListener(dragAndDrop);
            panel.addMouseMotionListener(dragAndDrop);
        }

    }

    public class Widget {

        private Border      normalBorder;
        private Border      movingBorder;

        /** x, y, width, and height in pixels */
        private Rectangle   widgetBounds;

        private JLabel      originLabel;

        private JPanel      panel;

        private String      widgetName;

        public Widget(String widgetName) {
            this.widgetName = widgetName;
            this.normalBorder = BorderFactory.createLineBorder(Color.BLUE, 3);
            this.movingBorder = BorderFactory.createLineBorder(Color.RED, 3);
        }

        public void setWidgetBounds(Rectangle widgetBounds) {
            this.widgetBounds = widgetBounds;
            setOriginLabel();
        }

        public void createWidget() {
            panel = new JPanel();
            panel.setBorder(normalBorder);
            panel.setLayout(new GridBagLayout());
            panel.setPreferredSize(new Dimension(widgetBounds.width,
                    widgetBounds.height));

            GridBagConstraints gbc = new GridBagConstraints();

            JLabel nameLabel = new JLabel(widgetName + " widget");
            panel.add(nameLabel, gbc);

            originLabel = new JLabel();
            setOriginLabel();
            gbc.gridy = 1;
            panel.add(originLabel, gbc);

            String s = widgetBounds.width + "x" + widgetBounds.height
                    + " pixels";
            JLabel sizeLabel = new JLabel(s);
            gbc.gridy = 2;
            panel.add(sizeLabel, gbc);
        }

        private void setOriginLabel() {
            if (originLabel != null) {
                String s = widgetBounds.x + "," + widgetBounds.y;
                originLabel.setText(s);
            }
        }

        public void setNormalBorder() {
            panel.setBorder(normalBorder);
        }

        public void setMovingBorder() {
            panel.setBorder(movingBorder);
        }

        public boolean isClickedWidget(Point clickedPoint) {
            int x = clickedPoint.x - widgetBounds.x;
            int y = clickedPoint.y - widgetBounds.y;

            boolean insideWidth = (x >= 0) && (x < widgetBounds.width);
            boolean insideHeight = (y >= 0) && (y < widgetBounds.height);

            return insideWidth && insideHeight;
        }

        public JPanel getPanel() {
            return panel;
        }

        public Rectangle getWidgetBounds() {
            return widgetBounds;
        }

    }

    public class WidgetFactory {

        private List<Widget>    widgets;

        public WidgetFactory() {
            widgets = new ArrayList<Widget>();
            createWidgets();
        }

        private void createWidgets() {
            Widget widget = new Widget("Square");
            widget.setWidgetBounds(new Rectangle(0, 0, 150, 150));
            widget.createWidget();
            widgets.add(widget);

            widget = new Widget("Tall");
            widget.setWidgetBounds(new Rectangle(150, 0, 150, 250));
            widget.createWidget();
            widgets.add(widget);

            widget = new Widget("Wide");
            widget.setWidgetBounds(new Rectangle(0, 250, 250, 150));
            widget.createWidget();
            widgets.add(widget);
        }

        public List<Widget> getWidgets() {
            return widgets;
        }

    }

    public class DragAndDrop extends MouseAdapter {

        /** drag and drop resolution in pixels */
        private int             resolution  = 10;

        private DrawingPanel    drawingPanel;

        private List<Widget>    widgets;

        private Point           mouseClicked;

        private Rectangle       movingWidgetBounds;

        private Widget          movingWidget;

        public void setDragAndDrop(DrawingPanel drawingPanel) {
            this.drawingPanel = drawingPanel;
            this.widgets = drawingPanel.getWidgets();
        }

        @Override
        public void mousePressed(MouseEvent event) {
            mouseClicked = event.getPoint();
            for (Widget widget : widgets) {
                if (widget.isClickedWidget(mouseClicked)) {
                    movingWidget = widget;
                    movingWidgetBounds = copy(widget.getWidgetBounds());
                    return;
                }
            }
            movingWidget = null;
        }

        @Override
        public void mouseReleased(MouseEvent event) {
            if (movingWidget != null) {
                movingWidget.setNormalBorder();
                Rectangle r = calculateWidgetOrigin(event);
                movingWidget.setWidgetBounds(r);
                drawingPanel.drawWidgets();
                movingWidget = null;
            }
        }

        @Override
        public void mouseDragged(MouseEvent event) {
            if (movingWidget != null) {
                movingWidget.setMovingBorder();
                Rectangle r = calculateWidgetOrigin(event);
                movingWidget.setWidgetBounds(r);
                drawingPanel.drawWidgets();
            }
        }

        private Rectangle calculateWidgetOrigin(MouseEvent event) {
            Rectangle r = copy(movingWidgetBounds);
            Point endPoint = event.getPoint();

            int diffX = endPoint.x - mouseClicked.x;
            int diffY = endPoint.y - mouseClicked.y;

            diffX = (diffX + resolution / 2) / resolution * resolution;
            diffY = (diffY + resolution / 2) / resolution * resolution;

            r.x += diffX;
            r.y += diffY;

            return r;
        }

        private Rectangle copy(Rectangle r) {
            Rectangle s = new Rectangle();
            s.x = r.x;
            s.y = r.y;
            s.width = r.width;
            s.height = r.height;
            return s;
        }

    }

}
Gilbert Le Blanc
  • 50,182
  • 6
  • 67
  • 111