0

I have 3 JPanels (let's name each one as "BLOCK") in another JPanel (let's name it "CENTER_DECORS") inside JScollPane that sits in one of tabs of JTabbedPane.

Now: when I dynamically change the height of any of those BLOCK-s ("fold" them by setting their height from 150 to 20) I want the other BLOCK-s to update their vertical position accordingly so that they would still stack on each other, but as of now I have "hole" between the BLOCK with changed (lowered) height and the next one.

I am using BoxLayout for the CENTER_DECORS JPanel:

CENTER_DECORS.setLayout(new BoxLayout(CENTER_DECORS, BoxLayout.Y_AXIS));

After I change the height of a BLOCK...

BLOCK.setSize(BLOCK.getWidth(), 20);

...I call immediately this code:

CENTER_DECORS.repaint();
CENTER_DECORS.validate();
CENTER_DECORS.revalidate();

Strangely enough nothing happens at all, not even the height is changed, but when I leave just:

CENTER_DECORS.repaint();

...then it at least change the height but no stacking-on-each-other occurs.

UPDATE: the solution must have option to storing folded/unfolded state with each row so when program starts it could gp to the appropriate state (folded/expanded).

Does anyone know of a solution so that those BLOCK-s would still stick to each other vertically when their height is changed?

EDIT: only after I fully tested @MadProgrammer solution I realized the duplicate answer is not the correct one as it only allows 1 "block" to be expanded/opened at a time and I need it to be expanded freely no matter how many "blocks" + his code starts with everything folded/collapsed and I need it to be in their normal that is - most of the time - expanded state + my solution doesn't require any special listener to be used (except MouseListener for button states which is normal, of course), thus less coding.

So, after fiddling around whole day I finally made my own version inspired by @MadProgrammer's approach - my code now behaves exactly s I wanted (reason I was searching for this solution was that my application may have like tens of different "block" in a JPanel that are quite hard to manage as they occupy too many space which may be most of the time not needed so option to fold those up would be very good option to have), so this code below is actually the right solution, please, remove that "duplicate" thing as it is no longer accurate (see my separate answer below).

Cœur
  • 37,241
  • 25
  • 195
  • 267
