GWT DataGrid checkboxes - java

Using GWT 2.6.1, UiBinder, DataGrid.
Also using SingleSelectionModel to select a single row:
final SingleSelectionModel<User> selectionModel = new SingleSelectionModel<>(keyProvider);
Checkboxes column:
// checkboxes
Column<User, Boolean> checkBoxColumn = new Column<User, Boolean>(
new CheckboxCell(false, false)) {
#Override
public Boolean getValue(User user) {
return user.isChecked();
}
};
checkBoxColumn.setFieldUpdater(new FieldUpdater<User, Boolean>() {
#Override
public void update(int index, User user, Boolean value) {
user.setChecked(value);
}
});
So i store "checked" user state as a boolean field in the User entity class, without
using a SelectionModel at all.
Now I need to implement custom header checkbox to select/deselect all checkboxes in the column.
public class CheckboxHeader extends Header<Boolean> {
public CheckboxHeader(CheckboxCell cell) {
super(cell);
}
#Override
public Boolean getValue() {
return null;
}
}
Have no ideas how to implement properly this header class to add column in the DataGrid:
dataGrid.addColumn(checkBoxColumn, new CheckboxHeader(new CheckboxCell(false, false)));
Another trouble is to enable/disable all those checkboxes by checking other checkbox that
isn't in the DataGrid.
How can i retrieve all checkboxes from the column/selectionmodel/etc and enable/disable them one by one?
Appreciate any suggestions.

Mixing the data model (User entity) and the state of user interface (isSelected) is never a good idea.
This is how you can do it (replace T with your object, or create a column object that you can re-use):
Column<T, Boolean> checkColumn = new Column<T, Boolean>(new CheckboxCell()) {
#Override
public Boolean getValue(T object) {
return getSelectionModel().isSelected(object);
}
};
checkColumn.setFieldUpdater(new FieldUpdater<T, Boolean>() {
#Override
public void update(int index, T object, Boolean value) {
getSelectionModel().setSelected(object, value);
dataProvider.refresh();
}
});
myDataGrid.setSelectionModel(getSelectionModel(), DefaultSelectionEventManager.<T> createCheckboxManager(0));
Header<Boolean> selectAllHeader = new Header<Boolean>(new HeaderCheckbox()) {
#Override
public Boolean getValue() {
for (T item : getVisibleItems()) {
if (!getSelectionModel().isSelected(item)) {
return false;
}
}
return getVisibleItems().size() > 0;
}
};
selectAllHeader.setUpdater(new ValueUpdater<Boolean>() {
#Override
public void update(Boolean value) {
for (T object : getVisibleItems()) {
getSelectionModel().setSelected(object, value);
}
}
});
myDataGrid.addColumn(checkColumn, selectAllHeader);

Related

GWT - How to programmatically uncheck a CheckboxCell when another CheckboxCell is checked?

