How to Style an Independent Tableview Column

Is there a way to style an independent TableView column?

You should to use TableColumn#setCellFactory() to customize cell item rendering.

For example, datamodel with like this Person class:

// init code vs..
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
firstNameCol.setCellFactory(getCustomCellFactory("green"));

TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
lastNameCol.setCellFactory(getCustomCellFactory("red"));

table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol);

// scene create code vs..

and the common getCustomCellFactory() method:

private Callback<TableColumn<Person, String>, TableCell<Person, String>> getCustomCellFactory(final String color) {
return new Callback<TableColumn<Person, String>, TableCell<Person, String>>() {

@Override
public TableCell<Person, String> call(TableColumn<Person, String> param) {
TableCell<Person, String> cell = new TableCell<Person, String>() {

@Override
public void updateItem(final String item, boolean empty) {
if (item != null) {
setText(item);
setStyle("-fx-text-fill: " + color + ";");
}
}
};
return cell;
}
};
}

Custom UITableViewCell selection style?

You can do this as follows. Set your table cell's selection style to UITableViewCellSelectionStyleNone. This will remove the blue background highlighting. Then, to make the text label highlighting work the way you want, instead of using the default UITableViewCell class, create a subclass of UITableViewCell and override the default implementation of setHighlighted:animated with your own implementation that sets the label colors to however you want depending on the highlighted state.

- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
if (highlighted) {
self.textLabel.textColor = [UIColor whiteColor];
} else {
self.textLabel.textColor = [UIColor blackColor];
}
}

JavaFX Table Cell Formatting

You can accomplish that through Cell Factories. See

https://stackoverflow.com/a/10149050/682495

https://stackoverflow.com/a/10700642/682495

Although the 2nd link is about ListCell, the same logic is totally applicable to TableCells too.

P.S. Still if you need some sample code, kindly will attach here.

How to modify the attributes of a single cell on a tableview

The solution to any problem involving changing the appearance of table cells based on certain state of the cell's item, and other data, is always to use a cell factory which returns a cell that updates its appearance accordingly.

The problem with the approach you are trying is that you are overlooking the fact that the table view reuses cells. For example, if the table contains a large amount of data and the user scrolls, new cells will not be created but cells that are scrolled out of view will be reused for the new items that scroll into view. Since you don't update the style of the cell when this happens, scrolling will make the wrong cells highlighted.

Here the logic is a little tricky as each cell essentially has to observe all values in the column (whether they are currently displayed or not). I think the easiest solution here is to independently maintain an ObservableSet that keeps a list of duplicate entries, and have the cell observe that. Here's an implementation. You can probably factor this out into a separate class for the cell factory (or something convenient) to make it more elegant and reusable.

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.stage.Stage;
import javafx.util.StringConverter;

public class HighlightDuplicateTableCells extends Application {

// create an observable list that fires events if the dataProperty of any elements change:

private final ObservableList<Item> items =
FXCollections.observableArrayList(item -> new Observable[]{item.dataProperty()});

// collection of strings that are duplicated in the data properties of all the items:

private final ObservableSet<String> duplicateData = FXCollections.observableSet();

private static final PseudoClass DUPLICATE_PC = PseudoClass.getPseudoClass("duplicate");

private final StringConverter<String> identityStringConverter = new StringConverter<String>() {

@Override
public String toString(String object) {
return object;
}

@Override
public String fromString(String string) {
return string;
}

};

@Override
public void start(Stage primaryStage) {

// listener to maintain collection of duplicates:
items.addListener((Change<? extends Item> change) -> updateDuplicateData());

TableView<Item> table = new TableView<>();
table.setEditable(true);
table.setItems(items);

TableColumn<Item, Number> idColumn = new TableColumn<>("Id");
idColumn.setCellValueFactory(cellData -> new SimpleIntegerProperty(cellData.getValue().getId()));

TableColumn<Item, String> dataColumn = new TableColumn<>("Data");
dataColumn.setCellValueFactory(cellData -> cellData.getValue().dataProperty());

dataColumn.setCellFactory(tc -> {

TextFieldTableCell<Item, String> cell = new TextFieldTableCell<Item, String>(identityStringConverter) {

// boolean binding that indicates if the current item is contained in the duplicateData set:
private BooleanBinding duplicate = Bindings.createBooleanBinding(
() -> duplicateData.contains(getItem()),
duplicateData, itemProperty());

// anonymous constructor just updates CSS pseudoclass if above binding changes:
{
duplicate.addListener((obs, wasDuplicate, isNowDuplicate) ->
pseudoClassStateChanged(DUPLICATE_PC, isNowDuplicate));
}
};

return cell ;
});

table.getColumns().add(idColumn);
table.getColumns().add(dataColumn);

// note best to minimize changes to items.
// creating a temp list and using items.setAll(...) achieves this:

List<Item> tmp = new ArrayList<>();
for (int i = 1 ; i <= 70; i++) {
char c = (char)('@' + (i % 60));
String data = Character.toString(c) ;
tmp.add(new Item(i, data));
}

items.setAll(tmp);

Scene scene = new Scene(table, 600, 600);
scene.getStylesheets().add("duplicate-cell-example.css");
primaryStage.setScene(scene);
primaryStage.show();
}

private void updateDuplicateData() {

// TODO: may not be most efficient implementation

// all data:
List<String> data = items.stream().map(Item::getData).collect(Collectors.toList());
// unique data:
Set<String> uniqueData = new HashSet<>(data);
// remove unique values from data:
uniqueData.forEach(data::remove);
// remaining values are duplicates: replace contents of duplicateData with these:
duplicateData.clear();
duplicateData.addAll(data);
}

public static class Item {
private final int id ;
private final StringProperty data = new SimpleStringProperty();

public Item(int id, String data) {
this.id = id ;
setData(data);
}

public final StringProperty dataProperty() {
return this.data;
}

public final String getData() {
return this.dataProperty().get();
}

public final void setData(final String data) {
this.dataProperty().set(data);
}

public int getId() {
return id ;
}

}

public static void main(String[] args) {
launch(args);
}
}

