Community
Participate
Working Groups
Build ID: I20070625-1500 Steps To Reproduce: to demonstrate the problem I have modified Snippet040TableViewerSorting. for example click on "Tod" and enter the editor. change the name to "zebra" and press enter. the items are rearranged due to a table.refresh() (not really necessary at this point but makes it easier to demonstrate the problem). now press enter again and see the strange behavior (the wrong editor (at the previous item position) is activated with the "correct" content). The modified Snippet: package org.eclipse.swt.snippets; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ColumnViewer; import org.eclipse.jface.viewers.ColumnViewerEditor; import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent; import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy; import org.eclipse.jface.viewers.EditingSupport; import org.eclipse.jface.viewers.FocusCellHighlighter; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.TableViewerEditor; import org.eclipse.jface.viewers.TableViewerFocusCellManager; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.jface.viewers.ViewerRow; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.TableItem; import com.swtdesigner.SWTResourceManager; /** * Example usage of ViewerComparator in tables to allow sorting * * @author Tom Schindl <tom.schindl@bestsolution.at> * */ public class Snippet040TableViewerSorting { private class MyContentProvider implements IStructuredContentProvider { public Object[] getElements(Object inputElement) { return (Person[]) inputElement; } public void dispose() { } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } } public class Person { public String givenname; public String surname; public String email; public Person(String givenname, String surname, String email) { this.givenname = givenname; this.surname = surname; this.email = email; } } protected abstract class AbstractEditingSupport extends EditingSupport { private TextCellEditor editor; public AbstractEditingSupport(TableViewer viewer) { super(viewer); this.editor = new TextCellEditor(viewer.getTable()); } protected boolean canEdit(Object element) { return true; } protected CellEditor getCellEditor(Object element) { return editor; } protected void setValue(Object element, Object value) { doSetValue(element, value); getViewer().update(element, null); TableViewer tableViewer = (TableViewer) getViewer(); TableItem item = tableViewer.getTable().getItem(tableViewer.getTable().getSelectionIndex()); Object obj = item.getData(); // refresh table tableViewer.refresh(); // search for the item that has the object from the // previously selected item TableItem itemtoselect = null; TableItem[] items = tableViewer.getTable().getItems(); for (TableItem it : items) { if (it.getData().equals(obj)) { itemtoselect = it; } } // select this item tableViewer.getTable().setSelection(itemtoselect); } protected abstract void doSetValue(Object element, Object value); } public Snippet040TableViewerSorting(Shell shell) { TableViewer v = new TableViewer(shell, SWT.BORDER | SWT.FULL_SELECTION); v.setContentProvider(new MyContentProvider()); final TableViewerFocusCellManager focusCellManager = new TableViewerFocusCellManager(v, new EditableFocusCellHighlighter(v)); ColumnViewerEditorActivationStrategy actSupport = new ColumnViewerEditorActivationStrategy(v) { protected boolean isEditorActivationEvent( ColumnViewerEditorActivationEvent event) { if ( event.sourceEvent instanceof KeyEvent && event.keyCode > 31 && event.keyCode < 127) { ((KeyEvent)event.sourceEvent).doit=false; } return event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL || event.eventType == ColumnViewerEditorActivationEvent.MOUSE_DOUBLE_CLICK_SELECTION || (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode > 31 && event.keyCode < 127) || (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == SWT.CR) || (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == 16777296) || (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == SWT.F2) || event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC; } }; TableViewerEditor.create(v, focusCellManager, actSupport, ColumnViewerEditor.TABBING_HORIZONTAL | ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR | ColumnViewerEditor.TABBING_VERTICAL | ColumnViewerEditor.KEYBOARD_ACTIVATION); TableViewerColumn column = new TableViewerColumn(v, SWT.NONE); column.getColumn().setWidth(200); column.getColumn().setText("Givenname"); column.getColumn().setMoveable(true); column.setLabelProvider(new ColumnLabelProvider() { public String getText(Object element) { return ((Person) element).givenname; } }); column.setEditingSupport(new AbstractEditingSupport(v) { protected Object getValue(Object element) { return ((Person) element).givenname; } protected void doSetValue(Object element, Object value) { ((Person) element).givenname = value.toString(); } }); ColumnViewerSorter cSorter = new ColumnViewerSorter(v,column) { protected int doCompare(Viewer viewer, Object e1, Object e2) { Person p1 = (Person) e1; Person p2 = (Person) e2; return p1.givenname.compareToIgnoreCase(p2.givenname); } }; column = new TableViewerColumn(v, SWT.NONE); column.getColumn().setWidth(200); column.getColumn().setText("Surname"); column.getColumn().setMoveable(true); column.setLabelProvider(new ColumnLabelProvider() { public String getText(Object element) { return ((Person) element).surname; } }); column.setEditingSupport(new AbstractEditingSupport(v) { protected Object getValue(Object element) { return ((Person) element).surname; } protected void doSetValue(Object element, Object value) { ((Person) element).surname = value.toString(); } }); new ColumnViewerSorter(v,column) { protected int doCompare(Viewer viewer, Object e1, Object e2) { Person p1 = (Person) e1; Person p2 = (Person) e2; return p1.surname.compareToIgnoreCase(p2.surname); } }; column = new TableViewerColumn(v, SWT.NONE); column.getColumn().setWidth(200); column.getColumn().setText("E-Mail"); column.getColumn().setMoveable(true); column.setLabelProvider(new ColumnLabelProvider() { public String getText(Object element) { return ((Person) element).email; } }); column.setEditingSupport(new AbstractEditingSupport(v) { protected Object getValue(Object element) { return ((Person) element).email; } protected void doSetValue(Object element, Object value) { ((Person) element).email = value.toString(); } }); new ColumnViewerSorter(v,column) { protected int doCompare(Viewer viewer, Object e1, Object e2) { Person p1 = (Person) e1; Person p2 = (Person) e2; return p1.email.compareToIgnoreCase(p2.email); } }; Person[] model = createModel(); v.setInput(model); v.getTable().setLinesVisible(true); v.getTable().setHeaderVisible(true); cSorter.setSorter(cSorter, ColumnViewerSorter.ASC); } private Person[] createModel() { Person[] elements = new Person[4]; elements[0] = new Person("Tom", "Schindl", "tom.schindl@bestsolution.at"); elements[1] = new Person("Boris", "Bokowski", "Boris_Bokowski@ca.ibm.com"); elements[2] = new Person("Tod", "Creasey", "Tod_Creasey@ca.ibm.com"); elements[3] = new Person("Wayne", "Beaton", "wayne@eclipse.org"); return elements; } private static abstract class ColumnViewerSorter extends ViewerComparator { public static final int ASC = 1; public static final int NONE = 0; public static final int DESC = -1; private int direction = 0; private TableViewerColumn column; private ColumnViewer viewer; public ColumnViewerSorter(ColumnViewer viewer, TableViewerColumn column) { this.column = column; this.viewer = viewer; this.column.getColumn().addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { if( ColumnViewerSorter.this.viewer.getComparator() != null ) { if( ColumnViewerSorter.this.viewer.getComparator() == ColumnViewerSorter.this ) { int tdirection = ColumnViewerSorter.this.direction; if( tdirection == ASC ) { setSorter(ColumnViewerSorter.this, DESC); } else if( tdirection == DESC ) { setSorter(ColumnViewerSorter.this, NONE); } } else { setSorter(ColumnViewerSorter.this, ASC); } } else { setSorter(ColumnViewerSorter.this, ASC); } } }); } public void setSorter(ColumnViewerSorter sorter, int direction) { if( direction == NONE ) { column.getColumn().getParent().setSortColumn(null); column.getColumn().getParent().setSortDirection(SWT.NONE); viewer.setComparator(null); } else { column.getColumn().getParent().setSortColumn(column.getColumn()); sorter.direction = direction; if( direction == ASC ) { column.getColumn().getParent().setSortDirection(SWT.DOWN); } else { column.getColumn().getParent().setSortDirection(SWT.UP); } if( viewer.getComparator() == sorter ) { viewer.refresh(); } else { viewer.setComparator(sorter); } } } public int compare(Viewer viewer, Object e1, Object e2) { return direction * doCompare(viewer, e1, e2); } protected abstract int doCompare(Viewer viewer, Object e1, Object e2); } private class EditableFocusCellHighlighter extends FocusCellHighlighter { private ViewerCell oldCell; /** * @param viewer * the viewer */ public EditableFocusCellHighlighter(ColumnViewer viewer) { super(viewer); hookListener(viewer); } private void markFocusedCell(Event event, ViewerCell cell) { GC gc = event.gc; gc.setBackground(SWTResourceManager.getColor(255, 255, 255)); gc.setForeground(SWTResourceManager.getColor(0, 0, 0)); gc.setLineWidth(4); int currentCol = event.index; Rectangle rect = ((TableItem)event.item).getBounds(currentCol); gc.drawRectangle(rect); event.detail &= ~SWT.FOCUSED; event.detail &= SWT.FocusOut; event.detail &= ~SWT.SELECTED; } private void removeSelectionInformation(Event event, ViewerCell cell) { } private void hookListener(final ColumnViewer viewer) { Listener listener = new Listener() { public void handleEvent(Event event) { if ((event.detail & SWT.SELECTED) > 0) { ViewerCell focusCell = getFocusCell(); ViewerRow row = focusCell.getViewerRow(); Assert .isNotNull(row, "Internal structure invalid. Item without associated row is not possible."); //$NON-NLS-1$ ViewerCell cell = row.getCell(event.index); if (focusCell == null || !cell.equals(focusCell)) { removeSelectionInformation(event, cell); } else { markFocusedCell(event, cell); } } } }; viewer.getControl().addListener(SWT.EraseItem, listener); } /** * @param cell * the cell which is colored * @return the color */ protected Color getSelectedCellBackgroundColor(ViewerCell cell) { return null; } /** * @param cell * the cell which is colored * @return the color */ protected Color getSelectedCellForegroundColor(ViewerCell cell) { return null; } /* * (non-Javadoc) * * @see org.eclipse.jface.viewers.FocusCellHighlighter#focusCellChanged(org.eclipse.jface.viewers.ViewerCell) */ protected void focusCellChanged(ViewerCell cell) { super.focusCellChanged(cell); // Redraw new area if (cell != null) { Rectangle rect = cell.getBounds(); int x = cell.getColumnIndex() == 0 ? 0 : rect.x; int width = cell.getColumnIndex() == 0 ? rect.x + rect.width : rect.width; // 1 is a fix for Linux-GTK cell.getControl().redraw(x, rect.y-1, width, rect.height+1, true); } if (oldCell != null) { Rectangle rect = oldCell.getBounds(); int x = oldCell.getColumnIndex() == 0 ? 0 : rect.x; int width = oldCell.getColumnIndex() == 0 ? rect.x + rect.width : rect.width; // 1 is a fix for Linux-GTK oldCell.getControl().redraw(x, rect.y-1, width, rect.height+1, true); } this.oldCell = cell; } } /** * @param args */ public static void main(String[] args) { Display display = new Display(); Shell shell = new Shell(display); shell.setLayout(new FillLayout()); new Snippet040TableViewerSorting(shell); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } }
Created attachment 74555 [details] snippet to demonstrate the bug the snippet copy-pasted above here also as an attachment.
moving to UI and assign to me
Created attachment 74561 [details] and here's fix the problem is that we are not updating the focus cell appropiately when the selection is changed using StrucutureViewer#setSelection(). Please note that Control#setSelection() can never work because this won't give us the required events.
If you need a workaround you can call: tableViewer.editElement(element, 0) after having refresh() the viewer this is the only method I can think of currently fixing the problem. This way you can even keep the editor in editing mode if you put this into a asyncExec call: protected void setValue(final Object element, Object value) { doSetValue(element, value); getViewer().update(element, null); final TableViewer tableViewer = (TableViewer) getViewer(); tableViewer.refresh(); Display.getCurrent().asyncExec(new Runnable() { public void run() { tableViewer.editElement(element, 0); } }); } Another thing which might not be a bad idea is that you do the refresh() not in the setValue-method but add a ColumnViewerEditorActivationListener listener to your ColumnViewerEditor and do the refresh there. You might not need a async-call then.
Comment on attachment 74561 [details] and here's fix not appropriate need to redesign :-(
Created attachment 74580 [details] now a real fix The problem when we want to fix this SWTFocusCellManager is that we are not informed about selection changes (at the SWT-Level) which occur when we are resorting. Boris is this a 3.3.1 candidate?
Created attachment 74581 [details] Too much things commented
As a workaround for me, I changed the isEditorActivationEvent(ColumnViewerEditorActivationEvent) a bit: ColumnViewerEditorActivationStrategy actSupport = new ColumnViewerEditorActivationStrategy(v) { protected boolean isEditorActivationEvent( ColumnViewerEditorActivationEvent event) { if ( event.sourceEvent instanceof KeyEvent && event.keyCode > 31 && event.keyCode < 127) { ((KeyEvent)event.sourceEvent).doit=false; } if ((event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == SWT.CR) || (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == 16777296) || (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == SWT.F2) || (event.sourceEvent instanceof KeyEvent && event.keyCode > 31 && event.keyCode < 127) || (event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL)) { int index = ((ViewerCell)event.getSource()).getColumnIndex(); v.editElement(((ViewerCell)event.getSource()).getElement(), index); } return event.eventType == ColumnViewerEditorActivationEvent.MOUSE_DOUBLE_CLICK_SELECTION || event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC; } }; The only problem I have with this is the activation of the editor now does not get the KeyPressed Event so I don't know in my own implementation of the CellEditor what key is pressed. I needed this to get the character of the KeyEvent and put it in the editor as well. But I don't see how to solve this at the moment.
Why not using the code i showed you. This way the ViewerCell is updated correctly the draw back is that your editor is activated for a short time but you can solve this by setting the editor edit mode to false for a short time probably. I admit it is not ideal because when ever you update a selection using (viewer#setSelection()) you have to append a call to viewer#editElement(). I hope we can fix this for 3.3.1 although I can't promise it.
(In reply to comment #9) > Why not using the code i showed you. This way the ViewerCell is updated > correctly the draw back is that your editor is activated for a short time but > you can solve this by setting the editor edit mode to false for a short time > probably. With your code from above the editor is always active. That's why I was looking for an other solution. Can you tell me how to set the edit mode to false for a short time? I don't see how to do this at the moment. > I admit it is not ideal because when ever you update a selection using > (viewer#setSelection()) you have to append a call to viewer#editElement(). I > hope we can fix this for 3.3.1 although I can't promise it. With my solution I don't have to call viewer#editElement() all the time. But it has other drawbacks. And we can't wait till 3.3.1 or later.
Hm. Setting the editor none editable is not possible because then the update is not happening! The only work-around currently is: -------------8<------------- protected void saveCellEditorValue(CellEditor cellEditor, ViewerCell cell) { super.saveCellEditorValue(cellEditor, cell); TableViewer tableViewer = (TableViewer) getViewer(); tableViewer.refresh(); tableViewer.editElement(cell.getElement(), cell.getColumnIndex()); } -------------8<------------- You are relying on internals here that the editor is not activated and this might break in a future version! So once we fixed the problem I would advice you to remove this workaround or somehow wrap it in an IF-Clause.
Thanks for your help, but I think this does not really help. It does not solve the same problem when re-sorting the table. And with my own CellEditor (TableCellEditor) that is the same as TextCellEditor but has an activate(ColumnViewerEditorActivationEvent) (and does not set text.selectAll() in doSetFocus()) this ends in an endless loop. // TableCellEditor#activate(ColumnViewerEditorActivationEvent) public void activate(ColumnViewerEditorActivationEvent event) { if (event.keyCode > 31 && event.keyCode < 127) { text.setText(event.character+text.getText()); text.setSelection(1); } }
Another possibility is that you Subclass TableViewerFocusCellManager and do what the fix does. To access som of the methods you of course need Reflection but this should work and the performance hit is not too big because the methods you need reflection for are part of the last if block.
One more note on this I still have the feeling that we have to provide a ListenerAPI for Viewers where they can inform interested parties about: - refresh - resort - filter - .... And we definately need an API to set the selected cell from the out-side just like StructuredSelection for rows. This is somehow related to bug #151377.
(In reply to comment #13) > Another possibility is that you Subclass TableViewerFocusCellManager and do > what the fix does. To access som of the methods you of course need Reflection > but this should work and the performance hit is not too big because the methods > you need reflection for are part of the last if block. > sounds good. I like to do this, but I don't know much about reflection. So I don't really see how to do this. Can you help me again?
Created attachment 74695 [details] Example for the reflection approach !!!!!!!!! ATTENTION !!!!!!!!! I haven't put too much thought into the algorithm to find methods you should query them in a loop and find the right one. This also relies on implementation details (method names which are not API) and might break in future versions. !!!!!!!!! ATTENTION !!!!!!!!!
Created attachment 76566 [details] Patch without commented parts
Created attachment 76567 [details] mylyn/context/zip
missed the M4 cut
This is also ready for review. It fixes the problem but we should really think about some listener-API for structural Viewer-changes (refresh/filter/sort)
Created attachment 90220 [details] apply to HEAD
Thanks for idea shown here (attachment 74695 [details]). I have problem with loosing focused cell after setInput (my way how to do refresh with ILazyContentProvider). I can restore selection, but whole line is selected instead of cell. So I added following code fixing my problem: public ViewerCell getFocusCell() { ViewerCell cell = super.getFocusCell(); Table t = viewer.getTable(); .... if(cell == null && t.getSelection().length > 0) { //choose cell from selection }
maybe we could and internal methods people could call using reflection because API freeze is gone, I'm sorry we missed this for M6
Created attachment 95656 [details] Test snippet
Boris?
+1
released to CVS-HEAD >= 20080427
There's still a problem which is common to all viewers who get resorted because the focus indicator is not fixed but entering bugs is not possible ATM
Filed new bug 229613 for the Focus-Indicator problem
verified in I20080430 by running the attached snippet