I am just exploring Vaadin 7 and I'm a bit frustrated to face a wall right at the start. Experienced in Swing I was happy to find Vaadin layouts are so simple and they are just like other components (they actually are Components, according to the class hierarchy). However, I faced a problem building my first Window.
So let's say I have a CustomComponent of such composition:
VerticalLayout
|
--TextArea
|
--Button
Which in the code will look like this:
public class SOComplicatedComponent extends CustomComponent {
private VerticalLayout mainLayout;
private TextArea textArea;
private Button button;
public SOComplicatedComponent() {
buildMainLayout();
setCompositionRoot(mainLayout);
}
private VerticalLayout buildMainLayout() {
// common part: create layout
mainLayout = new VerticalLayout();
mainLayout.setWidth("100%");
mainLayout.setHeight("100%");
// top-level component properties
setWidth("100.0%");
setHeight("100.0%");
// textArea
textArea = new TextArea();
textArea.setValue("hey, this button is supposed to be under me!");
textArea.setSizeUndefined();
mainLayout.addComponent(textArea);
//button
button = new Button("Ooops");
button.setSizeUndefined();
mainLayout.addComponent(button);
return mainLayout;
}
}
Then I'm constructing a Window in a following way:
public class MyUI extends UI{
#Override
protected void init(VaadinRequest request) {
...
Window window = new Window("Help me SO", new SOComplicatedComponent());
addWindow();
}
}
As a result I get a window with the TextArea and Button overlapping. When I resize the Window, contents stretch and the Layout becaomes OK, however I thought the Window is supposed to automatically fit to the contents size, isn't it?
OK, time for a final
QUESTION
I want the Button to be under the TextArea in my Window and make the Window size automatically fit its contents. What is the most proper way of achieving that in Vaadin 7?
Thanks
In this particular instance, there is no need for a separate Window - in Vaadin 7, Windows are actually child windows of the main UI; according to your comments, you do want a floating window. That's cool - but but a UI really should have some content, even if it's empty (otherwise rendering looks a little odd).
So,
You should simply be able to do
public class MyUI extends UI {
#Override
protected void init(VaadinRequest request) {
// You need to have some content on the UI, even if it's empty - otherwise it looks odd
// Here, I'm just adding an empty layout
VerticalLayout content = new VerticalLayout();
content.setSizeFull();
setContent(content);
// Adding a child window, and centering it for kicks
Window window = new Window("Help me SO", new SOComplicatedComponent());
window.center();
addWindow(window);
}
}
The AbsoluteLayout requires that you specify the location of your components. For a simple Vertical layout (i.e. TextField above Button) you would typically use the VerticalLayout
Also, setSizeFull means "make this component take all of the space allowed by it's container" -
which gets a bit confusing when you want the parent to make the child component as big as it needs to be, and no bigger. I think you really want to use "setSizeUndefined" for the CustomComponent too. So, putting all that together should give you this:
public class SOComplicatedComponent extends CustomComponent {
private VerticalLayout mainLayout;
private TextArea textArea;
private Button button;
public SOComplicatedComponent() {
buildMainLayout();
setCompositionRoot(mainLayout);
}
private VerticalLayout buildMainLayout() {
// common part: create layout
mainLayout = new VerticalLayout();
mainLayout.setSpacing(true);
mainLayout.setMargin(true);
// top-level component properties
/* CSA : SizeUndefined means "take as much space as my content needs" */
setSizeUndefined();
// textArea
textArea = new TextArea();
textArea.setValue("hey, this button is supposed to be under me!");
textArea.setSizeUndefined();
mainLayout.addComponent(textArea);
//button
button = new Button("Ooops");
button.setSizeUndefined();
mainLayout.addComponent(button);
return mainLayout;
}
}
For me, that renders like this :
Related
I've got a JFrame that looks like this:
It's got two JTextFields on it, one JComboBox between them and a JPanel at the bottom (that you can't see).
One of the features of the JComboBox is that it can be given a custom editor. These implement the ComboBoxEditor interface. In each of the following three cases, the GUI looks exactly the same, and I would have expected them all to behave exactly the same:
I do not specify a custom editor, and use the default one.
I create a custom editor whose editor component is a JTextField.
I create a custom editor whose editor component is a JPanel with a JTextField on it (using a BorderLayout).
When the editor for the editable combo box is set to the default, pressing Tab moves the focus from the top JTextField into the editing area on the JComboBox and then into the other JTextField. If I create a custom editor whose editor component is a JTextField and otherwise does what you would expect, the same thing happens.
BUT, if I instead create a custom editor whose editor component is a JPanel with a JTextField added to it, the focus makes one additional stop. If the focus is on the top JTextField, then pressing Tab moves the focus to the little arrow at the right of the editable combo box before moving into the text area.
Why is this happening? The focus never moves on to the JPanel at the bottom of the frame, so why does the presence of a JPanel holding the JTextField affect the tab order on the combo box?
The following is an S(-ish)SCCE, which has one text field and all three types of combo box on it:
import javax.swing.*;
import java.awt.event.*;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
public class ComboBoxTest extends JFrame
{
private JPanel layoutPanel;
private JTextField meaninglessTextField;
private JComboBox defaultEditorComboBox;
private JComboBox textFieldEditorComboBox;
private JComboBox panelEditorComboBox;
public ComboBoxTest()
{
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
layoutPanel = new JPanel();
layoutPanel.setLayout(new BoxLayout(layoutPanel, BoxLayout.Y_AXIS));
meaninglessTextField = new JTextField();
defaultEditorComboBox = new JComboBox(); // Just a default JComboBox.
defaultEditorComboBox.setEditable(true);
textFieldEditorComboBox = new JComboBox();
textFieldEditorComboBox.setEditable(true);
textFieldEditorComboBox.setEditor(new TextFieldEditor());
panelEditorComboBox = new JComboBox();
panelEditorComboBox.setEditable(true);
panelEditorComboBox.setEditor(new PanelEditor());
layoutPanel.add(Box.createRigidArea(new Dimension(500,0)));
layoutPanel.add(meaninglessTextField);
layoutPanel.add(defaultEditorComboBox);
layoutPanel.add(textFieldEditorComboBox);
layoutPanel.add(panelEditorComboBox);
Container contentPane = getContentPane();
contentPane.add(layoutPanel, BorderLayout.CENTER);
pack();
}
public static void main(String[] args)
{
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run()
{
new ComboBoxTest().setVisible(true);
}
});
}
private class PanelEditor extends JPanel implements ComboBoxEditor
{
public JTextField inputTextField = new JTextField();
public PanelEditor()
{
setLayout(new BorderLayout());
add(inputTextField, BorderLayout.CENTER);
}
#Override
public String getItem()
{
return inputTextField.getText();
}
#Override
public void setItem(Object newText)
{
if (newText != null) {
inputTextField.setText(newText.toString());
}
else {
inputTextField.setText("");
}
}
#Override
public Component getEditorComponent()
{
return this;
}
#Override
public void removeActionListener(ActionListener listener)
{
inputTextField.removeActionListener(listener);
}
#Override
public void addActionListener(ActionListener listener)
{
inputTextField.addActionListener(listener);
}
#Override
public void selectAll()
{
inputTextField.selectAll();
}
}
private class TextFieldEditor extends PanelEditor implements ComboBoxEditor
{
// The same, except that the editor component is now just the JTextField
// rather than the whole panel.
public TextFieldEditor()
{
}
#Override
public JTextField getEditorComponent()
{
return inputTextField;
}
}
}
Note: this behaviour becomes a problem if I want to add a JLabel to the editor. Then I have to put a JPanel there to hold both the label and the text field.
The basic problem is that the combo's ui delegate can't handle compound editor components. There are several places where it assumes that the editor component is the target of whatever configuration it needs to do. The concrete mis-behaviour here is that it explicitly sets the editor's focusability to that of the combo itself
// in BasicComboBoxUI
protected void configureEditor() {
....
editor.setFocusable(comboBox.isFocusable());
....
]
The implications
by default, the panel's focusable is true because the combo's is true
forcing the panel's focusable to false in its constructor has no effect (the ui resets it later on and whenever the LAF is switched)
disabling combo's focusable disables the panel's as well
To fix on the level of the editor, you can implement its isFocusable to return false unconditionally:
private class PanelEditor extends JPanel implements ComboBoxEditor
public boolean isFocusable() {
return false;
}
...
}
An aside: for code hygiene, better not extend a view to implement its role as ComboBoxEditor (even though here you need a subclassed JPanel to avoid the problem, so it's arguably borderline :-) - instead implement the editor and let it use the tweaked panel.
Also beware that you might stumble into more problems with the compound editor (check the code of BasicComboUI for more places where it assumes a plain childless component), so you might consider not doing it at all but think of a different way to achieve your requirement.
Try this:
public PanelEditor()
{
// other code...
addFocusListener(new FocusAdapter()
{
#Override
public void focusGained(FocusEvent e)
{
inputTextField.requestFocusInWindow();
}
});
}
The focus isn't transferring to the JPanel; it's transferring to the JComboBox itself.
You can stop a component from receiving the focus by using its setFocusable method. If you add the line
setFocusable(false)
to the constructor of the PanelEditor in the example above, then the strange behaviour is still there, since the PanelEditor implements JPanel, so the setFocusable method of the JPanel overrides that of the JComboBox. Since the setFocusable method of a JPanel essentially does nothing, nothing changes.
If instead you add the line
panelEditorComboBox.setFocusable(false)
to the constructor of the JFrame itself then the JComboBox will not be able to receive the focus, but the JTextField inside the editor will. This isn't a perfect fix, since it would be better if the editor itself were responsible for turning off the focusability of the JComboBox, so you could always pass in the parent JComboBox as a parameter to the constructor of the editor, and have the focusability turned off there.
I don't know why the behaviour is different when you've got a JTextField as the editor. Some weird Swing thing.
I was wondering if there is a way or a method that allows me to get the current JEditorPane being displayed. For example I have a JFrame where I can create several tabs. Whenever a tab is created a new JEditorPane object is created and the content of that pane are displayed in the tab. I've implemented a ChangeListener that currently just gets me the index of the current tab whenever I open a new one, close one or navigate between tabs. What I want to do is whenever a new tab is opened or navigated to I want to get the current JEditorPane object that resides at this tab. Is there any way in which I can achieve that?
Sorry if the question is a bit vague.
Thanks in advance.
The best way to do this would be to subclass JPanel and add your custom JPanel to the tabbed pane instead:
public class EditorPanel extends JPanel {
private JEditorPane editorPane;
// ...
public EditorPanel() {
// ...
editorPane = new JEditorPane( ... );
super.add(editorPane);
// ...
}
// ...
public JEditorPane getEditorPane() {
return editorPane;
}
}
Adding a new tab:
JTabbedPane tabbedPane = ... ;
tabbedPane.addTab(name, icon, new EditorPanel());
And then when you need to access it using the tabbed pane:
Component comp = tabbedPane.getComponentAt(i);
if (comp instanceof EditorPanel) {
JEditorPane editorPane = ((EditorPanel) comp).getEditorPane();
}
This is a better alternative to maintaining a separate list and trying to maintain it alongside the tabbed pane's indices.
I am building an application using the VAADIN framework.
I am trying to add a panel in a view containing a VerticalSplitPanel which contains two components (a button and a label for the moment).
Pretty straight forward but I'm having big problems getting it done.
I can identify that something happens, because I see the "split-divider" show when I run it in a browser, but no components inside the split-panel.
This is how I initialize the panel for the moment.
public class M2MInventory_SubscriptionsView extends AbstractView {
private Panel panel = new Panel();
private VerticalSplitPanel vSplit = new VerticalSplitPanel();
private Button upperButton = new Button("Upper Button");
private Button lowerButton = new Button("Lower Button");
public M2MInventory_SubscriptionsView() {
panel.setContent(vSplit);
vSplit.setFirstComponent(new Button("Upper"));
vSplit.setSecondComponent(new Label("Lower"));
addComponent(panel);
}
Can anyone spot an error in my ways?
Try to set panel height first. It will work but I'm not sure why.
The default layout of Panel is VerticalLayout with undefined height. It's strange, because I thought If you insert enough components in such a layout, it will grow.
I'm looking to create an Outlook style UI in a Java desktop app, with a list of contexts or nodes in a lefthand pane, and the selected context in a pane on the right. How do I go about this?
I'm looking for a bit more detail than 'use a JFrame'. A tutorial or walk through would be good, or some skeleton code, or a framework/library that provides this kind of thing out of the box.
Thanks.
Edit
My (edited) code so far:
UIPanel
public class UIPanel extends javax.swing.JPanel {
private final JSplitPane splitPane;
public UIPanel() {
super(new BorderLayout());
initComponents();
JPanel contextPnl = new ContextPanel();
JPanel treePnl = new NodePanel(contextPnl);
this.splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
true, new JScrollPane(treePnl), new JScrollPane(contextPnl));
add(splitPane, BorderLayout.CENTER);
//not sure I need these?
splitPane.setVisible(true);
treePnl.setVisible(true);
contextPnl.setVisible(true);
}
NodePanel
public class NodePanel extends javax.swing.JPanel {
JPanel _contextPanel;
public NodePanel(JPanel contextPanel) {
initComponents();
_contextPanel = contextPanel;
initialise();
}
private void initialise(){
nodeTree.addTreeSelectionListener(getTreeListener());
}
private TreeSelectionListener getTreeListener(){
return new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)
nodeTree.getLastSelectedPathComponent();
// if nothing is selected
if (node == null)
return;
// get selected node
Object nodeInfo = node.getUserObject();
CardLayout layout = (CardLayout) _contextPanel.getLayout();
//layout.show(_contextPanel, "test"); //show context for selected node
}
};
}
ContextPanel
public class ContextPanel extends javax.swing.JPanel {
JPanel _cards;
final static String CONTEXT1 = "Context 1";
final static String CONTEXT2 = "Context 2";
JPanel _context1;
JPanel _context2;
public ContextPanel() {
initComponents();
intialiseContexts();
}
public void updateContext(String contextName){
//TODO
}
private void intialiseContexts(){
_context1 = new NodeContext();
_context2 = new NodeContext();
_cards = new JPanel(new CardLayout());
_cards.add(_context1, CONTEXT1);
_cards.add(_context2, CONTEXT2);
}
The key concept here is to define a JSplitPane as your top-level Component with a horizontal split. The left-hand side of the split pane becomes your "tree" view while the right-side is the context panel.
The trick is to use CardLayout for your context panel and to register a TreeSelectionListener with the tree panel's JTree so that whenever a tree node is selected, the CardLayout's show method is called in order to update what the context panel is currently showing. You will also need to add the various Components to the context panel in order for this approach to work.
public class UIPanel extends JPanel {
private static final String BLANK_CARD = "blank";
private final JSplitPane splitPane;
public UIPanel() {
super(new BorderLayout());
JPanel treePnl = createTreePanel();
JPanel contextPnl = createContextPanel();
this.splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
true, new JScrollPane(treePnl), new JScrollPane(contextPnl));
add(splitPane, BorderLayout.CENTER);
}
}
EDIT: Example Usage
public class Main {
public static void main(String[] args) {
// Kick off code to build and display UI on Event Dispatch Thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("UIPanel Example");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
// Add UIPanel to JFrame. Using CENTER layout means it will occupy all
// available space.
frame.add(new UIPanel(), BorderLayout.CENTER);
// Explicitly set frame size. Could use pack() instead.
frame.setSize(800, 600);
// Center frame on the primary display.
frame.setLocationRelativeTo(null);
// Finally make frame visible.
frame.setVisible(true);
}
});
}
}
Additional Advice
I can see you've created separate classes for your NodePanel and ContextPanel. Given the simplicity of these classes and how tightly coupled they are it probably makes more sense to embed all the UI components directly within UIPanel and have utility methods that build the two sub-panels. If you do keep with NodePanel and ContextPanel try to make them package private rather than public.
The CardLayout approach works well if you have a small(ish) number of nodes and you know them in advance (and hence can add their corresponding Components to the CardLayout in advance). If not, you should consider your context panel simply using BorderLayout and, whenever you click on a node you simply add the relevant node component to the BorderLayout.CENTER position of the NodePanel and call panel.revalidate() to cause it to perform its layout again. The reason I've used CardLayout in the past is that it means my nodes only need to remember one piece of information: The card name. However, now I think of it I don't see any real disadvantage with this other approach - In fact it's probably more flexible.
You might want to look at using a platform like eclipse as a starting point. It provides a very rich environment for creating these applications so you do not have to start everything from scratch. The online guides and help are very good and there are several books on the subject.
I am currently desigining a calculator panel using Java Swing. However, it's an extreme PAIN to line up all of the buttons because they are always resizing and repositioning themseleves whenever I add a new button or change the size of a button.
Is there a type of layout or something that can "lock" the buttons in position so they are not affected when I move/resize/add other buttons?
Thanks,
Bob
Extending what Tom said...
To allow a component to become invisible yet hold its place, you can place it in a CardLayout along with an empty label and just swap which is visible.
You can create a class to do this for you as follows The main just shows an example where if you click a button it's deleted while retaining its position. I put in showComponent/hideComponent and setVisible(t/f) - depends on the style you like.
This might not exactly answer what you're looking for, but might be a useful piece for part of your application.
public class Placeholder extends JPanel {
private static final long serialVersionUID = 1L;
private CardLayout cardLayout_;
public Placeholder(JComponent component) {
cardLayout_ = new CardLayout();
setLayout(cardLayout_);
add(component, "visible"); // the component
add(new JLabel(), "hidden"); // empty label
}
public void showComponent() {
cardLayout_.show(this, "visible");
}
public void hideComponent() {
cardLayout_.show(this, "hidden");
}
public void setComponentVisible(boolean visible) {
if (visible)
showComponent();
else
hideComponent();
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setLayout(new GridLayout(2,0));
for (int n = 1; n < 10; n++) {
JButton b = new JButton(n + "");
final Placeholder placeholder = new Placeholder(b);
f.add(placeholder);
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
placeholder.hideComponent();
}
});
}
f.pack();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.setVisible(true);
}
}
I suggest instead of adding and removing buttons, you change their visibility property (setVisible). Some layout manager ignore non-visible components, so you may need to add a non-opqaue (or matching background) component in the same position that follows the preferred/minimum/maximum sizes of the original component. More simple you may want to use OverlayLayout.
You could use GridBagLayout to keep them in a grid, but allow them to resize
Extending what the others said...
Instead of making the components invisible, you can also disable them...
Of course, it depends on your application and your preferences.
Beside the above mentioned layouts, there are some freely available ones, like TableLayout and the versatile MigLayout.
It probably depends on the development environment you are using.
In Netbeans you can right-click the control container (form/panel) and select from a number of layout options. 'Absolute Layout' will let you position the controls without them all being resized automatically, although it won't let you place controls on top of each other.
I suggest you to check A Visual Guide to Layout Managers. Depending on the look and functionality you want there are different layouts that could work for you. If the main problem is the resize operation (of the parent JFrame o JPanel I assume), the Spring Layout allows you to establish constraints regarding the relative place of components.