I use the org.eclipse.core.databinding framework to bind some Text fields in an SWT application. I add an update strategy to validate the data and to set the value on the model only when the user click on the save button:
UpdateValueStrategy toModel = new UpdateValueStrategy(UpdateValueStrategy.POLICY_CONVERT);
if (validator != null) {
toModel.setAfterGetValidator(validator);
}
UpdateValueStrategy fromModel = new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE);
binding = bindingContext.bindValue(SWTObservables.observeText(this, SWT.Modify),
BeansObservables.observeValue(pVO, propertyName), toModel, fromModel);
This piece of code works really well.
But how can I do the same on a TableViewer?
I want it to work so that when I add something in the IHM, the model stay unchanged until I call getBindingContext().updateModels();
You do not need use the JFace Databinding Framework in TableViewer. Manipulation the structured data is simpler then SWT controls, such TableViewer, ListViewer and TreeViewer. You can use those viewer in the same way:
create viewer
set content provider
set label provider (suggested)
set filter (optional)
set sorter (optional)
After the viewer created, just invoke viewer.setInput(data) to put all the things to your viewer.
There are a list of model:
TableViewer tableViewer = new TableViewer(parent);
Table table = tableViewer.getTable();
table.setHeaderVisible(true);
table.setLinesVisible(true);`
for (int i = 0; i < COLUMN_NAMES.length; i++) {
TableColumn tableColumn = new TableColumn(table, SWT.LEFT);
tableColumn.setText(COLUMN_NAMES[i]);
tableColumn.setWidth(COLUMN_WIDTHS[i]);
}
tableViewer.setContentProvider(new ModelContentProvider());
tableViewer.setLabelProvider(new ModelLabelProvider());
tableViewer.setInput(models);
The magic happens in the content provider:
class ModelContentProvider implements IStructuredContentProvider {
#SuppressWarnings("unchecked")
#Override
public Object[] getElements(Object inputElement) {
// The inputElement comes from view.setInput()
if (inputElement instanceof List) {
List models = (List) inputElement;
return models.toArray();
}
return new Object[0];
}
/* ... other methods */
}
Each model will become a TableItem and the model in the TableItem(item.getData()).
However, a table composed by many columns, you need the LabelProvider to help you mapping the property of model to the TableItem:
class ModelLabelProvider extends LabelProvider implements
ITableLabelProvider {
#Override
public Image getColumnImage(Object element, int columnIndex) {
// no image to show
return null;
}
#Override
public String getColumnText(Object element, int columnIndex) {
// each element comes from the ContentProvider.getElements(Object)
if (!(element instanceof Model)) {
return "";
}
Model model = (Model) element;
switch (columnIndex) {
case 0:
return model.getFoo();
case 1:
return model.getBar();
default:
break;
}
return "";
}
}
The propagation of models to viewer is easy. If you will propagate viewer to the binded model, using the CellEditor is simple as well.
To use CellEditor, you need set the column properties, cell editors and cell modifier to TableViewer:
tableViewer.setColumnProperties(COLUMNS_PROPERTIES);
tableViewer.setCellEditors(new CellEditor[] {
new TextCellEditor(table), new TextCellEditor(table) });
tableViewer.setCellModifier(new ModelCellModifier(tableViewer));
The CellModifier likes this:
class ModelCellModifier implements ICellModifier {
TableViewer viewer;
public ModelCellModifier(TableViewer viewer) {
this.viewer = viewer;
}
#Override
public boolean canModify(Object element, String property) {
// property is defined by viewer.setColumnProperties()
// allow the FOO column can be modified.
return "foo_prop".equals(property);
}
#Override
public Object getValue(Object element, String property) {
if ("foo_prop".equals(property)) {
return ((Model) element).getFoo();
}
if ("bar_prop".equals(property)) {
return ((Model) element).getBar();
}
return "";
}
#Override
public void modify(Object element, String property, Object value) {
if ("foo_prop".equals(property)) {
TableItem item = (TableItem) element;
((Model) item.getData()).setFoo("" + value);
// refresh the viewer to show the changes to our user.
viewer.refresh();
}
}
}
Everything is simple but there are many steps to make all together.
Use ViewerSupport:
TableViewer tableViewer = ...
IObservableList tableElements = ...
IValueProperty[] columnProperties = ...
ViewerSupport.bind(tableViewer, tableElements, columnProperties);
i agree with qualidafial.
Snippet017TableViewerWithDerivedColumns from the jface.databinding snippets is a full example of this.
Related
I have a UI bug in a legacy code in our Java project. We display a table, with three columns (HumanReadable, name and value) in a window. In that window, users can click on each cell and update the values. Before that, user clicks the "add" button to add a new row (three new cells). Each cell has a default value, until the user decides to update the value. Now, when the users decides to update the value of the cell, he clicks on the cell and types in the value. The bug is that, once done editing, it keeps the default value in the UI. In the backend, the value has changed (if you click the cell again, it will go into editing mode and show you the value).
I uploaded a short GIF that shows the issue and can be found here.
In that GIF you can see that I updated the default value of the first column to be test. Then I click some other place (to exit the edit mode) and it showed the default value instead of test in the first column.
The method that creates the table:
private void createTable(final Composite parent) {
final Table varTable = new Table(parent, SWT.MULTI);
varTable.setHeaderVisible(true);
varTable.setLinesVisible(true);
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(varTable);
varTableViewer = new TableViewer(varTable);
final DataBindingContext bindingContext = new DataBindingContext();
final TableViewerColumn col1 = GuiUtils.createTableColumn(varTableViewer, "Human Readable");
col1.setEditingSupport(new StringEditingSupport(varTableViewer, bindingContext, dataProperty));
col1.getColumn().setWidth(120);
final TableViewerColumn col2 = GuiUtils.createTableColumn(varTableViewer, "Name");
col2.getColumn().setWidth(120);
col2.setEditingSupport(new StringEditingSupport(varTableViewer, bindingContext, nameProperty));
final TableViewerColumn col3 = GuiUtils.createTableColumn(varTableViewer, "Value");
col3.setEditingSupport(new StringEditingSupport(varTableViewer, bindingContext, valueProperty));
KeyBoardNavigationSupport.createSupport(varTableViewer);
input = new WritableList(globalVars, FlowVar.class);
ViewerSupport.bind(varTableViewer, input, BeanProperties.values(new String[] { dataProperty, nameProperty, valueProperty }));
}
The StringEditingSupport class:
public class StringEditingSupport extends ObservableValueEditingSupport {
private class CellEditorPrintValidatorErrors extends TextCellEditor {
public CellEditorPrintValidatorErrors(Composite control) {
super(control);
}
#Override
protected void focusLost(){
if(this.getErrorMessage() != null) {
MessageDialog.openError(this.getControl().getShell(), "Invalid input", this.getErrorMessage());
}
}
}
private final CellEditor cellEditor;
String propertyName;
public StringEditingSupport(final ColumnViewer viewer, final DataBindingContext dbc, final String propertyName) {
super(viewer, dbc);
cellEditor = new TextCellEditor((Composite) viewer.getControl());
this.propertyName = propertyName;
}
public StringEditingSupport(final ColumnViewer viewer, final DataBindingContext dbc, final String propertyName, final ICellEditorValidator validator) {
super(viewer, dbc);
cellEditor = new CellEditorPrintValidatorErrors((Composite) viewer.getControl());
cellEditor.setValidator(validator);
this.propertyName = propertyName;
}
#Override
protected IObservableValue doCreateCellEditorObservable(final CellEditor cellEditor) {
return SWTObservables.observeText(cellEditor.getControl(), SWT.Modify);
}
#Override
protected IObservableValue doCreateElementObservable(final Object element, final ViewerCell cell) {
return BeansObservables.observeValue(element, propertyName);
}
#Override
protected CellEditor getCellEditor(final Object element) {
return cellEditor;
}
public String getErrorMessage(){
return cellEditor.getErrorMessage();
}
}
I believe it has something to do with the StringEditingSupport class. This class allows to edit the value in each cell of table. But I couldn't figure out a way to "update" the value shown in the GUI. As I understand input (of type WritableList) contains all the information. Here is the add button listener method:
private class AddButtonSelectionListener extends SelectionAdapter {
#Override
public void widgetSelected(final SelectionEvent e) {
String name = nameProperty;
String meaning = dataProperty;
final List<String> names = new ArrayList<String>();
final List<String> meanings = new ArrayList<String>();
for (final Object var : input) {
names.add(((FlowVar) var).getName());
meanings.add(((FlowVar) var).getData());
}
int index = 0;
while (names.contains(name)) {
name = nameProperty + ++index;
}
index = 0;
while (meanings.contains(meaning)) {
meaning = dataProperty + ++index;
}
input.add(new FlowVar(name, valueProperty, meaning));
}
}
So, as I understand, I need to somehow bind the input to the UI (the content of each cell). I did try many attempts like trying to set a listener to the whole table (varTableViewer.addSelectionChangedListener) but none of them worked. Is it possible to suggest a way to solve this kind of issue?
If anything is missing, please let me know and I'll add it.
i have a data model "Rule"
A Rule consists of 1-x String parts saved as a List and an boolean value weather the rule is active or not.
To show this in my UI i want to add a TableView with 2 Columns.
Column 1 should display the Rule Text as a whole, but heavily customized. In the cell i add a textfield for each rule part which then get binded to the StrinProperty (Thats why i need a List of String Properties.
The 2. column should display a checkbox to activate or deactivate the rule (this is no problem an works fine)
Before my rule Model had the boolean isActive flag i used a Listview which had the whole Rule model class as Object. I made my own ListCell implementation and overrode updateItem(Object item, boolean isEmpty) to customize the cell to look like this:
I want the tablecell in column 1 to look exactly how the listcell in my listview looked.
Because ListCell and Tablecell both inherit from IndexedCell i saw no problem in my way of changing the visual of the cell.
My problem is to bind the new datamodel to the table:
private TableView<Rule> tvRules;
this.tvRules = new TableView<Rule>();
this.tvRules.setPrefSize(GuiCore.prefWidth * 0.32, GuiCore.prefHeight * 0.32);
this.tvRules.setEditable(true);
headerBoxLbl = new Label("Active");
headerBox = new CheckBox();
headerBoxLbl.setGraphic(headerBox);
headerBoxLbl.setContentDisplay(ContentDisplay.RIGHT);
headerBox.setOnAction(e -> this.changeAllActiveBoxes());
rulePartsColumn = new TableColumn<Rule, List<SimpleStringProperty>>("Rule");
rulePartsColumn.setCellFactory((callback) -> new RuleTableCell());
rulePartsColumn.setCellValueFactory(cellData -> cellData.getValue().getRulePartsProperty());
rulePartsColumn.setResizable(false);
rulePartsColumn.prefWidthProperty().bind(this.widthProperty().multiply(0.8));
isActiveColumn = new TableColumn<Rule, Boolean>();
isActiveColumn.setCellValueFactory(cellData -> cellData.getValue().getIsActiveProperty());
isActiveColumn.setCellFactory(cellData -> new CheckBoxTableCell<>());
isActiveColumn.setResizable(false);
isActiveColumn.prefWidthProperty().bind(this.widthProperty().multiply(0.2));
isActiveColumn.setStyle( "-fx-alignment: CENTER;");
isActiveColumn.setGraphic(headerBoxLbl);
this.tvRules.getColumns().addAll(rulePartsColumn, isActiveColumn);
As you see i create 2 Columns with the TableDataType Rule, one with Boolean type and one with the List as Data type.
The problem ist that i dont get the binding of the rulePartsColumn to the rule Model to work:
I really dont know how to bind this so in the cell i can work with a List of StringProperties (or SimpleStringProperties).
For reference my Model class Rule:
public class Rule {
private SimpleListProperty<SimpleStringProperty> ruleParts;
private SimpleBooleanProperty isActive;
public Rule() {
this(true, Arrays.asList("", "=", ""));
}
public Rule(final boolean isActive, final List<String> ruleParts) {
this.isActive = new SimpleBooleanProperty(isActive);
this.ruleParts = new SimpleListProperty<SimpleStringProperty>(FXCollections.observableArrayList());
for(int i = 0; i < ruleParts.size(); i++) {
this.ruleParts.add(new SimpleStringProperty(ruleParts.get(i)));
}
}
public SimpleListProperty<SimpleStringProperty> getRulePartsProperty() {
return this.ruleParts;
}
public List<SimpleStringProperty> getRulePartsProperties() {
return (List<SimpleStringProperty>)this.ruleParts;
}
public List<String> getRuleParts() {
List<String> parts = new ArrayList<String>();
for(int i = 0; i < this.ruleParts.size(); i++) {
parts.add(this.ruleParts.get(i).get());
}
return parts;
}
public SimpleBooleanProperty getIsActiveProperty() {
return this.isActive;
}
public boolean isActive() {
return isActive.get();
}
public void setActive(boolean isActive) {
this.isActive.set(isActive);
}
}
Thanks in advance
I want to filter GXT ComboBox Store. for example if I type 'st' in combobox I want combobox to show only values that contain 'st'
Here is my implementation
combo = new ComboBox<MerchantDTO>(store, label);
StoreFilter<MerchantDTO> filter = new StoreFilter<MerchantDTO>() {
#Override
public boolean select(Store<MerchantDTO> store, MerchantDTO parent, MerchantDTO item) {
boolean canView = (item.getName() != null && item.getName().toLowerCase().contains(combo.getText().toLowerCase()));
return canView;
}
};
store.setEnableFilters(true);
store.addFilter(filter);
This filter works and shows correct values, But combobox's dropdown list does not open automatically. I have to click on combobox manually to open dropdown list and see filtered results. I am using GXT 3.1.0 and GWT 2.7.0
I tried using combo.expand(); function but it didnt open dropdown list.
Any help would be appreciated.
I found solution. Here is sample how to add custom filter to GXT (version 3.1.0) ComboBox
1) Create class which extends ListStore and add String variable for user input text
public abstract class XListStore<M> extends ListStore<M> {
private String userText;
public XListStore(ModelKeyProvider<? super M> keyProvider) {
super(keyProvider);
}
#Override
protected boolean isFilteredOut(M item) {
return filter(item);
}
public abstract boolean filter(M item);
public String getUserText() {
return userText;
}
public void setUserText(String userText) {
this.userText = userText;
}
}
2) Initialize custom list store and implement filter method
XListStore<SampleDTO> store = new XListStore<SampleDTO>(new ModelKeyProvider<SampleDTO>() {
#Override
public String getKey(SampleDTO item) {
return item.getId();
}
}) {
public boolean filter(SampleDTO item) {
boolean result = false;
//Write you filter logic here
return result;
}
};
store.setEnableFilters(true);
3) Initialize ComboBox and add Key up handler
ComboBox<SampleDTO> comboBox = new ComboBox<SampleDTO>(store, label);
comboBox.addKeyUpHandler(new KeyUpHandler() {
#Override
public void onKeyUp(KeyUpEvent event) {
store.setUserText(comboBox.getText());
}
});
Done. Now ComboBox will filter store according to user input and will open dropdown window automatically
Is there any way to add clickHandlers (or any type of handler) to the headers of the columns in a CellTable? I want to add some sorting functionality to my CellTable and I dont see any methods in the Column or Header classes that will allow this. I used this post to figure out how to use the CellTable.
Workaround for click events:
Header<String> columnHeader = new Header<String>(new ClickableTextCell()) {
#Override
public String getValue() {
return columnName;
}
};
columnHeader.setUpdater(new ValueUpdater<String>() {
#Override
public void update(String value) {
Window.alert("Header clicked!");
}
});
table.addColumn(column, columnHeader);
There is no out of the box way of supporting sort as yet on the CellTable. However there is a manual workaround involving a lot of code drudgery. Refer the classes SortableHeader and SortableColumn in the bike shed under expenses sample. You will find the usage in com.google.gwt.sample.expenses.gwt.client.ExpenseDetails. You can use this until something concrete comes out in the next release.
check out directory: http://google-web-toolkit.googlecode.com/svn/trunk/bikeshed
With the final release of GWT 2.1, has there been any support for sortable columns added to the CellTable? Or is it still a roll your own solution after looking at the bikeshed example?
CellTable<Contact> table = new CellTable<Contact>();
// Create name column.
final TextColumn<Contact> nameColumn = new TextColumn<Contact>() {
#Override
public String getValue(Contact contact) {
return contact.name;
}
};
// Create a data provider.
ListDataProvider<Contact> dataProvider = new ListDataProvider<Contact>();
// Connect the table to the data provider.
dataProvider.addDataDisplay(table);
final List<Contact> list = dataProvider.getList();
for (Contact contact : CONTACTS) {
list.add(contact);
}
final ListHandler<Contact> columnSortHandler = new ListHandler<Contact>(
list);
Header<String> columnHeader = new Header<String>(new ClickableTextCell()) {
#Override
public String getValue() {
return "Name";
}
};
columnHeader.setUpdater(new ValueUpdater<String>() {
#Override
public void update(String value) {
if (Window.confirm("Want to do?")){
nameColumn.setSortable(true);
columnSortHandler.setComparator(nameColumn,
new Comparator<Contact>() {
public int compare(Contact o1, Contact o2) {
if (o1 == o2) {
return 0;
}
// Compare the name columns.
if (o1 != null) {
return (o2 != null) ? o1.name.compareTo(o2.name) : 1;
}
return -1;
}
});
} else nameColumn.setSortable(false);
}
});
// Make the name column sortable.
nameColumn.setSortable(false);
// Create address column.
TextColumn<Contact> addressColumn = new TextColumn<Contact>() {
#Override
public String getValue(Contact contact) {
return contact.address;
}
};
// Add the columns.
table.addColumn(nameColumn, columnHeader);
table.addColumn(addressColumn, "Address");
// Add the data to the data provider, which automatically pushes it to the
// widget.
// Add a ColumnSortEvent.ListHandler to connect sorting to the
// java.util.List.
//------------------ Code to add --------------------------------//
VerticalPanel vp = new VerticalPanel();
table.addColumnSortHandler(columnSortHandler);
//------------------ Code end --------------------------------//
// We know that the data is sorted alphabetically by default.
table.getColumnSortList().push(nameColumn);
// Add it to the root panel.
vp.add(table);
RootPanel.get().add(vp);
As title says, i wonder if you could you direct me to some document, or give me advice here, on designing (GUI design) form which main part is occupied by jtable, which has several filters.Main goal is to avoid visual clutter.
I have implemented a simple TableFilterPanel in the past which has one JTextField per table column and performs regular expression matching when text is present in a given field. I typically lay this out as a list of vertical labels + text fields (i.e. so it's fairly compact).
My key class is called ColumnSearcher, which offers the ability to manufacture a RowFilter using the contents of the JTextField:
protected class ColumnSearcher {
private final int[] columns;
private final JTextField textField;
public ColumnSearcher(int column, JTextField textField) {
this.columns = new int[1];
this.textField = textField;
this.columns[0] = column;
}
public JTextField getTextField() {
return textField;
}
public boolean isEmpty() {
String txt = textField.getText();
return txt == null || txt.trim().length() == 0;
}
/**
* #return Filter based on the associated text field's value, or null if the text does not compile to a valid
* Pattern, or the text field is empty / contains whitespace.
*/
public RowFilter<Object, Object> createFilter() {
RowFilter<Object, Object> ftr = null;
if (!isEmpty()) {
try {
ftr = new RegexFilter(Pattern.compile(textField.getText(), Pattern.CASE_INSENSITIVE), columns);
} catch(PatternSyntaxException ex) {
// Do nothing.
}
}
return ftr;
}
}
When I wish to change the filter settings I build an "and" filter from each individual filter:
protected RowFilter<Object, Object> createRowFilter() {
RowFilter<Object, Object> ret;
java.util.List<RowFilter<Object, Object>> filters = new ArrayList<RowFilter<Object, Object>>(columnSearchers.length);
for (ColumnSearcher cs : columnSearchers) {
RowFilter<Object, Object> filter = cs.createFilter();
if (filter != null) {
filters.add(filter);
}
}
if (filters.isEmpty()) {
ret = NULL_FILTER;
} else {
ret = RowFilter.andFilter(filters);
}
return ret;
}
Typically I fire a PropertyChangeEvent when I wish to update the filters and have a PropertyChangeListener respond to it and rebuild my aggregate filter. You may then choose to fire the "rowFilter" PropertyChangeEvent if the user types in one of the text fields (e.g. by adding a DocumentListener to each JTextField).
Hope that helps.