Given two CheckboxCell's checkboxA and checkboxB, how can I programmatically uncheck checkboxB if checkboxA is unchecked? From what I've tried, I'm able to set the value of checkboxB = false when checkboxA = false, but this change is not being reflected in the UI.
My table with the checkboxes -
public void buildTable() {
myDataProvider.addDataDisplay(myTable);
// other columns
Column<Note, Boolean> checkboxA = new Column<Note, Boolean>(new CheckboxCell()){
#Override
public Boolean getValue(Note object) {
return object.isAChecked();
}
};
checkboxA.setFieldUpdater(new FieldUpdater<Note, Boolean>() {
#Override
public void update(int index, Note object, Boolean value) {
performCheckboxAOperation(object, value);
}
});
Column<Note, Boolean> checkboxB = new Column<Note, Boolean>(new CheckboxCell()){
#Override
public Boolean getValue(Note object) {
return object.isBChecked();
}
};
checkboxB.setFieldUpdater(new FieldUpdater<Note, Boolean>() {
#Override
public void update(int index, Note object, Boolean value) {
performCheckboxBOperation(object, value);
}
});
myTable.addColumn(checkboxA, "BOX A");
myTable.addColumn(checkboxB, "BOX B");
}
Method to store checkbox A's value in the DB, and if checkboxA == false, also set checkboxB = false in the DB -
public void performCheckboxAOperation(Note note, Boolean bool) {
this.presenter.performBoxOperation(note, bool, new AsyncCallback<CNote>() {
#Override
public void onFailure(Throwable caught) {
// error logic
}
#Override
public void onSuccess(CompanyNote result) {
cleanupDataProvider.flush();
if (result.isAChecked() == false) {
if (result.isBChecked() == true) {
unsetB(result);
}
}
}
});
}
This is my method to unset checkboxB which is changing the value in the DB but not in the UI -
private void unsetB(Note note) {
this.presenter.performBoxOperation(note, false, new AsyncCallback<Note>() {
#Override
public void onFailure(Throwable caught) {
// error logic
}
#Override
public void onSuccess(Note result) {
myDataProvider.flush();
myTable.redraw();
}
});
}
From what I understand, ListDataProvider needs to be flushed for the changes to be reflected in the UI. This has worked very well for all the other fields in the table, but it is not working with CheckboxCells.
I've already tried to redraw() the table as can be seen above but without any luck.
So what should I be doing to trigger a UI change in another CheckboxCell when one CheckboxCell is clicked?
I think that you forgot to actually change the object's value.
private void unsetB(final Note note) {
this.presenter.performBoxOperation(note, false, new AsyncCallback<Note>() {
// ...
#Override
public void onSuccess(Note result) {
note.setB(false); // set B value or copy `result` to `note`
// (`note` must be final)
myDataProvider.flush();
myTable.redraw();
}
});
}
Also, in performCheckboxAOperation you call unsetB(result); - it should be unsetB(note);
You need to understand that in onSuccess you get a copy of Note. The result object is not in the DataProvider, note is. And you should change note's value.

JXTreeTable model doesn't allow to use insertNodeInto()

