Can other methods make use of JScrollPane's viewport? - java

It seems like the only way to display text with multiple styles in a text area is to use a JEditorPane. To explore this, I wrote a small app to list all the fonts in their font.
Everything works, and the list is displayed in a JEditorPane, which is inside a JScrollPane.
However, it takes a few seconds to launch and to repopulate, because it generates the entire new list before displaying it. The code is below, following MCV. Minimalizing it to just the message and text made less work so the lag is no longer nearly as noticeable, but my question (below) was more about rendering and keeping track of JScrollPane's position anyway than about this app.
FontFrame.java
public class FontFrame extends JFrame {
private FontFrame() {
JFrame frame = new JFrame("Fonts Displayer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
FontPanel fontPanel = new FontPanel();
frame.getContentPane().add(fontPanel);
frame.getRootPane().setDefaultButton(fontPanel.getDefaultButton());
frame.setMinimumSize(new Dimension(600, 600));
frame.setPreferredSize(new Dimension(1000, 700));
frame.pack();
frame.setVisible(true);
}
public static FontFrame launchFontFrame() {
return new FontFrame();
}
public static void main(String[] args) {
FontFrame.launchFontFrame();
}
}
FontPanel.java
class FontPanel extends JPanel {
private String message = "ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz";
private JEditorPane fontPane;
private JTextField messageInput;
private JButton changeButton;
protected FontPanel() {
buildPanel();
}
private void buildPanel() {
setLayout(new BorderLayout());
// Build message input panel
JPanel inputPanel = new JPanel();
messageInput = new JTextField(message);
messageInput.setFont(new Font("SansSerif", Font.PLAIN, 14));
messageInput.setColumns(40);
JLabel messageLabel = new JLabel("Message:");
changeButton = new JButton("Change");
changeButton.addActionListener((ActionEvent e) -> {
String text = messageInput.getText();
if (!text.isEmpty() && !text.equals(message)) {
message = text;
refreshFontList();
}
});
inputPanel.add(messageLabel);
inputPanel.add(messageInput);
inputPanel.add(changeButton);
// Build scrolling text pane for fonts display
fontPane = new JEditorPane();
fontPane.setContentType("text/html");
fontPane.setEditable(false);
fontPane.setVisible(true);
JScrollPane scrollPane = new JScrollPane(fontPane);
refreshFontList();
// Add components to main panel
add(inputPanel, BorderLayout.NORTH);
add(scrollPane, BorderLayout.CENTER);
}
private void refreshFontList() {
String[] list = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getAvailableFontFamilyNames();
StringBuilder messages = new StringBuilder();
// Set global table style settings
messages.append("<table>");
// Make a row for each font
for (String font : list) {
messages.append("<tr>");
//Append data cells
messages.append("<td>").append(font).append("</td>")
.append("<td style=\"font-family:").append(font).append("\">")
.append(message)
.append("</td>");
messages.append("</tr>");
}
messages.append("</table>");
fontPane.setText(messages.toString());
}
JButton getDefaultButton() {
return changeButton;
}
}
Is there any way of redoing refreshFontList so it can generate only what fontPane would show in its viewport first, possibly by using JScrollBar, and then generate the rest of the list right after so the computation time doesn't cause lag in display? Is there a different component that would work better?

Related

JButton not showing up in scroll pane

I'm trying to setup a basic GUI Library that will import a list of books and display each book as a JButton within a scroll pane. But, before getting there I'm just trying to orient the panels first and adding a test button to make sure the basics are working before moving on to the details.
I've tried moving code around to add panels in different orders to see if that was an issue but keep getting the same result. I'm completely new to this, so my understanding of it is very limited.
public class LibraryPanel extends JPanel{
private Library library;
private JPanel bookButtons, importBooks;
JScrollPane bookList;
JTextField importField;
JButton load;
public LibraryPanel() {
setPreferredSize(new Dimension(200,500));
Library library = new Library();
setLayout(new BorderLayout());
this.setBorder(BorderFactory.createTitledBorder("Library"));
// Import Books Panel
importBooks = new JPanel();
importBooks.setLayout(new BoxLayout(importBooks,BoxLayout.X_AXIS));
importBooks.setBorder(BorderFactory.createTitledBorder("Import Books"));
importField = new JTextField(15);
importBooks.add(importField);
load = new JButton("Load");
importBooks.add(load);
this.add(importBooks,BorderLayout.SOUTH);
load.addActionListener(new loadButtonListener());
// Book List buttons
JPanel bookButtons = new JPanel();
bookButtons.setLayout(new BoxLayout(bookButtons,BoxLayout.Y_AXIS));
JButton testButton = new JButton("TEST Button");
bookButtons.add(testButton);
//for(int i = 0; i<library.getBooks().size(); i++) {
// BookButton button = new BookButton(library.getBook(i));
//button.addActionListener(new BookButtonListener());
// bookButtons.add(button);
//}
// Scroll Pane
bookList = new JScrollPane();
bookList.setBorder(BorderFactory.createTitledBorder("Book List"));
bookList.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
bookList.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
this.add(bookList,BorderLayout.CENTER);
bookList.add(bookButtons);
}
private class loadButtonListener implements ActionListener{
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
String filename = new String(importField.getText());
library.loadLibraryFromCSV(filename);
}
}
}
However, I'm having an issue with the test button not showing up at all within the scroll pane. The panels are there but not the test button.
You can't "add" components to a JScrollPane, this isn't how they work. A JScrollPane uses a JViewport as it's primary component, which is then used to determine when the scrollbars should be used.
See How to Use Scroll Panes for more details
Instead of...
bookList = new JScrollPane();
//...
bookList.add(bookButtons);
simply do...
bookList = new JScrollPane(bookButtons);
//...
//bookList.add(bookButtons);

