Create a nice-looking listview in Java - java

I'm new to Java swing and I'm trying to create a list view showing content of a directory. I expect to create a view like following image:
I know how to use JList, but I don't know how to show icons matching with types of files. As you can see, from the image, we can visually differentiate pdf files with text files and others. Should I try to use JList or another UI component?

I have done something similar; here's an example of my output.
I used a custom renderer for the tree; it produces the indentation, icon, and text that you see in one cell of the leftmost column of the display. Here's the source for that:
package spacecheck.ui.renderer;
import java.awt.Component;
import javax.swing.ImageIcon;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import spacecheck.filedata.FileCollection;
import spacecheck.images.TreeIcon;
/**
* renders the name of a collection for the tree display.
* #author rcook
*
*/
public class CollectionNameRenderer extends DefaultTableCellRenderer // which implements JLabel
// implements TableCellRenderer
{
private static final long serialVersionUID = 1L;
#SuppressWarnings({"unused"})
private void say(String msg) { System.out.println(msg); }
private static TreeIcon tIcon = null;
/**
* set the value of the CollectionName for the JTable; includes using
* indent so that the correct icon can be obtained (icons are different widths
* to implement different indent levels).
*/
public void setValue(Object value)
{
FileCollection fc = (FileCollection)value;
boolean expanded = fc.isExpanded();
int level = fc.getDisplayLevel();
// boolean isSelected = table.
ImageIcon icon = tIcon.getIcon(level, expanded);
if (icon != null) { setIcon(icon); }
setText(value.toString());
}
/**
* get the renderer component for a collection name.
*/
public Component getTableCellRendererComponent
(JTable table, Object value, boolean isSelected, boolean hasFocus,
int rowIndex, int colIndex)
{
if (tIcon == null) { tIcon = new TreeIcon(table.getBackground()); }
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, rowIndex, colIndex);
}
}
That class uses another of mine named TreeIcon; it implements the indentation of the folder icon as shown in the picture as well as choosing the icon depending on the expanded/unexpanded state of the folder. Here's that class:
package spacecheck.images;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
/**
* represents an icon used in the directory tree; handles 'expanded' and
* 'unexpanded' directories as well as indentation representing different
* levels.
* #author rcook
*
*/
public class TreeIcon
{
public static int UNEXPANDED = 1;
public static int EXPANDED = 2;
#SuppressWarnings({"unused"})
private void say (String msg) { System.out.println(msg); }
private static ImageIcon expandedIcon = null;
private static ImageIcon unexpandedIcon = null;
private static int iconHeight = 0;
private static int iconWidth = 0;
private static ArrayList<ImageIcon> cachedExpandedIcons = new ArrayList<ImageIcon>();
private static ArrayList<ImageIcon> cachedUnexpandedIcons = new ArrayList<ImageIcon>();
static
{
expandedIcon = new ImageIcon(TreeIcon.class.getResource("images/Expanded.png"));
unexpandedIcon = new ImageIcon(TreeIcon.class.getResource("images/Unexpanded.png"));
iconHeight = unexpandedIcon.getIconHeight();
iconWidth = unexpandedIcon.getIconWidth();
}
public TreeIcon(Color givenColor) { }
public static void main(String ... arguments)
{
JFrame frame = new JFrame("icon test");
JLabel label = new JLabel("background test");
label.setBackground(Color.blue);
TreeIcon treeIcon = new TreeIcon(Color.black);
ImageIcon icon = treeIcon.getIcon(2, false);
label.setIcon(icon);
frame.add(label);
frame.pack();
frame.setVisible(true);
}
/**
* return the icon for an expanded or unexpanded level
* #param int level of folder relative to other levels displayed;
* starts at 0 and increases with depth
* #param boolean indicates whether this level is expanded or not.
* #return ImageIcon appropriate for expansion flag and level.
*/
public ImageIcon getIcon(int level, boolean expanded)
{
ImageIcon result = null;
if (level < 0)
{ System.out.println("level is " + level + ", setting to 0");
level = 0;
}
// set our list of icons depending on whether we are expanded.
ArrayList<ImageIcon> cachedIcons = cachedUnexpandedIcons;
if (expanded) { cachedIcons = cachedExpandedIcons; }
// if we already have this icon in our cache, return it.
if (cachedIcons.size() >= (level+1) && cachedIcons.get(level) != null)
{
result = cachedIcons.get(level);
}
else
{
// generate this icon and store it in the cache before returning it.
ImageIcon baseIcon = unexpandedIcon;
if (expanded) { baseIcon = expandedIcon; }
int iconH = iconHeight;
int iconW = iconWidth*(level+1);
BufferedImage bufferedImage = new BufferedImage(iconW,iconH,BufferedImage.TYPE_INT_ARGB);
Graphics g = bufferedImage.getGraphics();
g.drawImage(baseIcon.getImage(), iconWidth*level, 0, null);
result = new ImageIcon(bufferedImage);
// we've created an icon that was not in the cached list;
// the cached list may have a null at this slot, or it may not yet be
// long enough to have this slot. Ensure that we have enough slots
// in the list, and then add this icon.
for (int i=cachedIcons.size(); i<=level; i++)
{
cachedIcons.add(null);
}
// if (cachedIcons.size() < level + 1) { cachedIcons.add(result); }
// else {
cachedIcons.set(level, result);
// }
// say("adding icon, level = " + level + (expanded ? " " : " un") + "expanded, width = " + iconW);
}
return result;
}
}
To choose icons for different kinds of files, you could have your renderer and icon-chooser look at the file extension (or whatever) to determine which icon out of a map to use.
Hope that helps!