theoneiam
  • 11
  • 6
  • Nothing strange about it as most layout managers don't respect a component's size. They usually respect its `preferredSize` and sometimes its `minimumSize` and `maximumSize`. – Hovercraft Full Of Eels Sep 14 '18 at 23:23
  • @HovercraftFullOfEels that is OK and I am aware of it, my main problem/question is how to make those BLOCKs stick to each other still after I change height of some of them, you know, like flexi-menu on some websites – theoneiam Sep 14 '18 at 23:27
  • I'm not sure what you're trying to do, what visual effect you're trying to gain, but I suspect that you want to *nest* JPanels, using various layouts..... – Hovercraft Full Of Eels Sep 14 '18 at 23:29
  • imagine 3 JPanels one after another each of height 150 - they stick to each other, that is they are stacked up under each other - no gap between them: where one ends the other one starts (all in vertical order), now when I change the height of 1st one to 20 normally there would be big gap of 130 but I want that all the remaining two JPanels would update their position and would stick to the 1st JPanel – theoneiam Sep 14 '18 at 23:33
  • 1
    Do you mean something [like this for example](https://stackoverflow.com/questions/32368190/too-many-jpanels-inside-a-jpanel-with-gridbaglayout/32372506#32372506)? – MadProgrammer Sep 14 '18 at 23:46
  • 1) For better help sooner, post a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). 2) Provide ASCII art or a simple drawing of the *intended* layout of the GUI at minimum size, and if resizable, with more width and height - to show how the extra space should be used. – Andrew Thompson Sep 14 '18 at 23:47
  • @MadProgrammer yes, exactly that!!! – theoneiam Sep 14 '18 at 23:51
  • @MadProgrammer so now I finally going to test your code of accordion layout (which BTW look perfect to me - thanx a lot once again) but I see only one "row" can be opened at a time - is there a way that any number of them can be opened/closed? Cos that is what I need... – theoneiam Sep 15 '18 at 12:52
  • @elbilbub Without testing, `private Component expanded;` needs to support 0 or more components, so probably needs to become a `List` of `Component`s – MadProgrammer Sep 15 '18 at 15:19
  • @MadProgrammer so I made it inspired by your solution, thank you very much (see my updated post)! – theoneiam Sep 15 '18 at 19:03
  • @HovercraftFullOfEels please, see my updated initial post - this is not a duplicate anymore – theoneiam Sep 15 '18 at 19:10
  • @elbilbub, You should NOT be using a MouseListener to click a button. Use an ActionListener. Post a solution that we can copy/paste/compile/test, just like MadProgrammers solution. – camickr Sep 16 '18 at 00:08
  • @camickr as I said several times on my other posts I CANNOT POST WHOLE CODE cos it s huge, I have everythng in separate classes, I would have to post my whole project to fullfill your/StackOverflow needs/requirements which will not happen, of course + I do not get what you don't understand from the code I posted? I guess anyone with a little understanding of JAVA syntax/coding has to understand it with ease + I have tp use MouseListener as I also tracks over/out/exited states, so please do not judge from the small snippet I posted ;-) – theoneiam Sep 16 '18 at 08:50
  • `as I said several times on my other posts` - as we had said several times we are NOT interested in your application. We are only interested in the "concept" you are demonstrating. Your "concept" is displaying "multiple" accordion panels. So all you need is a panel with a JLabel to represent the panel from your real application. The code you post shows how you use your custom layout manger and how you build the panels . In other words you do what MadProgrammer did!! Did you not understand his code because you were able to copy/paste/test the code? Well do the same for us with your code. – camickr Sep 16 '18 at 14:00
  • `I have tp use MouseListener as I also tracks over/out/exited states` - You are trying to promote a solution to a problem. Well then you use the proper tool for the problem. So you use it to track enter/exited events, but for a click even you use an ActionListener. If you want to use a MouseListener for everything, then use a JLabel, like was done in MadProgrammers code. – camickr Sep 16 '18 at 14:02
  • @camickr sorry but I find your comment insulting and absolutelly of no help at all, if you cannot help or do not know the answer it is OK, I understood you cannot get anything from what I posted - I accept it, but please, stop flooding this post and please let this space for those who actually would understand that I already has a solution, and, BTW, say it in another way: seeing your comment I wonder myself why I am trying to provide the right solution at all with ppl like you? Anyway, have a nice day, I can live with the status of this post as it is, actually... – theoneiam Sep 16 '18 at 14:06
  • I have no idea what you find insulting. I was explaining the concept of creating an [mcve] since you don't appear to have grasped what is required to create one.. What do you mean "say it another way?" I simply pointed out that you were able to solve your problem because you got complete code from MadProgrammer and I was asking you to return the courtesy, since you say you have an improved (more flexible?) solution. – camickr Sep 16 '18 at 17:27
  • @camickr there ya go - I updated my post with the final solution that you can finally test yourself... – theoneiam Sep 17 '18 at 17:01
  • @HovercraftFullOfEels can you please review my post once again as I have posted workable solution that anyone can test, this is not duplicate of MadProgrammer post... – theoneiam Sep 17 '18 at 17:22
  • If you have a solution, post it as an answer and accept it. No need to edit the question. – shmosel Sep 18 '18 at 03:57
  • This was original closed as a duplicate of: https://stackoverflow.com/questions/32368190/too-many-jpanels-inside-a-jpanel-with-gridbaglayout/32372506#32372506 which demonstrates how to expand/collapse a single panel in a group of panels. The requirement here is to be able to expand "multiple" panels at one time in a group of panels. I re-opened the question to allow people to post their solutions. – camickr Sep 18 '18 at 03:57
  • You still haven't grasped the concept of an [mcve]. It should only contain code directly relevant to the stated problem which is how to expand/collapse the size of a panel. We don't care about the font, foreground, background, borders etc of your solution. We don't care about the mouseEntered/exited events. None of the above is directly related to your question and posting code for all those things makes the code longer. I have include a proper "MCVE" with may answer. – camickr Sep 18 '18 at 04:08
  • I don't care what you want to see or not, man - who cares? can you test it? yes you can. Is there anything you find unnecessary in the code but have no effect on code itself? Fine, just ignore it or remove it - what is your problem? I have no nerves arguing with you anymore as it i obvious you just trying to find any ridiculous pseudo-problem you can which have no factual effect on merit of the question/problem - case closed for me (I'm deciding delete this post as it becomes place for your absurd attacks, like have some font specified in my code any relevance to the problem itself? gee!) – theoneiam Sep 18 '18 at 08:26

2 Answers2

0

Here is an example of an approach that will allow you to expand multiple panels at one time.

