Java Gui, gap between elements after hiding some of them - java

I am developing a GUI in java using Netbeans (7.0).
I have a JFrame including a JPanel and several elements inside this panel.
The layout type is free layout I think.
draft:
-------------------
|JFrame |
| |
| --------------- |
| |JPanel | |
| | | |
| | elem1 elem2 | |
| | elem3 elem4 | |
| | elem5 elem6 | |
| | | |
| |-------------- |
| |
-------------------
During my program is running I am hiding some of the elements being in one row (e. g. elem3 and elem4) by using setVisible(false).
Everything resizes as expected (JFrame and JPanel) except for the gaps.
It looks like that the elements are hidden correctly but there gaps are remaining so that in my example where I am hiding elem3 and elem4 a bigger gap between the row elem1/elem2 and elem5/6 remains.
Hopefully my problem was understandable :-)
Is there any way to fix this behavior?
Thanks in advance.
Steffen

Try removing that elements from jpanel instead of hiding but remember this will still not work for some layout. It depends on your layout to how to handle adding and removing of component.

with intend to avoid any missinterpretations:
if is TopLayoutContainer once visible then:
1/ for adding new JComponent is needed to call revalidate() and for compound JComponents f.e. with set programatically Item in JComboBox is needed call with repaint() too
2/ after removing JCmponent(s) you have to call revalidate() plus repaint()
3/ for example remove JComponents -> add new JComponents -> revalidate() plus repaint()
#Steffen Kuehn there are lots of possible problems, better would be to sent code that ilustrated your described issue

#Steffen Kuehn please check out the below code which presents the 'possible' problem you are experiencing. i.e. you are not revalidating and repainting the panel which was changed.
Please notice when you click the panel using left mouse button there is no repainting/revalidation, and at first it seems nothing is happening. Try to resize the frame. It calls the operations and you will see that the elements 5 & 6 are in fact visible. Thus if you want the changes to be instant use the right click which does visibility change and validation.
import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class FlowComponentsTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
final JPanel p = new JPanel();
for(int i = 1; i <= 20; i++)
{
JComponent c = new JTextField("I am element no. " + i);
//so any textfield will not steal the focus from the panel p
c.setFocusable(false);
p.add(c);
if(i == 5 || i == 6)
{
c.setForeground(Color.GREEN);
c.setVisible(false);
}
}
JFrame f = new JFrame();
f.addMouseListener(new MouseAdapter()
{
#Override
public void mousePressed(MouseEvent e)
{
JComponent c5 = (JComponent) p.getComponent(4);
JComponent c6 = (JComponent) p.getComponent(5);
c5.setVisible(!c5.isVisible());
c6.setVisible(!c6.isVisible());
if(e.getButton() == MouseEvent.BUTTON3)
{
System.out.println("Right click");
p.revalidate();
p.repaint();
}
}
});
f.setContentPane(p);
f.setSize(300, 330);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
});
}
}

Related

Java Nimbus Look and Feel per component customization ("Nimbus.Overrides") - other instances affected too

