JavaFx-14 put this method in the TableColumnHeader, rather than in the Skin. How does one find a TableColumnHeader from a TableColumn and a TableView?
Don't know if you still need this, but if anyone else is interested, this is how I surpassed the problem in java, based on David Goodenough's scala code above.
The class for the TableSkin
import javafx.scene.control.TableColumnBase;
import javafx.scene.control.TableView;
import javafx.scene.control.skin.NestedTableColumnHeader;
import javafx.scene.control.skin.TableColumnHeader;
import javafx.scene.control.skin.TableHeaderRow;
import javafx.scene.control.skin.TableViewSkin;
import java.util.ArrayList;
import java.util.List;
public class CustomTableViewSkin extends TableViewSkin<Track> {
private List<CustomTableColumnHeader> columnHeadersList = new ArrayList<>();
private class CustomTableColumnHeader extends TableColumnHeader {
/**
* Creates a new TableColumnHeader instance to visually represent the given
* {#link TableColumnBase} instance.
*
* #param tc The table column to be visually represented by this instance.
*/
public CustomTableColumnHeader(TableColumnBase tc) {
super(tc);
}
public void resizeColumnToFitContent() {
super.resizeColumnToFitContent(-1);
}
}
public CustomTableViewSkin(TableView<Track> tableView) {
super(tableView);
}
#Override
protected TableHeaderRow createTableHeaderRow() {
return new TableHeaderRow(this) {
#Override
protected NestedTableColumnHeader createRootHeader() {
return new NestedTableColumnHeader(null) {
#Override
protected TableColumnHeader createTableColumnHeader(TableColumnBase col) {
CustomTableColumnHeader columnHeader = new CustomTableColumnHeader(col);
if (columnHeadersList == null) {
columnHeadersList = new ArrayList<>();
}
columnHeadersList.add(columnHeader);
return columnHeader;
}
};
}
};
}
public void resizeColumnToFit() {
if (!columnHeadersList.isEmpty()) {
for (CustomTableColumnHeader columnHeader : columnHeadersList) {
columnHeader.resizeColumnToFitContent();
}
}
}
}
And the class for the TableView
import javafx.scene.control.TableView;
public class CustomTableView extends TableView<Foo> {
private final CustomTableViewSkin thisSkin;
public CustomTableView() {
super();
setSkin(thisSkin = new CustomTableViewSkin(this));
}
public void resizeColumnsToFitContent() {
if (thisSkin != null && getSkin() == thisSkin) {
thisSkin.resizeColumnToFit();
}
}
}
Well this code is Scala not Java, but for the record the code below works:-
skin = new TableViewSkin(this) {
override protected def createTableHeaderRow:TableHeaderRow = {
new TableHeaderRow(this) {
override protected def createRootHeader:NestedTableColumnHeader = {
new NestedTableColumnHeader(null) {
override protected def createTableColumnHeader(col:TableColumnBase[_,_]) = {
val tableColumnHeader = new MyTableColumnHeader(col)
if(col == null || col.getColumns.isEmpty || col == getTableColumn) tableColumnHeader else new NestedTableColumnHeader(col)
}
}
}
}
}
}
private class MyTableColumnHeader(tc:TableColumnBase[_,_]) extends TableColumnHeader(tc) {
def resizeCol():Double = {
resizeColumnToFitContent(-1)
width.value
}
}
and then when I want to use it I use kleopatra's suggestion and:-
val w = columns.map { col =>
// To find the TableColumnHeader we can use column.getStyleableNode as suggested by kleopatra on StackOverflow:-
// you get the header from coumn.getStyleableNode (took a moment, had to check if it's really implemented) – kleopatra Jul 1 at 20:46
col.getStyleableNode() match {
case mtch:MyTableColumnHeader => mtch.resizeCol
case _ => col.width.get
}
}.sum
This is a hack'ish way of getting the TableColumnHeader:
public TableColumnHeader getTableColumnHeader(TableView<?> table, int index) {
return (TableColumnHeader) table.queryAccessibleAttribute(AccessibleAttribute.COLUMN_AT_INDEX, index);
}
Or, as #kleopatra suggested, for a non-hack'ish approach you can do:
public TableColumnHeader getTableColumnHeader(TableView<?> table, int index) {
return (TableColumnHeader) table.getColumns().get(index).getStyleableNode();
}
Make sure that the TableView is part of the scene graph.
However, the resizeColumnToFitContent method is protected and you won't be able to access it.
Related
I want to update a row to be strikethrough in a tableview when the user deletes it. I'm somewhat new to javafx and have been searching with no luck.
donationsTable.setRowFactory(tv -> {
TableRow<Donation> row = new TableRow<Donation>() {
// to force updateItem called
#Override
protected boolean isItemChanged(Donation d,
Donation d2) {
return true;
}
#Override
public void updateItem(Donation d, boolean empty) {
super.updateItem(d, empty) ;
if (d == null) {
setStyle("");
} else if (d.getAction().equals(Donation.DELETE_DONATION)) {
setStyle("delete-row");
} else if (d.getAction().equals(Donation.NEW_DONATION)) {
setStyle("-fx-font-weight: bold;");
} else {
setStyle("");
}
}
};
row.setOnMouseClicked(event -> {
deleteDonation.setDisable(false);
});
return row;
});
The bold works for new donations, but I can't get the strikethrough to work. I did see that it needs to be set on the text, not the row so my css is:
.delete-row .text {
-fx-strikethrough: true;
}
However, I'm getting a warning: WARNING CSS Error parsing '*{delete-row}: Expected COLON at [1,12]
I only have a very basic understanding of css. This is what I have seen in other answers, but I don't understand why it is not working for me.
Any help is much appreciated.
Based on James_D's suggestion, I changed updateItem:
public void updateItem(Donation d, boolean empty) {
super.updateItem(d, empty) ;
PseudoClass delete = PseudoClass.getPseudoClass("delete-row");
pseudoClassStateChanged(delete, d != null && d.getAction().equals(Donation.DELETE_DONATION));
PseudoClass add = PseudoClass.getPseudoClass("add-row");
pseudoClassStateChanged(add, d != null && d.getAction().equals(Donation.NEW_DONATION));
}
css has
.table-row-cell:delete-row .text {
-fx-strikethrough: true;
}
.table-row-cell:add-row {
-fx-font-weight: bold;
}
strikethrough still not working and bold stopped working.
The setStyle method will set an inline style on a Node; this style is in the form of a CSS rule. This is what you do with the bold case:
if (d.getAction().equals(Donation.NEW_DONATION)) {
setStyle("-fx-font-weight: bold;");
}
To add a CSS class to the list of classes for a node, get the list of the node's CSS classes with getStyleClass(), and manipulate it.
You have to be a little careful here, as the list can contain multiple copies of the same value, and additionally you have no control over how many times updateItem() is called and with which Donations as a parameter. The best option is to remove all instances of the class delete-row and add one back in under the correct conditions:
#Override
public void updateItem(Donation d, boolean empty) {
super.updateItem(d, empty) ;
getStyleClass().removeAll(Collections.singleton("delete-row"));
if (d == null) {
setStyle("");
} else if (d.getAction().equals(Donation.DELETE_DONATION)) {
setStyle("");
getStyleClass().add("delete-row");
} else if (d.getAction().equals(Donation.NEW_DONATION)) {
setStyle("-fx-font-weight: bold;");
} else {
setStyle("");
}
}
Another option is to use a CSS pseudoclass instead:
#Override
public void updateItem(Donation d, boolean empty) {
super.updateItem(d, empty) ;
PseudoClass delete = PseudoClass.getPseudoClass("delete-row");
pseudoClassStateChanged(delete, d != null && d.getAction().equals(Donation.DELETE_DONATION));
if (d != null && d.getAction().equals(Donation.NEW_DONATION)) {
setStyle("-fx-font-weight: bold;");
} else {
setStyle("");
}
}
with
.table-row-cell:delete-row .text {
-fx-strikethrough: true;
}
I would probably refactor the NEW_DONATION style as a pseudoclass as well in this scenario, for consistency.
Here's a complete example using pseudoclasses. Note that I changed the CSS for bold (as I understand it, using font-weight depends on the system having a bold font for the currently-selected font; using something generic (sans-serif) with a -fx-font rule is more robust.)
Donation.java
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Donation {
public enum Action { NEW_DONATION, DELETE_DONATION, NO_ACTION }
private final StringProperty name = new SimpleStringProperty() ;
private final ObjectProperty<Action> action = new SimpleObjectProperty<>() ;
public Donation(String name, Action action) {
setName(name);
setAction(action);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final ObjectProperty<Action> actionProperty() {
return this.action;
}
public final Action getAction() {
return this.actionProperty().get();
}
public final void setAction(final Action action) {
this.actionProperty().set(action);
}
}
App.java
import java.util.Random;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.Property;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage stage) {
TableView<Donation> table = new TableView<>();
table.setRowFactory(tv -> {
TableRow<Donation> row = new TableRow<>() {
#Override
protected void updateItem(Donation donation, boolean empty) {
super.updateItem(donation, empty);
PseudoClass add = PseudoClass.getPseudoClass("add-row");
pseudoClassStateChanged(add,
donation != null && donation.getAction() == Donation.Action.NEW_DONATION);
PseudoClass delete = PseudoClass.getPseudoClass("delete-row");
pseudoClassStateChanged(delete,
donation != null && donation.getAction() == Donation.Action.DELETE_DONATION);
}
};
return row ;
});
Random rng = new Random();
for (int i = 1 ; i <= 40 ; i++) {
table.getItems().add(new Donation("Donation "+i, Donation.Action.values()[rng.nextInt(3)]));
}
table.getColumns().add(column("Donation", Donation::nameProperty));
table.getColumns().add(column("Action", Donation::actionProperty));
BorderPane root = new BorderPane(table);
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
private static <S,T> TableColumn<S,T> column(String name, Function<S, Property<T>> prop) {
TableColumn<S,T> col = new TableColumn<>(name);
col.setCellValueFactory(data -> prop.apply(data.getValue()));
return col ;
}
public static void main(String[] args) {
launch();
}
}
style.css:
.table-row-cell:delete-row .text {
-fx-strikethrough: true;
}
.table-row-cell:add-row {
/* -fx-font-weight: bold; */
-fx-font: bold 1em sans-serif ;
}
Update:
If the property determining the style of the table row is not being observed by one of the columns (e.g. in the above example, the "action" column is not present), you need to arrange for the row to observe that property itself. This is a little tricky, as the row is reused for different table items, so you need to add and remove the listener from the correct property when that happens. This looks like:
table.setRowFactory(tv -> {
TableRow<Donation> row = new TableRow<>() {
// Listener that updates style when the actionProperty() changes
private final ChangeListener<Donation.Action> listener =
(obs, oldAction, newAction) -> updateStyle();
{
// make sure listener above is registered
// with the correct actionProperty()
itemProperty().addListener((obs, oldDonation, newDonation) -> {
if (oldDonation != null) {
oldDonation.actionProperty().removeListener(listener);
}
if (newDonation != null) {
newDonation.actionProperty().addListener(listener);
}
});
}
#Override
protected void updateItem(Donation donation, boolean empty) {
super.updateItem(donation, empty);
updateStyle();
}
private void updateStyle() {
Donation donation = getItem();
PseudoClass add = PseudoClass.getPseudoClass("add-row");
pseudoClassStateChanged(add, donation != null && donation.getAction() == Donation.Action.NEW_DONATION);
PseudoClass delete = PseudoClass.getPseudoClass("delete-row");
pseudoClassStateChanged(delete, donation != null && donation.getAction() == Donation.Action.DELETE_DONATION);
}
};
return row ;
});
I want get a tab through the tab's attribute text, but I cannot find a native method. So I writed a class to implement the purpose.
Question:
- I want to know if there is a native method for that purpose?
- Is there a better implementation?
Thanks!
```
package pre.huangjs.tabpane;
import javafx.collections.ListChangeListener;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import java.util.HashMap;
import java.util.List;
/**
* Created by huangjs on 2018/4/9.
*/
public class TabPaneExpansion {
private TabPane tabPane;
private HashMap<String, Tab> tabsMap;
public TabPane getTabPane() {
return tabPane;
}
public void setTabPane(TabPane tabPane) {
this.tabPane = tabPane;
}
public TabPaneExpansion() {
this.tabPane = new TabPane();
this.tabsMap = new HashMap<>();
initial();
}
public TabPaneExpansion(TabPane tabPane) {
this.tabPane = tabPane;
this.tabsMap = new HashMap<>();
initial();
}
private void initial() {
tabPane.getTabs().addListener(new ListChangeListener<Tab>() {
#Override
public void onChanged(Change<? extends Tab> c) {
while (c.next()) {
// if elements were added into list, the elements's text
// and the elements themselves need to be added into HashMap
if (c.wasAdded()) {
List<? extends Tab> addedTabs = c.getAddedSubList();
for (Tab tab : addedTabs) {
tabsMap.put(tab.getText(), tab);
}
}
// if elements were removed from list, the elements's text
// and the elements themselves need to be removed from HashMap
if(c.wasRemoved()){
List<? extends Tab> removedTabs = c.getRemoved();
for(Tab tab : removedTabs){
tabsMap.remove(tab.getText());
}
}
}
}
});
}
public boolean addTab(Tab tab) {
return this.tabPane.getTabs().add(tab);
}
public boolean addTabs(Tab... tabs) {
return this.tabPane.getTabs().addAll(tabs);
}
public boolean removeTab(String text){
return this.tabPane.getTabs().remove(getTabByText(text));
}
public Tab getTabByText(String text) {
return tabsMap.get(text);
}
}
```
I want to track focused node to perform some action after user inactivity on that node. But focused for my application means that there could be only 1 focus node in the entire application (application has numerous Stages and they creation are out of my control. I also cannot force different department of my company to use registry to register stages after they create them).
E.g. I have a property for focused component scene.focusOwnerProperty(), I can track whether Stage is stage.focusedProperty() but I'm searching the way to retrieve all stages.
Do you know the way?
I found a solution. com.sun.javafx.stage.StageHelper.getStages() list all stages.
You can Use Window[] windows = Window.getWindows(); this is part of the Public Api and works fine for me.
Thanks guys for answers. I write a class which provides an easy way to track currently focused Node and "focused" Scene. "Focused" means the focused component from focused Stage. If there are any mistakes in my code I would be grateful for if you notice and inform me.
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.stage.Stage;
import com.sun.javafx.stage.StageHelper;
/**
* Tracks focused component among all {#link Stage}'s. Currently this is singleton but it will make service
* out of it.
*
* #author created: kszymanek on 8 sty 2016 15:19:32
*/
public class GlobalFocusTracker
{
private static final GlobalFocusTracker INSTANCE = new GlobalFocusTracker();
/**
* tracks stage list and each stage and registers {#link #sceneListener} on
* the focused stage
*/
private StagesListener stagesListener;
/**
* tracks scene of the focused stage and registers
* {#link #focusOwnerListener} on it
*/
private SceneListener sceneListener;
private FocusOwnerListener focusOwnerListener;
private ReadOnlyObjectWrapper focusedNodeProperty = new ReadOnlyObjectWrapper();
private ReadOnlyObjectWrapper focusedSceneProperty = new ReadOnlyObjectWrapper();
private GlobalFocusTracker()
{
}
public static GlobalFocusTracker getInstance()
{
return INSTANCE;
}
/**
* #return current {#link Scene} from the currently focused {#link Stage}
*/
public ReadOnlyObjectProperty focusedSceneProperty()
{
init();
return focusedSceneProperty.getReadOnlyProperty();
}
/**
* #return focused node among all stages. There could be one focus owner at
* the time. If end user focuses some other application there would
* be no focus node.
*/
public ReadOnlyObjectProperty focusedNodeProperty()
{
init();
return focusedNodeProperty.getReadOnlyProperty();
}
private void init()
{
if(stagesListener == null)
{
stagesListener = new StagesListener();
sceneListener = new SceneListener();
focusOwnerListener = new FocusOwnerListener();
stagesListener.register();
}
}
private class StagesListener implements ListChangeListener, ChangeListener
{
private ObservableList stages;
#Override
public void onChanged(javafx.collections.ListChangeListener.Change aChange)
{
while(aChange.next())
{
if(aChange.wasRemoved())
{
for(Stage stage : aChange.getRemoved())
{
stage.focusedProperty().removeListener(this);
}
}
if(aChange.wasAdded())
{
for(Stage stage : aChange.getAddedSubList())
{
stage.focusedProperty().addListener(this);
}
}
}
}
#Override
public void changed(ObservableValue aObservable, Boolean aOld, Boolean aNew)
{
Stage focusedStage = null;
for(Stage stage : stages)
{
if(stage.isFocused())
{
focusedStage = stage;
}
}
sceneListener.register(focusedStage);
}
public void register()
{
if(stages == null)
{
stages = StageHelper.getStages();
stages.addListener(this);
for(Stage stage : stages)
{
stage.focusedProperty().addListener(this);
if(stage.isFocused())
{
sceneListener.register(stage);
}
}
}
}
}
private class SceneListener implements ChangeListener
{
private Stage stage;
#Override
public void changed(ObservableValue aObservable, Scene aOld, Scene aNew)
{
focusOwnerListener.register(aNew);
}
/**
* #param aStage is {#code null} protected
*/
public void register(Stage aStage)
{
if(aStage != stage)
{
unregister();
stage = aStage;
if(aStage != null)
{
aStage.sceneProperty().addListener(this);
focusOwnerListener.register(aStage.getScene());
}
}
}
public void unregister()
{
if(stage != null)
{
focusOwnerListener.unregister();
stage.sceneProperty().removeListener(this);
stage = null;
}
}
}
private class FocusOwnerListener implements ChangeListener
{
private Scene scene;
#Override
public void changed(ObservableValue aObservable, Node aOld, Node aNew)
{
focusedNodeProperty.set(aNew);
}
/**
* #param aScene can be {#code null} in such case it is only an equivalent
* of {#link #unregister()}
*/
public void register(Scene aScene)
{
if(scene != aScene)
{
unregister();
scene = aScene;
focusedSceneProperty.set(aScene);
if(aScene != null)
{
focusedNodeProperty.set(aScene.getFocusOwner());
aScene.focusOwnerProperty().addListener(this);
}
}
}
public void unregister()
{
if(scene != null)
{
focusedSceneProperty.set(null);
focusedNodeProperty.set(null);
scene.focusOwnerProperty().removeListener(this);
scene = null;
}
}
}
}
I am building my first javafx (2.2) application. The user selects a number of tasks to execute, by selecting checkboxes in a treeview.
I am trying to figure out how, after a task completes, to change the style of the related TreeCell.
public class WorkbenchSscce extends Application {
public static void main(String...args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
final CheckBoxTreeItem<String> rootNode = new CheckBoxTreeItem<>("parent");
final CheckBoxTreeItem<String> taskOne = new CheckBoxTreeItem<>("task one");
final CheckBoxTreeItem<String> taskTwo = new CheckBoxTreeItem<>("task two");
rootNode.getChildren().addAll(taskOne, taskTwo);
TreeView<String> treeView = new TreeView<>(rootNode);
treeView.setEditable(true);
treeView.setCellFactory(CheckBoxTreeCell.<String>forTreeView());
treeView.setShowRoot(false);
Button executeButton = new Button("Execute");
executeButton.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (taskOne.isSelected()) {
executeTask(1);
/**
* ?????
* give the TreeCell for taskOne a green background, to indicate it is complete
* ?????
*/
}
if (taskTwo.isSelected()) {
executeTask(2);
/**
* ?????
* give the TreeCell for taskTwo a green background, to indicate it is complete
* ?????
*/
}
}
});
VBox box = new VBox();
box.getChildren().addAll(treeView, executeButton);
Scene scene = new Scene(box);
stage.setScene(scene);
stage.show();
}
public void executeTask(int input) {
// do something
}
}
I can see how to style the CheckBoxTreeCells at creation time.
I see how to change styles when user events happen to the TreeView (using EventListeners).
But I can't see how to style a tree cell when the source of the event is internal to the application. See comments in the MouseEvent handler above.
The key is to observe the state of the Task (I used a Service instead of a Task in this example, as it can be run multiple times) from the cell factory. To do this, you need the data type of the TreeItem to be something that has an observable property representing the current state of the task/service. The easiest way to do this, if you can, is to make the data type of the TreeItems the Task itself (so conceptually, your TreeView is displaying Tasks).
This is slightly subtle as the item (i.e. Task) represented by a given cell can change. In this example I just observe the cell's item property, removing a listener that observes the task's state from an item the cell is no longer representing and adding the listener to the item it now represents. If you use the EasyBind framework (and Java 8, which it requires), you can clean this up a bit, doing something like
EasyBind.select(cell.itemProperty())
.selectObject(Service::stateProperty)
.addListener((ov, oldState, newState) -> updateCell(cell) );
Full example (using JavaFX 2.2, though I compiled under Java 8, so some Java 8 features may have snuck in):
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.concurrent.Worker.State;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.CheckBoxTreeCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.StringConverter;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
final BorderPane root = new BorderPane();
final TreeView<SelectableService> tree = new TreeView<>();
final TreeItem<SelectableService> treeRoot = new TreeItem<>(new SelectableService("Parent"));
for (int i=1; i<=10; i++) {
treeRoot.getChildren().add(new TreeItem<>(new SelectableService("Task "+i)));
}
tree.setRoot(treeRoot);
final Button startButton = new Button("Start selected tasks");
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
for (SelectableService service : findSelectedTasks(treeRoot)) {
service.restart();
}
}
});
final HBox controls = new HBox(5);
controls.getChildren().add(startButton);
controls.setPadding(new Insets(10));
controls.setAlignment(Pos.CENTER);
root.setCenter(tree);
root.setBottom(controls);
tree.setCellFactory(new Callback<TreeView<SelectableService>, TreeCell<SelectableService>>() {
#Override
public TreeCell<SelectableService> call(TreeView<SelectableService> param) {
return createCell();
}
});
Scene scene = new Scene(root, 400, 600);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
private CheckBoxTreeCell<SelectableService> createCell() {
// CheckBoxTreeCell whose check box state is mapped to the selected property of the task:
final CheckBoxTreeCell<SelectableService> cell = new CheckBoxTreeCell<SelectableService>(new Callback<TreeItem<SelectableService>, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(TreeItem<SelectableService> treeItem) {
SelectableService task = treeItem.getValue();
if (task != null) {
return task.selectedProperty();
} else {
return null ;
}
}
});
final ChangeListener<State> taskStateListener = new ChangeListener<State>() {
#Override
public void changed(
ObservableValue<? extends State> observable,
State oldValue, State newValue) {
updateCell(cell);
}
};
cell.itemProperty().addListener(new ChangeListener<SelectableService>() {
#Override
public void changed(
ObservableValue<? extends SelectableService> observable,
SelectableService oldTask, SelectableService newTask) {
if (oldTask != null) {
oldTask.stateProperty().removeListener(taskStateListener);
}
if (newTask != null) {
newTask.stateProperty().addListener(taskStateListener);
}
updateCell(cell);
}
});
cell.setConverter(new StringConverter<TreeItem<SelectableService>>() {
#Override
public String toString(TreeItem<SelectableService> treeItem) {
SelectableService task = treeItem.getValue();
if (task == null) {
return null ;
} else {
return task.getName();
}
}
#Override
public TreeItem<SelectableService> fromString(String string) {
// Not supported
throw new UnsupportedOperationException("Uneditable tree cell does not create SelectableTasks");
}
});
return cell;
}
private void updateCell(CheckBoxTreeCell<SelectableService> cell) {
cell.getStyleClass().removeAll(Arrays.asList("running", "finished", "failed"));
SelectableService task = cell.getItem();
if (task != null) {
State state = task.getState();
// Update style class:
if (state == State.RUNNING) {
cell.getStyleClass().add("running");
} else if (state == State.SUCCEEDED) {
cell.getStyleClass().add("finished");
} else if (state == State.FAILED){
cell.getStyleClass().add("failed");
}
}
}
private Set<SelectableService> findSelectedTasks(TreeItem<SelectableService> treeItem) {
Set<SelectableService> selectedTasks = new HashSet<>();
addTaskAndChildTasksIfSelected(selectedTasks, treeItem) ;
return selectedTasks ;
}
private void addTaskAndChildTasksIfSelected(Set<SelectableService> selectedTasks, TreeItem<SelectableService> treeItem) {
SelectableService task = treeItem.getValue();
if (task != null && task.isSelected()) {
selectedTasks.add(task);
}
for (TreeItem<SelectableService> child : treeItem.getChildren()) {
addTaskAndChildTasksIfSelected(selectedTasks, child);
}
}
public static class SelectableService extends Service<Void> {
private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected", false);
public final BooleanProperty selectedProperty() {
return this.selected;
}
public final boolean isSelected() {
return this.selectedProperty().get();
}
public final void setSelected(final boolean selected) {
this.selectedProperty().set(selected);
}
private final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper(this, "name");
private final void setName(String name) {
this.name.set(name);
}
public final String getName() {
return name.get() ;
}
public final ReadOnlyStringProperty nameProperty() {
return name.getReadOnlyProperty();
}
public SelectableService(String name) {
setExecutor(Executors.newCachedThreadPool(new ThreadFactory() {
#Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t ;
}
}));
setName(name);
}
#Override
public Task<Void> createTask() {
return new Task<Void>() {
#Override
public Void call() throws Exception {
// just a mock task: pauses for a random time, then throws an exception with
// probability 0.25
Random rng = new Random();
Thread.sleep(2000 + rng.nextInt(2000));
if (rng.nextDouble() < 0.25) {
throw new Exception("Task failed");
}
return null ;
}
};
}
}
public static void main(String[] args) {
launch(args);
}
}
application.css is simply
.finished {
-fx-background: green ;
}
.failed {
-fx-background: red ;
}
.running {
-fx-background: yellow ;
}
This is quite considerably cleaner in Java 8, by the way, but since you posted JavaFX 2.2-style code, I assumed you were still using the old version. Java 8 also allows you to use pseudoclasses for the css style, which is a bit nicer (and in general has better performance, though it's a moot point here).
How i can refresh listfragment? no tutorials from net work :(
i have no idea, how reload this listfragment. I tried reload by transaktionmanager, but its colide with ActionBar.TabListener, this is not support.v4. how can i retrieve new data from loadermanager and update listfragment?
Activity:
package sk.test;
import android.app.ActionBar;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.app.FragmentTransaction;
import android.support.v4.app.LoaderManager;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import java.util.List;
import java.util.Locale;
import sk.test.frags.TodoFragment;
import sk.test.prefs.EditPreferences;
import sk.test.task.DataListLoader;
import sk.test.xml.Item;
public class MainActivity extends FragmentActivity implements ActionBar.TabListener {
SectionsPagerAdapter mSectionsPagerAdapter;
/**
* The {#link ViewPager} that will host the section contents.
*/
ViewPager mViewPager;
private TodoFragment todoFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set up the action bar.
final ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// Create the adapter that will return a fragment for each of the three
// primary sections of the app.
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
// When swiping between different sections, select the corresponding
// tab. We can also use ActionBar.Tab#select() to do this if we have
// a reference to the Tab.
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
}
});
// For each of the sections in the app, add a tab to the action bar.
for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
// Create a tab with text corresponding to the page title defined by
// the adapter. Also specify this Activity object, which implements
// the TabListener interface, as the callback (listener) for when
// this tab is selected.
actionBar.addTab(
actionBar.newTab()
.setText(mSectionsPagerAdapter.getPageTitle(i))
.setTabListener(this));
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
switch (item.getItemId()) {
case R.id.action_settings:
startActivity(new Intent(getApplicationContext(), EditPreferences.class));
return true;
case R.id.refresh:
//HERE CODE FOR RELOAD todoFragment.reloadData() ????
return true;
default:
return true;
}
}
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
// When the given tab is selected, switch to the corresponding page in
// the ViewPager.
mViewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
}
#Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
}
/**
* A {#link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
private boolean wantDone = false;
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
todoFragment = new TodoFragment();
this.wantDone = position == 0 ? false : true;
Bundle args = new Bundle();
args.putBoolean(TodoFragment.TASK_TYPE, this.wantDone);
todoFragment.setArguments(args);
return todoFragment;
}
#Override
public int getCount() {
return 2;
}
public boolean getWantDone(){
return this.wantDone;
}
#Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
switch (position) {
case 0:
return getString(R.string.todotask_planned).toUpperCase(l);
case 1:
return getString(R.string.todotask_done).toUpperCase(l);
}
return null;
}
}
}
ListFragment:
package sk.test;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.View;
import android.widget.ListView;
import java.util.List;
import sk.test.adapter.CustomArrayAdapter;
import sk.test.task.DataListLoader;
import sk.test.xml.Item;
/**
* Created by Peter on 29.7.2013.
*/
public class TodoFragment extends ListFragment implements LoaderManager.LoaderCallbacks<List<Item>> {
public static String TASK_TYPE = "taskType";
private static final String XML_SOURCE = "http://******/";
private boolean wantDone = false;
CustomArrayAdapter mAdapter;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.i("TODOLIST", "DataListFragment.onActivityCreated");
this.wantDone = getArguments().getBoolean(TASK_TYPE);
// Initially there is no data
setEmptyText("No Data Here");
// Create an empty adapter we will use to display the loaded data.
mAdapter = new CustomArrayAdapter(getActivity());
setListAdapter(mAdapter);
// Start out with a progress indicator.
setListShown(false);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
setHasOptionsMenu(true);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Insert desired behavior here.
Log.i("TODOLIST", "Item clicked: " + id);
}
#Override
public Loader<List<Item>> onCreateLoader(int i, Bundle bundle) {
Log.i("TODOLIST", "DataListFragment.onCreateLoader");
return new DataListLoader(getActivity(), this.wantDone);
}
#Override
public void onLoadFinished(Loader<List<Item>> listLoader, List<Item> items) {
mAdapter.setData(items);
Log.i("TODOLIST", "DataListFragment.onLoadFinished");
// The list should now be shown.
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
#Override
public void onLoaderReset(Loader<List<Item>> listLoader) {
}
public void reloadData(){
//UPDATE LIST.. HOW?
}
}
Loader:
package sk.test;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v4.content.AsyncTaskLoader;
import android.util.Log;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import sk.test.commons.Commons;
import sk.test.xml.Item;
import sk.test.xml.Response;
/**
* Created by Peter Chovan on 29.7.2013.
*/
public class DataListLoader extends AsyncTaskLoader<List<Item>> {
private List<Item> todoTasks;
private boolean wantDone;
SharedPreferences prefs;
public DataListLoader(Context context, boolean wantDone) {
super(context);
this.wantDone = wantDone;
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
}
#Override
public List<Item> loadInBackground() {
Log.i("TODOLIST", "DataListLoader.loadInBackground");
String xmlData = getXmlData(prefs.getString("service_url", "http://*****/"));
List<Item> entries = new ArrayList<Item>();
String state = wantDone ? "WANT DONE" : "WANT PLANNED";
if (xmlData != null) {
xmlData = xmlData.replaceAll("<([^/]+?)/>", "<$1> </$1>");
Serializer serializer = new Persister();
try {
Response res = serializer.read(Response.class, xmlData, false);
for (Item i : res.getItems().getItem()) {
if (i.isDone() == wantDone) {
entries.add(i);
}
}
} catch (Exception e) {
for (StackTraceElement s : e.getStackTrace()) {
Log.e("TEST serializer", s.toString());
}
}
} else {
Log.e("TODOLIST DATA", "NULL");
}
return entries;
}
public String getXmlData(String uri) {
try {
URL url = new URL(uri);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setDoOutput(true);
con.setRequestProperty("Accept", "application/xml");
Map<String, String> params = new HashMap<String, String>();
params.put("user", prefs.getString("service_login", "devel"));
params.put("pass", prefs.getString("service_password", "devel"));
params.put("class", "GetList");
OutputStreamWriter wr = new OutputStreamWriter(con.getOutputStream());
wr.write(Commons.getRequestData(params)); //add request params
wr.flush();
String xmlData = readStream(con.getInputStream());
wr.close();
con.disconnect();
return xmlData;
} catch (Exception e) {
for (StackTraceElement s : e.getStackTrace()) {
Log.e("TODOLIST", "doInBackground" + s.toString());
}
}
return null;
}
private String readStream(InputStream in) {
BufferedReader reader = null;
String result = "";
try {
reader = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = reader.readLine()) != null) {
result += line + "\n";
}
return result;
} catch (IOException e) {
for (StackTraceElement s : e.getStackTrace()) {
Log.e("TODOLIST", "ReadStream || " + s.toString());
}
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
for (StackTraceElement s : e.getStackTrace()) {
Log.e("TODOLIST", "ReadStream || " + "Error while closing Reader");
}
}
}
}
return null;
}
/**
* Called when there is new data to deliver to the client. The
* super class will take care of delivering it; the implementation
* here just adds a little more logic.
*/
#Override
public void deliverResult(List<Item> listOfData) {
if (isReset()) {
// An async query came in while the loader is stopped. We
// don't need the result.
if (listOfData != null) {
onReleaseResources(listOfData);
}
}
List<Item> oldApps = listOfData;
todoTasks = listOfData;
if (isStarted()) {
// If the Loader is currently started, we can immediately
// deliver its results.
super.deliverResult(listOfData);
}
// At this point we can release the resources associated with
// 'oldApps' if needed; now that the new result is delivered we
// know that it is no longer in use.
if (oldApps != null) {
onReleaseResources(oldApps);
}
}
/**
* Handles a request to start the Loader.
*/
#Override
protected void onStartLoading() {
if (todoTasks != null) {
// If we currently have a result available, deliver it
// immediately.
deliverResult(todoTasks);
}
if (takeContentChanged() || todoTasks == null) {
// If the data has changed since the last time it was loaded
// or is not currently available, start a load.
forceLoad();
}
}
/**
* Handles a request to stop the Loader.
*/
#Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
/**
* Handles a request to cancel a load.
*/
#Override
public void onCanceled(List<Item> apps) {
super.onCanceled(apps);
// At this point we can release the resources associated with 'apps'
// if needed.
onReleaseResources(apps);
}
/**
* Handles a request to completely reset the Loader.
*/
#Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
// At this point we can release the resources associated with 'apps'
// if needed.
if (todoTasks != null) {
onReleaseResources(todoTasks);
todoTasks = null;
}
}
/**
* Helper function to take care of releasing resources associated
* with an actively loaded data set.
*/
protected void onReleaseResources(List<Item> apps) {
}
}
Use this to restart the Loader: getLoaderManager().restartLoader(0, null, this);