Add text background color to JTable column - swing

The motivation is that I want to see trailing spaces in table cells. For instance, if the cell contains "Foo Bar ", I would like to see the space character after "Bar". Is there a way to change the text background color so that I can see all the characters easily in a JTable cell? I'm looking to do this for a whole column.

try modifying the component in the cell defaulttablecellrenderer for column i.
I think arg4 and arg5 are row and column of the table so you can control the renderer for the cells here too.
JTable table = new JTable();
table.getColumn(i).setCellRenderer(new DefaultTableCellRenderer() {
#Override
public Component getTableCellRendererComponent(JTable arg0, Object arg1,
boolean arg2, boolean arg3, int arg4, int arg5) {
Component component = super.getTableCellRendererComponent(arg0, arg1, arg2, arg3, arg4, arg5);
// modify this component here
// component.setForeground(Color.black.black);
// component.setBackground(Color.black.black);
return component;
}
});

final JTable table = new JTable(tableModel);
table.setPreferredScrollableViewportSize(TABLE_SIZE);
table.setFillsViewportHeight(true);
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.getTableHeader().addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent mouseEvent) {
int selectedHeader = table.convertColumnIndexToModel(table
.columnAtPoint(mouseEvent.getPoint()));
table.getColumn(table.getColumnName(selectedHeader))
.setCellRenderer(new DefaultTableCellRenderer() {
public void setBackground(Color c) {
super.setBackground(Color.blue);
}
});
};
});

Related

JavaFX : TableView inside Dialog has duplicate items