I am new to Java Nimbus Look and Feel. I am trying to use the ability of Nimbus to customize individual component instances using putClientProperty("Nimbus.Overrides", overrides): https://docs.oracle.com/javase/8/docs/api/javax/swing/plaf/nimbus/package-summary.html
I have encountered the following problem (bug?):
"Nimbus.Overrides" value unfortunately affects apparently not only the component object to which it is explicitely set, but other objects too.
It seems that the customized properties are "inherited" somehow to other (later "styled", apparently not previously) instances of the same type. I need to ensure that the changes are made only to one individual instance (without affecting any other objects). Example - JButton used, but the same problem encountered with e.g. JTabbedPane and custom painters:
button 1 - property A (content margins) customized.
button 2 - only property B (font) customized - but change in property A visible too (bigger manrgins), "somehow inherited" from button 1.
button 3 - no property customized (empty property map used) - both changes (A+B) seem to be inherited (bigger margins, bigger font) from button1 and button2
button 4 - default look o JButton (no customization)
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Insets;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.UIDefaults;
public class NimbusPerComponentTest extends JFrame {
public NimbusPerComponentTest() {
super("Test");
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
// 4 buttons to test
JButton button1 = new JButton("Button 1");
JButton button2 = new JButton("Button 2");
JButton button3 = new JButton("Button 3");
JButton button4 = new JButton("Button 4");
add(button1);
add(button2);
add(button3);
add(button4);
pack();
// style
// button 1
UIDefaults overrides1 = new UIDefaults();
overrides1.put("Button.contentMargins", new Insets(10,10,10,10));
button1.putClientProperty("Nimbus.Overrides", overrides1);
// button 2
UIDefaults overrides2 = new UIDefaults();
overrides2.put("Button.font", new Font("Sans Serif", Font.BOLD, 15));
button2.putClientProperty("Nimbus.Overrides", overrides2);
// button 3
UIDefaults overrides3 = new UIDefaults();
// nothing = left empty
button3.putClientProperty("Nimbus.Overrides", overrides3);
// button 4
// no styling
}
public static void main(String args[]) {
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException ex) {
java.util.logging.Logger.getLogger(NimbusPerComponentTest.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new NimbusPerComponentTest().setVisible(true);
}
});
}
}
Any idea WHY? What am I missing? Any elegant workarounds?
(Java 8, Windows 10)
EDIT
After inspiration by some of the answers:
Tried reseting the lookandfeel in the end of my original code (to null and back to Nimbus again, incl. SwingUtilities.updateComponentTreeUI), and the only result:
now even the button4 is painted wrong (with both margins and font changed) although the universal defaults were never touched... Weird.
EDIT 2
I managed to find a single line workaround /hack. See my own answer to my question...
It worked for me if I alter the creation of the UIDefaults. Instead of
UIDefaults overrides1 = new UIDefaults();
I used
UIDefaults overrides1 = (UIDefaults) UIManager.getLookAndFeelDefaults().clone();
Do this for overrides2 and overrides3 as well:
// style
// button 1
UIDefaults overrides1 = (UIDefaults) UIManager.getLookAndFeelDefaults().clone();
overrides1.put("Button.contentMargins", new Insets(10, 10, 10, 10));
button1.putClientProperty("Nimbus.Overrides", overrides1);
// button 2
UIDefaults overrides2 = (UIDefaults) UIManager.getLookAndFeelDefaults().clone();
overrides2.put("Button.font", new Font("Sans Serif", Font.BOLD, 15));
button2.putClientProperty("Nimbus.Overrides", overrides2);
// button 3
UIDefaults overrides3 = (UIDefaults) UIManager.getLookAndFeelDefaults().clone();
// nothing = left empty
button3.putClientProperty("Nimbus.Overrides", overrides3);
// button 4
// no styling
This produces the following output on my machine:
Please note that the size of the UIDefaults created this way may be quite big.
After quite a long time of debugging, it seems that I have managed to find a single line workaround / hack which might also suggest the cause of the problem:
Immediately after styling each component with putClientProperty("Nimbus.Overrides", overrides) you can prevent "inheriting" the properties to subsequently styled components by this code:
button1.putClientProperty("Nimbus.Overrides", overrides1);
UIManager.getDefaults().putDefaults(new Object[0]);
// add after each "styling"
// - clears the compiledDefaults in NimbusLookAndFeel
What it does?
Functionally, nothing (empy array = nothing put), but it fires PropertyChange event (see source here), which is listened in DefaultsListener private class in NimbusLookAndFeel class (see source here), which was the only way I managed to find to clear the compiledDefaults cache (?) in NimbusLookAndFeel, which - as it seems to me - is causing the problem:
if ("UIDefaults".equals(key)) {
compiledDefaults = null;
}
I noticed the problem during debugging in getDefaultsForPrefix method (using the compiledDefaults) in NimbusLookAndFeel class - see source here. For later components, it was returning not only "real defaults", but for some reason also the custom properties set to the previous component.
To make it clear: I am a total amateur and have no detailed knowledge or understanding of the Nimbus classes details or architecture. I may be wrong, but the solutions works for me...
Is it unsuitable for some reason? Any risks? Is it really a bug?
One problem:
I noticed that if I pack() my frame not before styling the components with putClientProperty (as in my code in my question), but after, the solution does not work (nothing helps)...
I suspect this is due to a bug in the shouldUpdateStyleOnEvent method of javax.swing.plaf.nimbus.NimbusLookAndFeel. From the source:
protected boolean shouldUpdateStyleOnEvent(PropertyChangeEvent ev) {
String eName = ev.getPropertyName();
// These properties affect style cached inside NimbusDefaults (6860433)
if ("name" == eName ||
"ancestor" == eName ||
"Nimbus.Overrides" == eName ||
"Nimbus.Overrides.InheritDefaults" == eName ||
"JComponent.sizeVariant" == eName) {
JComponent c = (JComponent) ev.getSource();
defaults.clearOverridesCache(c);
return true;
}
return super.shouldUpdateStyleOnEvent(ev);
}
Needless to say, that is not a valid way to compare Strings. I haven’t been able to find anything in the bug database about this; perhaps later when I have time, I’ll submit a bug report.

