Community
Participate
Working Groups
Checked check boxes loose their checked state after the check box has been scrolled out of the visible range. Here is the test case: 1. Run CheckBoxLooseTheirState and scroll to the bottom of the table 2. Check off a couple of rows 3. Scroll to the top 4. Scroll back down to the bottom. The boxes that you previously checked are no longer checked. I ran this test case under 3.1 M5 Build id: I20050219-1500 Here is the test case source code: import java.util.ArrayList; import java.util.Calendar; import java.util.Comparator; import java.util.Date; import java.util.List; import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.deferred.AbstractConcurrentModel; import org.eclipse.jface.viewers.deferred.DeferredContentProvider; import org.eclipse.jface.viewers.deferred.IConcurrentModelListener; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; /** * * @author cgustafson * */ public class CheckBoxLooseTheirState { CheckboxTableViewer tableViewer; public CheckBoxLooseTheirState(Composite parent){ createContents(parent); } private void createContents(Composite parent){ parent.setLayout(new GridLayout()); Table table = new Table(parent,SWT.VIRTUAL | SWT.V_SCROLL | SWT.CHECK); table.setHeaderVisible(true); table.setLinesVisible(true); GridData gd = new GridData(); gd.grabExcessVerticalSpace = true; gd.verticalAlignment = SWT.FILL; gd.grabExcessHorizontalSpace = true; gd.horizontalAlignment = SWT.FILL; gd.heightHint = 500; table.setLayoutData(gd); TableColumn checkCol = new TableColumn(table, SWT.LEFT); checkCol.setWidth(20); TableColumn dateCol = new TableColumn(table, SWT.LEFT); dateCol.setText("Date"); dateCol.setWidth(200); tableViewer = new CheckboxTableViewer(table); tableViewer.setContentProvider(new TestContentProvider(new TestComparator(true))); tableViewer.setLabelProvider(new TestLabelProvider()); int size = 90; Date data[] = new Date[size]; Calendar cal = Calendar.getInstance(); for (int i=0;i < size;i++){ cal.add(Calendar.DAY_OF_MONTH,1); data[i] = cal.getTime(); } TestModel model = new TestModel(); model.set(data); tableViewer.setInput(model); tableViewer.refresh(); } class TestContentProvider extends DeferredContentProvider{ public void dispose() { } public TestContentProvider(Comparator sorter){ super(sorter); } } class TestLabelProvider implements ITableLabelProvider{ private List listeners = new ArrayList(); public String getColumnText(Object element, int columnIndex){ if (columnIndex == 1){ return element.toString(); } else return null; } public Image getColumnImage(Object element, int columnIndex){ return null; } public void dispose(){ } public void removeListener(ILabelProviderListener listener){ listeners.remove(listener); } public boolean isLabelProperty(Object element, String property){ return false; } public void addListener(ILabelProviderListener listener){ listeners.add(listener); } } class TestComparator implements Comparator{ boolean ascending; TestComparator(boolean ascending){ this.ascending = ascending; } public int compare(Object o1, Object o2) { Date lhs = (Date) o1; Date rhs = (Date) o2; int answer; if (ascending){ answer = lhs.compareTo(rhs); } else{ answer = rhs.compareTo(lhs); } return answer; } } public class TestModel extends AbstractConcurrentModel { private ArrayList data = new ArrayList(); public Object[] getElements() { return data.toArray(); } /** * Sets the contents to the given array of elements * * @param newContents new contents of this set */ public void set(Object[] newContents) { data.clear(); for (int i = 0;i<newContents.length;i++){ data.add(newContents[i]); } IConcurrentModelListener[] listeners = getListeners(); for (int i = 0; i < listeners.length; i++) { IConcurrentModelListener listener = listeners [i]; listener.setContents(getElements()); } } /** * Empties the set */ public void clear() { Object[] removed = getElements(); data.clear(); fireRemove(removed); } /* (non-Javadoc) * @see org.eclipse.jface.viewers.deferred.IConcurrentContentProvider#requestUpdate (org.eclipse.jface.viewers.deferred.IConcurrentContentProviderListener) */ public void requestUpdate(IConcurrentModelListener listener) { listener.setContents(getElements()); } public int size(){ return data.size(); } } public static void main(String[] args){ System.out.println("Starting table"); Display display = new Display(); Shell shell = new Shell(display); shell.setText("Testing Stuff"); CheckBoxLooseTheirState table = new CheckBoxLooseTheirState (shell); shell.pack(); shell.open(); while (!shell.isDisposed()){ if (!display.readAndDispatch()){ display.sleep(); } } display.dispose(); } }
The issue here is that I am getting handleEvent calls from the SWT table even if I have already created the item previously so it is getting reinitialized in the viewer. The SWT scenario is this 1) Items are created on scroll 2) Scroll away 3) Scroll back - setData is called again
I'll take this one back for now. I wrote and SWT only example that is not showing this and I need to figure out the differences import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; 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.Table; import org.eclipse.swt.widgets.TableItem; public class CheckTable { public static void main(String[] args) { Display display = new Display(); Shell parent = new Shell(display,SWT.CLOSE); parent.setLayout(new GridLayout()); final Table table = new Table(parent, SWT.VIRTUAL | SWT.V_SCROLL | SWT.CHECK); table.setHeaderVisible(true); table.setLinesVisible(true); table.setItemCount(90); table.addListener(SWT.SetData, new Listener() { /* (non-Javadoc) * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event) */ public void handleEvent(Event event) { TableItem item = (TableItem) event.item; final int index = table.indexOf(item); boolean newItem = true; String label = "Element " + String.valueOf(index); if(item.getData() == null) item.setData(label); else{ newItem = false; System.out.println("Recalled data for " + label); } if(item.getText().length() == 0) item.setText(label); else{ newItem = false; System.out.println("Recalled text for " + label); } if(newItem) System.out.println("New item for " + label); } }); GridData gd = new GridData(); gd.grabExcessVerticalSpace = true; gd.verticalAlignment = SWT.FILL; gd.grabExcessHorizontalSpace = true; gd.horizontalAlignment = SWT.FILL; gd.heightHint = 500; table.setLayoutData(gd); parent.setSize(300,500); parent.open(); while(!parent.isDisposed()) display.readAndDispatch(); } }
The issue is with using the DeferredContentProvider, not the VIRTUAL TableViewer Here is the example updated using a standard content provider that does not have this issue import java.util.ArrayList; import java.util.Calendar; import java.util.Comparator; import java.util.Date; import java.util.List; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.deferred.AbstractConcurrentModel; import org.eclipse.jface.viewers.deferred.DeferredContentProvider; import org.eclipse.jface.viewers.deferred.IConcurrentModelListener; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; /** * * @author cgustafson * */ public class CheckBoxLooseTheirState { CheckboxTableViewer tableViewer; static final int SIZE = 90; public CheckBoxLooseTheirState(Composite parent){ createContents(parent); } private void createContents(Composite parent){ parent.setLayout(new GridLayout()); Table table = new Table(parent,SWT.VIRTUAL | SWT.V_SCROLL | SWT.CHECK); table.setHeaderVisible(true); table.setLinesVisible(true); table.setItemCount(SIZE); GridData gd = new GridData(); gd.grabExcessVerticalSpace = true; gd.verticalAlignment = SWT.FILL; gd.grabExcessHorizontalSpace = true; gd.horizontalAlignment = SWT.FILL; gd.heightHint = 500; table.setLayoutData(gd); TableColumn checkCol = new TableColumn(table, SWT.LEFT); checkCol.setWidth(20); TableColumn dateCol = new TableColumn(table, SWT.LEFT); dateCol.setText("Date"); dateCol.setWidth(200); tableViewer = new CheckboxTableViewer(table); tableViewer.setContentProvider(new TestContentProvider2()); tableViewer.setLabelProvider(new TestLabelProvider()); // tableViewer.addCheckStateListener(new ICheckStateListener(){ // public void checkStateChanged(CheckStateChangedEvent event) { // System.out.println(event.getElement().toString()); // // } // }); // Calendar cal = Calendar.getInstance(); // // for (int i=0;i < size;i++){ // cal.add(Calendar.DAY_OF_MONTH,1); // data[i] = cal.getTime(); // } // // TestModel model = new TestModel(); // model.set(data); tableViewer.setInput(this); tableViewer.refresh(); } class TestContentProvider2 implements IStructuredContentProvider{ public Object[] getElements(Object inputElement) { String[] elements = new String[SIZE]; for (int i = 0; i < elements.length; i++) { elements[i] = "Element " + String.valueOf(i); } return elements; } public void dispose() { // TODO Auto-generated method stub } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { // TODO Auto-generated method stub } } // // class TestContentProvider extends DeferredContentProvider{ // // public void dispose() { // } // // public TestContentProvider(Comparator sorter){ // super(sorter); // } // } class TestLabelProvider implements ITableLabelProvider{ // private List listeners = new ArrayList(); public String getColumnText(Object element, int columnIndex){ if (columnIndex == 1){ return element.toString(); } else return null; } public Image getColumnImage(Object element, int columnIndex){ return null; } public void dispose(){ } public void removeListener(ILabelProviderListener listener){ // listeners.remove(listener); } public boolean isLabelProperty(Object element, String property){ return false; } public void addListener(ILabelProviderListener listener){ // listeners.add(listener); } } class TestComparator implements Comparator{ boolean ascending; TestComparator(boolean ascending){ this.ascending = ascending; } public int compare(Object o1, Object o2) { Date lhs = (Date) o1; Date rhs = (Date) o2; int answer; if (ascending){ answer = lhs.compareTo(rhs); } else{ answer = rhs.compareTo(lhs); } return answer; } } public class TestModel extends AbstractConcurrentModel { private ArrayList data = new ArrayList(); public Object[] getElements() { return data.toArray(); } /** * Sets the contents to the given array of elements * * @param newContents new contents of this set */ public void set(Object[] newContents) { data.clear(); for (int i = 0;i<newContents.length;i++){ data.add(newContents[i]); } IConcurrentModelListener[] listeners = getListeners(); for (int i = 0; i < listeners.length; i++) { IConcurrentModelListener listener = listeners [i]; listener.setContents(getElements()); } } /** * Empties the set */ public void clear() { Object[] removed = getElements(); data.clear(); fireRemove(removed); } /* (non-Javadoc) * @see org.eclipse.jface.viewers.deferred.IConcurrentContentProvider#requestUpdate (org.eclipse.jface.viewers.deferred.IConcurrentContentProviderListener) */ public void requestUpdate(IConcurrentModelListener listener) { listener.setContents(getElements()); } public int size(){ return data.size(); } } public static void main(String[] args){ System.out.println("Starting table"); Display display = new Display(); Shell shell = new Shell(display); shell.setText("Testing Stuff"); CheckBoxLooseTheirState table = new CheckBoxLooseTheirState (shell); shell.pack(); shell.open(); while (!shell.isDisposed()){ display.readAndDispatch(); } display.dispose(); } }
Created attachment 21324 [details] Zip files of my test classes Here are zipped versions of my SWT only test and the example in the problem report using a standard content provider
Comment on attachment 21324 [details] Zip files of my test classes Obsolete. See Bug 92763 for a zip with all of the examples and thier modified versions.
I put prints in SetData and the clear() methods of Table. The application code is clearing the last item (index 89) so we ask for it again. I showed Tod.
DeferredContentProvider needs to clear all rows outside the visible range or it won't get notified about changes in scroll position. Note: the selection will also be lost... and I suspect this problem will also occur if new rows are inserted at the top of table. From what I can tell, there are only two solutions: 1. Table + TableViewer needs to provide a VisibleRangeChanged callback along with a means to reorder rows in constant time. 2. TableViewer needs to remember the state of each element (selection and enablement state, etc.) independantly of what is stored in the widget, and restore that state when the same element is reinserted into the table. I never liked the clear-things-when-they-become-invisible algorithm, and would prefer solution 1. However, if the algorithm really must stay then we need to use solution 2. Either way, there's nothing that can be done in the content provider without additional support from the table.
Hitesh is now responsible for watching bugs in the [Viewers] component area.
This bug hasn't had any activity in quite some time. Maybe the problem got resolved, was a duplicate of something else, or became less pressing for some reason - or maybe it's still relevant but just hasn't been looked at yet. As such, we're closing this bug. If you have further information on the current state of the bug, please add it and reopen this bug. The information can be, for example, that the problem still occurs, that you still want the feature, that more information is needed, or that the bug is (for whatever reason) no longer relevant. -- The automated Eclipse Genie.