Bug 92769 - [Viewers] Virtual Table using a DefferredContentProvider will call setData on already created items
Summary: [Viewers] Virtual Table using a DefferredContentProvider will call setData on...
Status: CLOSED WONTFIX
Alias: None
Product: Platform
Classification: Eclipse Project
Component: UI (show other bugs)
Version: 3.1   Edit
Hardware: PC Windows XP
: P5 major with 4 votes (vote)
Target Milestone: ---   Edit
Assignee: Platform UI Triaged CLA
QA Contact:
URL:
Whiteboard: stalebug
Keywords: helpwanted
Depends on: 106030
Blocks: 509006 92763
  Show dependency tree
 
Reported: 2005-04-26 10:13 EDT by Chad Gustafson CLA
Modified: 2020-06-10 16:50 EDT (History)
3 users (show)

See Also:


Attachments
Zip files of my test classes (2.98 KB, application/octet-stream)
2005-05-18 08:05 EDT, Tod Creasey CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Chad Gustafson CLA 2005-04-26 10:13:48 EDT
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();
	}
}
Comment 1 Tod Creasey CLA 2005-05-17 16:06:26 EDT
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
Comment 2 Tod Creasey CLA 2005-05-18 07:39:12 EDT
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();
	}
}
Comment 3 Tod Creasey CLA 2005-05-18 08:00:12 EDT
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();
	}
}
Comment 4 Tod Creasey CLA 2005-05-18 08:05:06 EDT
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 5 Tod Creasey CLA 2005-05-18 08:49:22 EDT
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.
Comment 6 Steve Northover CLA 2005-05-18 12:00:35 EDT
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.
Comment 7 Stefan Xenos CLA 2005-05-18 13:02:04 EDT
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.
Comment 8 Boris Bokowski CLA 2009-11-26 09:50:50 EST
Hitesh is now responsible for watching bugs in the [Viewers] component area.
Comment 9 Eclipse Genie CLA 2020-06-10 16:50:40 EDT
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.