I have an issue with my TableView and its items. I have created a small Dialog window to display warnings about my app, and inside the Dialog I have a TableView which displays the name of the warning and some information about it upon clicking on a button.
I have created a WarningUtil class (Singleton pattern) just to open / close the Dialog. The relevant code follows.
The constructor of the WarningUtil class (called once only) :
private WarningUtil(RootCtrl rootCtrl) {
this.rootCtrl = rootCtrl;
warnings = new HashMap<>();
setupWarningCallbacks(); // not relevant
setupTable();
setupColumns(); // not relevant
setupDialog();
}
The function managing the construction of the Dialog :
private void setupTable() {
// create the content pane
content = new AnchorPane(); // class variable - reference needed for further uses
content.setPrefSize(480, 240);
// create the root nodes of the view (table + 2 columns)
warningTable = new TableView<>(); // class variable - reference needed for further uses
warnDescriptionCol = new PTableColumn<>(); // class variable - reference needed for further uses
warnDetailsCol = new PTableColumn<>(); // class variable - reference needed for further uses
// settings anchors to keep the ration between dialog <-> table
AnchorPane.setBottomAnchor(warningTable, 15.0);
AnchorPane.setTopAnchor(warningTable, 15.0);
AnchorPane.setLeftAnchor(warningTable, 15.0);
AnchorPane.setRightAnchor(warningTable, 15.0);
// setting up the columns
warnDescriptionCol.setText(i18n("label.desc"));
warnDetailsCol.setText(i18n("label.details"));
warnDescriptionCol.setPercentageWidth(0.7);
warnDetailsCol.setPercentageWidth(0.3);
warnDescriptionCol.setResizable(false);
warnDetailsCol.setResizable(false);
// adding nodes to containers
warningTable.getColumns().addAll(warnDescriptionCol, warnDetailsCol);
content.getChildren().add(warningTable);
}
The function used to create the Dialog and set the content :
private void setupDialog() {
// creation and saving of the dialog in a variable reused later
warningDialog = DialogFactory.getInstance(rootCtrl.getPrimaryStage()).createWarningDialog();
warningDialog.getDialogPane().setContent(content);
warningDialog.getDialogPane().getScene().getWindow().sizeToScene();
}
// The DialogFactory function creating the dialog
public Dialog createWarningDialog(){
CustomDialog dialog = new CustomDialog(rootStage);
dialog.setTitle(i18n("warning.description"));
ButtonType cancelBt = new ButtonType(i18n("button.close"), ButtonData.OK_DONE);
dialog.getDialogPane().getButtonTypes().add(cancelBt);
return dialog.setupLayout();
}
The Main class is in charge of loading the warnings (stored in a .json file and deserialized upon starting the app). For now, the file only contains one entry.
When I click on my Warning button, the following function is called :
public void showWarnings() {
warningTable.getItems().clear(); // BP
warningTable.setItems(FXCollections.observableArrayList(warnings.values()));
warningDialog.showAndWait();
}
What happens is the following : When I have only one entry in my .json file, the first time I click on the button, only one warning is shown. If I click a second time, a second entry appears (the same) which should not be possible because of the following reasons :
Logic constraint : warnings.values() comes from an HashMap where the key is the type of the warning (WarningType class) > Not possible to have two identical keys
Debugging : When I set a breakpoint at "//BP", I clearly see that the warningTable has one item, and after clear the number of items is zero
Debugging : Still with the same breakpoint, I also check that warnings.values() has only one item, which is the case
After five clicks on the button, the Dialog clearly shows something is bugging.
More surprisingly, when I add a second warning (different from the first one, another type), the problem does not occur : No duplicates, warnings are correctly displayed and no matter how many times I open the window.
My question is : Could that be that the way I am creating this warning dialog leads to uncommon errors ? If so, why isn't it the case with two warnings ?
EDIT Include of the cellFactories / cellValueFactories
private void setupColumns() {
warnDescriptionCol.setCellFactory(new Callback<TableColumn<CustomWarning, String>, TableCell<CustomWarning, String>>() {
#Override
public TableCell<CustomWarning, String> call(TableColumn<CustomWarning, String> param) {
TableCell<CustomWarning, String> cell = new TableCell<CustomWarning, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
Label label = new Label(item);
setGraphic(label);
}
}
};
return cell;
}
});
warnDetailsCol.setCellFactory(new Callback<TableColumn<CustomWarning, CustomWarning>, TableCell<CustomWarning, CustomWarning>>() {
#Override
public TableCell<CustomWarning, CustomWarning> call(TableColumn<CustomWarning, CustomWarning> param) {
TableCell<CustomWarning, CustomWarning> cell = new TableCell<CustomWarning, CustomWarning>() {
#Override
protected void updateItem(CustomWarning item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
Button button = new Button(i18n("button.view"));
button.getStyleClass().add("save");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
showWarning(item);
}
});
setGraphic(button);
}
}
};
return cell;
}
});
warnDescriptionCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<CustomWarning, String>, ObservableValue<String>>() {
TableViewObjectWrapper<CustomWarning, String> wrapper = new TableViewObjectWrapper<CustomWarning, String>() {
#Override
public String getData() {
return getModel().getTitle();
}
};
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<CustomWarning, String> param) {
return new ReadOnlyObjectWrapper<>(wrapper.setModel(param.getValue()).getData());
}
});
warnDetailsCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<CustomWarning, CustomWarning>, ObservableValue<CustomWarning>>() {
TableViewObjectWrapper<CustomWarning, CustomWarning> wrapper = new TableViewObjectWrapper<CustomWarning, CustomWarning>() {
#Override
public CustomWarning getData() {
return getModel();
}
};
#Override
public ObservableValue<CustomWarning> call(TableColumn.CellDataFeatures<CustomWarning, CustomWarning> param) {
return new ReadOnlyObjectWrapper<>(wrapper.setModel(param.getValue()).getData());
}
});
}
You have to clear your cells in the cell factory if the cell is empty, as explained in the documentation:
It is very important that subclasses of Cell override the updateItem method properly, as failure to do so will lead to issues such as blank cells or cells with unexpected content appearing within them. Here is an example of how to properly override the updateItem method:
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
setText(item.toString());
}
}
Note in this code sample two important points:
We call the super.updateItem(T, boolean) method. If this is not done, the item and empty properties are not correctly set, and you are likely to end up with graphical issues.
We test for the empty condition, and if true, we set the text and graphic properties to null. If we do not do this, it is almost guaranteed that end users will see graphical artifacts in cells unexpectedly.
Since the cells are reused, you have to clear the graphic if it has become empty, not just set it if it's not.

WinRT TextBox MaxLength does not count \n\r as two characters