I am using a tree table model in my app which extends AbstractTreeTableModel in order to create a JXTreeTable. Below is my model.
import org.jdesktop.swingx.treetable.AbstractTreeTableModel;
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;
import org.jdesktop.swingx.treetable.TreeTableModel;
import javax.swing.tree.TreeModel;
public class MyDataModel extends AbstractTreeTableModel{
static protected String[] columnNames = { "Field", "Value" };
static protected Class<?>[] columnTypes = { Object.class, Object.class};
public MyDataModel(MyDataNode rootNode) {
super(rootNode);
root = rootNode;
}
#Override
public Object getChild(Object parent, int index) {
return ((MyDataNode) parent).getChildren().get(index);
}
#Override
public int getChildCount(Object parent) {
return ((MyDataNode) parent).getChildren().size();
}
#Override
public int getIndexOfChild(Object parent, Object child) {
return 0;
}
#Override
public int getColumnCount() {
return columnNames.length;
}
#Override
public String getColumnName(int column) {
return columnNames[column];
}
#Override
public Class<?> getColumnClass(int column) {
return columnTypes[column];
}
#Override
public Object getValueAt(Object node, int column) {
MyDataNode mNode=(MyDataNode)node;
Object obj =mNode.getNodeDataObject();
if(column==0){
return mNode.getName();
}
else if (column==1){
if(obj instanceof Field){
Field field=(Field)mNode.getNodeDataObject();
if(field.getFieldDef().getListValue().size()>0){
return field.getFieldDef().getListValue();
}
else
return mNode.getDefaultValue();
}
else
return mNode.getDefaultValue();
}
return null;
}
#Override
public boolean isCellEditable(Object node, int column) {
//only allow Field values to be editable
if(((MyDataNode)node).getNodeDataObject() instanceof Field && column==1)
return true;
else
return false;
}
#Override
public boolean isLeaf(Object node) {
MyDataNode mNode=(MyDataNode)node;
Object obj=mNode.getNodeDataObject();
if(obj instanceof Field){
Field field=(Field)obj;
if(field.getFieldDef().getDataType().equalsIgnoreCase("MULTIPLE_MESSAGE")){
return false;
}
else
return true;
}
return false;
}
#Override
public void setValueAt(Object aValue, Object node, int column) {
MyDataNode mNode=(MyDataNode)node;
if (mNode.getNodeDataObject() instanceof Field && column == 1) {
Field field = (Field) mNode.getNodeDataObject();
field.setDefaultValue(aValue);
field.setSelectedValue(aValue);
}
}
}
This is how I use the JXTreeTable in my app
MyDataModel treeTableModel = new MyDataModel(createDataStructure(message));
jTreeTable = new JXTreeTable(treeTableModel);
private static MyDataNode createDataStructure(Message message) {
//setting fields as children of the root
nodeList = new ArrayList<>();
for (int index=0;index<message.getListFields().size() ; index++) {
if(message.getListFields().get(index).getFieldDef()
.getDataType().equalsIgnoreCase("MULTIPLE_MESSAGE")){
nodeList.add(new MyDataNode(message.getListFields()
.get(index).getFieldDef().getfName(), "", childMessagesRoot,
message.getListFields().get(index)));
}
else{
nodeList.add(new MyDataNode(message.getListFields()
.get(index).getFieldDef().getfName(), (String)(message.getListFields().
get(index).getDefaultValue()),
null,message.getListFields().get(index)));
}
}
//setting the Message to the root of the tree
root = new MyDataNode(message.getMsgName(), "", nodeList,message);
return root;
}
when I need to add a new node to the JXTreeTable, I try to get its model and use insertNodeInto() function call but the model doesn't support the insertNodeInto() function.
Someone please let me know where I am going wrong in the code. This is the first time I am using tree tables so there could be something missing.
insertNodeInto is a method of DefaultTreeTableModel, but not one of AbstractTreeTableModel.
Let MyDataModel extend DefaultTreeTableModel rather than AbstractTreeTableModel to be able to use insertNodeInto.
When accessing the TreeTableModel through JXTreeTable#getTreeTableModel, remember to cast the returned object of type TreeTableModel to DefaultTreeTableModel before calling insertNodeInto.
I found where I had gone wrong. I extended MyDataModel class with DefaultTreeTableModel and made my node class(I am using a custom node class) extend DefaultMutableTreeTableNode this gave the solution to use insertNodeInto method to get the current selected node of the tree table.

When editing TextFieldListCell the corresponding class is shown

I have the following code for editing a cell in a ListView:
listView.setCellFactory(new Callback<ListView<TextModule>, ListCell<TextModule>>() {
#Override public ListCell<TextModule> call(ListView<TextModule> param) {
TextFieldListCell<TextModule> textCell = new TextFieldListCell<TextModule>() {
#Override public void updateItem(TextModule item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText( item.getSummary());
}
else {
setText(null);
}
}
};
return textCell;
}
});
Now the problem is, that if I enter any cell within the ListView with double click, I can edit the cell, but the property (text which is displayed) is changed to the class definition like com.test.tools.tbm.model.TextModule#179326d. Normally it displays a text like "Hello World" or something else.
If you don't provide a proper string converter for TextFieldListCell it will use the default implementation (from CellUtils):
private static <T> String getItemText(Cell<T> cell, StringConverter<T> converter) {
return cell.getItem().toString();
}
showing in your case com.test.tools.tbm.model.TextModule#179326d, as cell.getItem() returns an instance of TextModule.
So you need to override toString() in your TextModule class.
class TextModule {
private final String summary;
public TextModule(String summary){
this.summary=summary;
}
public String getSummary(){ return summary; }
#Override
public String toString(){
return summary;
}
}
Or alternatively you could provide your own StringConverter:
listView.setCellFactory(TextFieldListCell.forListView(new StringConverter<TextModule>(){
#Override
public String toString(TextModule item) {
return item.getSummary();
}
#Override
public TextModule fromString(String string) {
return new TextModule(string);
}
}));

Use DataBinding on Combo