Java Swing JTabbedPane layout

I am new to Swing and cannot find a page that helps me understand JTabbedPane. I cannot find a way to control the layout of components of the tabbed panels. I can layout each of my panels correctly as separate GUIs but not in a tabbed pane like I need to do. I would like to use the BorderLayout not FlowLayout.
Also, you can see I'm trying to use colors to keep track of my panels and their components. I cannot set the background of the JTabbedPane. It is still the default grey. Can someone tell me why this is?
Thank you for any advice you can give.
What I have so far appears to follow a 'flow layout' despite any changes I've tried
(Methods have been removed or nearly removed to keep code shorter)
public class GUIFrame extends JFrame {
public GUIFrame(String title) {
JFrame frame = new JFrame(title);
Container c = frame.getContentPane();
buildGUI(c);
setFrameAttributes(frame);
}
private void buildGUI(Container c) {
c.setLayout(new BorderLayout());
c.setBackground(Color.BLACK);
JTabbedPane tabs = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.WRAP_TAB_LAYOUT);
tabs.setBackground(Color.YELLOW);
c.add("Center", tabs);
tabs.addTab("Specialty", new SpecialtyPanel());
tabs.addTab("Treatment", new TreatmentPanel());
tabs.addTab("Doctor", new DoctorPanel());
tabs.addTab("Patient", new PatientPanel());
}
private void setFrameAttributes(JFrame f) {
f.setSize(500, 500);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String args[]) {
MedicalSystemIO test = new MedicalSystemIO();
new GUIFrame("Tabbed Title");
}
public class SpecialtyPanel extends JPanel implements ActionListener {
JTextField jteInput = null;
DefaultListModel<String> model = new DefaultListModel<String>();
JList<String> list = new JList(model);
JScrollPane pane = new JScrollPane(list);
public SpecialtyPanel() {
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createLineBorder(Color.black));
buildGUI(panel);
}
private void buildGUI(JPanel panel) {
JPanel jpaInput = createInputPanel();
JPanel jpaProcess = createProcessPanel();
JPanel jpaOutput = createOutputPanel();
//panel.setLayout(new BorderLayout());
add("North", jpaInput);
add("Center", jpaProcess);
add("South", jpaOutput);
}
private JPanel createInputPanel() {
JPanel jpaInput = new JPanel();
jpaInput.setBackground(Color.RED);
return jpaInput;
}
private JPanel createProcessPanel() {
JPanel jpaProcess = new JPanel();
jpaProcess.setBackground(Color.BLUE);
return jpaProcess;
}
private JPanel createOutputPanel() {
JPanel jpaOutput = new JPanel();
jpaOutput.add(pane);
return jpaOutput;
}
The SpecialtyPanel is shown that way (flow layout) as you are putting the components on it in the wrong way:
No need for passing a new panel into the buildGUI method as you want to put them directly on the SpecialtyPanel which already is a JPanel,
you commented out the setting of the BorderLayout and
you used the wrong notation of passing the layout constraints in the add methods.
Your constructor and build method should look like this:
public SpecialtyPanel() {
buildGUI();
}
private void buildGUI() {
setBorder(BorderFactory.createLineBorder(Color.black));
JPanel jpaInput = createInputPanel();
JPanel jpaProcess = createProcessPanel();
JPanel jpaOutput = createOutputPanel();
setLayout(new BorderLayout());
add(jpaInput, BorderLayout.NORTH);
add(jpaProcess, BorderLayout.CENTER);
add(jpaOutput, BorderLayout.SOUTH);
}
To have the panel another color than gray you have to color the component that is put on the tabbed pane as it covers the whole space. Add the desired color to the buildGUI method, e.g.:
private void buildGUI(JPanel panel) {
// ...
setBackground(Color.YELLOW);
}
As a JPanel is opaque by default (that means not transparent), you need to set panels on top (except those which you colored explicitly) to be transparent. In case of SpecialtyPanel:
private JPanel createOutputPanel() {
JPanel jpaOutput = new JPanel();
jpaOutput.add(pane);
jpaOutput.setOpaque(false); // panel transparent
return jpaOutput;
}

