I have a table model containing elements of type JCheckBox. I want the content of this table to be different according to the value of a JComboBox.
My problem is the following : if I toggle a few checkboxes and then change the value of my combo box, all the check boxes take the default value (this is what I want, because the boolean values are those of the selected item in the JCheckBox) except the last one I toggled before changing the value of the combo box.
And here is how I implemented this :
public class ValsSelectionTableModel extends MyAbstractTableModel {
private final JComboBox<Data> dataField;
private final Map<Data, JCheckBox[][]> modifiedVals = new HashMap<>();
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
Data data = (Data) dataField.getSelectedItem();
if (!modifiedVals.containsKey(data))
modifiedVals.put(data,
buildCheckBoxesFrom(ClassWithStaticFields.defaultBoolArray));
return modifiedVals.get(data)[rowIndex][columnIndex];
}
private JCheckBox[][] buildCheckBoxesFrom(boolean[][] boolArray) {
JCheckBox[][] checkBoxArray =
new JCheckBox[boolArray.length][boolArray[0].length];
for (int i = 0 ; i < checkBoxArray.length ; i++)
for (int j = 0 ; j < checkBoxArray[i].length ; j++) {
checkBoxArray[i][j] = new JCheckBox();
checkBoxArray[i][j].setSelected(boolArray[i][j]);
checkBoxArray[i][j].setHorizontalAlignment(SwingConstants.CENTER);
}
return checkBoxArray;
}
}
Has anyone got an idea what's wrong with this ?
EDIT : I forgot something important (otherwise the JComboBox selection would not change the display) : I added this actionListener to my JComboBox :
public class MyListener implements ActionListener {
private final ValsSelectionTableModel tableModel;
public MyListener(ValsSelectionTableModel tableModel) {
this.tableModel = tableModel;
}
#Override
public void actionPerformed(ActionEvent e) {
tableModel.fireTableDataChanged();
}
}
Manage elements of type Boolean in your TableMode to get the default renderer and editor.
I already tried with Boolean, but the rendered checkboxes cannot be toggled.
Your implementation of TableMode appears to extend AbstractTableModel; ensure that the following things happen for the relevant column:
Return Boolean.class from getColumnClass().
Return true from isCellEditable().
Fire the appropriate TableModelEvent in setValueAt() when updating your internal Map<…>.
Complete examples using AbstractTableModel are seen here and here. An example of adding multiple components to a column is examined here.
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 am using glazedlists for JComboBox in java to make a JComboBox searchable and sortable. But there is situation I can't get to solve it.
I have a JComboBox attached with glazedlists. glazedlists takes String array to fill this combobox and make it searchable like this
String[] Values = {"ABC", "DEF", "GHI", "JKL", "MNO"};
JComboBox cmb = new JComboBox();
AutoCompleteSupport.install(cmb , GlazedLists.eventListOf(Values));
This works good but the problem is that I want to add ID along with value coming from Database and for that I am implementing my own custom ListCellRenderer like this
class MyListRenderer extends JLabel implements ListCellRenderer
{
#Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
Object[] itemData = (Object[])value;
setText((String)itemData[1]);
return this;
}
}
cmb.setRenderer(new MyListRenderer());
And to add value in JComboBox, I have to
while(rs.next()){
int id=rs.getInt("company_id");
String category=rs.getString("company_name");
Object[] itemData = new Object[] {id, category};
cmb.addItem(itemData);
}
Now how can I implement my JComboBox with glazedlists while setting my own custom renderer?
Didn't have any success with your way, but I found a solution in an earlier project. You can set the model of the JComboBox instead by doing something like this:
//load the list of objects to use
ContainerClass[] allOptions = ContainerClass.getAll();
//create an EventList with this list and set is as the combobox model
final EventList<ContainerClass> eventList = GlazedLists.eventList(Arrays.asList(allOptions));
comboBox.setModel(new DefaultComboBoxModel<ContainerClass>(allOptions));
//finally initialize the combobox by SwingUtilities if done on a non-UI thread
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
AutoCompleteSupport<ContainerClass> install =
AutoCompleteSupport.install(comboBox, eventList);
install.setFilterMode(TextMatcherEditor.CONTAINS);
install.setHidesPopupOnFocusLost(true);
install.setSelectsTextOnFocusGain(false);
install.setCorrectsCase(false);
}
});
And with a ContainerClass like:
class ContainerClass{
int id;
String company;
//helper method to get all the objects needed
static ContainerClass[] getAll(){
ContainerClass test = new ContainerClass();
test.id = 2;
test.company = "abc";
return new ContainerClass[]{test,test,test};
}
#Override
//this String is what actually will be displayed in the combobox
public String toString(){return "(" + id + ") " + company;}
}
And I'm assuming that your JComboBox has the following type:
JComboBox<ContainerClass> comboBox;
(I had to obfuscate the names of all variables, so there might be errors in the code. Let me know and I will correct them)
So to recap. GlazedLists uses the model to get the names, which again asks the ContainerClass for it's toString() method which will return the name to display in the JComboBox.
As a note, when you call comboBox.getSelectedItem() it will return an object of type ContainerClass, or null if it isn't a valid selection.
UPDATE
If you want to be able to control the order as well as the name, you would need to implement your own model for the ComboBox. Found this that seems to explain it well:
class MyComboBoxModel extends AbstractListModel implements ComboBoxModel {
String[] ComputerComps = { "Monitor", "Key Board", "Mouse", "Joy Stick",
"Modem", "CD ROM", "RAM Chip", "Diskette" };
String selection = null;
public Object getElementAt(int index) {
return ComputerComps[index];
}
public int getSize() {
return ComputerComps.length;
}
public void setSelectedItem(Object anItem) {
selection = (String) anItem; // to select and register an
} // item from the pull-down list
// Methods implemented from the interface ComboBoxModel
public Object getSelectedItem() {
return selection; // to add the selection to the combo box
}
}
In this code, I'm adding the data from Database. I want to set cell renderer with a label. But if I run this code I got only check box.
try {
List<Group> listgrChild = grMgmtModel.performList();
for (final Group group : listgrChild) {
table.getColumnModel().getColumn(0)
.setCellRenderer(new TableCellRenderer() {
// the method gives the component like whome the
// cell must
// be rendered
public Component getTableCellRendererComponent(
JTable table, Object value,
boolean isSelected, boolean isFocused,
int row, int col) {
boolean marked = new Boolean(String
.valueOf(value));
JCheckBox rendererComponent = new JCheckBox();
if (marked) {
rendererComponent.setSelected(true);
}
return rendererComponent;
}
});
tbModel.addRow(new Object[] { group.getGroupName() });
}
You comment, "renderComponent.setText("Hello") is giving only last value."
Verify that your TableModel has individual storage for each row's check box state and label value. In this example, the class Value holds the relevant data:
private static class Value implements Comparable<Value> {
private Boolean selected;
private Double value;
…
}
The corresponding TableModel manages a List<Value> and the required renderer and editor use the data from each Value instance accordingly. As an aside, Value implements the Comparable interface for convenience in sorting.
I created a Java GUI that displays the table using the following syntax:
table = new JTable(new MyTableModel(columnNames,
updateTable(cmbAdversary.getSelectedItem().toString(),
cmbdataType.getSelectedItem().toString())));
where columnNames is a Vector of Strings
cmbadversary and smbdataType are the selection od combo boxes.
and updateTable is a method that returns a Vector of Vectors depending on the combo box selection as follows:
static Vector updateTable(String FilterVal1 , String FilterVal2)
{
try {
myVector = tssc.testSeverityFunctionService(FilterVal1,FilterVal2);
} catch (Exception e) {
e.printStackTrace();}
return myVector;
}
This is how my custom class MyTableModel that extends AbstractTableModel looks like:
class MyTableModel extends AbstractTableModel
{
Vector columnNames = new Vector();
Vector Fdb = new Vector();
public MyTableModel(Vector cName,Vector rName){
this.columnNames = cName;
this.Fdb = rName;}
public int getColumnCount() { // number of columns in the model.
return columnNames.size();
}
public int getRowCount() { // number of rows in the model.
return Fdb.size();
}
#Override
public String getColumnName(int col) {
return columnNames.get(col).toString();
}
public Object getValueAt(int row, int col) {
Vector v = (Vector) this.Fdb.get(row);
return v.get(col);
}
#Override
public Class getColumnClass(int c) {
Vector v = (Vector) Fdb.get(0);
return v.get(c).getClass();}
public boolean isCellEditable(int row, int col)
{ return true; }
public void setValueAt(Vector value, int row, int col)
{
for(int i=0;i<value.size();i++)
{ for(int j=0;j<columnNames.size();j++) {
Fdb.setElementAt(value.get(j),j); }
}
fireTableCellUpdated(row, col);
}
}
The problem is that when I run the code, the table GUI show me initial values but fails to update when I change the selection in the 2 comboboxes and click the selection button.
The Selection button, btw, calls a method which implements the action listener.
Please help me out. Am no pro in Java, but willing to learn. If you have any followup qs., I'll be happy to provide details.
Your solution seems overly complicated. If I understand the basics, the user chooses a value from a combo box, then based on the selection some data is loaded into the table.
There is no need to create a custom table model to do this.
A TableModel contains data. If you want to change the data, then one way to do this is to simply create a new TableModel. So you add an ActionListener to your combo box. When an item is selected you retrive your data and load the data into an Vector or an Array. Using this data you can create a new TableModel and update the JTable in two lines of code:
DefaultTableModel model = new DefaultTableModel(...);
table.setModel( model );
If you need to customize the model to override the getColumnClass() or isCellEditable() methods, then you should extend the DefaultTableModel. I don't see any need to implement the whole model.