Unten ist ein (mit SWT und JFace direkt compilier- und testbares) Beispiel für etwas, was eigentlich gar nicht so kompliziert und aufwändig sein sollte: Ein CheckboxTreeViewer
, mit zwei Spalten. In einer steht einfach ein Name für die Knoten, und in der anderen ob der Knoten “gecheckt” ist oder nicht (soll nur ein Test sein).
Wenn man den Check-Status eines Knotens ändert, werden auch die Kinder des Knoten ensprechend gecheckt- und ungecheckt.
Dass schon das Erstellen (bzw. erstmaligen Ausklappen) eines Baumes mit lächerlichen 1000 Knoten mehrere Sekunden dauert, ist irritierend. Das könnte aber gerade noch so hinnehmbar sein, weil es nur einmal passieren muss, und man da notfalls wohl mit diesem Lazy Provider etwas tricksen könnte.
Was die Grenze zum Absurden aber schon deutlich überschreitet, ist, dass ein Aktualisieren der Labels bei einer Änderung des Checkbox-Status für 1000 Knoten sage und schreibe fünfeinhalb Sekunden dauert.
(Man könnte das zwar für einige Fälle optimieren, indem man die zu aktualisiernden Elemente beim Event mit übergibt - aber spätestens wenn man den Wurzelknoten klickt, ist man wieder bei den 5 Sekunden).
Da das ganze auch irgendwann mal für bis zu 1000000 Knoten flott laufen sollte, hoffe ich, dass ich da etwas sehr einfaches und naheliegendes falsch gemacht habe…
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ICheckStateProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
public class CheckboxTreeViewerPerformanceTest
{
public static void main(String[] args)
{
Display display = new Display();
Shell shell = new Shell(display);
shell.setBounds(100, 100, 800, 600);
shell.setLayout(new FillLayout());
new CheckboxTreeViewerPerformanceTest(shell);
shell.open();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
{
display.sleep();
}
}
display.dispose();
}
// A simple node containing a name and children, and a flag indicating
// whether the node is "checked" or not.
// Changes in the "checked" flag will be propagated to the children.
static class TestNode
{
private final String name;
private final List<TestNode> children;
private boolean checked;
public TestNode(int id)
{
this.name = "node"+id;
this.children = new ArrayList<TestNode>();
this.checked = true;
}
void addChild(TestNode child)
{
children.add(child);
}
String getName()
{
return name;
}
boolean isChecked()
{
return checked;
}
void setChecked(boolean checked)
{
this.checked = checked;
for (TestNode child : children)
{
child.setChecked(checked);
}
}
List<TestNode> getChildren()
{
return children;
}
}
private static TestNode createModel()
{
int id = 0;
TestNode root = new TestNode(id++);
int n = 1000;
for (int i=0; i<n; i++)
{
root.addChild(new TestNode(id++));
}
return root;
}
public CheckboxTreeViewerPerformanceTest(Composite parent)
{
CheckboxTreeViewer viewer = new CheckboxTreeViewer(
parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
viewer.setContentProvider(new TestNodeContentProvider());
viewer.setCheckStateProvider(new TestCheckStateProvider());
Tree tree = viewer.getTree();
tree.setHeaderVisible(true);
// Create one column showing the node name
TreeViewerColumn nameColumn = new TreeViewerColumn(viewer, SWT.NONE);
nameColumn.getColumn().setText("Name");
nameColumn.getColumn().setWidth(200);
nameColumn.setLabelProvider(
new DelegatingStyledCellLabelProvider(new NameLabelProvider()));
// Create one column showing the "checked" state of the nodes
TreeViewerColumn checkedColumn = new TreeViewerColumn(viewer, SWT.NONE);
checkedColumn.getColumn().setText("Checked");
checkedColumn.getColumn().setWidth(70);
checkedColumn.getColumn().setAlignment(SWT.RIGHT);
CheckedLabelProvider checkedLabelProvider = new CheckedLabelProvider();
checkedColumn.setLabelProvider(
new DelegatingStyledCellLabelProvider(checkedLabelProvider));
viewer.addCheckStateListener(new ICheckStateListener()
{
@Override
public void checkStateChanged(CheckStateChangedEvent event)
{
Object element = event.getElement();
if (element instanceof TestNode)
{
TestNode node = (TestNode)element;
// Set the checked state of the node and all its children
node.setChecked(event.getChecked());
viewer.setSubtreeChecked(element, event.getChecked());
// Trigger an update of the "checked" labels
long before = System.nanoTime();
checkedLabelProvider.fireUpdateLabels();
long after = System.nanoTime();
System.out.println("update took "+(after-before)/1e6);
}
}
});
TestNode root = createModel();
viewer.setInput(new Object[] { root });
}
// The label provider for the "checked" column
class CheckedLabelProvider extends LabelProvider implements
IStyledLabelProvider
{
void fireUpdateLabels()
{
fireLabelProviderChanged(
new LabelProviderChangedEvent(this));
}
@Override
public StyledString getStyledText(Object element)
{
if (element instanceof TestNode)
{
TestNode node = (TestNode) element;
return new StyledString(String.valueOf(node.isChecked()));
}
return null;
}
}
// The label provider for the "name" column
class NameLabelProvider extends LabelProvider
implements IStyledLabelProvider
{
@Override
public StyledString getStyledText(Object element)
{
if (element instanceof TestNode)
{
TestNode node = (TestNode) element;
return new StyledString(node.getName());
}
return null;
}
}
// The check state provider that reads the state from the model
private final class TestCheckStateProvider implements ICheckStateProvider
{
@Override
public boolean isGrayed(Object element)
{
return false;
}
@Override
public boolean isChecked(Object element)
{
if (element instanceof TestNode)
{
TestNode node = (TestNode)element;
return node.isChecked();
}
return false;
}
}
// A simple content provider for the tree model
class TestNodeContentProvider implements ITreeContentProvider
{
@Override
public void inputChanged(Viewer v, Object oldInput, Object newInput)
{
// Nothing to do here
}
@Override
public void dispose()
{
// Nothing to do here
}
@Override
public Object[] getElements(Object inputElement)
{
return (Object[])inputElement;
}
@Override
public Object[] getChildren(Object parentElement)
{
TestNode node = (TestNode)parentElement;
return node.getChildren().toArray();
}
@Override
public Object getParent(Object element)
{
return null;
}
@Override
public boolean hasChildren(Object element)
{
TestNode node = (TestNode)element;
return !node.getChildren().isEmpty();
}
}
}