Expandable JTextArea - java

Part of the application I am building demands that I display a variable amount of text in a non-editable component of some sort. Currently this has been implemented in JTextArea, but JTextArea has only the setRows() to set the vertical size of the component.
What I want is a component that will expand to the size needed. This does not pose a problem since the panel on which this thing is embedded is scrollable. It doesn't have to all show up at any particular time but it has to be visible. (And I don't want scrollbars within scrollbars, which I consider an abomination.
What Swing component is best for these requirements?
(Note: I am only asking this here because the entire #$%^&* Oracle Java documentation site including all the Swing demos and tutorials appears to be down now).

I've managed a working prototype for this addressing the dynamic resize issues in the original problem. As more text is added, the text area is resized to be big enough to contain the text. Obviously use setEditable(false) to stop editing of text. Hopefully it will give you some ideas.
set the text
change the column count to an approximate value - here I used square root of total characters * a arbitrary factor.
not the text area is a reasonable width, but we still need to fix the height.
set preferred size to a low value - this will force a recalculation
set preferred height to the minimum height - this is calculated from minimum bounding box of content.
Code
JFrame frame = new JFrame();
GroupLayout gLayout = new GroupLayout(frame.getContentPane());
frame.getContentPane().setLayout(gLayout);
final JTextArea area = new JTextArea();
area.setEditable(false);
area.setLineWrap(true);
area.setWrapStyleWord(true);
JButton button = new JButton("Add more");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
area.setText(area.getText()
+ "apple banana carrot dingo eagle fox gibbon ");
// set approx number of cols
int chars = area.getText().length();
int cols = (int) Math.round(Math.sqrt(chars) * 1.3);
area.setColumns(cols);
// force recalculation
area.setPreferredSize(new Dimension(25, 25));
// downsize
area.setPreferredSize(new Dimension(
area.getPreferredSize().width,
area.getMinimumSize().height));
}
});
ParallelGroup hGroup = gLayout
.createParallelGroup()
.addComponent(button)
.addComponent(area, GroupLayout.PREFERRED_SIZE,
GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE);
gLayout.setHorizontalGroup(hGroup);
SequentialGroup vGroup = gLayout
.createSequentialGroup()
.addComponent(button)
.addComponent(area, GroupLayout.PREFERRED_SIZE,
GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE);
gLayout.setVerticalGroup(vGroup);
frame.setSize(600, 500);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.invalidate();
frame.validate();
frame.setVisible(true);

Emm... In the case you don't want to enter text you don't need JTextArea... Just to display some text you can simply use JLabel; JLabel supports html text format so you can easily use it in some way like this
...
JPanel aPanel=new JLanel(new FlowLayout());
JLabel aLabel=new JLabel();
aPanel.add(aLabel);
void showFormattedText(String html)
{
aLabel.setText(html);
}
...
As you may guessed, the formatted text can be anything like this
<html>
Put some text<br>
...<br>
</html>
I hope you got the conception
...
mini parser - not tested
String getFormattedText(String text)
{
char commonBR='\n';
String htmlBR="<br>";
char check;
String result="";
for(int i=0; i<text.length(); i++)
{
check=text.charAt(i);
if(check==commonBR)
{
result+=htmlBR;
continue;
}
result+=check;
}
return result;
}
...
void test
{
String text="Hello world \n Hello World once again \n ...and again ";
System.out.println(this.getFormattedText(text));
}
... it is not a final solution though but a basis conception. I hope it was helpful
Good luck

Related

Space under JTextPane text when using .setText method

