Java JTextArea dynamic column and row numbering - java

I've had a quick Google around, and I can't seem to find a good solution to this, mostly because I'm not sure how to describe it.
Essentially, I need to display an arbitrary amount of hex characters in a JTextArea, and I'd like to have them spaced evenly, and have the positions of the characters shown at the top and left of the characters.
This is an example of what I'd like to achieve, this is the hex viewer WinHex.
I've been playing around with converting a byte array to a String, and then text-wrapping it, but I've had some odd results. Any advice on how to achieve something similar to this would be appreciated.
Another option I've considered is using a JTable, but I'm wondering if that's over complicating the matter slightly. Maybe.
Thanks

I've considered is using a JTable, but I'm wondering if that's over complicating the matter slightly
A decade ago, when I was trying to understand JTable I created myself a simple hex editor to try to understand table models, renderers and editors.
Check out Hex Editor for my result. Just unzip the file and compile all the java files and then execute the Hex class.
I haven't looked at the code in 10 years so I don't know if I followed all best coding practices, but have fun anyway.

This should get you started, using a very simple implementation of AbstractTableModel. This only took me 15 minutes to write (in response to "overcomplicating the issue").
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
public class HexText extends JFrame {
public static void main(String... args) {
final HexText window = new HexText();
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
window.setVisible(true);
}
});
}
private static class HexTableModel extends AbstractTableModel {
List<Integer> data = new ArrayList<>();
#Override
public int getRowCount() {
return data.size();
}
#Override
public int getColumnCount() {
return 9;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (columnIndex == 0) {
return Integer.toHexString(rowIndex << 5);
} else {
int row = data.get(rowIndex);
int theByte = 0xFF & (row >> (columnIndex * 2));
String output = Integer.toHexString(theByte);
if (output.length() == 1)
output = "0" + output;
return output;
}
}
public void addRow(int rowElement) {
data.add(rowElement);
fireTableRowsInserted(data.size() - 1, data.size() - 1);
}
}
public HexText() {
JPanel contentPane = new JPanel(new BorderLayout());
HexTableModel theModel = new HexTableModel();
JTable theTable = new JTable(theModel);
Random r = new Random();
for (int i = 0; i < 20; i++) {
theModel.addRow(r.nextInt());
}
contentPane.add(theTable, BorderLayout.CENTER);
this.add(theTable);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.pack();
}
}

Related

Repainting problem when scrolling nested table in Java 9+