The solution contains an "ExpandingPanel" which essentially has two components:

  1. in the BorderLayout.PAGE_START a button which when clicked will expand/collapse the panel
  2. In the BorderLayout.CENTER the component which will be toggled visible/invisible

The ExpandingPanels are added to a parent container using a vertical BoxLayout so the vertical size changes as the state of each ExpandingPanel is toggled.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ExpandingPanel2 extends JPanel
{
    private JPanel expanding;

    public ExpandingPanel2(String text, Component expanding)
    {
        setLayout( new BorderLayout() );

        JButton button = new JButton( text );
        add(button, BorderLayout.PAGE_START);

        expanding.setVisible(false);
        add(expanding, BorderLayout.CENTER);

        button.addActionListener( new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                expanding.setVisible( !expanding.isVisible() );

                revalidate();
                repaint();
            }
        });
    }

    @Override
    public Dimension getMaximumSize()
    {
        Dimension size = getPreferredSize();
        size.width = Integer.MAX_VALUE;

        return size;
    }

    private static void createAndShowGUI()
    {
        Box content = Box.createVerticalBox();

        content.add( createExpandingPanel("Red", Color.RED, 50));
        content.add( createExpandingPanel("Blue", Color.BLUE, 100));
        content.add( createExpandingPanel("Green", Color.GREEN, 200));

        JFrame frame = new JFrame("Expanding Panel");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//        frame.add( content);
        frame.add( new JScrollPane(content));
        frame.setLocationByPlatform( true );
        frame.setSize(200, 300);
        frame.setVisible( true );
    }

    private static ExpandingPanel2 createExpandingPanel(String text, Color background, int size)
    {
        JPanel content = new JPanel();
        content.setBackground(background);
        content.setPreferredSize(new Dimension(100, size));

        return new ExpandingPanel2(text, content);
    }


    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
}
camickr
  • 321,443
  • 19
  • 166
  • 288
  • your code changes components width as soon as scroller appears - this is not exactly what I was asking for...besides: your code is folded when it starts and I need it to be in the expanded state + it has to have option for each component ("row") to maintain its folded/expanded state when the program starts which your code does not allow (mine does) - this is completely different to what I wanted, wrong answer, sorry! – theoneiam Sep 18 '18 at 08:44
  • Of course it is not exactly what you asked for because you just updated the requirements 4 days after the question was asked. Nobody will ever have exactly the same requirements as you. The point of the answer was to show you how to create a [mcve] to demonstrate a concept, of "folding panels" which is what your original question was about. Other (changing) requirements are up to you to implement. – camickr Sep 18 '18 at 14:25
0

