Java Swing - Difficulty achieving desired layout - java
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.
Related
populating JTabbed Pane with JScroll panes according to user selection
We have a project for university which is a program to hold handouts and feedback for courseworks done. What we've thought of is breaking the whole thing down into smaller pieces, for example: You have a coursework which requires to write a program and a report on results etc. So the user will create a new coursework by selecting the "code" and "report" options, since that's what is required. And then we need to create the respective tabs in the program so the user can input what is needed. I have created all necessary forms and windows, It's just I'm not sure how to move on forward. a) where should I put my code? should I have it on the "create" event? b) how do I do this whole custom population thing? Obviously, I'm not asking for the entire thing in code. I'm not even sure what to read and what to search for. Following are some screenshots of the ui to help explain what I mean. New project window How the main window should be after creating a new projet. Notice the various tabs. A form for report feedback
On your "Create" button click check for the checkbox.isSelected() and use the method below as: if(reportCheckbox.isSelected()){ addonScreen(new reportFrame(),"Report Submission"); addonScreen(new reportFeedbackFrame(),"Report Feedback"); } Use a desktop pane as a container...add your tabbed pane to it public static JTabbedPane tabbedPane = new JTabbedPane(); jDesktopPane1.add(tabbedPane); Use this method to add tabs to the layout at runtime public static void addOnScreen(JInternalFrame inFrame, String title) { //border for the internal frame javax.swing.plaf.InternalFrameUI ifu = inFrame.getUI(); ((javax.swing.plaf.basic.BasicInternalFrameUI) ifu).setNorthPane(null); Border b1 = new LineBorder(new Color(114, 139, 173), 3, true) { }; tabbedPane.setBounds(0, 0, jDesktopPane1.getWidth(), jDesktopPane1.getHeight()); inFrame.setLocation(0, 0); inFrame.setSize(jDesktopPane1.getWidth(), jDesktopPane1.getHeight()); inFrame.setBorder(b1); JPanel jp = new JPanel(); jp.setLayout(new GridLayout()); jp.setOpaque(true); jp.add(inFrame); tabbedPane.addTab(title, jp); tabbedPane.setSelectedComponent(jp); inFrame.requestFocusInWindow(); inFrame.setVisible(true); tabbedPane.setVisible(true); }
Why does the JTable not show the Jtable heading even when added to JScrollPane?
import javax.swing.*; import javax.swing.border.*; import javax.swing.table.TableColumn; import java.awt.*; import static java.awt.GraphicsDevice.WindowTranslucency.*; import java.awt.Checkbox; import java.awt.Paint; import java.awt.Toolkit; import java.awt.event.*; public class Main extends JPanel { static Object [][] Services = {{"Google.exe","Chickeaen.exe","Crp.exe"}}; static String [] ColNames = {"Processes:","Crolly:","Haler:"}; static JFrame Fram = new JFrame(); static JTextField CBox = new JTextField(); static JTable Tabs = new JTable(Services,ColNames); JScrollPane ScrollArea = new JScrollPane(); static JButton ExitB = new JButton(); Dimension ScreenSize = new Dimension(Toolkit.getDefaultToolkit().getScreenSize()); Border BlackLineB = BorderFactory.createLineBorder(new Color(50,50,50)); public Main() { Fram.setTitle("Jared Console"); Fram.setUndecorated(true); Fram.setVisible(true); Fram.setDefaultCloseOperation(Fram.EXIT_ON_CLOSE); Fram.setResizable(false); Fram.setSize((int)Math.round(ScreenSize.getWidth()*0.45),(int)Math.round(ScreenSize.getHeight()*0.33)); Fram.setBackground(new Color(0,0,0,150)); Fram.add(this); CBox.setSize((int)Math.round(Fram.getWidth()*0.80),(int)Math.round(Fram.getHeight()*0.25)); CBox.setBackground(new Color(255,255,255)); CBox.setBorder(BorderFactory.createCompoundBorder(BlackLineB,BorderFactory.createEmptyBorder(0,20,0,0))); CBox.setLocation((int)Math.round(Fram.getWidth()*0.1),(int)Math.round(Fram.getHeight()*0.70)); CBox.setFont(new Font("Arial",Font.BOLD,20)); CBox.setVisible(true); ScrollArea.setSize((int)Math.round(Fram.getWidth()*0.80),(int)Math.round(Fram.getHeight()*0.50)); ScrollArea.setLocation((int)Math.round(Fram.getWidth()*0.10),(int)Math.round(Fram.getHeight()*0.10)); ScrollArea.setBorder(BlackLineB); ScrollArea.setLayout(null); ScrollArea.setVisible(true); Tabs.setSize((int)Math.round(Fram.getWidth()*0.995),(int)Math.round(Fram.getHeight()*0.995)); Tabs.setLocation((int)Math.round(Fram.getWidth()*0.003),(int)Math.round(Fram.getHeight()*0.005)); Tabs.setFillsViewportHeight(true); Tabs.setBackground(new Color(255,255,255)); this.add(CBox); this.add(Tabs); this.add(ExitB); ScrollArea.add(Tabs); this.add(ScrollArea); this.setBorder(BorderFactory.createLineBorder(new Color(50,50,50),5)); this.setLayout(null); this.setBackground(new Color(0,0,0)); this.setVisible(true); } public void paintComponent(Graphics Gla) { Paint Plat = new GradientPaint(0f, 0f, new Color(0, 40, 0, 0), 0.0f, Fram.getHeight(), new Color(0, 0, 0, 150), true); //Made 200 equal to Fram Background Alpha. Graphics2D Quo = (Graphics2D)Gla; Quo.setPaint(Plat); Quo.fillRect(0, 0, Fram.getWidth(), Fram.getHeight()); } public static void main(String[] args) { Main CScreen = new Main(); GraphicsEnvironment GE = GraphicsEnvironment.getLocalGraphicsEnvironment(); // Have to study lines 57,58 and 59 GraphicsDevice GD = GE.getDefaultScreenDevice(); boolean CheckTransL = GD.isWindowTranslucencySupported(PERPIXEL_TRANSLUCENT); if (!CheckTransL) { System.out.println("PERPIXEL TRANSLUCENT NOT SUPPORTED - LOL UPDATESCRUB!"); System.exit(0); }; } } Why does the JTable not show the Jtable heading even when added to JScrollPane? Also the Console shows a error message at first then quickly goes away and launches the program? So yea I'd like to know what's wrong with this and also can you can note me of some bad habits in this program such as the way it's being typed.
Problems null layout. Avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify. See Laying Out Components Within a Container for more details Over use of static. static is not your friend and you should avoid using it. It is not a cross object communication mechanism and over use like this will burn you You don't actually wrap the JTable in JScrollPane Breaking the paint chain by not calling super.paintComponent, this is going to produce a series of wonderful paint artifacts. See Painting in AWT and Swing and Performing Custom Painting for more details about how painting works in Swing You might like to have a read through Code Conventions for the Java TM Programming Language, it will make it easier for people to read your code and for you to read others All interactions, creations and modifications to the UI should be done from within the context of the Event Dispatching Event, to reduce the risk of potential race conditions, dead locks and rendering artifacts. See Initial Threads for more details Toolkit.getDefaultToolkit().getScreenSize() is a bad indicator for the viewable space of a screen, it does not take into account OS specific elements, like the dock or task bar, which could have your application appearing under them (and I really, really, REALLY hate it when that happens). Use appropriate layout managers and pack to pack the window around the content. You can then use JFrame#setLocationRelativeTo and pass it null and it will center the frame in the screen JScrollPane issue... The simply solution would be to use JScrollPane's constructor to pass it the reference of the JTable... static JTable Tabs = new JTable(Services,ColNames); JScrollPane ScrollArea = new JScrollPane(Tabs); But, then you do this later in your code... this.add(Tabs); This will remove the table from the scroll pane to add it to you panel, as a component can only have a single parent. Another option would be to specifiy the scroll pane's viewport's view component... this.add(CBox); //this.add(Tabs); this.add(ExitB); //ScrollArea.add(Tabs); ScrollArea.setViewportView(Tabs); this.add(ScrollArea); You should never add components directly to a scroll pane (or it's underlying viewport), they have their own internal layout management functionality going on. Instead, you need to supply the component as the "view" to the JViewport Take a look at How to Use Scroll Panes for more details.
First of all you should add a JTable to the ViewPort of the a JScrollPane in order to JTableHeader could be visible. After that you should not add your JTable to both the JScrollPane and also the underlying container. You should: add the JTable to the ViewPort of the JScrollPane. add the JScrollPane to the underlying container. and remove the line that add the JTable to the container explicitly. Good Luck.
JList displaying String[] text cut off
I'm trying to populate a JList with items from an ArrayList<String[]>. Each String[] is of the form ["I","am","an","example"] and there's nothing I can do about the input form - its from a third party. What I'd like is simply a JList with each String[] expanded out on a different line. When I use the following code, though, the first few characters are cut off the left side of the JList - its cutting off mid character so its an issue with pixels not characters. The class below is set as the content pane on a JFrame elsewhere in the program, I didn't think it was necessary to copy that over here but if it would be useful then I can trim it down and put it up for viewing. public class BookScreen extends JPanel{ ListSelectionModel lsm; ArrayList <String> atList; JList atBox; MainForm mf; public BookScreen (MainForm mf){ //I'm aware this bit is clunky, it was a quick and dirty to test it displays //properly before I cleaned it up ArrayList<String[]> books= mf.getWorld().getBooks(); atList=new ArrayList(); for (String[] s:books){ atList.add(Arrays.toString(s)); } //end clunky atBox = new JList(atList.toArray()); lsm = atBox.getSelectionModel(); lsm.addListSelectionListener(new BookScreen.AtListSelectionHandler()); atBox.setVisibleRowCount(-1); atBox.setLayoutOrientation(JList.HORIZONTAL_WRAP); atBox.setLocation(0, 0); atBox.setVisible(true); this.add(atBox); this.setVisible(true); } class AtListSelectionHandler implements ListSelectionListener{ #Override public void valueChanged(ListSelectionEvent e){ } } } Screenshot of problem:
The problem is that you don't set a layout manager on the panel, which means that the default FlowLayout will be used. If there's only one component this layout places it centered on the container; if the component is wider than the container its edges are trimmed. To solve the problem simply set a different layout manager, for example BorderLayout: this.setLayout(new BorderLayout()); this.add(atBox); More information: Creating a GUI With JFC/Swing: Using layout managers.
JLabels to be resized in a GridLayout
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.
No vertical scroll in browser
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