I have a Swing UI element that uses nested JTables. That is, the TableCellRenderer of the outer table returns JTable objects as the Component to render for each cells. This code works great under Java 8. Using the exact same code under Java 9 or Java 10 causes the inner table to not be repainted properly when scrolling the table. The table IS repainted properly when the window is resized.
Edit:
I did some more digging and traced the problem to lines 1862:1877 in BasicTableUI.java - these lines were added in Java 9. If I make my own TableUI without these lines, the problem disappears. From debugging, it appears that the issue is that in my case rMin and rMax are equal and setting rMax = rMax - 1 makes rMax < rMin which is obviously is bad. The added code seems to be there to deal with issues printing a table. Not sure why nested tables makes this break.
See the SSCE below:
package testing.test_painting;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import java.awt.Component;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class InnerTable extends JTable {
public InnerTable(TableModel dm) {
super(dm);
}
}
class InnerTableModel extends AbstractTableModel {
#Override
public int getRowCount() {
return 500;
}
#Override
public int getColumnCount() {
return 10;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return rowIndex + "," + columnIndex;
}
}
class OuterTableModel extends AbstractTableModel {
private final List<JTable> termTables;
public OuterTableModel() {
this.termTables = Arrays.asList(new InnerTable(new InnerTableModel()));
}
#Override
public int getRowCount() {
return 1;
}
#Override
public int getColumnCount() {
return termTables.size();
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return termTables.get(columnIndex);
}
public JTable getTermTable(int modelColumn) {
return termTables.get(modelColumn);
}
}
class OuterTable extends JTable {
private final List<TableRenderer> renderers;
private class TableRenderer implements TableCellRenderer {
private final OuterTableModel tableModel;
public TableRenderer(OuterTableModel tableModel) {
this.tableModel = tableModel;
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
int modelColumn = convertColumnIndexToModel(column);
JTable termTable = tableModel.getTermTable(modelColumn);
termTable.setVisible(true);
return termTable;
}
}
private final OuterTableModel tableModel;
public OuterTable(OuterTableModel tableModel) {
super(tableModel);
renderers = new ArrayList<>(tableModel.getColumnCount());
for (int i = 0; i < tableModel.getColumnCount(); i++) {
renderers.add(new TableRenderer(tableModel));
}
this.tableModel = tableModel;
setCellDimensions();
}
#Override
public void setTableHeader(JTableHeader tableHeader) {
tableHeader.setUI(new BasicTableHeaderUI());
super.setTableHeader(tableHeader);
}
private void setCellDimensions() {
Dimension preferredSize = tableModel.getTermTable(0).getPreferredSize();
if (getRowHeight() != preferredSize.height) {
setRowHeight(preferredSize.height);
}
TableColumnModel columnModel = getColumnModel();
for (int i = 0; i < columnModel.getColumnCount(); i++) {
TableColumn column = columnModel.getColumn(i);
column.setMinWidth(preferredSize.width);
column.setMaxWidth(preferredSize.width);
column.setPreferredWidth(preferredSize.width);
column.setWidth(preferredSize.width);
}
setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
}
#Override
public TableCellRenderer getCellRenderer(int row, int column) {
return renderers.get(column);
}
}
public class TestNestedTable {
public static void main(String[] args) {
OuterTableModel mainTableModel = new OuterTableModel();
OuterTable mainTable = new OuterTable(mainTableModel);
JFrame jFrame = new JFrame();
JScrollPane scrollPane = new JScrollPane(mainTable);
scrollPane.getVerticalScrollBar().setUnitIncrement(10);
jFrame.getContentPane().add(scrollPane);
jFrame.pack();
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setVisible(true);
}
}
make my own TableUI without these lines, the problem disappears.
Or simpler, you might be able to use JViewport#SIMPLE_SCROLL_MODE:
JScrollPane scrollPane = new JScrollPane(mainTable);
scrollPane.getVerticalScrollBar().setUnitIncrement(10);
scrollPane.getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
I know its and old question, but I had the same issue and maybe someone else with the same problem finds this helpful.
This is an JDK bug reported several times (so a lot of duplicates) but most information can be found in the following ticket:
https://bugs.openjdk.java.net/browse/JDK-8202702
There has been added these lines to the BasicTableUI:
// Do not decrement rMax if rMax becomes
// less than or equal to rMin
// else cells will not be painted
if (rMax - rMin > 1) {
rMax = rMax - 1;
}
(s. http://hg.openjdk.java.net/jdk/client/rev/3ba3d39b91c7
or
https://github.com/openjdk/jdk/commit/bb9fed1008dee377725dc669401c389da685f618#diff-ae77528c5d554a9870371e7075801adc4ecd8f992ff484804f164b692b388858)
It is fixed for JDK 12 onward. Unfortunaltey, it is not backported to JDK 11, so if you are forced to use this LTS version one can either do it like #MattWallace and use own TableUI or you can overwrite your Table extending JTable with
#Override
public int getSelectedRow()
{
final int i = super.getSelectedRow();
return i == -1 ? -2 : i;
}
This might bring the problems back mentioned in BasicTableUI comments:
// We did rMax-1 to paint the same number of rows that are drawn on console
// otherwise 1 extra row is printed per page than that are displayed
// when there is no scrollPane and we do printing of table
// but not when rmax is already pointing to index of last row
// and if there is any selected rows
But if you can live with that, this can be also a workaround. Other workarounds like wrapping the Table in a JViewPort or a JScrollPane I cannot recommend as I got size issues with tables which can have changing row heights due to long texts.

JTable, how can I put icons in the header?

i'm trying to render some icons in a JTable header without success. I've already tried something like this: http://www.java2s.com/Tutorial/Java/0240__Swing/CustomizingColumnHeaderswithIcons.htm
but it doesn't show any icon in the header. How can I reach this goal?
Your link is good code , but in fact when you load a picture using absolute path or relative path , this code is not portable .So it is better to load picture from a class. Levels :
1) Create a empty class with this name : ImageResources
2) Please copy your picture
3) Please find ImageResources class in package explorer (in eclipse) and do right click on it (ImageResources) and press "Paste" menuItem.
4) Finally you need a small change in your code like this
ImageIcon blueIcon = new ImageIcon(ImageResources.class.getResource(yourPictureName));
It is working :)
Below method worked for me.
MyTable.getColumn("ColumnName").setHeaderRenderer(new TableCellRenderer() {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
return new JLabel(new ImageIcon(getClass().getResource("/project/img/ImgName.png")));
}
});
The question is a bit older, but I was only now faced with the same question.
Another approach is to override the TableModel's getColumnName function. HTML formatted text is supported.
Here is the sample code:
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
public class TableColumnHeaderIcon {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
TableModel tableModel = new AbstractTableModel() {
#Override
public String getColumnName(int columnIndex) {
// Build the html formatted text
StringBuilder sb = new StringBuilder();
sb.append("<html>");
sb.append("<img src=\"https://via.placeholder.com/16/007AAE/\">");
sb.append(" Column Title");
sb.append("</html>");
return sb.toString();
}
#Override
public int getColumnCount() {
return 1;
}
#Override
public int getRowCount() {
return 0; // No data required
}
#Override
public Object getValueAt(int i, int i1) {
return null; // No data required
}
};
// Setup UI
JScrollPane scrollPaneTable = new JScrollPane();
JTable table = new JTable(tableModel);
scrollPaneTable.setViewportView(table);
JFrame mdi = new JFrame("Test JTable Column Header with Icon");
mdi.setSize(600, 400);
mdi.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
mdi.add(scrollPaneTable);
mdi.setVisible(true);
});
}
}

