I have a find function that locates a string in a JTable with quite a few thousand entries. Michael Meyers was kind enough to help me out with the goto portion of the function. There appears to be a bug though...
When the user searches for the string the application correctly finds the line in the JTable and highlights it. It also attempts to focus on it, but does not always. Sometimes it will jump 10+ lines short of the line I am looking for and I need to scroll down to see it. As I said, there are a few thousand entries in this JTable and if I'm searching for something, it's difficult to scroll around. Is it possible to focus the selected entry in the center of the visible area?
if (logs.get(i).getLine().contains(findStr))
{
logTable.scrollRectToVisible(logTable.getCellRect(thisPos, 1, true)); // goto
logTable.setRowSelectionInterval(thisPos, thisPos); // highlight
}
I'm not sure that it helps any, but here is the JTable setup code:
JTable logTable = new JTable(logTableModel);
logTable.setShowGrid(true);
logTable.setShowVerticalLines(true);
logTable.setShowHorizontalLines(false);
logTable.setRowSorter(sorter);
logTable.getSelectionModel().addListSelectionListener(new LogRowListener());
JScrollPane scrollPane = new JScrollPane();
scrollPane.getViewport().add(logTable);
scrollPane.setPreferredSize(new Dimension(800, 450));
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
Thanks
EDIT
Below is a link to download a .jar file. This file is a limited version of the code that illustrates the problem. This version seems to consistently jump 2-3 lines short, that is not always the case in the full version.
Demo.jar
The code even for this demo is still a few hundred lines, so below is the sections that I believe are relevant.
public class Proto extends JFrame implements ActionListener
{
public Proto() { ... }
#Override
public void actionPerformed(ActionEvent event)
{
String command = event.getActionCommand();
if (BUTTON_NEXT_FIND.equals(command))
{
findNext();
}
}
...
private void findNext()
{
String findStr = findField.getText();
int pos = selectedLogRow;
// if we're searching for the same string again step forward once
if (pos == lastFoundPos)
++pos;
// search through the log for the string
while (pos < logs.size())
{
if (logs.get(pos).getLine().contains(findStr))
{
logTable.scrollRectToVisible(logTable.getCellRect(pos, 1, true));
logTable.setRowSelectionInterval(pos, pos);
lastFoundPos = pos;
break;
}
++pos;
}
}
...
}
Sometimes it will jump 10+ lines short of the line I am looking for and I need to scroll down to see it.
Post your SSCCE that demonstrates the problem. You can just create a table with a thousand rows and then use setValueAt(...) method to populate a few random cells with the text you are searching for.
Is it possible to focus the selected entry in the center of the visible area?
You need to adjust the row number you want to scroll to. That is if you are searching down you would need to subtract "x" from the row number so the viewport is position on a row before your row that contains the text.
Related
Is it possible to control whether a column should be available in a column control popup menu? I'm aware of toggling (Disable/enable using CheckBoxList) and gray-out the column. But I do not want column entry in popup menu as The column is must-have column in Jtable. I'm using the JXTable. Anyone that have any hints?
A TableColumnExt has a property hideable which effectly disables the hiding. It is still shown in the popup and you can toggle the checkbox (that's a bug, just filed - the menu item should be disabled ;), but at least the column isn't hidden. To work around the bug, you can implement a custom column control (as Robin correctly suggested) which doesn't add the checkbox, something like:
JXTable table = new JXTable(new AncientSwingTeam());
// here the hideable property is configured manually,
// in production code you'll probably have a custom ColumnFactory
// doing it based on some data state
table.getColumnExt(0).setHideable(false);
ColumnControlButton columnControl = new ColumnControlButton(table) {
#Override
protected ColumnVisibilityAction createColumnVisibilityAction(
TableColumn column) {
if (column instanceof TableColumnExt
&& !((TableColumnExt) column).isHideable())
return null;
return super.createColumnVisibilityAction(column);
}
};
table.setColumnControl(columnControl);
table.setColumnControlVisible(true);
As to not including the menu item: when introducing the hideable property, we decided to go for keeping the item in the list but disable it because users might get confused not seeing all columns in the control. So once the bug will be fixed (just done, committed as of revision #4315), I would recommend to remove the custom column control again. Just my 2 euro-cents, though :-)
ColumnControlButton#createColumnVisibilityAction looks like the method you are looking for. According to the documentation:
Creates and returns a ColumnVisibilityAction for the given TableColumn. The return value might be null, f.i. if the column should not be allowed to be toggled
you can return null for your case.
You should be able to plug this in by using the JXTable#setColumnControl method.
First way:
myTable().getColumnExt(_column_number_).setHideable(false);
This works smooth but has one UI drawback: text in menu is gray and thick is black - bad user experience.
So try to fix it, text will be gray and thick won't be here:
public class MyTable extends JXTable
{
public MyTable(AbstractTableModel model)
{
//first two columns won't be hiddeable
ColumnControlButton controlButton = new ColumnControlButton(this)
{
#Override
protected ColumnControlPopup createColumnControlPopup()
{
return (new NFColumnControlPopup());
}
class NFColumnControlPopup extends DefaultColumnControlPopup
{
#Override
public void addVisibilityActionItems(List<? extends AbstractActionExt> actions)
{
for(int i = 0; i < actions.size(); i++)
{
AbstractActionExt action = actions.get(i);
JCheckBoxMenuItem chk = new JCheckBoxMenuItem(action);
//Disabling unwanted items but they will be still shown for smooth user experience
if(i == 0 || i == 1)
{
chk.setEnabled(false);
chk.setSelected(false);
//chk.setIcon(new ImageIcon(Icons.class.getResource("check.png")));
}
else
{
chk.setSelected(true);
}
chk.addItemListener(action);
super.addItem(chk);
}
}
}
};
this.setColumnControl(controlButton);
}
}
and if you need to hide controls for "show horizontal scrollbar", "pack" and "pack all" add into code:
//remove items for horizontal scrollbar, pack and packall
this.getActionMap().remove("column.horizontalScroll");
this.getActionMap().remove("column.packAll");
this.getActionMap().remove("column.packSelected");
right after calling super(model)
A very similar question has been asked here, and while I acknowledge it, the question's solution doesn't quite solve my question. A JList, when clicked on, will select the index list item closest to the mouse click. A JList will also do this for every click+drag event fire.
I would like to prevent my JList from selecting items during click+drag events when the click+drag location is outside the visible list. How would I go about this?
I had considered overriding a different method, one involved in the click+drag events of selecting list items. I thought to try the setSelectionInterval() method.
JList<String list = new JList<String>(){
private static final long serialVersionUID = 1L;
#Override
public int locationToIndex(Point location) {
int index = super.locationToIndex(location);
if (index != -1 && !getCellBounds(index, index).contains(location)) {
clearSelection();
return -1;
//an excellent click-only solution to prohibit the selecting of
//items from beyond the visible list
}
else {
return index;
}
}
#Override
public void setSelectionInterval(int anchor, int lead) {
super.setSelectionInterval(anchor, lead);
System.out.println("setSelectionInterval");
}
};
I found that each time I click+drag anywhere on the displayed JList, I get the System.out message of "setSelectionInterval" that I added to the method above. I don't know where to go from here in terms of overriding methods. Maybe that is not how I should approach this. In the source code for setSelectionInterval() I got lost trying to find my way through to a whatever listener is involved, so I came here. :p
I'd greatly appreciate any pointers to where I should be looking or a solution altogether. Thanks in advance!
This is an SSCCE example that is close to how I am set up. As it is, the list will not select an item when a click-only event is fired away from the list items themselves. I would like this same effect to happen when click+drag events are fired away from the list items.
import java.awt.BorderLayout;
import java.awt.Point;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
public class TestMain {
public static void main(String[] args) {
JFrame frame = new JFrame();
JPanel content = new JPanel(new BorderLayout());
String[] data = {"Luke", "Jeff", "Bryce"};
JList<String> list = new JList<String>(data){
private static final long serialVersionUID = 1L;
#Override
public int locationToIndex(Point location) {
System.out.println("location to index");
int index = super.locationToIndex(location);
if (index != -1 && !getCellBounds(index, index).contains(location)) {
clearSelection();
return -1;
}
else {
return index;
}
}
}
content.add(list, BorderLayout.CENTER);
frame.setContentPane(content);
frame.setSize(200,200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
If disabling the whole dragging feature is acceptable, then the following should help:
#Override
protected void processMouseMotionEvent(MouseEvent e) {
if(MouseEvent.MOUSE_DRAGGED != e.getID()) {
super.processMouseMotionEvent(e);
}
}
I would like to prevent my JList from selecting items during click+drag events when the click+drag location is outside the visible list.
Not sure I understand the requirement. Lets start with the first row being selected.
If you click on the first row and then continue to drag to the second the 2nd row gets highlighted. Continue dragging and the 3rd row gets highlighted. Continue dragging past the 3rd row and what do you want to happen?
Are you saying that you want the first row to be selected because that is where you started? If this is what you are asking, then you would need to look at the BasicListUI. This is where the mouse listeners are added to the JList. You would somehow need to handle the mousePressed event to save the currently selected row. You would then somehow need to override the default behaviour of the mouseDragged code to reset the selected row when you start to drag outside the last row.
I have no idea how to do all this.
I get the System.out message of "setSelectionInterval" that I added to the method above
Trying to override the code in that method is too late. The selection will continue to be updated as you drag the mouse. You don't know if this method is being called because the mouse is really on the last row or past the last row because you don't have access to the mouse point.
I suppose you could try to use the MouseInfo class to determine where the mouse is when this method is invoked, but then somehow you will still need to know that dragging originally started when the first row was selected. I have no idea how to do this either.
I would like to customize JTableHeader so it would offer serval actions (for example 2 buttons which one of them would sort column and second show properties of this column etc). Unfortunately it is not possible to set CellEditor for JTableHeader so i'm stuck with using mouse adapter. But maybe it is possible to dispatch event from this particular JTableHeader component so it will show up a popup menu which will contains all options i desire and it would dispatch event if option other than sorting would be chosen. This way standard JTable sorting operation will be available, along with my operations and it will maintain a decent visual apperance. So my question is - Is it possible and how it should be done.
In response to trashgod comment - i understand that you mean to treat defaultheader as an ordinary component and just use "add" function to add Components. It doesnt work well with JTableHeader. After reading trashgod example i wrote this:
private class mouseList extends MouseAdapter {
#Override
public void mouseClicked(MouseEvent e) {
TableColumnModel thisColumnModel = thisTable.getColumnModel();
int xCor = e.getX();
//int Cols = thisColumnModel.getColumnCount();
int thisColNum = thisColumnModel.getColumnIndexAtX(xCor);
int prevWidth=0;
for(int i = 0 ;i<thisColNum;i++)
{
prevWidth+=thisColumnModel.getColumn(i).getWidth();
}
int width = xCor-prevWidth;
/////////////////////////////////////////////////////////////////////////////////////
customHeader thisHeader = (customHeader)((JTableHeader)e.getSource()).getDefaultRenderer();
System.out.println(thisHeader.mainB.getText() + " text of thisHeader");
//////////////////////////////////////////////////
test thisTest = new test(null,false,thisHeader);
thisTest.setVisible(true);
///////////////////////////////////////////////////////////////
//System.out.println(width + " width of the header");
Object thisComp = thisHeader.getComponentAt(width, e.getY());
System.out.println(thisComp + "\n" + width + " + " + e.getY() +"\n" + thisHeader.getMainButton().getText());
((JTableHeader)e.getSource()).repaint();
if(thisComp instanceof JButton)
{
//System.out.println("sdfdsf");
String name = ((JButton)thisComp).getName();
if(name.equals("mainB"))
{
System.out.println("its working on main");
((JButton)thisComp).doClick(1000);
}else{
System.out.println("its working on menu");
((JButton)thisComp).doClick(1000);
}
}
((JTableHeader)e.getSource()).repaint();
}
}
MouseListener is applied to JTableHeader. HeaderRender is an extension of JPanel that contains 2 JButtons. Strange thing happens in line
Object thisComp = thisHeader.getComponentAt(width, e.getY());
When i left lines
test thisTest = new test(null,false,thisHeader);
thisTest.setVisible(true);
(This dialog shows selected component)
uncommented, function "getComponentAt" seems to work allmost fine (allmost because it never goes for else condition even when mouse is targeting second button, and it does not repaint clicked buttons[Strangely its repainting buttons in test dialog window]),otherwise it allways returns null object.
I dont know if it is important but i set Header renderer globally by invoking "setDefaultRenderer" on JTableHeader.
Im pretty much running out of ideas so i would appreciate any help.
This example shows the basic infrastructure, while this answer offers several important caveats regarding usability. This example shows how to change the RowFilter dynamically, but changing the RowSorter is similar. Both examples use JToggleButton to manage two states, but a JComboBox could be used to select from among more alternatives.
I have a JTable in my application. I have a custom renderer setup on the table (a JTextArea, with line-wrapping enabled) which allows for multi-line content. The contents of the JTable cells are expected to overflow the bounds of the cell in some cases. I want to do the following:
Instead of making the user drag the row border to resize the cell, I want the user to be able to double-click the row border, so when I detect this, I can automatically resize the height of the cell (height of the row) to show the entire contents of the cell.
My question is, what is the best way to detect a double-click on the row border? I have gotten this to work by setting up a MouseListener on the JTable with a mouseClicked method that looks like this:
public class MouseButtonInputListener extends MouseInputAdapter
{
private JTable fTable;
public MouseButtonInputListener(JTable parentTable)
{
fTable = parentTable;
}
#Override
public void mouseClicked(MouseEvent e)
{
// Some pre-processing here...
if(e.getClickCount() == 2 &&
fTable.getCursor().getType() == Cursor.N_RESIZE_CURSOR)
{
// Auto-adjust row height to fit contents...
}
}
}
While this works, i'm not very happy with the line:
fTable.getCursor().getType() == Cursor.N_RESIZE_CURSOR
Any suggestions on a better way to do this? Is the above a reliable approach on all platforms?
After some searching online, I found the following page:
http://java.sun.com/products/jlf/ed2/book/HIG.Behavior.html
amongst other things, it specifies mouse cursor behavior under different circumstances. According to the section on "Pointer Feedback", the cursor switches between a N_RESIZE_CURSOR and S_RESIZE_CURSOR depending on whether it is the upper or lower boundary of a component that is being hovered over. It's interesting to note that on 2 out of 3 platforms (Mac and Windows), these cursors are exactly the same. Anyway, it follows that my code should therefore read:
#Override
public void mouseClicked(MouseEvent e)
{
// Some pre-processing here. Determine row at mouse click location
int cursorType = fTable.getCursor().getType();
if(e.getClickCount == 2 &&
(cursorType == Cursor.N_RESIZE_CURSOR || cursorType == CURSOR.S_RESIZE_CURSOR))
{
// Resize row appropriately.
}
}
This will work on all platforms. Thanks for the inputs and comments everyone.
I have a JTable with a custom Cell Renderer for multi-line cells. Everything is ok, the JTable is painted ok in the screen and I am very happy with it, but ast night when I tried to simply print it, I came up with a very strange issue. Using:
table.print(PrintMode.FIT_WIDTH, new MessageFormat("..."), new MessageFormat("..."));
I saw that the table did not print entirely. Then using another class made from a colleague for printing JTables I had the same result:
The table (with multi-line cells) needed 22 pages to print. The printed document (which I only viewed in xps format since I do not own a printer) had also 22 pages. But up to page 16 everything was printed as expected and after that only the borders and the column headers of the table were printed.
Strangely (to me) enough, when I tried to print the table using another cell renderer that does not allow for multi line cells, the table needed exactly 16 pages and was printed entirely, albeit the cropping in the lengthy cell values.
I searched all over the net but I had no luck. Does anybody know why could this be happening? Is there a solution?
Update:
My cell renderer is the following:
public class MultiLineTableCellRenderer extends JTextPane implements TableCellRenderer {
private List<List<Integer>> rowColHeight = new ArrayList<List<Integer>>();
public MultiLineTableCellRenderer() {
setOpaque(true);
}
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column) {
String s = (String)value;
if (s.equals("<περιοδάριθμος>")) {
setForeground(Color.blue);
}
else if(s.equals("<παραγραφάριθμος>")) {
setForeground(Color.red);
}
else {
setForeground(Color.black);
}
setBackground(new Color(224, 255, 255));
if (isSelected) {
setBackground(Color.GREEN);
}
setFont(table.getFont());
setFont(new Font("Tahoma", Font.PLAIN, 10));
if (hasFocus) {
setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
if (table.isCellEditable(row, column)) {
setForeground(UIManager.getColor("Table.focusCellForeground"));
setBackground(UIManager.getColor("Table.focusCellBackground"));
}
} else {
setBorder(new EmptyBorder(1, 2, 1, 2));
}
if (value != null) {
setText(value.toString());
} else {
setText("");
}
adjustRowHeight(table, row, column);
SimpleAttributeSet bSet = new SimpleAttributeSet();
StyleConstants.setAlignment(bSet, StyleConstants.ALIGN_CENTER);
StyleConstants.setFontFamily(bSet, "Tahoma");
StyleConstants.setFontSize(bSet, 11);
StyledDocument doc = getStyledDocument();
doc.setParagraphAttributes(0, 100, bSet, true);
return this;
}
private void adjustRowHeight(JTable table, int row, int column) {
int cWidth = table.getTableHeader().getColumnModel().getColumn(column).getWidth();
setSize(new Dimension(cWidth, 1000));
int prefH = getPreferredSize().height;
while (rowColHeight.size() <= row) {
rowColHeight.add(new ArrayList<Integer>(column));
}
List<Integer> colHeights = rowColHeight.get(row);
while (colHeights.size() <= column) {
colHeights.add(0);
}
colHeights.set(column, prefH);
int maxH = prefH;
for (Integer colHeight : colHeights) {
if (colHeight > maxH) {
maxH = colHeight;
}
}
if (table.getRowHeight(row) != maxH) {
table.setRowHeight(row, maxH);
}
}
}
Furthermore, if you test the following very simple example you will notice that something is terribly wrong with the printing, but I really can't find what!
public static void main(String[] args) throws PrinterException {
DefaultTableModel model = new DefaultTableModel();
model.addColumn("col1");
model.addColumn("col2");
model.addColumn("col3");
int i = 0;
for (i = 1; i <= 400; i++) {
String a = "" + i;
model.addRow(new Object[]{a, "2", "3"});
}
JTable tab = new JTable(model);
tab.print();
}
I believe you are having the same problem that I had when I asked this question:
Truncated JTable print output
I found a solution to my problem, and I believe it may help you as well.
The answer is here:
Truncated JTable print output
To summarize my answer:
If your TableCellRenderer is the only place in your code where you are setting rows to their correct height, then you are going to run into trouble caused by an optimization inside JTable: JTable only invokes TableCellRenderers for cells that have been (or are about to be) displayed.
If not all of your cells have been displayed on-screen, then not all of your renderers have been invoked, and so not all of your rows have been set to the desired height. With your rows not being their correct height, your JTable overall height is incorrect. After all, part of determining the overall JTable height is accounting for the height of each of that table's rows. If the JTable overall height isn't correct, this causes the print to truncate, since the JTable overall height is a parameter that is considered in the print layout logic.
An easy (but perhaps not squeaky clean) way to fix this is to visit all of your cell renderers manually before printing. See my linked answer for an example of doing this. I actually chose to do the renderer visitation immediately after populating my table with data, because this fixes some buggy behavior with the JTable's scrollbar extents (in addition to fixing the printing.)
The reason the table looks and works OK on-screen even when printing is broken, is because as you scroll around in the table, the various renderers are invoked as new cells come on screen, and the renderers set the appropriate row height for the newly visible rows, and various dimensions are then are recalculated on the fly, and everything works out OK in the end as you interact with the table. (Although you may notice that the scrollbar "extent" changes as you scroll around, which it really shouldn't normally do.)
Strange thing is that behavior is not deterministic.
Such behavior always makes me suspect incorrect synchronization.
It's not clear how your TableCellRenderer works, but you might try HTML, which is supported in many Swing components.
Another useful exercise is to prepare an sscce that reproduces the problem in minature. A small, complete example might expose the problem. It would also allow others to test your approach on different platforms.
This answer is probably too late for the one who asked this question, but for everybody with a similar problem, here is my solution;
I had exactly the same problem, I have my own TableCellRenderer to handle multi-line Strings which works flawless for showing the table but makes the printing of the table unreliable.
My solutions consists of 2 parts;
Part 1: I have created my own TableModel, in the getValueAt() I 'copied' a part of the StringCellRenderer logic, I make it recalculate and set the height of the table row in case af a multi-line String AND return the String as HTML with 'breaks' instead of line-separators.
Part 2: Before invoking the table.print() I call the getValueAt() for all cells (a for-loop over the columns with an inner for loop over the rows invoking the getValueAt()), this has to be done 'manually' because the print functionality doesn't invoke all getValueAt's (I have found reasons on different fora regarding this issue regarding the execution of the TableCellRenderers).
This way the clipping of the table is done like it is supposed to, only complete rows are printed per page and it devides the rows over severall pages if required with a table header at each page.