and the duplicate-cell-example.css:

.table-cell:duplicate {
-fx-background-color: -fx-background ;
-fx-background: red ;
}

table view with multiple columns

If you want all your UITableViews dependants and scrolling at the same time, there is no reason to create multiple table view.

You can just create un custom UITableViewCell with the layout and style you want! This will be easier and will consume less resources.

Accounting Style Table Cell in JavaFX

Using two labels in an AnchorPane should work.

(Update: Following @kleopatra's suggestion, I incorporated a DecimalFormat into this solution, which will (at least partially) localize the currency symbol, as well as the number of decimal digits, etc. This will make the assumption that the currency symbol is displayed to the left of the currency value, which isn't necessarily true for all currencies, but the assumption is somewhat implicit in the question anyway.)

public class PriceTableCell<S> extends TableCell<S, Long> {

private final AnchorPane pane ;
private final Label valueLabel ;
// locale-aware currency format to use for formatting
private DecimalFormat format;

public PriceTableCell() {
// grab an instance
format = (DecimalFormat) NumberFormat.getCurrencyInstance();
//get the currency symbol
String symbol = format.getCurrency().getSymbol();
// replace the currency symbol with an empty string
DecimalFormatSymbols symbols = format.getDecimalFormatSymbols();
symbols.setCurrencySymbol("");
format.setDecimalFormatSymbols(symbols);

Label currencySignLabel = new Label(symbol);
valueLabel = new Label();
pane = new AnchorPane(currencySignLabel, valueLabel);
AnchorPane.setLeftAnchor(currencySignLabel, 0.0);
AnchorPane.setRightAnchor(valueLabel, 0.0);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}

@Override
protected void updateItem(Long price, boolean empty) {
super.updateItem(price, empty);
if (empty) {
setGraphic(null);
} else {
// manual formatting
//String text = String.format("%,d.%02d", price / 100, Math.abs(price % 100));
valueLabel.setText(format.format(price));
setGraphic(pane);
}
}
}

Here is a SSCCE:

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Random;
import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class TableViewWithAccountingStyleCell extends Application {

public static class PriceTableCell<S> extends TableCell<S, Long> {

private final AnchorPane pane ;
private final Label valueLabel ;
// locale-aware currency format to use for formatting
private DecimalFormat format;

public PriceTableCell() {
// grab an instance
format = (DecimalFormat) NumberFormat.getCurrencyInstance();
//get the currency symbol
String symbol = format.getCurrency().getSymbol();
// replace the currency symbol with an empty string
DecimalFormatSymbols symbols = format.getDecimalFormatSymbols();
symbols.setCurrencySymbol("");
format.setDecimalFormatSymbols(symbols);

Label currencySignLabel = new Label(symbol);
valueLabel = new Label();
pane = new AnchorPane(currencySignLabel, valueLabel);
AnchorPane.setLeftAnchor(currencySignLabel, 0.0);
AnchorPane.setRightAnchor(valueLabel, 0.0);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}

@Override
protected void updateItem(Long price, boolean empty) {
super.updateItem(price, empty);
if (empty) {
setGraphic(null);
} else {
// manual formatting
//String text = String.format("%,d.%02d", price / 100, Math.abs(price % 100));
valueLabel.setText(format.format(price));
setGraphic(pane);
}
}
}

public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final LongProperty price = new SimpleLongProperty();

public Item(String name, long price) {
setName(name);
setPrice(price);
}

public StringProperty nameProperty() {
return name ;
}

public final String getName() {
return nameProperty().get();
}

public final void setName(String name) {
nameProperty().set(name);
}

public LongProperty priceProperty() {
return price ;
}

public final long getPrice() {
return priceProperty().get();
}

public final void setPrice(long price) {
priceProperty().set(price);
}
}

@Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.getColumns().add(column("Item", Item::nameProperty));
TableColumn<Item, Long> priceColumn = column("Price", item -> item.priceProperty().asObject());
priceColumn.setPrefWidth(300);

priceColumn.setCellFactory(tc -> new PriceTableCell<>());

table.getColumns().add(priceColumn);

Random rng = new Random();
for (int i = 1 ; i <= 20 ; i++) {
table.getItems().add(new Item("Item "+i, rng.nextInt(1_000_000)));
}

Scene scene = new Scene(table);
primaryStage.setScene(scene);
primaryStage.show();
}

private <S,T> TableColumn<S,T> column(String name, Function<S, ObservableValue<T>> property) {
TableColumn<S,T> column = new TableColumn<>(name);
column.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return column ;
}

public static void main(String[] args) {
launch(args);
}
}

which produces

Sample Image

Change JavaFX TableView font size

How to use the code below❓

You can add it into an external.css file and then connect it to your app simple like this:

1)What is wrong with my syntax calling a stylesheet (css) from an FXML file?
2)https://blog.idrsolutions.com/2014/04/use-external-css-files-javafx/

Below is some css code that can be used to modify the table look.Note that a lot more exist,for that you can check modena.css.

//Style of entire tableView
.table-view{
/*-fx-background-color: transparent;*/
}

//Style of entire tableView when is getting focused
.table-view:focused{
/*-fx-background-color: transparent;*/
}

//Style of each column header in the tableView
.table-view .column-header {
-fx-background-color: transparent;
}