I am using MigLayout 3.5.5, as the newer updates are not compatible with my older code.
Problem
When setting text to a JTextPane in a MigLayout, the JTextPane will take double the space (according to font size) IF the text I am setting the JTextPane contains space characters. It does not happen all the time, but in the specific program I am making, it happens frequently.
The program's goal is to present information in a letter-by-letter basis, so there is a button that updates the text to the next letter. However, the text bounces around, because the JTextPane is sometimes occupying more space than usual. I identified a certain pattern to the height differences.
Pattern
A new line indicates that I added a letter.
"|" represents a space character in the text.
"Space" means JTextPane is taking double the space.
Full String: "The quick brown fox jumps over the lazy dog."
T
Th
The
The|
The|q (Space)
The|qu
The|qui (Space)
The|quic
The|quick (Space)
The|quick|
Note: I stopped the pattern here, because from this point on (starting with The|quick|b), every single letter addition resulted in the JTextPane occupying double its height.
I've already tried printing out the letter-by-letter text to the console to see if there were any new line characters within the text being added, but to no avail. I also thought it might be a problem with the automatic wrapping of the JTextPane, but the text I inserted isn't quite long enough to wrap in the JFrame's size.
Here is a short example to reproduce the behavior:
public class MainFrame extends JFrame {
int currentLetter = 1;
final String FULL_TEXT = "The quick brown fox jumps over the lazy dog.";
JTextPane text;
JButton addLetter;
MainFrame() {
setSize(500, 500);
setLayout(new MigLayout("align center, ins 0, gap 0"));
addElements();
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
MainFrame application = new MainFrame();
}
});
}
private void addElements() {
text = new JTextPane();
text.setEditable(false);
text.setFont(new Font("Times New Roman", Font.BOLD, 19));
text.setForeground(Color.WHITE);
text.setBackground(Color.BLACK);
add(text, "alignx center, wmax 80%, gapbottom 5%");
addLetter = new JButton("Add Letter");
addLetter.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (currentLetter != FULL_TEXT.length()) {
currentLetter++;
updateText();
}
}
});
add(addLetter, "newline, alignx center");
updateText();
}
private void updateText() {
String partialText = new String();
for (int letter = 0; letter < currentLetter; letter++) {
partialText += FULL_TEXT.toCharArray()[letter];
}
text.setText(partialText);
}
}
Why am I using JTextPane?
I tried using JLabel for this task, and it worked well... until the text was long enough to wrap. Then, when I used HTML within the JLabel text to wrap it, every time I updated the text, it would take time for the HTML to render and result in some pretty nasty visual effects.
Next, I tried JTextArea to disguise it as a JLabel, since it not only has line wrapping, but word wrapping as well. It was a great solution, until I found out that I couldn't use a center paragraph alignment in a JTextArea.
So I settled for a JTextPane, which will work well if only I got rid of the extra space at the bottom of it.
Thanks in advance for your help!
The solution is to append text by using the insertString() method on the StyledDocument of the JTextPane instead of using setText() on the JTextPane itself.
For example, instead doing this every time:
JTextPane panel = new JTextPane();
panel.setText(panel.getText() + "test");
You should do this:
JTextPane panel = new JTextPane();
StyledDocument document = panel.getStyledDocument();
document.insertString(document.getLength(), "test", null);
And of course you need to catch the BadLocationException.
Then the space disappears. Here's the question where I found my answer to the rendering problem: JTextPane appending a new string
The answers to those questions don't address the problem with the space, but they do show the correct way to edit text in the JTextPane.

Making JLable in JTextPane Undeletable