Forcing minimum JPanel size

I'm trying to code a layout for a small phonebook app using Java Swing. I came across a problem with size I cannot fix. I want filters (displayed under search results) to be wide enough to show whole title when search phrase is short. Here's what it looks like:
As you can see name is shortened. Surname is carrying longer text and is displayed just as I want.
Filter class:
import javax.swing.*;
import javax.swing.border.CompoundBorder;
import java.awt.*;
public class Filter
{
private JLabel label;
private JPanel filterPane;
Filter(String name)
{
// Filter text
label = new JLabel();
label.setForeground(new Color(0xAA0000));
// Closing button
JButton closeButton = new JButton("X");
closeButton.setFont(new Font("Dialog", Font.BOLD, 20));
closeButton.setMargin(new Insets(0, 0, 0, 0));
filterPane = new JPanel();
filterPane.setLayout(new BoxLayout(filterPane, BoxLayout.X_AXIS));
// Frame with title + 50px padding on the right side for the next filter
filterPane.setBorder(new CompoundBorder(
BorderFactory.createEmptyBorder(0, 0, 0, 50),
BorderFactory.createTitledBorder(name)));
filterPane.add(closeButton);
filterPane.add(Box.createRigidArea(new Dimension(5, 0)));
filterPane.add(label);
filterPane.setVisible(false);
}
public void setValue(String value) {
if (value == null || value.isEmpty())
filterPane.setVisible(false);
else
{
this.label.setText(value);
filterPane.setVisible(true);
}
}
public JPanel getFilterPane() {
return filterPane;
}
}
Main class:
import javax.swing.*;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;
public class TestWindow
{
private static void init()
{
JFrame frame = new JFrame("Stackoverflow test window");
frame.setSize(new Dimension(480, 320));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
// Main panel - 20 px padding from each side
JPanel pane = new JPanel();
pane.setLayout(new BorderLayout());
pane.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
// Create panel for filters
JPanel filterPane = new JPanel();
filterPane.setLayout(new BoxLayout(filterPane, BoxLayout.X_AXIS));
filterPane.setPreferredSize(new Dimension(0, 60));
// Add sample filters
Filter filter = new Filter("name");
filter.setValue("i");
filter.getFilterPane().setAlignmentY(Component.BOTTOM_ALIGNMENT);
filterPane.add(filter.getFilterPane());
filter = new Filter("surname");
filter.setValue("loooooong text");
filter.getFilterPane().setAlignmentY(Component.BOTTOM_ALIGNMENT);
filterPane.add(filter.getFilterPane());
// Add everything to main panel
pane.add(new JTextArea("Nothing fancy here, just to fill out space"), BorderLayout.CENTER);
pane.add(filterPane, BorderLayout.SOUTH);
frame.setContentPane(pane);
frame.setVisible(true);
}
public static void main (String [] args) throws InterruptedException, InvocationTargetException {
SwingUtilities.invokeLater(TestWindow::init);
}
}
I tried setMinimumSize and setPreferredSize both for label and filterPane in Filter constructor, but it didn't help. Could you help me please?
The problem is actually composed of two problems.
When using TitledBorder, the component does not take the size of the title into account when calculating its preferred size.
When using BoxLayout, the preferred width is ignored, unless you add "horizontal glue" after the component.
So, to fix the first problem, you need to override the getPreferredSize() method of JPanel. But for this to work, you also need to bypass the second problem by adding glue.
To override the getPreferredSize(), you can use something like:
class MinSizePanel extends JPanel {
public Dimension getPreferredSize() {
Dimension original = super.getPreferredSize();
TitledBorder titleBorder = (TitledBorder)((CompoundBorder)getBorder()).getInsideBorder();
int width = (int)Math.max( original.getWidth(), 60 + (int)titleBorder.getMinimumSize(this).getWidth());
return new Dimension( width, (int)original.getHeight() );
}
}
This gets the TitledBorder from the CompoundBorder, gets its minimum size (this is a method that takes the title width into consideration), adds some extra for the empty border and insets (you can add the appropriate calculations, I did a shortcut and just used 60...), and uses that (if it's more than the width of the component as-is).
Instead of
filterPane = new JPanel();
Use
filterPane = new MinSizePanel();
(You should really move all the rest of the construction of the panel into that class as well).
And in the TestWindow class, after the last
filterPane.add(filter.getFilterPane());
Also add
filterPane.add(Box.createHorizontalGlue());

Struggling with the append method for JTextArea

This is the code I am struggling with. It is refusing to amend the JTextArea with the new text. I create the window and set it to visible in the main function of the project.
Thanks ahead.
EDIT:
By refusing, I mean the JTextArea will simply not display the text. It just stays empty. I'm not getting and error or exception. It is all logical.
class Window extends JFrame{
protected JTextArea text;
public Window() {
setTitle("Create a list of names");
setSize(500,400);
Container containerPane = getContentPane();
JPanel jp = new JPanel();
text = new JTextArea(10,50);
text.setPreferredSize(new Dimension(256,256) );
text.setEditable(false);
JScrollPane scrollText = new JScrollPane(text);
scrollText.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
jp.add(scrollText);
containerPane.add(jp, BorderLayout.CENTER);
text.append("Test");
}
public static void main(String[] args) {
Window w = new Window();
w.setVisible(true);
}
}
The column width of 50 is greater than the width of the frame so the added text appears offscreen. Reduce its value to fit the parent window
textArea = new JTextArea(10, 35);
Don't use setPrerredSize. Let the layout manager do its job and call pack after all components have been added.

Java CardLayout Main Menu Problem

Ok so im working on this game in java called 8 bit chimera. Im working on the main menu right now but when im using the card layout the window wont open for some reason. Here is some code.
import javax.swing.*;
import java.awt.*;
public class MainScreen extends JFrame{
String Title = "MainMenu";
MainMenuComp MMC = new MainMenuComp();
BreedingGround BGR = new BreedingGround();
public MainScreen() {
setTitle("8-bit Chimera "+Title);
setSize(800,600);
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
add(MMC);
add(BGR);
}
public static void main(String[] args){
new MainScreen();
}
}
that was the Main window
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class MainMenuComp extends JPanel implements ActionListener{
BreedingGround BGR = new BreedingGround();
ImageData ID = new ImageData();
Image TitleBg;
Image Title;
CardLayout CL;
JButton Play;
public MainMenuComp() {
setLayout(new GridBagLayout());
GridBagConstraints GBC = new GridBagConstraints();
ImageIcon TitleData = new ImageIcon(ID.TitleSource);
ImageIcon TitleBackGroundData = new ImageIcon(ID.TitleBackGroundSource);
ImageIcon PlayData = new ImageIcon(ID.PlaySource);
TitleBg = TitleBackGroundData.getImage();
Title = TitleData.getImage();
Play = new JButton();
Play.setIcon(PlayData);
add(Play,GBC);
add(BGR,"Breed");
}
public void actionPerformed(ActionEvent AE){
if(AE.getSource() == Play){
CL.show(this, "Breed");
}
}
public void paintComponent(Graphics g){
g.drawImage(TitleBg,0,0,800,600,this);
g.drawImage(Title,250,80,280,140,this);
}
}
this was the card layout
import javax.swing.*;
import java.awt.*;
public class BreedingGround extends JPanel{
ImageData ID = new ImageData();
Image Swamp;
CardLayout CL;
public BreedingGround(){
setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
ImageIcon SwampData = new ImageIcon(ID.SwampSource);
Swamp = SwampData.getImage();
}
public void paintComponent(Graphics g){
g.drawImage(Swamp,0,0,800,600,this);
}
}
and that was what i wanted the CardLayout to open. The problem is that when i try to run it the window wont run and this keeps showing in the compiler.
--------------------Configuration: 8-bit Chimera - JDK version 1.6.0_26 - --------------------
Exception in thread "main" java.lang.IllegalArgumentException: cannot add to layout: constraints must be a GridBagConstraint
at java.awt.GridBagLayout.addLayoutComponent(GridBagLayout.java:685)
at java.awt.Container.addImpl(Container.java:1074)
at java.awt.Container.add(Container.java:927)
at MainMenuComp.<init>(MainMenuComp.java:26)
at MainScreen.<init>(MainScreen.java:7)
at MainScreen.main(MainScreen.java:23)
Process completed.
All i really want to know is what this is saying.
I don't see where you ever set the layout of a container to be CardLayout, and if you don't set the layout to this, you can't magically use it. If you haven't yet gone through the CardLayout tutorial, consider doing so as it's all explained there.
Edit 1
Comment from Alexander Kim:
when i added the cardbagLayout it wont load the image and the button size filled the whole screen. I also took away the grids
You need to nest your JPanels in order to nest layouts. Use a single JPanel as the CardLayout container whose single function it is is to display other JPanels (the "cards"). These other JPanels will use whatever layouts that are necessary to properly display the components that they hold such as your JButton or "grids" (whatever they are). And even these JPanels may hold other JPanels that use other layouts.
Again, please read the layout tutorials as it's all described well there. You will not regret doing this.
Edit 2
Here's a very simple example that uses a CardLayout. The component displayed by the CardLayout using JPanel (called the cardContainer) is changed depending on which item is selected in a combobox.
Here's the CardLayout and the JPanel that uses it:
private CardLayout cardLayout = new CardLayout();
// *** JPanel to hold the "cards" and to use the CardLayout:
private JPanel cardContainer = new JPanel(cardLayout);
And here's how I add a component to the cardlayout-using JPanel:
JPanel redPanel = new JPanel();
//...
String red = "Red Panel";
cardContainer.add(redPanel, red); // add the JPanel to the container with the String
I also add the String to a JComboBox so I can use this combo box later to tell the CardLayout to display this JPanel (redPanel) if the user selects the item "Red" in this same JComboBox:
cardCombo.addItem(red); // also add the String to the JComboBox
Here's the ActionListener in the JComboBox that lets me change the item displayed in the cardlayout using JPanel:
cardCombo.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String item = cardCombo.getSelectedItem().toString();
// *** if combo box changes it tells the CardLayout to
// *** swap views based on the item selected in the combo box:
cardLayout.show(cardContainer, item);
}
});
And here's the whole shebang:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SimpleCardLayoutDemo {
private CardLayout cardLayout = new CardLayout();
// *** JPanel to hold the "cards" and to use the CardLayout:
private JPanel cardContainer = new JPanel(cardLayout);
private JComboBox cardCombo = new JComboBox();
private JPanel comboPanel = new JPanel();;
public SimpleCardLayoutDemo() {
JPanel greenPanel = new JPanel(new BorderLayout());
greenPanel.setBackground(Color.green);
greenPanel.add(new JScrollPane(new JTextArea(10, 25)), BorderLayout.CENTER);
greenPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
greenPanel.add(new JButton("Bottom Button"), BorderLayout.PAGE_END);
String green = "Green Panel";
cardContainer.add(greenPanel, green);
cardCombo.addItem(green);
JPanel redPanel = new JPanel();
redPanel.setBackground(Color.red);
redPanel.add(new JButton("Foo"));
redPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
String red = "Red Panel";
cardContainer.add(redPanel, red);
cardCombo.addItem(red);
JPanel bluePanel = new JPanel();
bluePanel.setBackground(Color.blue);
JLabel label = new JLabel("Blue Panel", SwingConstants.CENTER);
label.setForeground(Color.white);
label.setFont(label.getFont().deriveFont(Font.BOLD, 32f));
bluePanel.add(label);
String blue = "Blue Panel";
cardContainer.add(bluePanel, blue);
cardCombo.addItem(blue);
comboPanel.add(cardCombo);
cardCombo.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String item = cardCombo.getSelectedItem().toString();
// *** if combo box changes it tells the CardLayout to
// *** swap views based on the item selected in the combo box:
cardLayout.show(cardContainer, item);
}
});
}
public JPanel getCardContainerPanel() {
return cardContainer;
}
public Component getComboPanel() {
return comboPanel ;
}
private static void createAndShowUI() {
SimpleCardLayoutDemo simplecardDemo = new SimpleCardLayoutDemo();
JFrame frame = new JFrame("Simple CardLayout Demo");
frame.getContentPane().add(simplecardDemo.getCardContainerPanel(), BorderLayout.CENTER);
frame.getContentPane().add(simplecardDemo.getComboPanel(), BorderLayout.PAGE_END);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
// to run Swing in a thread-safe way
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
Your problem is with add(BGR,"Breed");. The layout of MainMenuComp is a GridBagLayout, so the constraint must be a GridBagConstraint, not a String (you have "Breed" as the constraint).

Categories

Resources