getDisplayPosition method is returning 0 in software made with SceneBuilder [duplicate]

How to get the height or prefer height of a node in JavaFX, I have 3 VBox and I want to add nodes to the the most freer panel, example:
Childrens Total Height of the children's(Sum)
VBoxA 5 890
VBoxB 4 610
VBoxC 2 720
in this case, the most freer is the VBoxB, I calculate the most freer pane with this method:
private int getFreerColumnIndex() {
if(columns.isEmpty())
return -1;
int columnIndex = 0;
int minHeight = 0;
for(int i = 0; i < columns.size(); i++) {
int height = 0;
for(Node n : columns.get(i).getChildren()) {
height += n.getBoundsInLocal().getHeight();
}
if(i == 0) {
minHeight = height;
} else if(height < minHeight) {
minHeight = height;
columnIndex = i;
}
if(height == 0)
break;
}
return columnIndex;
}
This method only works if I add 1 element at the time. But if I add more elements at the time:
for (int i = 0; i < 10; i++) {
SomeNode r1 = new SomeNode ();
myPane.addElement(r1);
}
the method getFreerColumnIndex return the same index. This is because the new nodes dont have heigth in local yet.
So this line:
height += n.getBoundsInLocal().getHeight();
will return 0 with the new nodes.
Anyone knows how to get the heigth of a node ?
Extra:
SomeNode extends of Node
Method addElement() at myPane:
public void addElement(final Node element) {
index = getFreerColumnIndex();
columns.get(index).getChildren().add(element);
}
Extra 2:
suppose we have 3 vbox:
Before:
A B C
| | |
| |
|
Run:
for (int i = 0; i < 10; i++) {
SomeNode r1 = new SomeNode ();
myPane.addElement(r1);
}
After:
A B C
| | |
| | |
| |
|
|
|
|
|
|
|
|
Correct:
A B C
| | |
| | |
| | |
| | |
| | |
|
| = Some node
What you need to do is:
Create the nodes which are to be placed in the VBoxes.
Add them to some scene (it can just be a new dummy scene not attached to the stage but having the same CSS rules as your main scene).
Call applyCss() and layout() on each of the nodes (or the dummy scene root).
Measure the layout bounds of each of the nodes.
Add the nodes to the VBoxes in your real scene according to your layout algorithm.
Related
To find out how to measure the size of a node, see the answer to:
How to calculate the pixel width of a String in JavaFX?
Background
JavaFX layout calculations work by applying CSS and a layout pass. Normally this occurs as part of a pulse (a kind of automated 60fps tick in the internal JavaFX system which checks for any dirty objects in the scene which need new css or layout applied to them). In most cases, you can just specify the changes you want to the scene and let the automated pulse layout mechanism handle the layout at that time; doing so is quite efficient as it means that any changes between a pulse are batched up and you don't need to manually trigger layout passes. However, when you need to actually get the size of things before the layout occurs (as in your case), then you need to manually trigger the CSS application and layout pass before you try to query the height and width extents of the node.
Documentation
Unfortunately the detailed Java 8u20 Javadoc for the node.applyCSS() method is currently broken, so I'll reproduce the sample which is in the Javadoc code here, so you can see the recommended usage in context. For the next Java feature release (8u40), the broken javadoc is fixed as part of RT-38330 Javadoc is missing for several methods of the Node class, so with that release, you will be able to find the following text in the JavaFX javadoc:
If required, apply styles to this Node and its children, if any. This method does not normally need to
be invoked directly but may be used in conjunction with layout() to size a Node before the
next pulse, or if the Scene is not in a Stage.
Provided that the Node's Scene is not null, CSS is applied to this Node regardless
of whether this Node's CSS state is clean. CSS styles are applied from the topmost parent
of this Node whose CSS state is other than clean, which may affect the styling of other nodes.
This method is a no-op if the Node is not in a Scene. The Scene does not have to be in a Stage.
This method does not invoke the layout() method. Typically, the caller will use the
following sequence of operations.
parentNode.applyCss();
parentNode.layout();
As a more complete example, the following code uses applyCss() and layout() to find
the width and height of the Button before the Stage has been shown. If either the call to applyCss()
or the call to layout() is commented out, the calls to getWidth() and getHeight()
will return zero (until some time after the Stage is shown).
public void start(Stage stage) throws Exception {
Group root = new Group();
Scene scene = new Scene(root);
Button button = new Button("Hello World");
root.getChildren().add(button);
root.applyCss();
root.layout();
double width = button.getWidth();
double height = button.getHeight();
System.out.println(width + ", " + height);
stage.setScene(scene);
stage.show();
}
layout() vs requestLayout()
Calling layout() will perform a layout pass immediately.
Calling requestLayout() will perform a layout pass in the future:
Requests a layout pass to be performed before the next scene is
rendered. This is batched up asynchronously to happen once per
"pulse", or frame of animation.
So requestLayout() informs the system that a layout needs to occur, but doesn't do the layout right away.
Usually, you don't need to call requestLayout() directly, because most of the time, the JavaFX system is able to internally determine that a region of the scene is dirty and needs to perform layout automatically when needed.
Alternative
The other option here is to override the layoutChildren() method of a parent node, which is "Invoked during the layout pass to layout the children in this Parent.". In doing so, it may also be necessary to override methods to computePrefHeight() as well as other computation methods for prefWidth and min & max height & width. A detailed description of using this approach is complicated and outside the scope of this answer.
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import java.io.Closeable;
public class Sizing extends Application {
#Override
public void start(Stage stage) {
var label = new Label();
var labelHeight = label.getHeight();
System.out.println(labelHeight);
try (var s = new SizingSandbox(label)) {
labelHeight = label.getHeight();
}
System.out.println(labelHeight);
}
public static void main(String[] args) { launch(args); }
}
class SizingSandbox extends Group implements Closeable {
public SizingSandbox(Node... nodes) {
new Scene(this);
getChildren().addAll(nodes);
layItOut();
}
#Override
public void close() {
try {
getChildren().removeAll();
} catch (Exception e) { }
}
private void layItOut() {
applyCss();
layout();
}
}
// output:
// 0.0
// 17.0