Related

Resize JList file elements in JFileChooser

I have a JFileChooser. I am trying to add a zoom feature to the files JList.
I would like to change the scale factor of the file name and of the file icon, for each element of the list.
How could we achieve this ?
Should I make a custom renderer like here [JList custom renderer example] (http://www.codejava.net/java-se/swing/jlist-custom-renderer-example)
or change the list Model ?
Well, I found out some ugly lazy hacks to do it.
It might not be just what you want, but it's a good starting point (and fairly simple):
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.plaf.basic.BasicListUI;
public class TJFileChooserDemo {
//Obtains the (first) JList which is found inside the component/container:
public static JList getFirstJList(final Component component) {
if (component instanceof JList)
return (JList) component;
if (component instanceof Container)
for (int i=0; i<((Container)component).getComponentCount(); ++i) {
final JList list = getFirstJList(((Container)component).getComponent(i));
if (list != null)
return list;
}
return null;
//As you can see, it's a bit lazy hack, which has to run for every JFileChooser once at start-up.
}
private static final double SCALE_STEP_SIZE = 0.125; //Smaller values of this makes zooming slower. Greater values makes zooming faster.
private static double scaleFactor = 1;
public static class TJListCellRenderer extends DefaultListCellRenderer {
public TJListCellRenderer() {
//Ensure every pixel is painted starting from the top-left corner of the label:
super.setVerticalAlignment(JLabel.TOP);
super.setHorizontalAlignment(JLabel.LEFT);
//We need to do this, because the scaling in paintComponent() is also relative to the top-left corner.
}
#Override
public void paintComponent(final Graphics g) {
//setRenderingHints here? Probably for ANTIALIAS...
((Graphics2D)g).scale(scaleFactor, scaleFactor); //Let's scale everything that is painted afterwards:
super.paintComponent(g); //Let's paint the (scaled) JLabel!
}
#Override
public Dimension getPreferredSize() {
final Dimension superPrefDim = super.getPreferredSize(); //Handles automatically insets, icon size, text font, etc.
final double w = superPrefDim.width * scaleFactor, //And we just scale the preferred size.
h = superPrefDim.height * scaleFactor; //And we just scale the preferred size.
return new Dimension((int)w + 5, (int)h + 5); //Add 5 extra pixels to spare.
}
#Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
// System.out.println(value.getClass()); //Something ugly...
return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
}
}
public static class TJListUI extends BasicListUI {
#Override
public void updateLayoutState() {
super.updateLayoutState(); //Just make the following method public.
/*Note: this is not really needed here:
The method could remain protected, but in the case you want this
code to be a bit more reusable, then you shall make it public.*/
}
}
public static void main(final String[] args) {
final JFileChooser jfc = new JFileChooser();
jfc.setDialogType(JFileChooser.OPEN_DIALOG);
final TJListUI ui = new TJListUI();
final JList list = getFirstJList(jfc);
list.setUI(ui);
list.setCellRenderer(new TJListCellRenderer());
final JButton buttonZoomIn = new JButton("Zoom in"),
buttonZoomOut = new JButton("Zoom out"),
buttonResetZoom = new JButton("Reset zoom");
buttonZoomIn.addActionListener(e -> {
scaleFactor = scaleFactor + SCALE_STEP_SIZE;
ui.updateLayoutState(); //Read the preferred sizes from the cell renderer.
list.revalidate(); //Update the JScrollPane.
list.repaint(); //Repaint the list.
});
buttonZoomOut.addActionListener(e -> {
scaleFactor = Math.max(scaleFactor - SCALE_STEP_SIZE, SCALE_STEP_SIZE); //Do not allow underflow.
ui.updateLayoutState(); //Read the preferred sizes from the cell renderer.
list.revalidate(); //Update the JScrollPane.
list.repaint(); //Repaint the list.
});
buttonResetZoom.addActionListener(e -> {
scaleFactor = 1;
ui.updateLayoutState(); //Read the preferred sizes from the cell renderer.
list.revalidate(); //Update the JScrollPane.
list.repaint(); //Repaint the list.
});
final JPanel buttons = new JPanel(); //FlowLayout.
buttons.add(buttonZoomIn);
buttons.add(buttonZoomOut);
buttons.add(buttonResetZoom);
final JPanel panel = new JPanel(new BorderLayout());
panel.add(buttons, BorderLayout.PAGE_START);
panel.add(jfc, BorderLayout.CENTER);
final JFrame frame = new JFrame("JFileChooser's JList cell sizes demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Alternatively you can check my answer here about individually resizable cells of a JList.
You can also probably add the JFileChooser's buttons for zooming in/out as an accessory. Read this simple example for how to do it.
Test this code, and I am waiting for comments...
In the end, I realized scaling the text wasn't needed.
To obtain the image files thumbnail, I used the code in making JFileChooser show image thumbnails - check BoffinbraiN answer.
Then for scaling :
1) add an ActionListener to the buttons of ThumbnailFileChooser.
public class ZoomListener implements ActionListener {
private boolean zoomIn = false;
private IconScaleManager iconScaleManager = null;
public ZoomListener(boolean zoom, IconScaleManager renderer) {
zoomIn = zoom;
iconScaleManager = renderer;
}
#Override
public void actionPerformed(ActionEvent e) {
iconScaleManager.scaleButton(zoomIn);
}
}
2) ActionListener::actionPerformed() calls a scale method of a ScaleManager.
#Override
public void actionPerformed(ActionEvent e) {
iconScaleManager.scaleButton(zoomIn);
}
3) The ScaleManager method changes and update the cells of the ThumbnailFileChooser's Jlist (the list is an attribute of the ScaleManager)
public class IconScaleManager {
static final int[] iconScales = new int[]{ 16, 32, 64, 128, 256, 512, 1024, 2048 };
private int scaleIndex = 4;
private JList fileList = null;
public IconScaleManager(JList list) {
fileList = list;
setFixedCellDimension();
}
public void scaleButton(boolean zoomIn) {
if (zoomIn && scaleIndex < iconScales.length - 1) {
scaleIndex++;
setFixedCellDimension();
} else if (!zoomIn && 0 < scaleIndex) {
scaleIndex--;
setFixedCellDimension();
}
}
private void setFixedCellDimension() {
fileList.setFixedCellWidth(iconScales[scaleIndex]);
fileList.setFixedCellHeight(iconScales[scaleIndex]);
}
}
Thank you #thanopi57 for your help. I didn't really use what you provided, but I appreciate your support.
Also, I will have to make sure that it works, because there might not be a JList for all JFileChooser