I currently have a JLabel embedded in a JTextPane using this:
import javax.swing.*;
import javax.swing.text.*;
public class MainFrame
{
JFrame mainFrame = new JFrame("Main Frame");
JTextPane textPane = new JTextPane();
public MainFrame()
{
String[] components = {"Title", "\n"};
String[] styles = {"LABEL_ALIGN", "LEFT_ALIGN"};
StyledDocument sd = textPane.getStyledDocument();
Style DEFAULT_STYLE = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
Style LEFT_STYLE = sd.addStyle("LEFT_ALIGN", DEFAULT_STYLE);
StyleConstants.setAlignment(LEFT_STYLE, StyleConstants.ALIGN_LEFT);
Style CENTER_STYLE = sd.addStyle("CENTER_ALIGN", DEFAULT_STYLE);
StyleConstants.setAlignment(CENTER_STYLE, StyleConstants.ALIGN_CENTER);
JLabel titleLbl = new JLabel("Title");
Style LABEL_STYLE = sd.addStyle("LABEL_ALIGN", DEFAULT_STYLE);
StyleConstants.setAlignment(LABEL_STYLE, StyleConstants.ALIGN_CENTER);
StyleConstants.setComponent(LABEL_STYLE, titleLbl);
for(int i = 0; i < components.length; i++)
{
try
{
sd.insertString(sd.getLength(), components[i], sd.getStyle(styles[i]));
sd.setLogicalStyle(sd.getLength(), sd.getStyle(styles[i]));
}
catch(BadLocationException e)
{
e.printStackTrace();
}
}
mainFrame.add(textPane);
mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
mainFrame.setLocationRelativeTo(null);
mainFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);
mainFrame.pack();
mainFrame.setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(MainFrame::new);
}
}
How can I make the label un-deletable? Because whenever I hold backspace, the label ends up getting removed from the JTextPane
You might be able to use a NavigationFilter to prevent the removal of the component at the beginning of the text pane. Check out: How to make part of a JTextField uneditable for an example of this approach. In this case the label represents a single character so the prefix length would be set to 1. Or maybe you can just use the prefix concept itself and don't even use the JLabel.
Otherwise, you might be able to create a custom DocumentFilter. Check out the section from the Swing tutorial on Implementing a DocumentFilter for the basics.
So you would need to track the offset off the location of the component. Then in the remove(...) method of the filter you would need to check if you are removing data in the range of your offset. If so you would ignore the remove.
Of course the offset can dynamically change if you add or remove text before the label so you would need to manage that as well.
Or you can check out the Protected Text Component which attempts to manage all of that for you.
Why not just put your title label outside the text area? That seems more intuitive.
It looks like there's no real way to avoid this while still allowing the textarea to be editable. You could place the label above the text frame so that it occupies the same space, or above the text frame so that it behaves like a proper title.
Unfortunately, the nature of the textarea is that all of its subcomponents are editable or none of them are.

Java Applet gridlayout issue