I have TextBox with MaxLength set to 10 but it is accepting 11 characters when Enter key is pressed. Looks like it is counting \n\r as 1 character instead of two. Is there anyway to make it count \n\r as two char length?
If you really want to allow line breaks in your text box and limit its text length, I see two options:
Either bind MaxLength through a converter so that it changes its value according to how many line breaks (\r\n) the text contains, as shown in this question
Alternatively, you might define your own attached property MaxLength that calculates text length correctly. This might look somewhat like the following (just as an example you'll need to adapt that to take into account special cases etc.):
public class TextBoxExtensions: DependencyObject
{
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.RegisterAttached(
"MaxLength", typeof (int), typeof (MaxLengthBehavior), new PropertyMetadata(default(int), PropertyChangedCallback));
public static void SetMaxLength(DependencyObject element, int value)
{
element.SetValue(MaxLengthProperty, value);
}
public static int GetMaxLength(DependencyObject element)
{
return (int) element.GetValue(MaxLengthProperty);
}
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var tb = dependencyObject as TextBox;
if (tb != null)
{
tb.KeyDown -= TbOnKeyDown;
tb.KeyDown += TbOnKeyDown;
}
}
private static void TbOnKeyDown(object sender, KeyRoutedEventArgs args)
{
var tb = sender as TextBox;
if (tb != null)
{
int max = GetMaxLength(tb);
if (tb.Text.Length >= max)
args.Handled = true;
}
}
}
<TextBox local:TextBoxExtensions.MaxLength="10" />

Add listener to all cell in column table editable mode of Vaadin

I want add listener to change "." to "," in one column table of vaadin, in editable mode.
I create the table data with BeanItemContainer.
One option is to use a custom TableFieldFactory to then add the listener to each field that needs it.
https://vaadin.com/book/-/page/components.table.html
table.setTableFieldFactory(new MyTableFieldFactory());
...
public class MyTableFieldFactory extends DefaultFieldFactory {
#Override
public Field createField(Container container, Object itemId,
Object propertyId, Component uiContext) {
String prop = (String) propertyId;
if ("a".equals(prop)) { // propertyId of the column you wish to change
AbstractField f = (AbstractField) super.createField(container, itemId, propertyId, uiContext); // casting to AbstractField to set the field to immediate mode
f.setImmediate(true);
f.addValueChangeListener(new Property.ValueChangeListener() {
#Override
public void valueChange(ValueChangeEvent event) {
String val = (String) event.getProperty().getValue();
val = val.replace(".", ",");
event.getProperty().setValue(val);
}
});
return f;
}
return super.createField(container, itemId, propertyId, uiContext);
}
}

Sorting data held by JTable after insertion