Java Swing countrycombobox with icons

I hope someone can help me out. Iam trying to create a "countrycombobox" with icons in Java Swing. I found some stuff, but nothing did work for me. Maybe the problem is, that Iam still "new" to Java.
I just want it simple like this: http://www.zomex.com/libs/images/layout/whmcs-template-language-select-w-flags-eco.jpg
Just the flags in front of the countrys.
I would really appreciate a working example. I really wonder, that there is no standard option or a good code snippet(used Google a lot to find help here) for stuff like this.
I found a better example and wanna share my stuff with you. There is just one problem left, that I dont get it sized.
package view;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CountryComboBox extends JPanel {
ImageIcon[] images;
String[] imgStrings = {"de"};
/*
* Despite its use of EmptyBorder, this panel makes a fine content
* pane because the empty border just increases the panel's size
* and is "painted" on top of the panel's normal background. In
* other words, the JPanel fills its entire background if it's
* opaque (which it is by default); adding a border doesn't change
* that.
*/
public CountryComboBox() {
super(new BorderLayout());
//Load the images and create an array of indexes.
images = new ImageIcon[imgStrings.length];
Integer[] intArray = new Integer[imgStrings.length];
for (int i = 0; i < imgStrings.length; i++) {
intArray[i] = new Integer(i);
images[i] = createImageIcon("/res/" + imgStrings[i] + ".png");
if (images[i] != null) {
images[i].setDescription(imgStrings[i]);
}
}
//Create the combo box.
JComboBox imgList = new JComboBox(intArray);
ComboBoxRenderer renderer= new ComboBoxRenderer();
imgList.setRenderer(renderer);
imgList.setMaximumRowCount(3);
//Lay out the demo.
add(imgList, BorderLayout.PAGE_START);
//setBorder(BorderFactory.createEmptyBorder(20,20,20,20));
}
/** Returns an ImageIcon, or null if the path was invalid. */
protected static ImageIcon createImageIcon(String path) {
java.net.URL imgURL = CountryComboBox.class.getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL);
} else {
System.err.println("Couldn't find file: " + path);
return null;
}
}
class ComboBoxRenderer extends JLabel
implements ListCellRenderer {
private Font uhOhFont;
public ComboBoxRenderer() {
setOpaque(true);
setHorizontalAlignment(CENTER);
setVerticalAlignment(CENTER);
}
/*
* This method finds the image and text corresponding
* to the selected value and returns the label, set up
* to display the text and image.
*/
#Override
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
//Get the selected index. (The index param isn't
//always valid, so just use the value.)
int selectedIndex = ((Integer)value).intValue();
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
//Set the icon and text. If icon was null, say so.
ImageIcon icon = images[selectedIndex];
String img = imgStrings[selectedIndex];
setIcon(icon);
if (icon != null) {
setText(img);
setFont(list.getFont());
} else {
setUhOhText(img + " (no image available)",
list.getFont());
}
return this;
}
//Set the font and text when no image was found.
protected void setUhOhText(String uhOhText, Font normalFont) {
if (uhOhFont == null) { //lazily create this font
uhOhFont = normalFont.deriveFont(Font.ITALIC);
}
setFont(uhOhFont);
setText(uhOhText);
}
}
}
I call it in a JPanel with absolute layout:
JComponent newContentPane = new CountryComboBox();
newContentPane.setOpaque(true); //content panes must be opaque
newContentPane.setBounds(10, 75, 50, 26);
contentPane.add(newContentPane);
setBounds isnt working, just to get the right position. I cant size it with this.
Best regards
Acanis