//Style of each column header's background in the tableView
.table-view .column-header-background{
-fx-background-color: linear-gradient(#131313 0.0%, #424141 100.0%);
}

//Style of each column header's label in the tableView
.table-view .column-header-background .label{
-fx-background-color: transparent;
-fx-font-weight:bold;
-fx-text-fill: white;
}

//Style of each column in the tableView
.table-view .table-column{
-fx-alignment:center;
}

//Style of each table cell
.table-view .table-cell{
-fx-font-weight:bold;
-fx-font-size:15px; //the font size you asked in the comments below
/* -fx-text-fill:orange; */

}

//Style for each < non odd> row of table view
/* .table-row-cell{
-fx-background-color: white;
-fx-background-insets: 0.0, 0.0 0.0 1.0 0.0;
-fx-padding: 0.0em;
}

//Style for each <odd> row of table view
.table-row-cell:odd{
-fx-background-color: orange;
-fx-background-insets: 0.0, 0.0 0.0 1.0 0.0;
-fx-padding: 0.0em;
}
*/

//Style of each entire row in the table view
.table-row-cell:selected {
/* -fx-border-color:transparent firebrick transparent firebrick ;
-fx-border-width:2.0; */
}

//Style of each entire row in the table view when is hovered
.table-row-cell:hover {
-fx-background-color:orange;
}

//Style of each entire row in the table view when is getting focused
.table-row-cell:focused {
-fx-background-color:purple;
}

JavaFx TableView shows long string representation of columns

JavaFX Properties

When a class exposes a JavaFX property, it should adhere to the following pattern:

import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty

public class Foo {

// a field holding the property
private final StringProperty name = new SimpleStringProperty(this, "name");

// a setter method (but ONLY if the property is writable)
public final void setName(String name) {
this.name.set(name);
}

// a getter method
public final String getName() {
return name.get();
}

// a "property getter" method
public final StringProperty nameProperty() {
return name;
}
}

Notice that the name of the property is name, and how that is used in the names of the getter, setter, and property getter methods. The method names must follow that format.

The PropertyValueFactory class uses reflection to get the needed property. It relies on the method naming pattern described above. Your ErpSheet class does not follow the above pattern. The implicit getter methods (not property getter methods) return the property objects, not the values of the properties.



Kotlin & JavaFX Properties

Kotlin does not work especially well with JavaFX properties. You need to create two Kotlin properties, one for the JavaFX property object, and the other as a delegate (manually or via the by keyword) for the JavaFX property's value.

Here is an example:

import javafx.beans.property.IntegerProperty
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty

class Person(name: String = "", age: Int = 0) {

@get:JvmName("nameProperty")
val nameProperty: StringProperty = SimpleStringProperty(this, "name", name)
var name: String
get() = nameProperty.get()
set(value) = nameProperty.set(value)

@get:JvmName("ageProperty")
val ageProperty: IntegerProperty = SimpleIntegerProperty(this, "age", age)
var age: Int
get() = ageProperty.get()
set(value) = ageProperty.set(value)
}

You can see, for instance, that the name Kotlin property delegates its getter and setter to the nameProperty Kotlin property.

The @get:JvmName("nameProperty") annotation is necessary for Kotlin to generate the correct "property getter" method on the Java side (the JVM byte-code). Without that annotation, the getter would be named getNameProperty(), which does not match the pattern for JavaFX properties. You can get away with not using the annotation if you never plan to use your Kotlin code from Java, or use any class that relies on reflection (e.g., PropertyValueFactory) to get the property.

See the Kotlin documentation on delegated properties if you want to use the by keyword instead of manually writing the getter and setter (e.g., var name: String by nameProperty). You can write extension functions for ObservableValue / WritableValue (and ObservableIntegerValue / WritableIntegerValue, etc.) to implement this.

Runnable Example

Here is a runnable example using the above Person class. It also periodically increments the age of each Person so you can see that the TableView is observing the model items.

import javafx.animation.PauseTransition
import javafx.application.Application
import javafx.beans.property.IntegerProperty
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
import javafx.scene.Scene
import javafx.scene.control.TableColumn
import javafx.scene.control.TableView
import javafx.scene.control.cell.PropertyValueFactory
import javafx.stage.Stage
import javafx.util.Duration

fun main(args: Array<String>) = Application.launch(App::class.java, *args)

class App : Application() {

override fun start(primaryStage: Stage) {
val table = TableView<Person>()
table.columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY
table.items.addAll(
Person("John Doe", 35),
Person("Jane Doe", 42)
)

val nameCol = TableColumn<Person, String>("Name")
nameCol.cellValueFactory = PropertyValueFactory("name")
table.columns += nameCol

val ageCol = TableColumn<Person, Number>("Age")
ageCol.cellValueFactory = PropertyValueFactory("age")
table.columns += ageCol

primaryStage.scene = Scene(table, 600.0, 400.0)
primaryStage.show()

PauseTransition(Duration.seconds(1.0)).apply {
setOnFinished {
println("Incrementing age of each person...")
table.items.forEach { person -> person.age += 1 }
playFromStart()
}
play()
}
}
}

class Person(name: String = "", age: Int = 0) {

@get:JvmName("nameProperty")
val nameProperty: StringProperty = SimpleStringProperty(this, "name", name)
var name: String
get() = nameProperty.get()
set(value) = nameProperty.set(value)

@get:JvmName("ageProperty")
val ageProperty: IntegerProperty = SimpleIntegerProperty(this, "age", age)
var age: Int
get() = ageProperty.get()
set(value) = ageProperty.set(value)
}


Avoid PropertyValueFactory

With all that said, you should avoid using PropertyValueFactory, whether you're writing your application in Java or Kotlin. It was added when lambda expressions were not yet part of Java to help developers avoid writing verbose anonymous classes everywhere. However, it has two disadvantages: it relies on reflection and, more importantly, you lose compile-time validations (e.g., whether the property actually exists).

You should replace uses of PropertyValueFactory with lambdas. For example, from the above code, replace:

val nameCol = TableColumn<Person, String>("Name")
nameCol.cellValueFactory = PropertyValueFactory("name")
table.columns += nameCol

val ageCol = TableColumn<Person, Number>("Age")
ageCol.cellValueFactory = PropertyValueFactory("age")
table.columns += ageCol

With:

val nameCol = TableColumn<Person, String>("Name")
nameCol.setCellValueFactory { it.value.nameProperty }
table.columns += nameCol

val ageCol = TableColumn<Person, Number>("Age")
ageCol.setCellValueFactory { it.value.ageProperty }
table.columns += ageCol


Related Topics



Leave a reply



Submit