I populate a JTable from a database. The data in JTable is sorted based on the auto-generated primary key in descending order. The table looks like the following.
The data in the table is held by a list which contains a list of objects of a JPA entity - List<Country>.
Since I display data in descending order by countryId (primary key), the list needs to be sorted in descending by countryId after data is inserted and before the fireTableRowsInserted(size, size) method is executed.
After sorting this list in descending order by countryId, the table looks wonky as follows.
Values through the given text fields are submitted after validation, when the given add button is pressed.
The row is added to database and to the list and the list is also sorted as mentioned but the data in the table are not shown as they are in the list.
See the last two rows in the preceding snap shot. The actual row which is created is not displayed. The last row is duplicated instead which is different from the underlying list where the new object is added and the list sorted too.
My AbstractTableModel is as follows.
package admin.model;
import entity.Country;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import javax.swing.table.AbstractTableModel;
public final class CountryAbstractTableModel extends AbstractTableModel
{
private List<Country> countries;
private List<String> columnNames;
public CountryAbstractTableModel(List<Country> countries)
{
this.countries = countries;
columnNames=getTableColumnNames();
}
//This is the method which sorts the list after adding a JPA entity object.
public void add(Country country)
{
int size = countries.size();
countries.add(country);
Comparator<Country> comparator = new Comparator<Country>()
{
#Override
public int compare(Country o1, Country o2)
{
return o2.getCountryId().compareTo(o1.getCountryId());
}
};
Collections.sort(countries, comparator);
fireTableRowsInserted(size, size);
}
#Override
public String getColumnName(int column)
{
return columnNames.get(column);
}
#Override
public Class<?> getColumnClass(int columnIndex)
{
switch (columnIndex)
{
case 0:
return String.class;
case 1:
return String.class;
case 2:
return String.class;
case 3:
return String.class;
default:
return String.class;
}
}
private List<String> getTableColumnNames()
{
List<String> names = new ArrayList<>();
names.add("Index");
names.add("Id");
names.add("Country Name");
names.add("Country Code");
return names;
}
#Override
public int getRowCount()
{
return countries.size();
}
#Override
public int getColumnCount()
{
return 4;
}
public void remove(List<Long>list)
{
Iterator<Country> iterator = countries.iterator();
while(iterator.hasNext())
{
Country country = iterator.next();
Iterator<Long> it = list.iterator();
while(it.hasNext())
{
if(country.getCountryId().equals(it.next()))
{
iterator.remove();
int index = countries.indexOf(country);
fireTableRowsDeleted(index, index);
break;
}
}
}
}
#Override
public Object getValueAt(int rowIndex, int columnIndex)
{
Country country = countries.get(rowIndex);
switch (columnIndex)
{
case 0:
return rowIndex+1;
case 1:
return country.getCountryId();
case 2:
return country.getCountryName();
case 3:
return country.getCountryCode();
}
return "";
}
#Override
public void setValueAt(Object value, int rowIndex, int columnIndex)
{
Country country = countries.get(rowIndex);
if(value instanceof String)
{
String stringValue = value.toString();
switch(columnIndex)
{
case 2:
country.setCountryName(stringValue);
break;
case 3:
country.setCountryCode(stringValue);
break;
}
}
fireTableCellUpdated(rowIndex, columnIndex);
}
#Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return columnIndex>1?true:false;
}
}
If I remove the given Comparator as in the add() method in the code snippet (i.e sorting is not done) then, the table is updated as it should be with the newly created row at the end of the table (which should be on top of the table. Hence sorting is necessary).
Why does this happen when the underlying list is sorted? (Again it doesn't happen, when the list is not sorted, it is left untouched.)
This is happening because you are telling the model that an element has been added at position 'size' (ie the last position in the list) but because you are sorting the list it is actually in the model at position 0 (in this example).
Probably the simplest way to fix this is to call fireTableDataChanged() and not worry about the index - I think your table would have to be pretty big for this to cause performance problems. Otherwise you could use list.indexOf() to find out where your new element ended up after sorting and call fireTableRowsInserted() with the correct indices.

Line Wrapping Cell Renderer - Java

I am having trouble implementing a custom cell renderer which will wrap message content when it extends past one line in length. The following is what I have:
public class MessageTable extends JTable
{
private static MessageTable messageTable;
private DefaultTableModel model = new DefaultTableModel();
private String[] emptyData = {};
private TreeMap<Integer, String> messages = null;
public class LineWrapCellRenderer extends JTextArea implements TableCellRenderer {
#Override
public Component getTableCellRendererComponent(
JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column) {
this.setText((String)value);
this.setWrapStyleWord(true);
this.setLineWrap(true);
this.setBackground(Color.YELLOW);
int fontHeight = this.getFontMetrics(this.getFont()).getHeight();
int textLength = this.getText().length();
int lines = textLength / this.getColumns() +1;//+1, because we need at least 1 row.
int height = fontHeight * lines;
table.setRowHeight(row, height);
return this;
}
}
public MessageTable()
{
super();
messageTable = this;
this.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
model.addColumn("Message Number", emptyData);
model.addColumn("Message Content", emptyData);
this.setModel(model);
this.setFont(MappingView.theFont);
this.setDefaultRenderer(String.class, new LineWrapCellRenderer());
}
/**
* Set the current messages.
* #param messages
*/
public void setCurrentMessages(TreeMap<Integer, String> messages)
{
clearCurrentMessages();
this.messages = messages;
if (messages != null)
{
for (Integer key : messages.keySet())
{
String[] row = { key.toString(), messages.get(key).toString() };
model.addRow(row);
}
}
}
For some reason, the LineWrapCellRenderer is never used and the rows only ever contain one line of text.
What am I doing wrong?
Your cellrenderer is not used because the default table model returns Object.class for any column (it does not override AbstractTableModel's implementation):
public Class<?> getColumnClass(int columnIndex) {
return Object.class;
}
So either override the method yourself for the model or assign the renderer to Object.class.