Vertical alignment of label with first row of adjacent control

How can I vertically align a label with a Combo in the first row of an adjacent Composite?
The label and the multi-row control are in a GridLayout with other controls, with left-alignment of the labels and the fields. The multi-row control is re-used elsewhere.
Ideally, the baselines of the text in the label and the Combo would be aligned. In practice, when the label and combo are siblings, vertically centering the two with SWT.CENTER is usually sufficient.
I can't just lower the label by a compile-time constant, because of differences in fonts and look-and-feels.
Ideas that have occurred to me include:
At runtime, while constructing the controls, ask Eclipse for the height of its combo widgets, somehow. Embed the label in a Composite and pad or align appropriately.
Put the label (possibly embedded in a Composite) in a StackLayout with a dummy Combo that's never displayed, to give the label cell the height of the first row in the sibling control. Center align the label within its parent.
Is there a simpler/cleaner approach?
I confirmed that the problem can be solved by the second approach I suggested in the question. If anyone has a better answer, please post.
This approach has these objects:
AbstractLabelWithHeightOfAnotherControl / custom StackLayout
<>-- Other control
Composite / GridLayout
<>-- Label / GridData( SWT.BEGINNING, SWT.CENTER, false, true )
The custom StackLayout has the width of the label, but the height of the other control.
This code provides an abstract class that supports the behavior for a variety of other controls.
public abstract class AbstractLabelWithHeightOfAnotherControl extends Composite {
private Label m_label;
private Control m_otherControl;
/** Constructor.
* #param parent
* #param style
*/
public AbstractLabelWithHeightOfAnotherControl(Composite parent, int style) {
super( parent, style );
StackLayout stackLayout = new MyStackLayout();
this.setLayout( stackLayout );
Composite layerLabel = new Composite( this, SWT.NONE );
GridLayout layerLabelLayout = new GridLayout( 1, false );
layerLabelLayout.marginWidth = 0;
layerLabelLayout.marginHeight = 0;
layerLabel.setLayout( layerLabelLayout );
m_label = new Label( layerLabel, SWT.NONE);
m_label.setLayoutData( new GridData( SWT.BEGINNING, SWT.CENTER, false, true ) );
m_otherControl = makeOtherControl( this );
stackLayout.topControl = layerLabel;
}
protected abstract Control makeOtherControl( #Nonnull Composite parent );
public Label getLabel() {
return m_label;
}
private final class MyStackLayout extends StackLayout {
MyStackLayout() {
this.marginHeight = 0;
this.marginWidth = 0;
}
#Override
protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
int width = m_label.computeSize( wHint, hHint, flushCache ).x;
int height = m_otherControl.computeSize( wHint, hHint, flushCache ).y;
if (wHint != SWT.DEFAULT) width = wHint;
if (hHint != SWT.DEFAULT) height = hHint;
return new Point(width, height);
}
}
}
The implementing class can just provide a method like this:
#Override
protected Control makeOtherControl( #Nonnull Composite parent ) {
return new Combo( parent, SWT.NONE );
}
What about creating a component like this:
+-------------------------------+
| +---------------------------+ |
| | +------+ +-------------+ | |
| | |Label | |Combobox | | |
| | +------+ +-------------+ | |
| +---------------------------+ |
| | +------+ +-------------+ | |
| | |Dummy | |Combobox | | |
| | +------+ +-------------+ | |
| +---------------------------+ |
| | +------+ +-------------+ | |
| | |Dummy | |Combobox | | |
| | +------+ +-------------+ | |
| +---------------------------+ |
+-------------------------------+
Have it created by a method that takes the label text as input. The Dummies are empty labels if you want. If the method label text is null you can omit the creation of the labels altogether or make all empty. That way you can reuse the multi combobox component with or without label.