This is exactly what I wanted - this is the exact solution to my question/problem (REMEMBER: this is just a tiny fraction derivate from my complete code where zillions of other stuff was omitted so it may seem like there is a lot of unnecessary stuff going on there BUT it have reason for me having it there and I would not go to create example "just-like-that" from a scratch - if there is anything you find unnecessary just ignore it or if you cannot stand it just remove it, I could not go that way as it would require for me to completely re-write what I am using in my actual code with quantum other separate classes that thus it would lost its functionality (broke my code in my program) once introduced back to my original huge program code - simply put: everything you see in the demo have to be the way it is tho it may look unnecessary to you + IT HAS NO EFFECT TO CODE FUNCTIONALITY WHATSOEVER!

To test my code (I am using NetBeans 8.0.2):

  • start new NetBeans project
  • create new class named "foldingcomponentsdemo"
  • copy the code below
  • paste it there
  • run (compile) it

I am not here to please some programmers feelings, I am posting working solution the the question I have asked.

package foldingcomponentsdemo;

import static foldingcomponentsdemo.Constants.BLOCK_COLOR_BG;
import static foldingcomponentsdemo.Constants.BLOCK_H;
import static foldingcomponentsdemo.Constants.BLOCK_HEADER_BG_COLOR;
import static foldingcomponentsdemo.Constants.BLOCK_HEADER_BORDER_COLOR;
import static foldingcomponentsdemo.Constants.BLOCK_HEADER_FG_COLOR;
import static foldingcomponentsdemo.Constants.BLOCK_HEADER_H;
import static foldingcomponentsdemo.Constants.BLOCK_HEADER_LABEL_FG_COLOR;
import static foldingcomponentsdemo.Constants.BLOCK_HEADER_LABEL_FONT_SIZE;
import static foldingcomponentsdemo.Constants.BUTTON_FOLD_COLOR_BG;
import static foldingcomponentsdemo.Constants.BUTTON_FOLD_COLOR_BG_DOWN;
import static foldingcomponentsdemo.Constants.BUTTON_FOLD_COLOR_BG_OVER;
import static foldingcomponentsdemo.Constants.BUTTON_WH;
import static foldingcomponentsdemo.Constants.GAP;
import static foldingcomponentsdemo.Constants.INNER_W;
import static foldingcomponentsdemo.Constants.LABEL_TEXT;
import static foldingcomponentsdemo.Constants.MAINWINDOW_H;
import static foldingcomponentsdemo.Constants.MAINWINDOW_W;
import static foldingcomponentsdemo.Constants.MARGIN;
import static foldingcomponentsdemo.Constants.NUMBER_OF_BLOCKS;
import static foldingcomponentsdemo.FoldingComponentsDemo.getScene;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

class Constants {

    public static final int MARGIN = 2;
    public static final int BLOCK_H = 100;
    public static final int NUMBER_OF_BLOCKS = 5;
    public static final int SCROLLER_HOR_WIDTH = 26;
    public static final int MAINWINDOW_W = 300;
    public static final int MAINWINDOW_H = BLOCK_H * (NUMBER_OF_BLOCKS - 2);
    public static final int INNER_W = MAINWINDOW_W - SCROLLER_HOR_WIDTH;
    public static final int BLOCK_HEADER_H = 20;
    public static final int BUTTON_WH = BLOCK_HEADER_H - (MARGIN * 2);
    public static final int BLOCK_HEADER_LABEL_FONT_SIZE = 11;
    public static final Color BLOCK_HEADER_BG_COLOR = Color.decode("#000000");
    public static final Color BLOCK_HEADER_FG_COLOR = Color.decode("#cccccc");
    public static final Color BLOCK_HEADER_BORDER_COLOR = Color.decode("#777777");
    public static final Color BUTTON_FOLD_COLOR_BG = Color.decode("#ff0000");
    public static final Color BUTTON_FOLD_COLOR_BG_OVER = Color.decode("#999999");
    public static final Color BUTTON_FOLD_COLOR_BG_DOWN = Color.decode("#cccccc");
    public static final Color BLOCK_COLOR_BG = Color.decode("#555555");
    public static final Color BLOCK_HEADER_LABEL_FG_COLOR = Color.decode("#ffffff");
    public static final String GAP = " ";
    public static final String LABEL_TEXT = "click red button on right to (un)fold";

}

public class FoldingComponentsDemo {

    private static Block block;
    private static final FoldingContainer SCENE = new FoldingContainer();
    private static final JScrollPane SCROLLER = new JScrollPane(SCENE);

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            createGUI();
        });
    }

    private static void createGUI() {

        for (int i = 0; i < NUMBER_OF_BLOCKS; i++) {
            block = new Block("BLOCK_" + i, INNER_W, BLOCK_H);
            SCENE.add(block);
        }

        JFrame frame = new JFrame("Folding components");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(SCROLLER);
        frame.setSize(MAINWINDOW_W, MAINWINDOW_H);
        frame.setVisible(true);
    }

    public static FoldingContainer getScene() {
        return SCENE;
    }
}

class Block extends JPanel {

    private static Dimension BLOCK_SIZE;
    public boolean isFolded;

    public Block(String name, int innerW, int blockHeight) {

        this.isFolded = false;
        BLOCK_SIZE = new Dimension((int) innerW, blockHeight);

        setName(name);
        setLayout(null);
        setPreferredSize(BLOCK_SIZE);
        setMinimumSize(BLOCK_SIZE);
        setMaximumSize(BLOCK_SIZE);
        setBackground(BLOCK_COLOR_BG);
        setBorder(BorderFactory.createLineBorder(Color.black, 1, false));
        add(new Header());
    }
}

class Label extends JLabel {

    public Label(String text, int innerW) {

        setText(text);
        setSize(innerW, BLOCK_HEADER_H);
        setForeground(BLOCK_HEADER_LABEL_FG_COLOR);
        setFont(new Font("Tahoma", Font.PLAIN, BLOCK_HEADER_LABEL_FONT_SIZE));
    }
}

class Header extends JPanel {

