I'm experiencing a problem when D&D a custom object from Swing to JavaFX and I'm wondering if I'm doing something wrong or its probably a Java FX bug.
My Transferable has been defined as the following:
public class TransferableEmployee implements Transferable {
public static final DataFlavor EMPLOYEE_FLAVOR = new DataFlavor(Employee[].class, "Employee");
public static final DataFlavor DEFINITION_FLAVOR = new DataFlavor(PropertyDefinition[].class, "Definition");
private static final DataFlavor FFLAVORS [] = {EMPLOYEE_FLAVOR, DEFINITION_FLAVOR};
private Employee[] employees;
private PropertyDefinition[] propertyDefinitions;
public MintTransferableEmployee(Employee[] employees, PropertyDefinition[] propertyDefinitions) {
this.employees = employees != null ? employees.clone() : null;
this.propertyDefinitions = propertyDefinitions != null ? propertyDefinitions.clone() : null;
}
public DataFlavor[] getTransferDataFlavors() {
return FFLAVORS.clone();
}
public Object getTransferData(DataFlavor aFlavor) throws UnsupportedFlavorException {
Object returnObject = null;
if (aFlavor.equals(EMPLOYEE_FLAVOR)) {
returnObject = employees;
}
else if(aFlavor.equals(DEFINITION_FLAVOR)){
returnObject = propertyDefinitions;
}
else{
throw new UnsupportedFlavorException(aFlavor);
}
return returnObject;
}
public boolean isDataFlavorSupported(DataFlavor aFlavor) {
boolean lReturnValue = false;
for (int i=0, n=FFLAVORS.length; i<n; i++) {
if (aFlavor.equals(FFLAVORS[i])) {
lReturnValue = true;
break;
}
}
return lReturnValue;
}
}
I've created an imageView (FX Component) where I added the setOnDragOver just as the following:
employeePhotoImageView.setOnDragOver(new EventHandler<DragEvent>() {
#Override
public void handle(DragEvent event) {
System.out.println("dragOver");
event.getDragboard().getContentTypes();
event.getDragboard().getContent(DataFormat.lookupMimeType("application/x-java-serialized-object"));
}
});
The getContentTypes() returns a Map with [[application/x-java-serialized-object]], so now I try to get the Content, and this only returns the List of PropertyDefinition but no Employee at all (which in this case, is the one I need).
If I remove the data of the PropertyDefinition in the transferable, the employee is returned in the getContent(DataFormat) method.
For me, this means that JavaFX only works with 1 DataFlavor or somehow it is only returning the last flavor found in the Transferable.
Any clues on this?
Thanks in advanced...
Related
I had implement a interceptor of myabtis. but we found a problem, execute interceptor lead to throw so many IllegalAccessException, it affects cpu performence
Shown below is where the problem is, why did not check access permision of feild befor executed code "field.get(target)".
public class GetFieldInvoker implements Invoker {
private final Field field;
public GetFieldInvoker(Field field) {
this.field = field;
}
#Override
public Object invoke(Object target, Object[] args) throws IllegalAccessException {
try {
return field.get(target);
} catch (IllegalAccessException e) {
if (Reflector.canControlMemberAccessible()) {
field.setAccessible(true);
return field.get(target);
} else {
throw e;
}
}
}
#Override
public Class<?> getType() {
return field.getType();
}
}
the intercepor of mine:
#Intercepts({
#Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class})
})
public class SqlIdInterceptor implements Interceptor {
private static final int MAX_LEN = 256;
private final RoomboxLogger logger = RoomboxLogManager.getLogger();
#Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = realTarget(invocation.getTarget());
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
String originalSql = boundSql.getSql();
MappedStatement mappedStatement =
(MappedStatement) metaObject.getValue("delegate.mappedStatement");
String id = mappedStatement.getId();
if (id != null) {
int len = id.length();
if (len > MAX_LEN) {
logger.warn("too long id", "id", id, "len", len);
}
}
String newSQL = "# " + id + "\n" + originalSql;
metaObject.setValue("delegate.boundSql.sql", newSQL);
return invocation.proceed();
}
#SuppressWarnings("unchecked")
public static <T> T realTarget(Object target) {
if (Proxy.isProxyClass(target.getClass())) {
MetaObject metaObject = SystemMetaObject.forObject(target);
return realTarget(metaObject.getValue("h.target"));
}
return (T) target;
}
}
Flame Graph
enter image description here
enter image description here
I need help, how to avoid throw exceptions, is any other way to reslove this problem?
thanks.
I've implemented a custom autocomplete text field in a cn1 app, but I've noticed it only loads the suggestions list once, after that any change in the text doesn't trigger a change in the list, and the getSuggestionModel() is never called again. How can I achieve this (in my mind, basic) functionality?
This is my autocomplete class:
public class ForumNamesAutocomplete extends AutoCompleteTextField {
List<String>suggestions = new LinkedList<String>();
List<Map<String,Object>> fData;
StateMachine mac;
int currentIndex;
String prevText;
public static final String KEY_FORUM_NAME = "name";
public static final String KEY_FORUM_ID = "id";
public static final String KEY_FORUM_DESC = "desc";
public ForumNamesAutocomplete(StateMachine sm){
super();
mac = sm;
if(sm.forumData != null){
fData = mac.forumData;
}
}
#Override
protected boolean filter(String text) {
if(text.equals(prevText)){
return false;
}
setSuggestionList(text);
fireDataChanged(DataChangedListener.CHANGED, text.length());
prevText = text;
return true;
}
#Override
public void fireDataChanged(int type, int index) {
super.fireDataChanged(type, index);
}
public void setSuggestionList(String s){
if(suggestions == null){
suggestions = new LinkedList<String>();
}else{
suggestions.clear();
}
LinkedList<String> descList = new LinkedList<String>();
for(int i = 0;i<fData.size();i++){
boolean used = false;
Map<String,Object> forumMap = fData.get(i);
if(((String)forumMap.get(KEY_FORUM_NAME)).indexOf(s) != -1){
suggestions.add((String)forumMap.get(KEY_FORUM_NAME));
used = true;
}
if(!used && ((String)forumMap.get(KEY_FORUM_DESC)).indexOf(s) != -1){
descList.add((String)forumMap.get(KEY_FORUM_NAME));
}
}
suggestions.addAll(descList);
}
#Override
protected ListModel<String> getSuggestionModel() {
return new DefaultListModel<String>(suggestions);
}
}
This used to be simpler and seems to be a bit problematic now as explained in this issues.
Technically what you need to do is return one model and then mutate said model/fire modified events so everything will refresh. This is non-trivial and might not work correctly for all use cases so ideally we should have a simpler API to do this as we move forward.
After additional debugging, I saw that the getSuggestionModel() method was being called only during initialization, and whatever the suggestion list (in suggestion object) was at that point, it remained so. Instead I needed to manipulate the underlying ListModel object:
public class ForumNamesAutocomplete extends AutoCompleteTextField {
ListModel<String>myModel = new ListModel<String>();
...
#Override
protected boolean filter(String text) {
if(text.length() > 1){
return false;
}
setSuggestionList(text);
return true;
}
private void setSuggestionList(String s){
if(myModel == null){
myModel = new ListModel<String>();
}else{
while(myModel.getSize() > 0)
myModel.removeItem(0);
}
for(int i = 0;i<fData.size();i++){
boolean used = false;
Map<String,Object> forumMap = fData.get(i);
if(((String)forumMap.get(KEY_FORUM_NAME)).indexOf(s) != -1){
myModel.addItem((String)forumMap.get(KEY_FORUM_NAME));
used = true;
}
if(!used && ((String)forumMap.get(KEY_FORUM_DESC)).indexOf(s) != -1){
myModel.addItem((String)forumMap.get(KEY_FORUM_NAME));
}
}
}
...
}
I have one [JavaFX] ComboBox that is populated with countries.
My object:
public static class CountryObj {
private String TCountryDescr;
private String TCountryCode;
private CountryObj(String CountryDescr,String CountryCode) {
this.TCountryDescr = CountryDescr;
this.TCountryCode = CountryCode;
}
public String getTCountryCode() {
return TCountryCode;
}
public void setTCountryCode(String fComp) {
TCountryCode= fComp;
}
public String getTCountryDescr() {
return TCountryDescr;
}
public void setCountryDescr(String fdescr) {
TCountryDescr = fdescr;
}
#Override
public String toString() {
return TCountryDescr;
}
}
Then I have my ObservableList:
private final ObservableList<CountryObj> CountrycomboList =
FXCollections.observableArrayList(
new CountryObj("United States", "US"),
new CountryObj("United Kingdom", "UK"),
new CountryObj("France", "FR"),
new CountryObj("Germany", "DE"));
Then my ComboBox which displays the name of the country and the code of the country is for my own use:
private ComboBox<CountryObj> cCountry1 = new ComboBox<>();
cbCountry1.setItems(CountrycomboList);
cbCountry1.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<CountryObj>() {
#Override
public void changed(ObservableValue<? extends CountryObj> arg0, CountryObj arg1, CountryObj arg2) {
if (arg2 != null) {
System.out.println("Selected Country: " + arg2.getTCountryCode());
}
}
});
How can I auto-select a country, for example Germany, if I only have the code of the country, which is "DE"?
comboBox.getSelectionModel().select(indexOfItem);
or
comboBox.setValue("item1");
Couple of months old question but here is more elegant solution for such type of problems.
Modify the CountryObj class and override the hashCode and equals functions as below:
public class CountryObj {
private String TCountryDescr;
private String TCountryCode;
public CountryObj(String CountryDescr,String CountryCode) {
this.TCountryDescr = CountryDescr;
this.TCountryCode = CountryCode;
}
public String getTCountryCode() {
return TCountryCode;
}
public void setTCountryCode(String fComp) {
TCountryCode= fComp;
}
public String getTCountryDescr() {
return TCountryDescr;
}
public void setCountryDescr(String fdescr) {
TCountryDescr = fdescr;
}
#Override
public String toString() {
return TCountryDescr;
}
#Override
public int hashCode() {
int hash = 0;
hash += (TCountryCode != null ? TCountryCode.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
String otherTCountryCode = "";
if (object instanceof Country) {
otherTCountryCode = ((Country)object).TCountryCode;
} else if(object instanceof String){
otherTCountryCode = (String)object;
} else {
return false;
}
if ((this.TCountryCode == null && otherTCountryCode != null) || (this.TCountryCode != null && !this.TCountryCode.equals(otherTCountryCode))) {
return false;
}
return true;
}
}
Now in you code if you will execute the following statement, it will automatically select "Germany" for you.
cmbCountry.getSelectionModel().select("DE")
You can also pass an object of CountryObj to select method above.
I think the simplest solution is to write an autoSelect function that finds the matching CountryObj in your ObservableList. Once you find the correct CountryObj, tell the combobox to set that as its value. It should looks something like this...
private void autoSelectCountry(String countryCode)
{
for (CountryObj countryObj : countryComboList)
{
if (countryObj.getTCountryCode().equals(countryCode))
{
cbCountry1.setValue(countryObj);
}
}
}
EDIT:
This can be further refactored to reusable method for all ComboBox'es that take different type parameter:
public static <T> void autoSelectComboBoxValue(ComboBox<T> comboBox, String value, Func<T, String> f) {
for (T t : comboBox.getItems()) {
if (f.compare(t, value)) {
comboBox.setValue(t);
}
}
}
where Func is an interface:
public interface Func<T, V> {
boolean compare(T t, V v);
}
How to apply this method:
autoSelectComboBoxValue(comboBox, "Germany", (cmbProp, val) -> cmbProp.getNameOfCountry().equals(val));
If both comboBox are from the same Array, assembly column one and two, then they have the same sequence. Then you can use the index.
a = comboBox1.getSelectionModel().getSelectedIndex();
comboBox2.getSelectionModel().select(a);
"United States" is on index position 1 "US" also on index position 1 then:
comboBox2.getSelectionModel().select(1); // is "US"
The solution of Brendan and Branislav Lazic is perfect, but we still can improve the call to the autoSelectComboBoxValue method :
public static <T, V> void autoSelectComboBoxValue(ComboBox<T> comboBox, V value, Func<T, V> f) {...}
:)
I am trying to understand this example from a MSDN article, to my understanding with the IEnumberable interface, we will be able to use Foreach to loop through the class collection, I am confused at the Main method, why don't we just use:
foreach (Person p in peopleArray)
Console.WriteLine(p.firstName + " " + p.lastName);
instead of
People peopleList = new People(peopleArray);
foreach (Person p in peopleList)
Console.WriteLine(p.firstName + " " + p.lastName);
Example:
using System;
using System.Collections;
public class Person
{
public Person(string fName, string lName)
{
this.firstName = fName;
this.lastName = lName;
}
public string firstName;
public string lastName;
}
public class People : IEnumerable
{
private Person[] _people;
public People(Person[] pArray)
{
_people = new Person[pArray.Length];
for (int i = 0; i < pArray.Length; i++)
{
_people[i] = pArray[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator) GetEnumerator();
}
public PeopleEnum GetEnumerator()
{
return new PeopleEnum(_people);
}
}
public class PeopleEnum : IEnumerator
{
public Person[] _people;
// Enumerators are positioned before the first element
// until the first MoveNext() call.
int position = -1;
public PeopleEnum(Person[] list)
{
_people = list;
}
public bool MoveNext()
{
position++;
return (position < _people.Length);
}
public void Reset()
{
position = -1;
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public Person Current
{
get
{
try
{
return _people[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
}
class App
{
static void Main()
{
Person[] peopleArray = new Person[3]
{
new Person("John", "Smith"),
new Person("Jim", "Johnson"),
new Person("Sue", "Rabon"),
};
People peopleList = new People(peopleArray);
foreach (Person p in peopleList)
Console.WriteLine(p.firstName + " " + p.lastName);
}
}
You're right, you could simply use the first version because arrays implement IEnumerable.
The reason they chose to iterate over People is simply for academic purposes; to demonstrate how iterators work (and how to implement IEnumerable). If they simply iterated over peoplearray, they wouldn't be using the People class, which is the main focus of that example.
Here I have an complete and very easy example to dynamically add/remove
nodes to an celltree. My example is not working very well. It seems there
is an refresh problem. Only closing/expanding the nodes will show the correct
result. I also did not found any answer in this forum which fits to this problem.
Maybe somebody can try my example and tell me where the problem is.
Any other hint is also very appreciated.
Greetings, Marco
import java.util.ArrayList;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.cellview.client.CellTree;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.SingleSelectionModel;
import com.google.gwt.view.client.TreeViewModel;
public class MyCelltreeTest implements EntryPoint {
private AbsolutePanel absolutePanel;
private CellTree cellTree;
private Button btnAdd;
private Button btnRemove;
private MyTreeModel myTreeModel;
private SingleSelectionModel<MyNode> selectionModelCellTree = null;
#Override
public void onModuleLoad() {
RootPanel rootPanel = RootPanel.get();
rootPanel.add(getAbsolutePanel(), 0, 0);
}
private AbsolutePanel getAbsolutePanel() {
if (absolutePanel == null) {
absolutePanel = new AbsolutePanel();
absolutePanel.setSize("612px", "482px");
absolutePanel.add(getCellTree(), 0, 0);
absolutePanel.add(getBtnAdd(), 265, 428);
absolutePanel.add(getBtnRemove(), 336, 428);
}
return absolutePanel;
}
private CellTree getCellTree() {
if (cellTree == null) {
myTreeModel = new MyTreeModel();
cellTree = new CellTree(myTreeModel, null);
cellTree.setSize("285px", "401px");
}
return cellTree;
}
private Button getBtnAdd() {
if (btnAdd == null) {
btnAdd = new Button("Add");
btnAdd.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
MyNode node = selectionModelCellTree.getSelectedObject();
if(node != null)
myTreeModel.addNew(node, "Bla");
}
});
}
return btnAdd;
}
private Button getBtnRemove() {
if (btnRemove == null) {
btnRemove = new Button("Remove");
btnRemove.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
MyNode node = selectionModelCellTree.getSelectedObject();
if(node != null)
myTreeModel.remove(node);
}
});
}
return btnRemove;
}
public class MyNode {
private String name;
private ArrayList<MyNode> childs; //nodes childrens
private MyNode parent; //track internal parent
private MyCell cell; //for refresh - reference to visual component
public MyNode(String name) {
super();
parent = null;
this.name = name;
childs = new ArrayList<MyNode>();
}
public void addSubMenu(MyNode m) {
m.parent = this;
childs.add(m);
}
public void removeMenu(MyNode m) {
m.getParent().childs.remove(m);
}
public boolean hasChildrens() {
return childs.size()>0;
}
public ArrayList<MyNode> getList() {
return childs;
}
public MyNode getParent() {
return parent;
}
public void setCell(MyCell cell) {
this.cell = cell;
}
public void refresh() {
if(parent!=null) {
parent.refresh();
}
if (cell!=null) {
cell.refresh(); //refresh tree
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class MyTreeModel implements TreeViewModel {
private MyNode officialRoot; //default not dynamic
private MyNode studentRoot; //default not dynamic
private MyNode testRoot; //default not dynamic
private MyNode root;
public MyNode getRoot() { // to set CellTree root
return root;
}
public MyTreeModel() {
selectionModelCellTree = new SingleSelectionModel<MyNode>();
root = new MyNode("root");
// Default items
officialRoot = new MyNode("Cat"); //some basic static data
studentRoot = new MyNode("Dog");
testRoot = new MyNode("Fish");
root.addSubMenu(officialRoot);
root.addSubMenu(studentRoot);
root.addSubMenu(testRoot);
}
//example of add add logic
public void addNew(MyNode myparent, String name) {
myparent.addSubMenu(new MyNode(name));
myparent.refresh(); //HERE refresh tree
}
public void remove(MyNode objToRemove) {
objToRemove.removeMenu(objToRemove);
objToRemove.refresh();
}
#Override
public <T> NodeInfo<?> getNodeInfo(T value) {
ListDataProvider<MyNode> dataProvider;
MyNode myValue = null;
if (value == null) { // root is not set
dataProvider = new ListDataProvider<MyNode>(root.getList());
} else {
myValue = (MyNode) value;
dataProvider = new ListDataProvider<MyNode>(myValue.getList());
}
MyCell cell = new MyCell(dataProvider); //HERE Add reference
if (myValue != null)
myValue.setCell(cell);
return new DefaultNodeInfo<MyNode>(dataProvider, cell, selectionModelCellTree, null);
}
#Override
public boolean isLeaf(Object value) {
if (value instanceof MyNode) {
MyNode t = (MyNode) value;
if (!t.hasChildrens())
return true;
return false;
}
return false;
}
}
public class MyCell extends AbstractCell<MyNode> {
ListDataProvider<MyNode> dataProvider; //for refresh
public MyCell(ListDataProvider<MyNode> dataProvider) {
super();
this.dataProvider = dataProvider;
}
public void refresh() {
dataProvider.refresh();
}
#Override
public void render(Context context, MyNode value, SafeHtmlBuilder sb) {
if (value == null) {
return;
}
sb.appendEscaped(value.getName());
}
}
}
Thanks Ümit for your explanation.
I tried the close-reopen version.
I have replaced my refresh method with the methods below.
Adding is working but removing not.
Very strange the whole topic. I'm very suprised that these basic
functions are not really supported by GWT.
Can somebody give me more help to run a real working example.
public void refresh() {
closeReopenTreeNodes(cellTree.getRootTreeNode());
}
private void closeReopenTreeNodes(TreeNode node) {
if(node == null) {
return;
}
for(int i = 0; i < node.getChildCount(); i++) {
if(node.getChildValue(i).equals(this)){
if(node.getParent() != null){
node.getParent().setChildOpen(i, false);
//node.getParent().setChildOpen(i, true);
}
node.setChildOpen(i, false);
node.setChildOpen(i, true);
}
TreeNode child = node.setChildOpen(i, node.isChildOpen(i));
closeReopenTreeNodes(child);
}
}
Here my third try:
This way is recommended by gwt-commiter.
Please see following issue:
http://code.google.com/p/google-web-toolkit/issues/detail?id=7160
Current status:
* Adding is possible
* Removing is possible if not last child!
So, last open point, refresh the tree if last open child!
package com.test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.cellview.client.CellTree;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.SingleSelectionModel;
import com.google.gwt.view.client.TreeViewModel;
public class MyCelltreeTest2 implements EntryPoint {
private AbsolutePanel absolutePanel;
private CellTree cellTree;
private Button btnAdd;
private Button btnRemove;
private MyTreeModel myTreeModel;
private SingleSelectionModel<MyNode> selectionModelCellTree = null;
private Map<MyNode, ListDataProvider<MyNode>> mapDataProviders = null;
private ListDataProvider<MyNode> rootDataProvider = null;
public void onModuleLoad() {
RootPanel rootPanel = RootPanel.get();
rootPanel.add(getAbsolutePanel(), 0, 0);
}
private AbsolutePanel getAbsolutePanel() {
if (absolutePanel == null) {
absolutePanel = new AbsolutePanel();
absolutePanel.setSize("612px", "482px");
absolutePanel.add(getCellTree(), 0, 0);
absolutePanel.add(getBtnAdd(), 265, 428);
absolutePanel.add(getBtnRemove(), 336, 428);
}
return absolutePanel;
}
private CellTree getCellTree() {
if (cellTree == null) {
myTreeModel = new MyTreeModel();
cellTree = new CellTree(myTreeModel, null);
cellTree.setSize("285px", "401px");
}
return cellTree;
}
private Button getBtnAdd() {
if (btnAdd == null) {
btnAdd = new Button("Add");
btnAdd.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
MyNode node = selectionModelCellTree.getSelectedObject();
myTreeModel.add(node, "Bla");
}
});
}
return btnAdd;
}
private Button getBtnRemove() {
if (btnRemove == null) {
btnRemove = new Button("Remove");
btnRemove.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
MyNode node = selectionModelCellTree.getSelectedObject();
myTreeModel.remove(node);
}
});
}
return btnRemove;
}
public class MyNode {
private String name;
private ArrayList<MyNode> childs; //nodes childrens
private MyNode parent; //track internal parent
public MyNode(String name) {
super();
parent = null;
this.name = name;
childs = new ArrayList<MyNode>();
}
public boolean hasChildrens() {
return childs.size()>0;
}
public ArrayList<MyNode> getList() {
return childs;
}
public MyNode getParent() {
return parent;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class MyTreeModel implements TreeViewModel {
public MyTreeModel() {
selectionModelCellTree = new SingleSelectionModel<MyNode>();
mapDataProviders = new HashMap<MyCelltreeTest2.MyNode, ListDataProvider<MyNode>>();
}
public void add(MyNode myparent, String name) {
MyNode child = new MyNode(name);
//root-node
if(myparent == null){
rootDataProvider.getList().add(child);
mapDataProviders.put(child, rootDataProvider);
}
else{
ListDataProvider<MyNode> dataprovider = mapDataProviders.get(myparent);
myparent.childs.add(child);
child.parent = myparent;
dataprovider.refresh();
}
}
public void remove(MyNode objToRemove) {
ListDataProvider<MyNode> dataprovider = mapDataProviders.get(objToRemove);
dataprovider.getList().remove(objToRemove);
// mapDataProviders.remove(objToRemove);
dataprovider.refresh();
dataprovider.flush();
if(objToRemove.parent != null){
ListDataProvider<MyNode> dataproviderParent = mapDataProviders.get(objToRemove.parent);
objToRemove.parent.childs.remove(objToRemove);
dataproviderParent.refresh();
dataproviderParent.flush();
}
else{
rootDataProvider.refresh();
rootDataProvider.flush();
}
}
#Override
public <T> NodeInfo<?> getNodeInfo(T value) {
if (value == null) { // root is not set
rootDataProvider = new ListDataProvider<MyNode>(new ArrayList<MyNode>());
MyCell cell = new MyCell();
return new DefaultNodeInfo<MyNode>(rootDataProvider, cell,
selectionModelCellTree, null);
} else {
MyNode myValue = (MyNode) value;
ListDataProvider<MyNode> dataProvider =
new ListDataProvider<MyNode>(myValue.childs);
MyCell cell = new MyCell();
for(MyNode currentNode : myValue.childs){
mapDataProviders.put(currentNode, dataProvider);
}
return new DefaultNodeInfo<MyNode>(dataProvider, cell,
selectionModelCellTree, null);
}
}
#Override
public boolean isLeaf(Object value) {
if (value instanceof MyNode) {
MyNode t = (MyNode) value;
if (!t.hasChildrens())
return true;
return false;
}
return false;
}
}
public class MyCell extends AbstractCell<MyNode> {
public MyCell() {
super();
}
#Override
public void render(Context context, MyNode value, SafeHtmlBuilder sb) {
if (value == null) {
return;
}
sb.appendEscaped(value.getName());
}
}
}
This is somehow a known problem with CellTree.
The reason for the refresh problem is that in the getNodeInfo() function you create a new ListDataProvider instance for each CellTree level.
The CellTree only updates/refreshes itself if you update the items in that ListDataProvider.
I believe that your removeMenu() and addSubMenu() functions add and remove items from the original list stored in your MyNode class but won't update the list in the corresponding ListDataProviders (you can try to check that in debug mode).
The reason why the CellTree is refreshed when you close and re
-open the nodes is because when you re-open the nodes the getNodeInfo() is called again and the whole CellTree structure will be constructed again (including the new menu or without the removed one respectively).
There are two possible solutions:
Keep a reference for each of the ListDataProviders somewhere and call remove/add on that list (although you do that I assume that the items are not really added/removed there).
Programatically close all nodes and re-open it.
Both are somehow a PITA to implement. Unfortunately there is no easy way around it.
I just clear the array of objects maintained in my data provider. I do this in onRangeChanged(final HasData<?> display). I guess I don't use a ListDataProvider here, I use something extending AbstractDataProvider<T> instead.
To add the node add it to your list within the onRangeChanged() method and then call updateRowData(). You can do this for a delete too.
I think I may have licked the problem...
Essentially I've extended and subclasses many parts of CellTree and have obtained an almost perfect working example. Too complex to document here, but suffice to say the solution involved using a node class where I stored the data provider within each node.
https://code.google.com/p/updatable-cell-tree/