im having a little issue with my code. I have created a gridlayout of 5,1,0,0. I have a textfield, 3 buttons and a label where the result analysis of whatever the user had input is displayed at the bottom. Now the results can come on multiple lines depending on how big words are in the sentence, my problem is when multiple lines of results are displayed, the layout of my program changes and i dont know how to keep it the same but just the label or Applet window itself resize if need be?
public class assignment_tauqeer_abbasi extends JApplet implements ActionListener {
JTextArea textInput; // User Input.
JLabel wordCountLabel; // To display number of words.
public void init() {
// This code from here is the customisation of the Applet, this includes background colour, text colour, text back ground colour, labels and buttons
setBackground(Color.black);
getContentPane().setBackground(Color.black);
textInput = new JTextArea();
textInput.setBackground(Color.white);
JPanel south = new JPanel();
south.setBackground(Color.darkGray);
south.setLayout( new GridLayout(5,1,0,0) );
/* Creating Analyze and Reset buttons */
JButton countButton = new JButton("Analyze");
countButton.addActionListener(this);
south.add(countButton);
JButton resetButton = new JButton("Reset");
resetButton.addActionListener(this);
south.add(resetButton);
JButton fileButton = new JButton("Analyze Text File");
fileButton.addActionListener(this);
south.add(fileButton);
/* Labels telling the user what to do or what the program is outputting */
wordCountLabel = new JLabel(" No. of words:");
wordCountLabel.setBackground(Color.black);
wordCountLabel.setForeground(Color.red);
wordCountLabel.setOpaque(true);
south.add(wordCountLabel);
/* Border for Applet. */
getContentPane().setLayout( new BorderLayout(2,2) );
/* Scroll bar for the text area where the user will input the text they wish to analyse. */
JScrollPane scroller = new JScrollPane( textInput );
getContentPane().add(scroller, BorderLayout.CENTER);
getContentPane().add(south, BorderLayout.SOUTH);
} // end init();
public Insets getInsets() {
// Border size around edges.
return new Insets(2,2,2,2);
}
// end of Applet customisation
This is my code for the layout. Any help would be apprecited!
A GridLayout will size every cell according to the content of the largest cell. Consider using a different layout, or a combination of layouts instead.
The gridLayout that you have used would possibly complicate the five contents that you have used. Try using flow Layout instead this would automatically make space for the new contents that are being entered.

Java Swing panel layered on top with centered text

I'm making a simple Jeopardy-esque game:
using Java Swing. It's obviously a JFrame with a JPanel in it and buttons in rows.
Now what I need is to add a layered panel with a centered and wrapped text in it:
Which I can remove later. I already tried using JTextPane and JTextArea and JPanel, none of those want to even display. The best effect I have achieved with AWT Panel, it does display but I can't center or wrap text in it.
Here's some code for which I appologise, I would usually try to make it short and readable but since it's not working I don't know what to do with it to make ti look better:
JLabel questionLabel = new JLabel(questionList.get(randomNumber).getQuestion(), SwingConstants.CENTER);
Font font = new Font("Arial", Font.PLAIN, 20);
//------------------JTextPane--------------------
JTextPane questionPane = new JTextPane();
questionPane.setForeground(Color.WHITE);
questionPane.setSize(gameWidth, gameHeight);
questionPane.setText(questionList.get(randomNumber).getQuestion());
questionPane.setFont(font);
questionPane.setEditable(false);
//------------------AWT panel--------------------
Panel awtPanel = new Panel();
awtPanel.setBackground(Color.blue);
awtPanel.setSize(game.getWidth(),game.getHeight());
Label labelQuestion = new Label("<html>" + questionList.get(randomNumber).getQuestion() + "</html>", Label.CENTER);
labelQuestion.setFont(font);
awtPanel.setForeground(Color.white);
awtPanel.add(labelQuestion);
//------------------JPanel-----------------------
JPanel layeredPanel = new JPanel();
layeredPanel.setBackground(Color.blue);
layeredPanel.setSize(game.getWidth(),game.getHeight());
JLabel jLabelQuestion = new JLabel("<html>" + questionList.get(randomNumber).getQuestion() + "</html>", SwingConstants.CENTER);
jLabelQuestion.setFont(font);
layeredPanel.setForeground(Color.WHITE);
layeredPanel.add(jLabelQuestion, BorderLayout.CENTER);
game.getLayeredPane().add(layeredPanel, JLayeredPane.DEFAULT_LAYER);
try {
Thread.sleep(3000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
button.setEnabled(false);
font = new Font("Arial", Font.PLAIN, 16);
button.add(jLabelQuestion, BorderLayout.CENTER);
button.setDisabledIcon(new ImageIcon(source.getScaledInstance(gameWidth/4, gameHeight/5, java.awt.Image.SCALE_SMOOTH)));
questionList.remove(randomNumber);
logger.info(questionList.size());
game.getLayeredPane().remove(layeredPanel);
UPDATE: I chnaged to SWT rather than Swing, and I use the StackLayout with a few Composites in it, and just change between them as I see fit.
You can generally solve issues like this with a JLabel.
I would recommend encapsulating the above grid in the BorderLayout.CENTER of another pane, perhaps a new content pane. Then, add the caption to BorderLayout.NORTH.
As a more tangible example,
private void createContent() {
this.getContentPane().setLayout(new BorderLayout());
//establish the panel currently set as center, here labeled "everythingElse"
this.getContentPane().add(everythingElse, BorderLayout.CENTER);
//Create a JLabel with your caption
JLabel jlbl = new JLabel("Question");
//format that caption, most details being rather obvious, but most importantly:
jlbl.setHorizontalAlignment(SwingConstants.CENTER); //keeps text centered
this.getContentPane().add(jlbl, BorderLayout.NORTH); //add it to the top of the panel
//...other cleanup operations...
}
The issue with grid panes is that they have a limited tolerance for the number of components visible in them. If you overload one, it won't show. For BorderLayout panes, you can easily swap new items into and out of them.
For efficiency's sake, I might recommend compiling this JLabel as a final somewhere else in your code, and holding onto it for when you need it. This way, you will also dodge overhead from repeatedly creating the label object.
Lastly, avoid AWT whenever you can. It's been deprecated for an excess of ten years, and if you do use it you will run into numerous critical problems involving heavyweight and lightweight component incompatibilities. If you intend to use another windowing kit, consider implementing the new standard, JavaFX, with a JFXPane-- it's much more tolerant of HTML syntax, as well.

Java: Linebreaks in JLabels?

I'm trying to make a Swing JLabel with multiple lines of text. It's added just fine, but the line breaks don't come through. How do I do this? Alternatively, can I just specify a maximum width for a JLabel and know that the text would wrap, like in a div?
private void addLegend() {
JPanel comparisonPanel = getComparisonPanel();
//this all displays on one line
JLabel legend = new JLabel("MMM FFF MMM FFFO O OOM M MMMM.\nMMM FFF MMM FFFO O OOM M MMMM.\nMMM FFF MMM FFFO O OOM M MMMM.\n");
comparisonPanel.add(legend);
}
Use HTML in setText, e.g.
myLabel.setText("<html><body>with<br>linebreak</body></html>");
You can get automatic line break if you set the paragraph width in html.
label.setText("<html><p style=\"width:100px\">"+paragraph+"</p></html>");
By default, Swing does not wrap text. If you specify a size on the JLabel it will only paint the part of the text that fits and then add "..." to the end.
As suggested you can use HTML to enable line wrapping. However, I've actually created a custom Swing UI delegate not long ago to achieve this and even more: MultiLineLabelUI.
It will wrap your text to fit the available space and also respect hard line breaks. If you choose to try it out, it is as simple as:
JLabel label = new JLabel("Text that'll wrap if necessary");
label.setUI(MultiLineLabelUI.labelUI);
Or alternatively use the custom MultiLineLabel class that in addition to wrapping text supports vertical and horizontal text alignment.
UPDATE
I lost the domain with the original code samples. It can now be viewed on github instead: https://github.com/sasjo/multiline
You can put HTML inside of a JLabel and use the linebreak tag to achieve this.
What about using the wrapping feature in a JTextArea?
String text = "some really long string that might need to"+
"be wrapped if the window is not wide enough";
JTextArea multi = new JTextArea(text);
multi.setWrapStyleWord(true);
multi.setLineWrap(true);
multi.setEditable(false);
JLabel single = new JLabel(text);
JPanel textpanel = new JPanel(new GridLayout(2,1));
textpanel.add(multi);
textpanel.add(single);
JFrame frame = new JFrame();
frame.add(textpanel);
frame.pack();
frame.setVisible(true);
Simple,use HTML. Java Swing components though does not provide a 'fantastic' support for the HTML, you can use it for such simple purposes.
label.setText("<html>This is first line.<br/>This is second line.</html>");
I did not manage to specify a maximum width for a label but you can specify a concrete width.
By measuring the current width of a JLabel we can only apply the new fixed width if the JLabels's width is higher that our maxWidth:
JLabel label = new JLabel("<html>" + myVeryLongMessage + "<html>");
int maxWidth = 400;
Dimension size = label.getPreferredSize();
if (size.width > maxWidth) {
// Estimate the number of lines
int lineCount = (int) Math.ceil(((double) size.width) / maxWidth);
lineCount += 1; // Add one extra line as reserve
size.width = maxWidth; // Apply the maximum width
// Increase the height so that all lines will be visible
size.height *= lineCount;
label.setPreferredSize(size);
}
You can use a JTextArea and disable the TextArea, this way, you will only display what you want, and the user won't be able to type in
JTextArea area = new JTextArea("Here \n\n you \n\n put \n\n your \n\n text");
area.setBounds(10, 11, 500, 143);
area.setEditable(false);
yourPannel.add(area);

Categories

Resources