Instead of the picture it shows me the x.png inside the cell, how can i make it SHOW the actual picture or put some background color when value=="1"

package dmaze2;
import java.awt.Dimension;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
public class Dmaze2 extends JPanel
{
JTable jt;
public Dmaze2()
{
String[] columns = {"1","2","3","4","5","6","7","8"};
Object[][] table={{"f","f","f","f","f","f","f","f"},
//if this table is string makes problem to add picture
{"f","f","f","f","f","f","f","f"},
{"f","f","f","f","f","f","f","f"},
{"f","f","f","f","f","f","f","f"},
{"f","f","f","o","f","f","f","f"},
{"f","f","f","f","f","f","f","f"},
{"f","f","f","f","f","f","f","f"},
{"f","f","f","f","f","f","f","f"}};
int num=0;
ImageIcon Icon = new ImageIcon("x.png");
//i have the image in all files of the project to be sure it finds it
for (int i = 0; i < 8 ; i++)
{
int a=1;
for (int j = 0; j<7 && a<8; j++,a++)
{
if(table[i][j]=="f" && table[i][a]=="f")
{
num=num+1;
table[i][j]=Icon;
//if i try to enter the image here it will show it as x.png (as string) instead of the actual picture
table[i][a]="u";
}
}
//int b=1;
for (int j = 0; j<8 && i<7; j++)
{
if(table[i][j]=="f" && table[i+1][j]=="f")
{
num=num+1;
table[i][j]="u";//we put the block used
table[i+1][j]="u";
}
}
System.out.println("");
}
jt = new JTable(table,columns);
{
}
jt.setPreferredScrollableViewportSize(new Dimension(350,363));
jt.setFillsViewportHeight(true);
JScrollPane jps = new JScrollPane(jt);
add(jps);
}
public static void main(String[] args)
{
JFrame jf = new JFrame();
Dmaze2 t = new Dmaze2();
jf.setTitle("Depth First Search");
jf.setSize(500, 500);
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.add(t);
}
}
You need to #Override the getColumnClass() of the table's XxxTableModel. If you don't the renderer will render the column as the Object.toString(). See more at Concepts: Editors and Renderers
DefaultTableModel model = new DefaultTableModel(tableData, columns) {
#Override
public Class<?> getColumnClass(int column) {
switch(column) {
case 1: return ImageIcon.class; // or whichever column you want
default: return String.class;
}
}
};
JTable table = new JTable(model);
Side Notes:
Have a look at How Do I Compare Strings in Java
Set your frame visible after adding all your components
Swing apps should be run on the Event Dispatch Thread. See more at Initial Threads
You may want to read your image files from the class path if the images are resources of your application. Passing a String path to the ImageIcon signifies a read from the local file system. At time of deployment, the path you use will no longer be valid. See the answers from this question and this question for more details on how you can accomplish this task of reading from the class path and embedding your resources.