JLabel positions not working in Java

I'm trying to make positions for my labels i have created and added to a panel.
Right now, I have some text, and an icon. I want to change to position for the icon to be in the bottom.
My code:
frame = new JFrame(); // Create a new frame
frame.setVisible(true); // Makes it visible
frame.setSize(900, 500); // Sets size
frame.setTitle(""); // Sets title
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null); // Sets the window on the center of the screen
//Create temperature panel
temp_panel = new JPanel(); // Creates new JPanel
temp_panel.setBackground(Color.decode("#f1c40f")); // Sets color
//Create temperature label
temp_label = new JLabel("Temperature");
label1 = new JLabel(new ImageIcon(temp_icon));
//Add label to temperature panel
temp_panel.add(temp_label);
temp_panel.add(label1);
// Creates the main panel for all panels
panel = new JPanel();
panel.setLayout(new GridLayout(1, 3));
panel.add(temp_panel);
// Add panel to frame
frame.add(panel);
My goal is to make multiple labels on a panel, and control where they are going to be placed, like this example:
--------------
| TEXT |
| |
| |
| Text |
| |
| |
| |
| Icon |
--------------
Have tried thing like:
temp_label = new JLabel("Temperature", BorderLayout.END.PAGE);
This dont seem to help, because my labels are just positioned in the top.
This doesn't make sense:
temp_label = new JLabel("Temperature", BorderLayout.END.PAGE);
since if you look at the JLabel API, you'll not find a constructor that takes a BorderLayout constant, nor would it make sense even if it were legal. We would sometimes call code like the above -- throwing poop at the wall and seeing what sticks. This heuristic rarely works in programming. You will want to use the Java API resource to avoid making up things like this.
As to your problem, you're adding your JLabel to a JPanel that uses a default FlowLayout. If you want its contents to stack as in your image, give that JPanel a different layout. Here a BoxLayout that uses the PAGE_AXIS sounds about right.
Please check out the layout manager tutorials for more on this and other layouts.

MouseListener gets overshadoved by another JPanel component

Program ScreenShot
I'm making a animation. When I press on a button it triggers a animation which makes a JPanel float in from the right side. The JPanel gets DeAnimated(JPanel exit animation) when the mouse leaves the JPanel, but the problem is that I have a JButton on the animated JPanel. So the panel does not only disappear when i move the mouse out of the animated JPanel but also when I move the mouse on the button (which is a component of the panel), which means that when I want to click on the button it disappears, because the MouseExit is fired when the mouse leaves the JPanel.
Look at the picture above where I marked certain areas.
Is the JPanel which is getting animated and disappears when i move over the button.
Is the button which causes the JPanel to be DeAnimated.
Is the area which should trigger the DeAnimation of the 1st JPanel.
I have been trying to fix this for a long time now, but I can't get it working. I would really appreciate it if you guys could help me. Thanks!
"but the problem is that I have a JButton on the animated JPanel. "
Don't put the button on the same panel then. Learn to use layout managers. With your image, I would use nested JPanels with BorderLayouts
The right side
JButton button = new JButton();
JPanel animatedPanel = new JPanel();
Add them to another JPanel
JPanel rightPanel = new JPanel(new BorderLayout());
rightPanel.add(button, BorderLayout.NORTH);
rightPanel.add(animatedPanel, BorderLayout.CENTER);
/** result **/
+-----+
| |
| |
+-----+
| |
| |
| |
| |
| |
+-----+
Then create another JPanel to hold the BigPanel and the RightPanel
JPanel mainPanel = new JPanel(new BorderLayout());
JPanel leftBigPanel = new JPanel();
mainPanel.add(leftBigPanel, BorderLayout.CENTER);
mainPanel.add(rightPanel);
/** result **/
*--------------------+-----+
| | |
| | |
| +-----+
| | |
| | |
| | |
| | |
| | |
+--------------------+-----+
The button no longer overlaps the right animated panel
Note : #override the getPreferredSize() in the JPanels for your desired dimension.

Categories

Resources