I try to use DataBinding on SWT Widgets.
I´m wondering if there is a way to connect a Combo Box to an underlying String in the model.
So I have a String in the Model and a Combo on my View?
As the standard way is not working:
//View
DataBindingContext ctx = new DataBindingContext();
IObservableValue target1 = WidgetProperties.singleSelectionIndex().observe(combo);
IObservableValue model1 = BeanProperties.value(OutputVariable.class, "type").observe(outputVariable);
ctx.bindValue(target1, model1);
//Model
public void setType(String type) {
//TYPES is a constant with the possible Combo values
if (contains(TYPES, type)) {
String oldType = this.type;
this.type = type;
firePropertyChange("type", oldType, this.type);
}else {
throw new IllegalArgumentException();
}
}
I tried to use the fireIndexedPropertyChangeMethod which didn't worked either.
Is there a way to connect those two together? Maybe I have to use another WidgetProperties or BeanProperties method?
As a workaround I could maybe use a new Property in the model, which defines the combo selection index, connect this to the Combo and transfer changes of this index to the type Property and vice versa. But that seems not as a great solution to me.
Edit:
The Solution with a selectionIndex Property is working. But a cleaner method would still be nice as now a type Property change in the model has to reset the selectionIndex too and vice versa.
I have a clean solution now, which is to use a Converter.
//View
IObservableValue comboObservable = WidgetProperties.singleSelectionIndex().observe(combo);
IObservableValue viewTypeObservable = BeanProperties.value(DebugModel.class, "type").observe(debugModel);
IConverter viewTypeToIntConverter = createViewTypeToIntConverter();
UpdateValueStrategy toTargetStrategy = new UpdateValueStrategy();
toTargetStrategy.setConverter(viewTypeToIntConverter);
IConverter intToViewTypeConverter = createIntToViewTypeConverter();
UpdateValueStrategy toModelStrategy = new UpdateValueStrategy();
toModelStrategy.setConverter(intToViewTypeConverter);
DataBindingContext context = new DataBindingContext();
context.bindValue(comboObservable, viewTypeObservable, toModelStrategy, toTargetStrategy);
//Converter
private IConverter createIntToViewTypeConverter() {
return new IConverter() {
#Override
public Object convert(Object value) {
if(value instanceof Integer) {
for(ViewType type : ViewType.values()) {
if(type.toString().equals(ViewType.getStringAtIndex((int)value))) {
return type;
}
}
}
throw new IllegalArgumentException("We need an Integer to convert it but got an " + value.getClass());
}
#Override
public Object getFromType() {
return Integer.class;
}
#Override
public Object getToType() {
return ViewType.class;
}
};
}
private IConverter createViewTypeToIntConverter() {
return new IConverter() {
#Override
public Object convert(Object value) {
if(value instanceof ViewType) {
String[] viewTypes = ViewType.getStringValues();
for(int i=0;i<viewTypes.length;i++) {
if(viewTypes[i].equals(((ViewType)value).toString())) {
return i;
}
}
}
throw new IllegalArgumentException("We need a View Type to be converted but got a " + value.getClass());
}
#Override
public Object getFromType() {
return ViewType.class;
}
#Override
public Object getToType() {
return Integer.class;
}
};
}
//Model
public class DebugModel extends ModelObject {
private ViewType type;
public ViewType getType() {
return type;
}
public void setType(ViewType type) {
firePropertyChange("type", this.type, this.type = type);
}
}
//Just to complete the example, be sure the Model class extends a ModelObject class like this
public class ModelObject {
private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
this);
public void addPropertyChangeListener(PropertyChangeListener listener) {
changeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
changeSupport.removePropertyChangeListener(listener);
}
public void addPropertyChangeListener(String propertyName,
PropertyChangeListener listener) {
changeSupport.addPropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(String propertyName,
PropertyChangeListener listener) {
changeSupport.removePropertyChangeListener(propertyName, listener);
}
protected void firePropertyChange(String propertyName, Object oldValue,
Object newValue) {
changeSupport.firePropertyChange(propertyName, oldValue, newValue);
}
protected void fireIndexedPropertyChange(String propertyName, int index, Object oldValue, Object newValue) {
changeSupport.fireIndexedPropertyChange(propertyName, index, oldValue, newValue);
}
}
Of course you can outsource the Converters to custom classes, I used it this way just to show a quick solution here.