    public Header() {
        setLayout(null);
        setSize(INNER_W, BLOCK_HEADER_H);
        setBackground(BLOCK_HEADER_BG_COLOR);
        setForeground(BLOCK_HEADER_FG_COLOR);
        setBorder(BorderFactory.createLineBorder(BLOCK_HEADER_BORDER_COLOR, 1, false));

        add(new Label(GAP + LABEL_TEXT, INNER_W));

        JButton BUTTON_FOLD = new JButton();
        BUTTON_FOLD.setBounds(INNER_W - (BUTTON_WH * 2), MARGIN, BUTTON_WH, BUTTON_WH);
        BUTTON_FOLD.setMargin(new Insets(0, 0, 0, 0));
        BUTTON_FOLD.setFocusPainted(false);
        BUTTON_FOLD.setBackground(BUTTON_FOLD_COLOR_BG);
        BUTTON_FOLD.setCursor(new Cursor(Cursor.HAND_CURSOR));
        BUTTON_FOLD.addMouseListener(new MouseAdapter() {
            public void mouseEntered(MouseEvent e) {
                BUTTON_FOLD.setBackground(BUTTON_FOLD_COLOR_BG_OVER);
            }

            public void mouseExited(MouseEvent e) {
                BUTTON_FOLD.setBackground(BUTTON_FOLD_COLOR_BG);
            }

            public void mousePressed(MouseEvent e) {
                BUTTON_FOLD.setBackground(BUTTON_FOLD_COLOR_BG_DOWN);
            }

            public void mouseClicked(MouseEvent e) {
                BUTTON_FOLD.setBackground(BUTTON_FOLD_COLOR_BG_DOWN);
                Block blck = (Block) BUTTON_FOLD.getParent().getParent();
                blck.isFolded = !blck.isFolded;
                ((FoldingLayout) getScene().getLayout()).setExpanded(blck);
            }
        });

        add(BUTTON_FOLD);
    }
}

class FoldingContainer extends JPanel {

    FoldingLayout layout;

    public FoldingContainer() {
        setName("Folding container");
        layout = new FoldingLayout();
        setLayout(layout);
    }
}

class FoldingLayout implements LayoutManager {

    private Component block;
    private String initialised;

    public void setExpanded(Component BLOCK) {
        this.block = BLOCK;
        this.initialised = "yes";
        layoutContainer(block.getParent());
    }

    public Component getExpanded() {
        return block;
    }

    @Override
    public void addLayoutComponent(String name, Component comp) {
    }

    @Override
    public void removeLayoutComponent(Component comp) {
    }

    @Override
    public Dimension preferredLayoutSize(Container cont) {
        return minimumLayoutSize(cont);
    }

    @Override
    public Dimension minimumLayoutSize(Container cont) {
        int width = INNER_W;
        int height = 0;
        for (Component comp : cont.getComponents()) {
            height += comp.getHeight();
        }
        return new Dimension(width, height);
    }

    @Override
    public void layoutContainer(Container cont) {
        Insets insets = cont.getInsets();
        int x = insets.left;
        int y = insets.top;

        for (Component comp : cont.getComponents()) {
            if (initialised == null) {
                comp.setSize(comp.getMinimumSize().width, comp.getMinimumSize().height);
            } else {
                if (((Block) comp).isFolded) {
                    comp.setSize(comp.getMinimumSize().width, BLOCK_HEADER_H);
                } else {
                    comp.setSize(comp.getMinimumSize().width, comp.getMinimumSize().height);
                }
            }
            comp.setLocation(x, y);
            y += comp.getHeight();
        }

        // update for scroller
        cont.getParent().revalidate();
    }
}
theoneiam
  • 11
  • 6
  • `I am posting working solution to the question I have asked.` - no you are not. Your original question was simply about "vertical folding when changing the height of a component". The code you posted here is about your application, which really isn't useful to many other people because other people will have different implementations for there application. However, the concept of "folding" is useful when presented properly in an answer. If the "folding" code was isolated in a simple demo then it would be more useful to people. That is how answers are given in the forum. – camickr Sep 18 '18 at 14:21
  • `IT HAS NO EFFECT TO CODE FUNCTIONALITY WHATSOEVER!` - excactly and that is why it should not be included. If people want to use your concept, they need to know what they need to change. By including unnecessary code it makes the code harder to read and understand and ultimately customize for their requirements. The solution MadProgrammer gave you was a general solution that you were able to modify meet your requirements. The answers given here should be the same - just a general solution that people can modify. – camickr Sep 18 '18 at 14:28