JTable doesn't update, calling the setValueAt method doesn't seem to work

I'm creating a sudoku game and I'm having quite some trouble with JTables ...
I just can't get it to fire events, for some reason even when calling the setValueAt from the main class nothing happens. It does work when doing it inside the model itself though ...
As you can see I've tried a tableModelListener but that doesn't work either.
To sum up my question: why doesn't my table fire events and the setValuesAt method doesn't do anything?
The abstractModel:
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.table.AbstractTableModel;
class SudokuTableModel extends AbstractTableModel
{
private int[][] data;
private int[][] originalBoard;
/**
*
* #param board the board to be played with
*/
public SudokuTableModel(int[][] board)
{
data = board;
originalBoard = board;
}
#Override
public int getColumnCount()
{
return 9;
}
#Override
public int getRowCount() {
// this
return 9;
}
#Override
public boolean isCellEditable(int row, int col)
{
System.out.println("Is it editable?");
if(originalBoard[row][col]<0) //if the value is -1 -> it is not one of the hints and thus can be edited
//it has to be checked with the original board because the user might want to change something they did
return true;
return false;
}
/**
* #return the value at row, col
*/
#Override
public Object getValueAt(int arg0, int arg1)
{//if its a "sudoku" number, return it, otherwise return null
//^thta doesn't seem to work so we'll just return the value. With the size of the grid, number that is not exactly 1 char will be displayed as 3 dots so its fine
return (data[arg0][arg1]<0)?-1:data[arg0][arg1];
}
/**
*
* #param value changes to this value #
* #param row
* #param col
*/
public boolean setValueAt(int value, int row, int col, int randomValue)
{
originalBoard[row][col]=value;
System.out.println("Setting value");
fireTableCellUpdated(row, col);
return true;
}
public Class getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
}
And the main code:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.text.*;
import javax.swing.border.*;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
/**
* #Program Sudoku
*
* #author Tomas Svitil
* #date November 2013
* #school CTU
* #hardware MacBook Pro 17", mid 2010, i7, 8GiB RAM
* #IDE eclipse SDK 4.3.1
* #purpose this is the main playing board. Options such as save and load will be handled by a helper class -SudokuTable Helper
*
*/
public class SudokuTablePreRenderer extends JFrame implements TableModelListener
{
JTable table;
int[][] board;
/**
*
* #param toBeUsed the board to be used
* #param isSolution if true the 'close' button just disposes the window
*/
public SudokuTablePreRenderer(int[][] toBeUsed,boolean isSolution)
{
//lets use our sudoku model
SudokuTableModel model = new SudokuTableModel(toBeUsed);
//make a copy of the passed
board=toBeUsed;
table = new JTable( model )
{
public Component prepareRenderer(TableCellRenderer renderer, int row, int column)
{
Component c = super.prepareRenderer(renderer, row, column);
JComponent jc = (JComponent)c;
if(!isRowSelected(row))
{
c.setBackground(toBeGray(row,column) ? Color.WHITE : Color.LIGHT_GRAY);
boolean top = top(row);
boolean left = left(column);
boolean bottom = bottom(row);
boolean right = right(column);
jc.setBorder(new MatteBorder(top?1:0,left?1:0,bottom?1:0,right?1:0, Color.BLACK));
}
return c;
}
//each returns true if the cell should have a *method name* border
private boolean top(int row)
{
if(row==0||row==3||row==6)
return true;
return false;
}
private boolean left(int col)
{
if(col==0||col==3||col==6)
return true;
return false;
}
private boolean bottom(int row)
{
if(row==8)
return true;
return false;
}
private boolean right(int col)
{
if(col==8)
return true;
return false;
}
//returns true if cell should be shaded in gray
private boolean toBeGray(int row, int col)
{
ArrayList<Integer> SetA = new ArrayList<Integer>();
ArrayList<Integer> SetB = new ArrayList<Integer>();
int[] a = {0,1,2,6,7,8};
int[] b = {3,4,5};
for(int i=0;i<a.length;i++ )
SetA.add(a[i]);
for(int i=0;i<b.length;i++)
SetB.add(b[i]);
if((SetA.contains(row)&&SetA.contains(col))||(SetB.contains(row)&&SetB.contains(col)))
return true;
return false;
}
};
//we don't need the table header
table.setTableHeader(null);
//set the base GOOEY stuff
table.setPreferredScrollableViewportSize(table.getPreferredSize());
table.changeSelection(0, 0, false, false);
JScrollPane scrollPane = new JScrollPane( table );
getContentPane().add( scrollPane );
//we're going to store only single digits, so the size we want is exactly one digit
table.getColumnModel().getColumn(0).setMaxWidth(15);
table.getColumnModel().getColumn(1).setMaxWidth(15);
table.getColumnModel().getColumn(2).setMaxWidth(15);
table.getColumnModel().getColumn(3).setMaxWidth(15);
table.getColumnModel().getColumn(4).setMaxWidth(15);
table.getColumnModel().getColumn(5).setMaxWidth(15);
table.getColumnModel().getColumn(6).setMaxWidth(15);
table.getColumnModel().getColumn(7).setMaxWidth(15);
table.getColumnModel().getColumn(8).setMaxWidth(15);
//if this table is used to display the result, "closing" the window will just dispose it, and not quit the program
if(isSolution)
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
else setDefaultCloseOperation( EXIT_ON_CLOSE );
pack();
//put it somewhere nice
setLocationRelativeTo( null );
//set the size so that its only the size of the
setSize(143,176);
//and we want it at one size only
setResizable(false);
setVisible(true);
table.getModel().addTableModelListener(this);
TableModelListener tableModelListener = new TableModelListener()
{
#Override
public void tableChanged(TableModelEvent e)
{
if(e.getType()==TableModelEvent.UPDATE)
{
System.out.println("YA?");
}
}
};
}
#Override
public void tableChanged(TableModelEvent e)
{
int row = e.getFirstRow();
int column = e.getColumn();
TableModel model = (TableModel)e.getSource();
String columnName = model.getColumnName(column);
Object data = model.getValueAt(row, column);
table.setValueAt(4, row, column);
System.out.println("Something happened yay");
// Do something with the data...
}
//
/**
*
* #return a copy of the board
*/
public int[][] getBoard()
{
int[][] returnee = new int[9][9];
for(int i = 0; i < board.length; i++)
returnee[i] = board[i].clone();
return returnee;
}
/**
* #return reference to the "real" board
* #WARNING - By manipulating the array from this method you're manipulating the methods array itself! IF NOT SURE USE THE GetBoard METHOD!
*
*/
public int[][] getRealBoard()
{
return board;
}
//------debugging---------------------------
private void setValue()
{
System.out.println(table.getValueAt(3,4));
}
public void doStuffs()
{
System.out.println(table.isCellEditable(2,2));
}
//----------main just for debugging----------
public static void main(String[] args)
{
int[][] hi = new int[9][9];
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
hi[i][j]=-1;
hi[3][3]=0;
SudokuTablePreRenderer frame = new SudokuTablePreRenderer(hi,false);
frame.setValue();
frame.doStuffs();
}
}
For some reason you have two 2-dimensional int arrays in your model, data and originalBoard.
The first array, data, you display information from in the JTable via getValueAt(...)
The second array, originalBoard, you add data to the model via setValueAt(...).
This disparity means that adding data to the model will have no effect on the data displayed in the JTable which begs the question -- why is your model set up this way?
You state,
To sum my question, why doesn't my table fire events and can't setValuesAt.
and I'll bet that the table events do in fact get fired, but since the data held by the data array never changes, the JTable will never change its display.
If you want new data added to update the display, then the array that gets the data should be the same as the one that displays the data. At least that seems to make the most sense to me.
Edit:
Regarding:
Sadly I don't think thats the case. This design is something I did before I found out that arrays are just 'references', and so in reality the two arrays should be the one and same. The other problem is that I put a System.out.print into the setValueAt, and when I called it from inside the model (called get valueAt from outside, and that called setValueAT) it worked perfectly, it set the value, it even fired When I call the method from outside though it doesn't work
You are right, sorry. I retract my answer above.
However, another potential problem: your setValueAt method is not a true override, is never called, and the actual AbstractTableModel setValueAt method which you call is never overridden and will have no effect.
Get rid of the TableModelListener, and change your setValueAt to:
#Override
public void setValueAt(Object value, int row, int col) {
originalBoard[row][col] = ((Integer) value).intValue();
System.out.println("Setting value");
fireTableCellUpdated(row, col);
// return true;
}

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);
}
}
}