JEditorPane linewrap in Java7

First of all I hope it's not a problem I started a new topic. Tbh I don't have a clue how to ask a question based on an already answered one, so I made this.
I'm pretty new with Java and my problem is the following. I'm writing a little chat program and I'm using a JEditorPane with an HTMLEditorKit to display text in different colors, to display smileys, and display hyperlinks.
My problem is, and after some research I found out the problem might be due to Java7, I can't get the linewrap working properly. I want the text to word wrap and to wrap in the middle of Strings exceeding the width of the component.
The word wrap works fine, but if someone types in a pretty long string the JEditorPane gets expanded and you need to resize the frame to get everything on screen, which is what I do not want to happen.
I've tried a few fixes for this problem, but they only allow letter wrap such that word wrap no longer works. Beside that, I want the user to be able to wrap his text by hitting Enter. For that I'm adding \n to the text and with the fixes this will no longer affect the result and everything's going to be displayed in one line.
I'm feeling like I've spent years in the web to find a solution but unitl now nothing worked for my case, especially since it appeared to be the same fix all the time. I hope you guys can help me.
This means in summary:
What I have:
Line wraps word in case of long strings separated by spaces
if you use Windows and your input contains line wraps created by hitting enter, they will also wrap
If you type in a very long string without spaces, the panel gets expanded and you need to resize the frame
HTML formatting allows me to display different colors as well as hyperlinks and emoticons
What I need:
Word wrap behaviour like it is at the moment in case it is possible but letter wrap ONLY in case of long strings not separated by spaces to prevent the panel from expanding.
Manually added line wraps made by hitting ENTER in the input area or if I copy an pre formatted text into the input panel
HTML formatting like I have already
What I've tried and what didn't help:
jtextpane doesn't wrap text and
JTextPane is not wrapping text
Here is some code to try it yourself. In the bottom left is an input area to type in some text. You can also add line wraps by hitting enter. After clicking on the button you will see the text in the area above.
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.border.TitledBorder;
import javax.swing.text.BadLocationException;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;
#SuppressWarnings("serial")
public class LineWrapTest extends JFrame implements ActionListener, KeyListener {
private JButton btnSend;
private JTextArea textAreaIn;
private JEditorPane textAreaOut;
private HTMLEditorKit kit;
private HTMLDocument doc;
public LineWrapTest() {
this.setSize(600, 500);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setTitle("Linewrap Test");
}
/**
* Not important for problem
*/
public void paintScreen() {
this.setLayout(new BorderLayout());
this.add(this.getPanelOut(), BorderLayout.CENTER);
this.add(this.getPanelIn(), BorderLayout.SOUTH);
this.textAreaIn.requestFocusInWindow();
this.setVisible(true);
}
/**
* Not important for problem
*
* #return panelOut
*/
private JPanel getPanelOut() {
JPanel panelOut = new JPanel();
panelOut.setLayout(new BorderLayout());
this.textAreaOut = new JEditorPane();
this.textAreaOut.setEditable(false);
this.textAreaOut.setContentType("text/html");
this.kit = new HTMLEditorKit();
this.doc = new HTMLDocument();
StyleSheet styleSheet = this.kit.getStyleSheet();
this.kit.setStyleSheet(styleSheet);
this.textAreaOut.setEditorKit(this.kit);
this.textAreaOut.setDocument(this.doc);
TitledBorder border = BorderFactory.createTitledBorder("Output");
border.setTitleJustification(TitledBorder.CENTER);
panelOut.setBorder(border);
panelOut.add(this.textAreaOut);
return panelOut;
}
/**
* Not important for problem
*
* #return panelIn
*/
private JPanel getPanelIn() {
JPanel panelIn = new JPanel();
panelIn.setLayout(new BorderLayout());
this.textAreaIn = new JTextArea();
this.textAreaIn.setLineWrap(true);
this.textAreaIn.setWrapStyleWord(true);
TitledBorder border = BorderFactory.createTitledBorder("Input");
border.setTitleJustification(TitledBorder.CENTER);
panelIn.setBorder(border);
panelIn.add(this.getBtnSend(), BorderLayout.EAST);
panelIn.add(this.textAreaIn, BorderLayout.CENTER);
return panelIn;
}
/**
* Not important for problem
*
* #return btnSend
*/
private JButton getBtnSend() {
this.btnSend = new JButton("Send");
this.btnSend.addActionListener(this);
return this.btnSend;
}
private void append(String text) {
try {
this.kit.insertHTML(this.doc, this.doc.getLength(), text, 0, 0, null);
} catch (BadLocationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private String getHTMLText() {
String txtIn = this.textAreaIn.getText().trim().replaceAll(SEPARATOR, "<br/>");
StringBuffer htmlBuilder = new StringBuffer();
htmlBuilder.append("<HTML>");
htmlBuilder.append(txtIn);
htmlBuilder.append("</HTML>");
return htmlBuilder.toString();
}
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == this.btnSend) {
this.append(this.getHTMLText());
this.textAreaIn.setText("");
this.textAreaIn.requestFocusInWindow();
}
}
public static void main(String[] args) {
LineWrapTest test = new LineWrapTest();
test.paintScreen();
}
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER)
if (!this.textAreaIn.getText().trim().isEmpty())
this.textAreaIn.setText(this.textAreaIn.getText() + SEPARATOR);
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
}
}
UPDATE:
base on some parts of http://java-sl.com/tip_java7_text_wrapping_bug_fix.html
Somehow I figured it out to step a bit closer to my goal. I've tried to combine the fix for an HTMLEditorKit with an StlyedEditorKit Fix. But I have to be honest, I don't have any clue what I actually did there :( The sad thing is, the manual line wraping does no longer work with this as a replacement of the HTMLEditorKit.
Maybe you can use this as a base for some better implementation.
To use it in my example just create a new class in the project with the CustomEditorKit and replace the HTMLEditorKit in the example with this CustomEditorKit.
You will notice that word and letter wrapping works now, but if you hit ENTER to get your own line wrap this change will no longer appear in the output-panel and everything will be displayed in one line.
Another strange problem of it is, that if you resize the frame the lines will sometimes lay on each other.
import javax.swing.SizeRequirements;
import javax.swing.text.Element;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.InlineView;
import javax.swing.text.html.ParagraphView;
#SuppressWarnings("serial")
public class CustomEditorKit extends HTMLEditorKit {
#Override
public ViewFactory getViewFactory() {
return new HTMLFactory() {
#Override
public View create(Element e) {
View v = super.create(e);
if (v instanceof InlineView) {
return new InlineView(e) {
#Override
public int getBreakWeight(int axis, float pos, float len) {
return GoodBreakWeight;
}
#Override
public View breakView(int axis, int p0, float pos, float len) {
if (axis == View.X_AXIS) {
this.checkPainter();
this.removeUpdate(null, null, null);
}
return super.breakView(axis, p0, pos, len);
}
};
}
else if (v instanceof ParagraphView) {
return new ParagraphView(e) {
#Override
protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
if (r == null) {
r = new SizeRequirements();
}
float pref = this.layoutPool.getPreferredSpan(axis);
float min = this.layoutPool.getMinimumSpan(axis);
// Don't include insets, Box.getXXXSpan will include them.
r.minimum = (int) min;
r.preferred = Math.max(r.minimum, (int) pref);
r.maximum = Integer.MAX_VALUE;
r.alignment = 0.5f;
return r;
}
};
}
return v;
}
};
}
}
OK! So, I finally got everything you were having problems with working. It took some research and a lot of trial and error, but here it is:
Here is what I did:
Put the JEditorPane in a JScrollPane so you can scroll up and down as the message gets bigger
Added a custom word wrap. The custom word wrap will wrap words and long words in the desired location of the word. You were right, this is a bug with the current version of Java. http://bugs.sun.com/view_bug.do?bug_id=7125737
Added the ability for the user to wrap to a new line by hitting Enter. This interfered with the custom word wrap though, so you may not like how I achieved this. In the code example I suggest other options.
Preserved your HTMLDocument abilities. I was tempted to not do this, but I found work arounds so that it could be preserved.
The application still uses a JEditorPane, but you could switch it to a JTextPane if you want. I tried both and they were both functional.
So here is the code. It's a bit long and you may wish to change it based on your preferences. I commented where I made changes and tried to explain them.
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.SizeRequirements;
import javax.swing.border.TitledBorder;
import javax.swing.text.*;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.InlineView;
import javax.swing.text.html.StyleSheet;
#SuppressWarnings("serial")
public class LineWrapTest extends JFrame implements ActionListener, KeyListener {
//This is the separator.
private String SEPARATOR = System.getProperty("line.separator");
private JButton btnSend;
private JTextArea textAreaIn;
private JEditorPane textAreaOut;
private JScrollPane outputScrollPane;
private HTMLEditorKit kit;
private HTMLDocument doc;
public LineWrapTest() {
this.setSize(600, 500);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setTitle("Linewrap Test");
}
/**
* Not important for problem
*/
public void paintScreen() {
this.setLayout(new BorderLayout());
this.add(this.getPanelOut(), BorderLayout.CENTER);
this.add(this.getPanelIn(), BorderLayout.SOUTH);
this.textAreaIn.requestFocusInWindow();
this.setVisible(true);
}
/**
* Not important for problem
*
* #return panelOut
*/
private JPanel getPanelOut() {
JPanel panelOut = new JPanel();
panelOut.setLayout(new BorderLayout());
this.textAreaOut = new JEditorPane();
this.textAreaOut.setEditable(false);
this.textAreaOut.setContentType("text/html");
//I added this scroll pane.
this.outputScrollPane = new JScrollPane(this.textAreaOut);
/*
* This is a whole whack of code. It's a combination of two sources.
* It achieves the wrapping you desire: by word and longgg strings
* It is a custom addition to HTMLEditorKit
*/
this.kit = new HTMLEditorKit(){
#Override
public ViewFactory getViewFactory(){
return new HTMLFactory(){
public View create(Element e){
View v = super.create(e);
if(v instanceof InlineView){
return new InlineView(e){
public int getBreakWeight(int axis, float pos, float len) {
//return GoodBreakWeight;
if (axis == View.X_AXIS) {
checkPainter();
int p0 = getStartOffset();
int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
if (p1 == p0) {
// can't even fit a single character
return View.BadBreakWeight;
}
try {
//if the view contains line break char return forced break
if (getDocument().getText(p0, p1 - p0).indexOf(SEPARATOR) >= 0) {
return View.ForcedBreakWeight;
}
}
catch (BadLocationException ex) {
//should never happen
}
}
return super.getBreakWeight(axis, pos, len);
}
public View breakView(int axis, int p0, float pos, float len) {
if (axis == View.X_AXIS) {
checkPainter();
int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
try {
//if the view contains line break char break the view
int index = getDocument().getText(p0, p1 - p0).indexOf(SEPARATOR);
if (index >= 0) {
GlyphView v = (GlyphView) createFragment(p0, p0 + index + 1);
return v;
}
}
catch (BadLocationException ex) {
//should never happen
}
}
return super.breakView(axis, p0, pos, len);
}
};
}
else if (v instanceof ParagraphView) {
return new ParagraphView(e) {
protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
if (r == null) {
r = new SizeRequirements();
}
float pref = layoutPool.getPreferredSpan(axis);
float min = layoutPool.getMinimumSpan(axis);
// Don't include insets, Box.getXXXSpan will include them.
r.minimum = (int)min;
r.preferred = Math.max(r.minimum, (int) pref);
r.maximum = Integer.MAX_VALUE;
r.alignment = 0.5f;
return r;
}
};
}
return v;
}
};
}
};
this.doc = new HTMLDocument();
StyleSheet styleSheet = this.kit.getStyleSheet();
this.kit.setStyleSheet(styleSheet);
this.textAreaOut.setEditorKit(this.kit);
this.textAreaOut.setDocument(this.doc);
TitledBorder border = BorderFactory.createTitledBorder("Output");
border.setTitleJustification(TitledBorder.CENTER);
panelOut.setBorder(border);
//I changed this to add the scrollpane, which now contains
//the JEditorPane
panelOut.add(this.outputScrollPane);
return panelOut;
}
/**
* Not important for problem
*
* #return panelIn
*/
private JPanel getPanelIn() {
JPanel panelIn = new JPanel();
panelIn.setLayout(new BorderLayout());
this.textAreaIn = new JTextArea();
this.textAreaIn.setLineWrap(true);
this.textAreaIn.setWrapStyleWord(true);
//This disables enter from going to a new line. Your key listener does that.
this.textAreaIn.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "none");
//For the key listener to work, it needs to be added to the component
this.textAreaIn.addKeyListener(this);
TitledBorder border = BorderFactory.createTitledBorder("Input");
border.setTitleJustification(TitledBorder.CENTER);
panelIn.setBorder(border);
panelIn.add(this.getBtnSend(), BorderLayout.EAST);
panelIn.add(this.textAreaIn, BorderLayout.CENTER);
return panelIn;
}
/**
* Not important for problem
*
* #return btnSend
*/
private JButton getBtnSend() {
this.btnSend = new JButton("Send");
this.btnSend.addActionListener(this);
return this.btnSend;
}
private void append(String text) {
try {
this.kit.insertHTML(this.doc, this.doc.getLength(), text, 0, 0, null);
} catch (BadLocationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private String getHTMLText() {
//I tried to find a work around for this but I couldn't. It could be done
//by manipulating the HTMLDocument but it's beyond me. Notice I changed
//<br/> to <p/>. For some reason, <br/> no longer went to the next line
//when I added the custom wrap. <p/> seems to work though.
String txtIn = this.textAreaIn.getText().trim().replaceAll(SEPARATOR, "<p/>");
//My IDE recommends you use StringBuilder instead, that's up to you.
//I am not sure what the difference would be.
StringBuffer htmlBuilder = new StringBuffer();
htmlBuilder.append("<HTML>");
htmlBuilder.append(txtIn);
htmlBuilder.append("</HTML>");
return htmlBuilder.toString();
}
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == this.btnSend) {
this.append(this.getHTMLText());
this.textAreaIn.setText("");
this.textAreaIn.requestFocusInWindow();
}
}
public static void main(String[] args) {
LineWrapTest test = new LineWrapTest();
test.paintScreen();
}
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER){
if (!this.textAreaIn.getText().trim().isEmpty()) {
//I made this work by defining the SEPARATOR.
//You could use append(Separator) instead if you want.
this.textAreaIn.setText(this.textAreaIn.getText() + SEPARATOR);
}
}
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
}
}
Here are (most of) the links that I used to solve this problem:
Enabling word wrap in a JTextPane with HTMLDocument
Custom wrap is a combination of these two:
http://java-sl.com/tip_html_letter_wrap.html
http://java-sl.com/wrap.html
Deleting the keybind for JTextArea:
http://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html
If you have any questions whatsoever, just comment below. I will answer them. I sincerely hope this solves your problems
A deadly better solution I found :
The <br> is correctly handled by the HTMLEditorKit, but the Patrick Sebastien's post mentionned that it won't. It's because its ViewFactory threat all InlineView object as wrappable, but the BRView is also an InlineView. See my solution below:
class WrapColumnFactory extends HTMLEditorKit.HTMLFactory {
#Override
public View create(Element elem) {
View v = super.create(elem);
if (v instanceof LabelView) {
// the javax.swing.text.html.BRView (representing <br> tag) is a LabelView but must not be handled
// by a WrapLabelView. As BRView is private, check the html tag from elem attribute
Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
if ((o instanceof HTML.Tag) && o == HTML.Tag.BR) {
return v;
}
return new WrapLabelView(elem);
}
return v;
}
}
class WrapLabelView extends LabelView {
public WrapLabelView(Element elem) {
super(elem);
}
#Override
public float getMinimumSpan(int axis) {
switch (axis) {
case View.X_AXIS:
return 0;
case View.Y_AXIS:
return super.getMinimumSpan(axis);
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
}
}

Swing GUI : Scrolling not updated when a JTable gets bigger

I got a Java Swing GUI and got a problem with a JTable in a JScrollPane. For some reason, when the rows of the table model are increased during the program execution, the JScrollPane isn't updated - that is, if the rows are increased so that the height of the table is over the height of the scroll view, the scroll panes aren't updated as supposed. (The new rows are shown at the screen as expected). If the window is resized, scrolling is updated as expected.
The vertical scrolling policy is VERTICAL_SCROLLBAR_AS_NEEDED, table models fireTableDataChanged is called..
Unfortunately the code's a bit complex so I can't provide an code sample causing the problem. But thought to ask if somebody got some ideas straight..
EDIT: Still a bit more confusing : horizontal scrolling policy is HORIZONTAL_SCROLLBAR_AS_NEEDED, and if the table width if over the view width (that is, the horizontal scrollbar is used), this problem doesn't occur...
EDIT2: The problem isn't that the table should be scrolled but that the scrollbar's aren't activated as they should.
You might need to post some of your code. I've just knocked up the following test and it works as advertised, i.e. vertical scrollbars are activated when the number of rows exceeds the viewport height:
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ScrollPaneConstants;
import javax.swing.table.AbstractTableModel;
public class JTableTest {
public static void main(String[] args) {
final MyTableModel tm = new MyTableModel();
tm.addData(new Data("R1C1", "R1C2"));
JTable table = new JTable(tm);
JScrollPane scrollPane = new JScrollPane(table);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
JFrame frame = new JFrame();
frame.setLayout(new GridLayout());
frame.add(scrollPane);
frame.pack();
frame.setSize(400, 150);
frame.setVisible(true);
Thread t = new Thread(new Runnable() {
private int count = 2;
public void run() {
for ( ; ; ) {
tm.addData(new Data("R" + count + "C1", "R" + count + "C2"));
count++;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
t.start();
}
private static class MyTableModel extends AbstractTableModel {
private List<Data> dataList = new ArrayList<Data>();
public int getColumnCount() {
return 2;
}
public void addData(Data data) {
dataList.add(data);
fireTableRowsInserted(dataList.size()-1, dataList.size()-1);
}
public int getRowCount() {
return dataList.size();
}
public Object getValueAt(int rowIndex, int columnIndex) {
Data data = dataList.get(rowIndex);
return columnIndex == 0 ? data.data1 : data.data2;
}
}
private static class Data {
public String data1;
public String data2;
public Data(String data1, String data2) {
this.data1 = data1;
this.data2 = data2;
}
}
}
Hmm.. after returning to the issue I found out that our row header customizing in JScrollPane was causing the problem. (Some preferredsizes were set with not-so-sensible values etc)..

Categories