I recently implemented a CheckboxTreeViewer in my own Dialog. This works fine so far except that the tree doesn't allow me to expand nodes by default. It only works when I check the checkbox, as you can see in the following images:
This is by default. As you can see, it's not possible to expand the node, though it has children:
After checking the check box, it works:
I already tried to use setExpandPreCheckFilters , but with no success:
Composite container = (Composite) super.createDialogArea(parent);
tv = new CheckboxTreeViewer(container, SWT.MULTI | SWT.H_SCROLL| SWT.V_SCROLL);
GridData gridData = new GridData(GridData.FILL_BOTH);
tv.getTree().setLayoutData(gridData);
tv.setContentProvider(new FeaturePropertyDialogContentProvider());
tv.setLabelProvider(new FeaturePropertyDialogLabelProvider());
tv.setAutoExpandLevel(2);
tv.setExpandPreCheckFilters(true);
Any ideas?
-----------------------------------Update-------------------------------------
I found the reason of the problem. I forgott to check every element in the hasChildren method. The following code is working now for me:
public boolean hasChildren(Object element) {
if (element instanceof ProductLine) {
ProductLine productLine = (ProductLine) element;
if (productLine.getPropertyList() != null) {
return true;
} else {
return false;
}
}
if (element instanceof PropertyList) {
PropertyList propertyList = (PropertyList) element;
if (!(propertyList.getGeneralPlatforms().isEmpty())) {
return true;
} else {
return false;
}
} else if (element instanceof GeneralPlatform) {
GeneralPlatform platform = (GeneralPlatform) element;
if (!(platform.getHardwareElements().isEmpty())) {
return true;
} else {
return false;
}
} else if (element instanceof HardwareElement) {
HardwareElement hw = (HardwareElement) element;
if (!(hw.getHardwareElements().isEmpty())
|| !(hw.getProperties().isEmpty())) {
return true;
} else {
return false;
}
} else {
return false;
}
}
Thx for your help!!
From the SWT javadoc of setAutoExpandLevel:
The value 0 means that there is no auto-expand;
1 means that the invisible root element is expanded (since most
concrete subclasses do not show the root element, there is usuallyno
practical difference between using the values 0 and 1);
2 means that top-level elements are expanded, but not their children;
3 means that top-level elements are expanded, and their children, but
not grandchildren;
So you should set auto expand level to 3, not 2.
Since the code you posted is not complete, I would like to also mention that it is important also when you call setAutoExpandLevel(). Internally it is called when input is changed. So it should be called before setRoot().
Below is a sample code that builds a tree like yours and expands the nodes:
public class CheckTreeSnippet {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new FillLayout());
CheckboxTreeViewer tv = new CheckboxTreeViewer(shell, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
GridData gridData = new GridData(GridData.FILL_BOTH);
tv.getTree().setLayoutData(gridData);
tv.setAutoExpandLevel(3);
tv.setContentProvider(new FeaturePropertyDialogContentProvider());
tv.setInput("root");
shell.setSize(200, 200);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
private static class FeaturePropertyDialogContentProvider implements ITreeContentProvider {
#Override
public Object[] getElements(Object inputElement) {
return this.getChildren(inputElement);
}
#Override
public Object[] getChildren(Object parentElement) {
switch ((String) parentElement) {
case "root":
return new String[]{"Platform XYZ12", "Platform ZUPP"};
case "Platform XYZ12":
return new String[]{"Microcontroller TPU23"};
case "Platform ZUPP":
return new Object[]{"Sensor", "Precaler IO"};
case "Sensor":
return new Object[]{"unknown child 1", "unknown child 3"};
default:
return new String[0];
}
}
#Override
public Object getParent(Object element) {
return null;
}
#Override
public boolean hasChildren(Object element) {
return this.getChildren(element).length > 0;
}
#Override
public void dispose() {
}
#Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
}
}
Related
Can someone show an example of a TreeCellEditor that can update multiple nodes on a single edit? From what I can tell, GetCellEditorValue() will only update a single node.
My editor currently takes in the selected Nodes, compares them and displays differing values as while displaying the other values that the nodes have in common.
My Constructor which initializes "myDevice".
public DeviceEditor(Collection<DefaultMutableTreeNode> nodes) throws NoSuchFieldException {
System.out.println("CREATING NEW EDITOR \n");
this.nodes = nodes;
ObjectMatcher matcher = new ObjectMatcher();
try {
myDevice = matcher.match(nodes, DefaultDevice.CREATE_MULTIVALUE_DEFAULTDEVICE(), new DefaultDevice());
//System.out.println("Device= " + myDevice.getAddress() + " " + myDevice.getHostName());
} catch (Exception e) {
System.out.println(e);
}
initComponents();
}
The methods of TreeCellEditor
#Override
public Component getTreeCellEditorComponent(JTree jtree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
// this.tree = jtree;
return this;
}
#Override
public Object getCellEditorValue() {
//This value is what is populated into the JTree
return this.myDevice;
}
#Override
public boolean isCellEditable(EventObject eo) {
//should be true only if it's a leaf.
return true;
}
#Override
public boolean shouldSelectCell(EventObject eo) {
//Don't Select
return false;
}
#Override
public boolean stopCellEditing() {
try {
System.out.println("\n Cell Editing Stopped");
//update myDevice
if (!this.addressField.getText().equals(DefaultDevice.MULTIVALUE)) {
myDevice.setAddress(this.addressField.getText());
}
myDevice.setDeviceType(this.deviceTypeField.getText());
myDevice.setLocation(this.locationField.getText());
myDevice.setSerialNumber(this.serialField.getText());
myDevice.setUser(this.userField.getText());
myDevice.setPassword(new String(this.passwordField.getPassword()));
myDevice.setVendor(this.vendorField.getText());
myDevice.setModel(this.modelField.getText());
myDevice.setOS(this.osField.getText());
myDevice.setDescription(this.descriptionField.getText());
myDevice.setVersion(this.versionField.getText());
myDevice.setDeviceType(this.deviceTypeField.getText());
myDevice.setDisplayHostName(this.hostNameCheckBox.isSelected());
myDevice.setDisplayIPV4Address(this.ipV4checkBox.isSelected());
myDevice.setDisplayIPV6Address(this.ipV6CheckBox.isSelected());
DeviceEditor.UPDATE_DEVICES(nodes, myDevice);
return true;
} catch (IPConverter.InvalidIPException ex) {
Exceptions.printStackTrace(ex);
return false;
}
}
I have created javaFX tree with custom Objects (SystemNode).
Tree Items has graphics: check-box and image icon which I have set through updateItems() method.
Whenever I expand or collapse Item in tree ,twice or thrice I get JAVA HEAP MEMORY OUT OF SPACE and whole UI hangs UP.
PS: updateItems() method is invoked every time I expand or collapse tree node
I have tried adding event handlers but they didn't work.
Can anyone give some solutions.
Here is how I set cellFactory :
treeView_technicalAreas.setCellFactory(Util.getTreeCellFactory());
Here is code for cell factory:
public static Callback<TreeView<SystemNode>, TreeCell<SystemNode>> getTreeCellFactory() {
Callback<TreeView<SystemNode>, TreeCell<SystemNode>> callback = new Callback<TreeView<SystemNode>, TreeCell<SystemNode>>() {
#Override
public TreeCell<SystemNode> call(TreeView<SystemNode> p) {
TreeCell<SystemNode> cell = new TreeCell<SystemNode>() {
#Override
protected void updateItem(SystemNode t, boolean isEmpty) {
super.updateItem(t, isEmpty); //To change body of generated methods, choose Tools | Templates.
if (!isEmpty) {
System.out.println("util call back : " + t.getSystem().getName());
setText(t.getSystem().getName());
HBox hBox = new HBox();
CheckBox checkBox = new CheckBox();
checkBox.setSelected(t.getSelected());
checkBox.selectedProperty().bindBidirectional(t.getSelectedProperty());
hBox.setSpacing(SPACING_BETWEEN_ICON_AND_CHECKBOX);
ImageView imageView_icon = null;
if (t.getSystem().getType() == TYPE.BAREA) {
imageView_icon = new ImageView(Constant.Image_AREAS);
} else if (t.getSystem().getType() == TYPE.AREA) {
imageView_icon = new ImageView(Constant.Image_AREAS);
} else if (t.getSystem().getType() == TYPE.DOCUMENT) {
imageView_icon = new ImageView(Constant.Image_DOCUMENTS);
} else if (t.getSystem().getType() == TYPE.NOUN_NAME) {
imageView_icon = new ImageView(Constant.Image_NOUN_NAME);
} else if (t.getSystem().getType() == TYPE.CHANGE) {
imageView_icon = new ImageView(Constant.Image_DCC);
} else if (t.getSystem().getType() == TYPE.TASK) {
imageView_icon = new ImageView(Constant.Image_TASK);
}
hBox.getChildren().addAll(checkBox, imageView_icon);
setGraphic(hBox);
}
}
};
return cell;
}
};
return callback;
}
I created TreeViewer and I put setAutoExapandLevel for the tree
treeViewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
treeViewer.setContentProvider(new TreeContentProvider());
treeViewer.setLabelProvider(new TreeLabelProvider());
treeViewer.setAutoExpandLevel(3);
treeViewer.setInput(new Model());
the problem that it is not auto expand for the tree
Do you have any idea why it is not working ?
Are you sure the model contains all the data when setInput() is called?
internalExpandToLevel(Widget widget, int level) (where expanding takes place) is called on inputChanged(Object input, Object oldInput). If at the time setInput is called the model is empty, no node will be expanded. Even if you later add nodes and call refresh.
To prove this, I changed my snippet from an answer for another question.
Run this code as it is, then run it with the empty field initialized to true. You will see the difference.
static boolean empty = false;
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new FillLayout());
TreeViewer treeViewer = new TreeViewer(shell, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
treeViewer.setContentProvider(new DummyContentProvider());
treeViewer.setAutoExpandLevel(3);
treeViewer.setInput("root");
empty = true;
treeViewer.refresh();
shell.setSize(200, 200);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
private static class DummyContentProvider implements ITreeContentProvider {
#Override
public Object[] getElements(Object inputElement) {
return this.getChildren(inputElement);
}
#Override
public Object[] getChildren(Object parentElement) {
if (!empty) {
return new Object[0];
}
switch ((String) parentElement) {
case "root":
return new String[]{"a", "b"};
case "a":
return new String[]{"1"};
case "b":
return new Object[]{"1", "2"};
case "1":
return new Object[]{"x", "y"};
default:
return new String[0];
}
}
#Override
public Object getParent(Object element) {
return null;
}
#Override
public boolean hasChildren(Object element) {
return this.getChildren(element).length > 0;
}
#Override
public void dispose() {
}
#Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
}
I'm using a custom Dialog with a CheckboxTreeViewer inside my GMF Editor, which works fine so far, as you can see below:
After closing the Dialog, the selected element are saved so far. Now my Problem:
When I open the dialog again, all elements are unchecked. So I thought it would be easy to tell the treeViewer that specific elements should be initially checked.
But it turned out that it's not that easy, as the Tree initially consist of the root element. The other elements are not added until the tree expands. Elements are added by calling the getChildren(Object parentElement) of the ContentProvider.
So it seems that I can't check specific elements initially, but rather have to provide a dynamically approach. I'm looking for something like an element added listener, but there seems none to exist.
Here is the part, where I'm creating the CheckboxTreeViewer
Composite container = (Composite) super.createDialogArea(parent);
tv = new CheckboxTreeViewer(container, SWT.MULTI | SWT.H_SCROLL
| SWT.V_SCROLL);
tv.getTree().setLayoutData(new GridData(GridData.FILL_BOTH));
tv.setAutoExpandLevel(2);
tv.setContentProvider(new FeaturePropertyDialogContentProvider(this));
tv.setLabelProvider(new FeaturePropertyDialogLabelProvider());
tv.setInput(productLine);
tv.setExpandPreCheckFilters(true);
return container;
And here is the getChildren method of my ContentProvider:
#Override
public Object[] getChildren(Object parentElement) {
if (parentElement instanceof PL) {
PL p = (PL) parentElement;
return new Object[] { p.getPropertyList() };
}
else if (parentElement instanceof PropertyList) {
PropertyList propertyList = (PropertyList) parentElement;
return propertyList.getGeneralPlatforms().toArray();
} else if (parentElement instanceof GeneralPlatform) {
GeneralPlatform platform = (GeneralPlatform) parentElement;
return platform.getHardwareElements().toArray();
}
} else {
return null;
}
}
Any ideas on this?
--------------------Solution---------------------
Found the following solution by myself, which works fine for me so far:
tv.expandAll();
tv.setCheckedElements(preSelectedProperties.toArray());
tv.collapseAll();
tv.expandToLevel(2);
You should add a listener on the tree viewer to get a notification when a node gets expanded (method addTreeListener() on TreeViewer). At that time, the nodes are already in the tree, and you can check the check-box.
Here is a snippet of a tree that sets the check state when the tree is expanded:
public class Snippet {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new FillLayout());
final Map<String, Boolean> userChecks = new HashMap<>();
final CheckboxTreeViewer tv = new CheckboxTreeViewer(shell, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
GridData gridData = new GridData(GridData.FILL_BOTH);
tv.getTree().setLayoutData(gridData);
final FeaturePropertyDialogContentProvider provider = new FeaturePropertyDialogContentProvider(tv);
tv.setContentProvider(provider);
tv.setInput("root");
tv.addCheckStateListener(new ICheckStateListener() {
#Override
public void checkStateChanged(CheckStateChangedEvent event) {
userChecks.put((String) event.getElement(), event.getChecked());
}
});
tv.addTreeListener(new ITreeViewerListener() {
#Override
public void treeCollapsed(TreeExpansionEvent event) {
}
#Override
public void treeExpanded(TreeExpansionEvent event) {
final Object element = event.getElement();
final Object[] children = provider.getChildren(element);
for (Object child : children) {
if (userChecks.containsKey(child)) {
tv.setChecked(child, userChecks.get(child));
} else if (child.equals("b")) {
tv.setChecked(child, true);
}
}
}
});
shell.setSize(200, 200);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
private static class FeaturePropertyDialogContentProvider implements ITreeContentProvider {
final CheckboxTreeViewer tv;
private FeaturePropertyDialogContentProvider(CheckboxTreeViewer tv) {
this.tv = tv;
}
#Override
public Object[] getElements(Object inputElement) {
return this.getChildren(inputElement);
}
#Override
public Object[] getChildren(Object parentElement) {
switch ((String) parentElement) {
case "root":
return new String[]{"1"};
case "1":
return new String[]{"a", "b", "c"};
default:
return new String[0];
}
}
#Override
public Object getParent(Object element) {
return null;
}
#Override
public boolean hasChildren(Object element) {
return this.getChildren(element).length > 0;
}
#Override
public void dispose() {
}
#Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
}
}
In this example, I only compare the node value with the string "b".
Edit: in this context, elements are immutable (for some reason they cannot be changed), and the solution with wrapping objects in tree nodes is not accepted (see comments).
In that case, you can save user check actions in a map, and when setting checked/unchecked state, search for any previous user check actions. I think it is reasonable to assume the map is simple and will not grow too much. User has to check a lot of nodes to get it big :)
I updated the snippet to keep track of user checks.
I'm using JXTreeTable to display some data and I want to use the provided mechanisms of SwingX to change the renderer for some columns.
I previously used a JXTable and custom implementations of TableCellRenderer but this doesn't work anymore (I see strings where I should have progress bars, buttons,...).
I thus tried to achieve what I want by doing:
examsTable.getColumn(6).setCellRenderer(new DefaultTableRenderer(new ButtonProvider()));
But the overriden method createRenderer of ComponentProvider is called once (even when I have more than one line in my JXTreeTable) and no button is shown (the method only contains return new JButton();).
Thanks!
Edit> Hoped you would answer kleopatra and thus happy you did.
I did my best but somehow the table is not displayed. I guess I forgot something (I'm a C++ developer new to Java) but I guess it's not a serious problem and it's probably not related to my main problem.
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.jdesktop.swingx.JXTreeTable;
import org.jdesktop.swingx.decorator.HighlighterFactory;
import org.jdesktop.swingx.renderer.CellContext;
import org.jdesktop.swingx.renderer.ComponentProvider;
import org.jdesktop.swingx.renderer.DefaultTableRenderer;
import org.jdesktop.swingx.treetable.AbstractTreeTableModel;
public class Test extends JFrame {
JXTreeTable table = new JXTreeTable();
JPanel panel = new JPanel();
public Test() {
setMinimumSize(new Dimension(400, 400));
table.setEditable(false);
table.setDragEnabled(false);
table.setColumnSelectionAllowed(false);
table.setHighlighters(HighlighterFactory.createAlternateStriping());
table.setRowHeight(20);
table.setMinimumSize(new Dimension(200, 200));
class Series {
public String seriesInstanceUID;
public String patientName;
public String patientBirthDate;
public String securityToken;
public Series(String seriesInstanceUID, String patientName, String patientBirthDate, String securityToken) {
this.seriesInstanceUID = seriesInstanceUID;
this.patientName = patientName;
this.patientBirthDate = patientBirthDate;
this.securityToken = securityToken;
}
}
class Study {
public List<Series> series = new ArrayList<Series>();
}
class Root {
public List<Study> studies = new ArrayList<Study>();
}
AbstractTreeTableModel model = new AbstractTreeTableModel() {
Root root = new Root() {
{
studies.add(new Study() {
{
series.add(new Series("Series 1.1", "Mr. X", "1988-10-23", "sec-xx-1"));
series.add(new Series("Series 1.2", "Mr. X", "1988-10-23", "sec-xx-2"));
series.add(new Series("Series 1.3", "Mr. X", "1988-10-23", "sec-xx-3"));
}
});
studies.add(new Study() {
{
series.add(new Series("Series 2.1", "Mrs. Y", "1960-02-11", "sec-yy-1"));
}
});
studies.add(new Study() {
{
series.add(new Series("Series 3.1", "HAL 9000", "1975-04-21", "sec-zz-1"));
series.add(new Series("Series 3.2", "HAL 9000", "1975-04-21", "sec-zz-2"));
}
});
}
};
#Override
public String getColumnName(int columnIndex) {
switch (columnIndex) {
case 0:
return "Series Instance UID";
case 1:
return "Patient Name";
case 2:
return "Patient Birth Date";
case 3:
return "View online";
default:
return "";
}
}
#Override
public int getIndexOfChild(Object parent, Object child) {
if (parent == root) {
return root.studies.indexOf(child);
}
if (parent instanceof Study) {
Study study = (Study) parent;
return study.series.indexOf(child);
}
return -1;
}
#Override
public int getChildCount(Object parent) {
if (parent == root) {
return root.studies.size();
}
if (parent instanceof Study) {
Study study = (Study) parent;
return study.series.size();
}
return 0;
}
#Override
public Object getChild(Object parent, int index) {
if (parent == root) {
return root.studies.get(index);
}
if (parent instanceof Study) {
Study study = (Study) parent;
return study.series.get(index);
}
return null;
}
#Override
public Object getValueAt(Object node, int columnIndex) {
if (!(node instanceof Series) && !(node instanceof Study))
return null;
if (columnIndex < 0 || columnIndex >= getColumnCount())
return null;
if (root == null)
return null;
if (node instanceof Series) {
Series series = (Series) node;
if (columnIndex == 0)
return series.seriesInstanceUID;
else if (columnIndex == 1)
return series.patientName;
else if (columnIndex == 2)
return series.patientBirthDate;
else if (columnIndex == 3)
return series.securityToken;
} else if (node instanceof Study) {
// Empty for now
}
return null;
}
#Override
public int getColumnCount() {
return 4;
}
#Override
public Object getRoot() {
return root;
}
public void update() {
modelSupport.fireNewRoot();
}
};
table.setTreeTableModel(model);
table.getColumnModel().getColumn(3).setCellRenderer(new DefaultTableRenderer(new ComponentProvider<JButton>() {
{
rendererComponent.setHorizontalAlignment(JButton.CENTER);
}
#Override
protected void format(CellContext context) {
rendererComponent.setText(getValueAsString(context));
}
#Override
protected void configureState(CellContext context) {
rendererComponent.setHorizontalAlignment(getHorizontalAlignment());
}
#Override
protected JButton createRendererComponent() {
return new JButton("View online");
}
}));
panel.add(table);
this.setContentPane(panel);
}
public static void main(String[] args) {
(new Test()).setVisible(true);
}
}
EDIT> I actually have observed that it works. But not the way I want. I want to see a real button (now it looks just a little bit different from the rest of the line), know the path to it (parent object and column index) and see effects when clicking or hovering on it (button should look pressed,...).
How do I achieve that?
The button is used, it only appears not to be in some LAFs:-)
Technically, the reasons are
the highlighter sets the button's background
the default visuals (used by the provider) sets the border to the default as returned by the cellContext
In combination the button doesn't look like a button for Metal (while it is unchanged f.i. in Windows)
No satisfying solution, options are
not use striping
let the button have a dont-touch-my-background property, extend the ColorHighlighter to respect that and use that custom highlighter for striping (there's an example in the test package)