Cannot access JList items in custom property editor

I have a simple OutlineView in the NetBeans editor area that shows two columns. The content of the cells of the second column shall be settable with a custom property editor via the PropertySupport. The custom property editor contains a JList that allows multiple selection of items.
The PropertySupport class looks like
public class CityProperty extends PropertySupport.ReadWrite<String> {
Customer c;
public CityProperty(Customer c, HashMap<String, Boolean> optionalCities) {
super("city", String.class, "City", "Name of City");
setValue("labelData", optionalCities);
this.c = c;
}
#Override
public String getValue() throws IllegalAccessException, InvocationTargetException {
return c.getCity();
}
#Override
public PropertyEditor getPropertyEditor() {
return new CityPropertyEditor(c);
}
#Override
public void setValue(String newValue) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
c.setCity(newValue);
}
}
The PropertyEditor looks like
public class CityPropertyEditor extends PropertyEditorSupport implements ExPropertyEditor {
Customer c;
PropertyEnv env;
public CityPropertyEditorPanel editor = null;
public CityPropertyEditor(Customer c) {
this.editor = new CityPropertyEditorPanel();
this.c = c;
}
#Override
public String getAsText() {
String s = (String) getValue();
if (s == null) {
return "No City Set";
}
return s;
}
#Override
public void setAsText(String s) {
setValue(s);
}
#Override
public void attachEnv(PropertyEnv env) {
this.env = env;
}
#Override
public Component getCustomEditor() {
HashMap<String, Boolean> cities = (HashMap<String, Boolean>) env.getFeatureDescriptor().getValue("labelData");
DefaultListModel model = new DefaultListModel();
/* selection in the gui */
int[] selectedIdxs = new int[cities.size()];
int idx = 0;
for (String str : cities.keySet()) {
model.addElement(str);
if (cities.get(str) == Boolean.FALSE) {
selectedIdxs[idx] = model.indexOf(str);
idx++;
}
}
if (selectedIdxs.length > 0){
editor.jList.setSelectedIndices(selectedIdxs);
}
editor.jList.setModel(model);
return editor;
}
#Override
public boolean supportsCustomEditor() {
return true;
}
#Override
public Object getValue() {
System.out.println("getValue(): " + editor.jList.getSelectedValuesList());
System.out.println("getValue(): " + editor.jtf.getText());
return super.getValue();
}
}
and the editor CityPropertyEditorPanel() itself is a simple JPanel with a JList and a JTextField.
My codes creates a nice custom editor with all the items listed, but it is not returning the new selected items from the list. My question is now, how do I get the selected items from the JList back to the CityProperty class? My try was to use
editor.jList.getSelectedValuesList());
in the getValue() method but the result is always empty. The same for the JTextField, where a new written value is also not transferred back.
What Am I doing wrong here?
I think I found a solution/workaround.
The CityPropertyEditor recognized the content of the "editor" object when I activated the PropertyEnv.STATE_NEEDS_VALIDATION feature. The code then in CityPropertyEditor should have to override the attacheEnv method and include the VetoableChangeListener
#Override
public void attachEnv(PropertyEnv env) {
this.env = env;
env.setState(PropertyEnv.STATE_NEEDS_VALIDATION);
env.addVetoableChangeListener(new VetoableChangeListener() {
#Override
public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
/* User has pushed OK */
for (Entry entry : editor.isoValNew.entrySet()){
isoVal.put((Double) entry.getKey(), (Boolean) entry.getValue());
}
}
});
}
while the Jlist in the CityPropertyEditorPanel() itself has a ListSelectionListener who updates the Map variable isoValNew
isoValueList.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
isoValNew.clear();
for (Object obj : isoValueList.getSelectedValues()) {
isoValNew.put((Double) obj, Boolean.TRUE);
}
}
});
I'm sure this is not a perfect solution, but it works fine in my case.
Hope this helps someone.

Categories