How to load a JList in horizontal manner?

How to load a JList in horizontal fashion?? Here is my code,I am trying to display the JListsimilar to the screen shot provided.
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
import java.awt.BorderLayout;
import java.io.File;
import java.util.ArrayList;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
public class Test extends JFrame{
private JList toolsList;
private ArrayList<File> toolXmlList;
public Test()
{
toolXmlList = new ArrayList<File>();
toolXmlList = loadFiles();
setVisible(true);
setSize(300,300);
setTitle("Test Jlist");
createComponents();
}
public void createComponents()
{
toolsList = new JList();
toolsList.setModel(displayDefaltTools());
toolsList.setLayoutOrientation(javax.swing.JList.VERTICAL_WRAP);
setLayout(new BorderLayout());
add(toolsList,BorderLayout.CENTER);
}
/**
* Creates a list model and add the tools to it
*
* #return DefaultListModel
*/
public DefaultListModel displayDefaltTools() {
DefaultListModel dlistModel = new DefaultListModel();
String presentation = "";
for (int i = 0; i < toolXmlList.size(); i++) {
//System.out.println(idSet.get(i));
presentation = presentation + toolXmlList.get(i).getName() ;
dlistModel.addElement(presentation);
presentation = "";
}
return dlistModel;
}
public ArrayList loadFiles()
{
ArrayList<File> xmlFiles = new ArrayList<File>();
File f = new File(".");
File [] folList = f.listFiles();
for(int i=0;i<folList.length;i++)
{
if(folList[i].getName().startsWith("Tool_Frag"))
{
File[] fileList=folList[i].listFiles();
for(int j=0;j<fileList.length;j++)
{
System.out.println(fileList[j].getName());
xmlFiles.add(fileList[j]);
}
}
}
return xmlFiles;
}
public static void main(String[] args)
{
new Test();
}
}
I am trying to get a jlist in this manner,items displayed one next to another
You will have to do two things:
Set the LayoutOrientation to JList.HORIZONTAL_WRAP or JList.VERTICAL_WRAP as per the documentation.
Make the list wide enough that it can display more than one element per row. Use setVisibleRowCount() for this.
Calling setPreferredSize() also works but can cause trouble when you use layout managers.
Alternatively, consider using a JTable if you must make sure a certain number of rows/columns (like all elements in a single line).

Categories