I am using the GWT CellTree class and I want to be able to update another UI element on the page when the selection changes.
I have this:
#UiField (provided = true)
CellTree folderTree;
Which I populate with data on the page load, then I need the #UIiHandler, something like:
#UiHandler("folderTree")
void onTreeCellChange(????? e){
//update UI
}
I've already tried things like:
ValueChangeEvent<List<Folder>> //List<Folder> being the underlying class for the CellTree.
SelectionHandler<CellTree>
ClickEvent
In all the above cases, I get a Deferred binding failed on form load.
I feel like I am missing something simple, but cannot seem to find it with my google-fu.
You can add handler to NodeInfo of TreeViewModel of CellTree.
Here is example how it is possible to do:
First of all let's define what is folder:
// the simplest possible folder even without subfolders just to make example working
public class Folder {
private String name;
public Folder(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Let's say part of your ui.xml is the following:
<g:HorizontalPanel>
<cellview:CellTree ui:field="folderTree"/>
<g:Label ui:field="folderName"/>
</g:HorizontalPanel>
Than you have UiBinder:
public static interface Binder extends UiBinder<HorizontalPanel, BinderOwner> {}
Here is how you define FolerTreeModel that is required for your CellTree:
public static class FolderTreeModel implements TreeViewModel {
private final ListDataProvider<Folder> dataProvider;
private final CellPreviewEvent.Handler<Folder> folderSelectionHandler;
public FolderTreeModel(List<Folder> folders, CellPreviewEvent.Handler<Folder> folderSelectionHandler) {
this.folderSelectionHandler = folderSelectionHandler;
dataProvider = new ListDataProvider<Folder>(folders);
}
#Override
public <T> TreeViewModel.NodeInfo<?> getNodeInfo(T value) {
return new DefaultNodeInfo<Folder>(dataProvider, new AbstractCell<Folder>() {
// simple renderer that renders folder name
#Override
public void render(Context context, Folder value, SafeHtmlBuilder sb) {
sb.appendEscaped(value.getName());
}
},
new SingleSelectionModel<Folder>(), // using single selection model
folderSelectionHandler, // add handler to the node info
null); // value updater can be null
}
#Override
public boolean isLeaf(Object value) {
return false; // I will say that every node is not leaf for simplicity
}
}
And your BinderOwner class:
public static class BinderOwner {
#UiField(provided = true)
CellTree folderTree;
#UiField
Label folderName; //your other UI element that you will change: show the name of selected folder
public BinderOwner() {
//List of folders to show something
ArrayList<Folder> folders = new ArrayList<Folder>();
folders.add(new Folder("A"));
folders.add(new Folder("B"));
folders.add(new Folder("C"));
// define folder tree
folderTree = new CellTree(new FolderTreeModel(folders, new CellPreviewEvent.Handler<Folder>() {
// this is your handler
#Override
public void onCellPreview(CellPreviewEvent<Folder> event) {
// set folder name to label on folder selection
folderName.setText(event.getValue().getName());
}
}), null);
}
}
Related
I am still new to Vaadin so, please bear with it.
We are currently migrating from Vaadin framework 8.0 to 8.3.2. One of the reasons of doing is that there's a requirement of using tree for the menu. Since 8.0 doesn't have tree, the workaround for generating a menu is by instantiating an inner Button class with the help of an Enum class in a loop (for user permission control):
public final class ValoMenuItemButton extends Button {
private static final String STYLE_SELECTED = "selected";
private final DashboardViewType view;
public ValoMenuItemButton(final DashboardViewType view) {
this.view = view;
setPrimaryStyleName("valo-menu-item");
setIcon(view.getIcon());
setCaption(view.getViewName().substring(0, 1).toUpperCase()
+ view.getViewName().substring(1));
DashboardEventBus.register(this);
addClickListener(new ClickListener() {
#Override
public void buttonClick(final ClickEvent event) {
UI.getCurrent().getNavigator()
.navigateTo(view.getViewName());
}
});
}
#Subscribe
public void postViewChange(final PostViewChangeEvent event) {
removeStyleName(STYLE_SELECTED);
if (event.getView() == view) {
addStyleName(STYLE_SELECTED);
}
}
}
The enum class structure is built in this manner:
AUDIT("Receipt Validation", RcptValidation.class, FontAwesome.BAR_CHART_O, false),
AUDIT1("Matching - Receipt not in SYCARDA", RcptNotInSycarda.class, FontAwesome.BAR_CHART_O, false),
AUDIT2("Matching - Receipt not in POS", RcptNotInPos.class, FontAwesome.BAR_CHART_O, false),
AUDIT3("Missing Sequence", MissSequence.class, FontAwesome.BAR_CHART_O, false),
AUDIT4("*Debug Purposes", LineAmtVsTotal.class, FontAwesome.BAR_CHART_O, false);
private DashboardViewType(final String viewName,
final Class<? extends View> viewClass, final Resource icon,
final boolean stateful) {
this.viewName = viewName;
this.viewClass = viewClass;
this.icon = icon;
this.stateful = stateful;
}
So far, I've not found any examples that are written around the v8 framework, while most of the sample code that I've seen are based on v7 framework.
I've attempted to write something like this, but the tree sub menus do not come out as it is (I've left out the expand and collapse event as that can be handled later).
My attempted code on the tree is this:
TreeData <String> treeData = new TreeData();
treeData.addRootItems("Dashboard","Sales","Sales Pattern","Top SKUs","Audit");
// The loop starts here (for DashboardViewType view: DashboardViewType.values)
if(enabled){
if(StringUtils.startsWith(view.getViewName(), "SALES")){
if (StringUtils.contains(view.getViewName(),"SALES_PATTERN")){
treeData.addItem( "Sales Pattern", view.getViewName());
}else{ treeData.addItem( "Sales", view.getViewName());
}
}else if (StringUtils.startsWith(view.getViewName(), "TOP_SKUS")){
treeData.addItem( "Top SKUs", view.getViewName());
}else if (StringUtils.startsWith(view.getViewName(), "AUDIT")){
treeData.addItem( "Audit", view.getViewName());
}else if (StringUtils.startsWith(view.getViewName(), "DASHBOARD")){
treeData.addItem( "Dashboard", view.getViewName());
}
DashboardEventBus.register(view);
}
// loop ends here
Tree<String> tree = new Tree<>("Sycarda Dashboard");
tree.setDataProvider(new TreeDataProvider<>(treeData));
tree.setItemIconGenerator(item -> { return FontAwesome.BAR_CHART_O; });
tree.expand("Sales","Sales Pattern","Top SKUs","Audit");
tree.addSelectionListener(e -> new Button.ClickListener() {
#Override public void buttonClick(Button.ClickEvent event) {
DashboardEventBus.register(event);
UI.getCurrent().getNavigator().navigateTo(event.getClass().getName());
}
});
This was posted originally at the Vaadin forum, but since there were no answers to that, I am putting it here. I'd appreciate if there's any input or another approach for this problem.
Thanks in advance.
In Vaadin 8 you can simply define the "get children" method when adding the data. In your case the enum class should provide some method like "getSubItems", which you could then set as the value provider. The following example shows it in a similar way, where "rootItems" is simply the same as your top level enum instances and MenuItem the same as your enumeration.
static {
rootItems = Arrays.asList(...);
}
#PostConstruct
private void init() {
Tree<MenuItem> tree = new Tree<>();
tree.setItems(rootItems, MenuItem::getSubItems);
}
private class MenuItem {
private String name;
private Resource icon;
private Collection<MenuItem> subItems;
public Collection<MenuItem> getSubItems() {
return subItems;
}
// ... other getter and constructor omitted;
}
Someone has shown an example and it is similar to what Stefan mentioned. In context with my requirement, the steps involved include:
Create a wrapper class that includes:
private DashboardViewType view;
private Resource icon;
private boolean stateful;
private Class<? extends View> viewClass;
private String viewName;
//Create the get / set methods for those attributes above
//Constructor for the wrapper class is below.
public TreeMenuItem(DashboardViewType view){
this.view = view;
}
For the Enum class additional main menu items are added. Default main class can be used since you can't put a null.
public enum DashboardViewType {
SALES("Sales",DashboardView.class,FontAwesome.HOME,false),
SALES_PATTERN("Sales Pattern",DashboardView.class,FontAwesome.HOME,false),
TOP_SKUs("Top SKUs",DashboardView.class,FontAwesome.HOME,false),
AUDIT("Audit",DashboardView.class,FontAwesome.HOME,false)
}
The tree is built in this manner:
private Component buildTree(){
Tree<TreeMenuItem> tree = new Tree<>();
TreeData<TreeMenuItem> treeData = new TreeData<>();
//This is for items that have no child.
TreeMenuItem dashboardItem = new TreeMenuItem(DashboardViewType.DASHBOARD);
dashboardItem.setIcon(VaadinIcons.HOME_O);
dashboardItem.setStateful(DashboardViewType.DASHBOARD.isStateful());
dashboardItem.setViewName(DashboardViewType.DASHBOARD.getViewName());
treeData.addItem(null, dashboardItem);
for (DashboardViewType type : DashboardViewType.values()) {
TreeMenuItem menuItem = new TreeMenuItem(type);
menuItem.setIcon(VaadinIcons.HOME_O);
menuItem.setViewName(type.getViewName());
menuItem.setStateful(false);
treeData.addItem(null, menuItem);
getSubMenuItems(type).forEach(subView -> {
TreeMenuItem subItem = new TreeMenuItem(subView);
subItem.setViewName(subView.getViewName().substring(0, 1).toUpperCase()
+ subView.getViewName().substring(1));
subItem.setIcon(subView.getIcon());
subItem.setStateful(subView.isStateful());
subItem.setView(subView);
subItem.setViewClass(subView.getViewClass());
treeData.addItem(menuItem, subItem);
});
}
}
tree.setDataProvider(new TreeDataProvider<>(treeData));
tree.setItemIconGenerator(TreeMenuItem::getIcon);
tree.setItemCaptionGenerator(TreeMenuItem::getViewName);
tree.addItemClickListener((Tree.ItemClick<TreeMenuItem> event) -> {
DashboardEventBus.register(event.getItem().getView()); UI.getCurrent().getNavigator().navigateTo(event.getItem().getViewName());
});
}
The logic to create subviews:
private List getSubMenuItems(DashboardViewType type) {
List<DashboardViewType> dashboardList;
switch(type){
case TOP_SKUs:
dashboardList = new LinkedList<>(Arrays.asList(DashboardViewType.TOP_SKUs1,
DashboardViewType.TOP_SKUs2,
DashboardViewType.TOP_SKUs3,
DashboardViewType.TOP_SKUs4));
filterByUserLevel(dashboardList,subACL4);
return dashboardList;
case AUDIT:
dashboardList = new LinkedList<>(Arrays.asList(DashboardViewType.AUDIT1,
DashboardViewType.AUDIT2,
DashboardViewType.AUDIT3,
DashboardViewType.AUDIT4,
DashboardViewType.AUDIT5));
filterByUserLevel(dashboardList,subACL5);
return dashboardList;
case DASHBOARD:
break;
default:
break;
}
return Collections.emptyList();
}
Add additional cases if required so. After that, the function controls remove the elements that are not part of the user level:
private List<DashboardType> filterByUserLevel(List<DashboardType>list, String u){
if(list.size() == subACL.length()){
for(int i=0; i<list.size(); i++){
if(StringUtils.substring(subACL, i, i+1).equalsIgnoreCase("0")){
list.remove(i);
}
}
Collections.sort(list);
return list;
//this removes unwanted sub-menu items according current user level.
}
}
To give some background: I now am able to load files onto my mp3 program and play them but all the values in my tableview are null?
My song class
package application;
//imports here
public class Song {
private String title;
private String artist;
private String album;
private SimpleStringProperty pTitle;
private SimpleStringProperty pArtist;
private SimpleStringProperty pAlbum;
private Media music;
private MediaPlayer mp;
private Image coverArt;
public Song(File file) {
music = new Media(file.toURI().toString());
music.getMetadata().addListener((Change<? extends String, ? extends Object> c) -> {
if (c.wasAdded()) {
if ("artist".equals(c.getKey())) {
System.out.println(c.getKey()+":"+c.getValueAdded());
this.pArtist = new SimpleStringProperty(c.getValueAdded().toString());
//pArtist.set(c.getValueAdded().toString());
artist = c.getValueAdded().toString();
} else if ("title".equals(c.getKey())) {
title = c.getValueAdded().toString();
System.out.println(c.getKey()+":"+c.getValueAdded());
} else if ("album".equals(c.getKey())) {
album = c.getValueAdded().toString();
System.out.println(c.getKey()+":"+c.getValueAdded());
} else if ("image".equals(c.getKey())) {
coverArt = (Image) c.getValueAdded();
}
}
});
mp = new MediaPlayer(music);
System.out.println(pArtist);
System.out.println(artist);
//artist = (String) mp.getMedia().getMetadata().get("artist");
//title = (String) music.getMetadata().get("title");
//album = (String) music.getMetadata().get("album");
//artist = "test";
//album = "test";
//title = "test";
}
public void play() {
mp.play();
}
public void pause() {
mp.pause();
}
public void stop() {
mp.stop();
}
public String getTitle(){
return title;
}
public void setTitle(String title){
this.title = title;
}
public String getArtist(){
return artist;
}
public void setArtist(String artist){
this.artist = artist;
}
public String getAlbum(){
return album;
}
public void setAlbum(String album){
this.album = album;
}
public Image getCover(){
return coverArt;
}
public MediaPlayer getMP(){
return mp;
}
}
Weirdly enough at first I thought it was because my String variables were not setting correctly and were set to null since it shows as null in the console when I put these print lines to test it when the Song object is being constructed. Here is a sample of the console when I test this.
null
null
artist:Foo Fighters
album:Saint Cecilia EP
title:Saint Cecilia
Here is my controller class
public class SceneController implements Initializable{
#FXML
private Button stopBtn;
#FXML
private Slider volume;
#FXML
private Button loadBtn;
#FXML
private Button playBtn;
#FXML
private TableView<Song> table;
#FXML
private Label label;
#FXML
private ProgressBar proBar;
private TableColumn songCol,artistCol,albumCol;
ObservableList<Song> songList = FXCollections.observableArrayList();
List<File> list;
FileChooser fileChooser = new FileChooser();
Desktop desktop;
Song mySong;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
TableColumn songCol = new TableColumn("Song");
TableColumn artistCol = new TableColumn("Artist");
TableColumn albumCol = new TableColumn("Album");
songCol.setCellValueFactory(
new PropertyValueFactory<Song,String>("title"));
//songCol.setCellFactory(new Callback);
artistCol.setCellValueFactory(
new PropertyValueFactory<Song,String>("artist"));
albumCol.setCellValueFactory(
new PropertyValueFactory<Song,String>("album"));
volume.setMin(0);
volume.setMax(100);
volume.setValue(100);
volume.valueProperty().addListener(new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
mySong.getMP().setVolume(volume.getValue()/100.0);
}
});
}
// Event Listener on Button[#loadBtn].onAction
#FXML
public void loadFile(ActionEvent event) {
Node source = (Node) event.getSource();
Window theStage = source.getScene().getWindow();
//set fileChooser filter
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("MP3 files", "*.mp3");
fileChooser.getExtensionFilters().add(extFilter);
fileChooser.setTitle("Select MP3 files");
//File file = fileChooser.showOpenDialog(theStage);
//mySong = new Song(file);
list = fileChooser.showOpenMultipleDialog(theStage);
if(list!=null){
for(File x: list) {
mySong = new Song(x);
System.out.println(mySong.getTitle());
songList.add(mySong);
}
}
table.setItems(songList);
}
#FXML
public void playSong(ActionEvent event) {
mySong.play();
}
#FXML
public void stopSong(ActionEvent event) {
//mySong.pause();
System.out.println("song title: "+mySong.getArtist()+mySong.getTitle());
ImageView img = new ImageView(mySong.getCover());
//img.fitWidthProperty().bind(label.widthProperty());
//img.fitHeightProperty().bind(label.heightProperty());
img.setFitHeight(120);
img.setFitWidth(200);
label.setGraphic(img);
//label.setGraphic(new ImageView(mySong.getCover()));
}
But I made another test print line for my "Stop" button in the controller class and after everything is loaded and I press it, it prints out the artist and title fine. I have saw this other thread and checked my getter methods and they seem to be correct? I am really lost on this and if anyone could provide some insight and a solution as to whether it is because my variables are null or my PropertyValueFactory is not done correctly
Also I notice that the nulls come first even though should they not be the last thing printed since when I create a new song object in my controller class the first print lines that run are in the if statements?
There are several things wrong with the way you have your current code, that are evident from the limited example you posted in the question:
Your Song class does not properly follow the JavaFX properties pattern. In particular, you store each "property" twice, once in a "traditional" JavaBean-style field, for example private String title, and once in a JavaFX property: private StringProperty pTitle;. Each property should be stored once. If you want the table to be aware when the value changes, you should use JavaFX properties, and have the "standard" getXXX() and setXXX() retrieve and set the underlying values stored in those properties.
The listener you attach to the media's metadata is called asynchronously at some indeterminate point in the future. When you add the song to the table's list, the cell value factories attached to the columns will, at some point, be executed, and retrieve the assigned property from the Song instance. With the code the way you currently have it, those property instances are only actually created once the listener on the metadata is invoked. So it is possible (perhaps likely) that the cell value factory will inspect the Song instance for its property before the JavaFX property is instantiated, making it impossible for the table to properly observe the property and respond to changes in it. You should instantiate the JavaFX properties when the Song instance is created, and set their value in the listener on the metadata.
At no point do you add the columns you create in the controller to the table. If you are creating them in the FXML file (which you didn't post in the question), you should inject those columns into the controller and initialize those columns with the cell value factories. (Since the screenshot shows there are columns in the table, I am going to assume they are defined in the FXML file, and have appropriate fx:ids.)
So your Song class should look something like this:
public class Song {
private final StringProperty title = new SimpleStringProperty();
private final StringProperty artist = new SimpleStringProperty();
private final StringProperty album = new SimpleStringProperty();
private Media music;
private MediaPlayer mp;
private Image coverArt;
public Song(File file) {
music = new Media(file.toURI().toString());
music.getMetadata().addListener((Change<? extends String, ? extends Object> c) -> {
if (c.wasAdded()) {
if ("artist".equals(c.getKey())) {
setArtist(c.getValueAdded().toString());
} else if ("title".equals(c.getKey())) {
setTitle(c.getValueAdded().toString());
} else if ("album".equals(c.getKey())) {
setAlbum(c.getValueAdded().toString());
} else if ("image".equals(c.getKey())) {
// maybe this needs to be a JavaFX property too: it is not clear from your question:
coverArt = (Image) c.getValueAdded();
}
}
});
mp = new MediaPlayer(music);
}
public void play() {
mp.play();
}
public void pause() {
mp.pause();
}
public void stop() {
mp.stop();
}
public StringProperty titleProperty() {
return title ;
}
public final String getTitle(){
return titleProperty().get();
}
public final void setTitle(String title){
titleProperty().set(title);
}
public StringProperty artistProperty() {
return artist ;
}
public final String getArtist(){
return artistProperty().get();
}
public final void setArtist(String artist){
artistProperty.set(artist);
}
public StringProperty albumProperty() {
return album ;
}
public final String getAlbum(){
return albumProperty().get();
}
public final void setAlbum(String album){
albumProperty().set(album);
}
public Image getCover(){
return coverArt;
}
public MediaPlayer getMP(){
return mp;
}
}
For your controller, I am going to assume your FXML file has defined table columns with fx:ids of "songCol", "artistCol", and "albumCol", respectively. You need to inject these into the controller as you do with the other columns. I also strongly recommend not using the PropertyValueFactory class, which uses reflection and lacks much in the way of compile-time checking, and implementing the callback yourself. Using lambda expressions makes this pretty easy.
So your controller should look like:
public class SceneController implements Initializable{
// non-table code omitted...
#FXML
private TableView<Song> table;
#FXML
private Label label;
#FXML
private ProgressBar proBar;
#FXML
private TableColumn<Song, String> songCol ;
#FXML
private TableColumn<Song, String> artistCol ;
#FXML
private TableColumn<Song, String> albumCol;
ObservableList<Song> songList = FXCollections.observableArrayList();
List<File> list;
FileChooser fileChooser = new FileChooser();
Desktop desktop;
Song mySong;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
songCol.setCellValueFactory(cellData -> cellData.getValue().titleProperty());
artistCol.setCellValueFactory(cellData -> cellData.getValue().artistProperty());
albumCol.setCellValueFactory(cellData -> cellData.getValue().albumProperty());
// ...
}
// other non-table code omitted...
}
You didn't post an minimal, complete, verifiable example, so there may well be other errors in your code which prevent the table from displaying correctly. This should get you started, however.
Normally the TableColumns would be defined in FXML and injected via #FXML.
See the Oracle TableView FXML example.
If you don't want to do it that way, you need to do:
table.getColumns().add(songCol);
And similarly for your other columns.
Also, as HypnicJerk pointed out in comments you also need to follow appropriate naming conventions when using the PropertyValueFactory.
songCol.setCellValueFactory(
new PropertyValueFactory<Song,String>("title")
);
For more details see:
Javafx tableview not showing data in all columns
Haii all i need help, i want to custom Label component in zk and i nedd to add a property wich is mandatory property when i set mandatory="true" the asterix symbol will be appear and if i set mandatory="false" the asterix symbol disappear,and i am trying like this :
private Label label;
private Label sign;
private String lblValue;
private String REQUIRED_SIGN = " *";
private boolean mandatory;
public SignLabelCustom()
{
label = new Label();
label.setSclass("form-label");
appendChild(label);
sign = new Label();
if(mandatory=true){
sign.setValue(REQUIRED_SIGN);
sign.setStyle("color: red");
appendChild(sign);
}
else{
sign.setValue("");
sign.setStyle("color: red");
removeChild(sign);
}
}
public String getValue() {
return lblValue;
}
public boolean isMandatory() {
return mandatory;
}
public void setMandatory(boolean mandatory) {
this.mandatory = mandatory;
}
public void setValue(String lblValue) {
label.setValue(lblValue);
this.lblValue = lblValue;
}
but the condition does'nt working, how to solve it?
What you probably want is called an HtmlMacroComponent, which combines a label and a textbox...
You start with a zul file:
<zk>
<label id="mcLabel"/><textbox id="mcTextbox"/>
</zk>
...and create a component for it...
public class MyTextbox extends HtmlMacroComponent {
#Wire("#mcTextbox")
private Textbox textbox;
#Wire("#mcLabel")
private Label label;
private String caption;
private boolean mandatory;
public MyTextbox() {
compose(); // this wires the whole thing
}
public void setMandatory(final boolean value) {
mandatory = value;
updateCaption();
}
public boolean isMandatory() {
return mandatory;
}
public void setCaption(final String value) {
caption = value;
updateCaption();
}
public String getCaption() {
return caption;
}
protected void updateCaption() {
label.setValue(mandatory ? caption + "*" : caption);
}
public String getValue() {
return textbox.getValue();
}
public void setValue(final String value) {
textbox.setValue(value);
}
}
...and now you can use it, for example by defining it on the top of your zul file... (adjust package and .zul name as required):
<?component name="mytextbox" macroURI="/zk/textbox.zul" class="com.example.MyTextbox"?>
...so you can simply use it...
<mytextbox id="name" value="Frank N. Furter" caption="Your name" mandatory="true"/>
Later you can define a language addon for it...
my-language-addon
xul/html
mytextbox
com.example.MyTextbox
/zk/textbox.zul
...so that you don't need to put the definition on top of every .zul file where you use it anymore. See the documentation for more on this.
Of course, you also could only create a new label, etc. but I found it's a good think to create MacroComponents for those jobs that combine various components, since this way, for example, you could also automatically add validation, etc.
I've got a project written in JavaFX and I'm trying to get a refresh on a tableview without result.
I've googled around and tried some examples I've found but it still doesn't work.
I populate a tableview with information each row in this table can have new comments added to by double click on the row. The a new Tabpane is opened and the new comment can be added there. On close of this tabpane I'd like the one I clicked from to be refreshed.
I must be doing something wrong. I just don't know what.
In my StoreController
private void populateTableView(List<Store> stores) {
ObservableList<Store> data = FXCollections.observableArrayList(stores);
storeNumberColumn.setCellValueFactory(
new PropertyValueFactory<Store, String>("id"));
storePhoneColumn.setCellValueFactory(
new PropertyValueFactory<Store, String>("phoneNbr"));
chainColumn.setCellValueFactory(
new PropertyValueFactory<Store, String>("chainId"));
commentColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Store, ImageView>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Store, ImageView> p) {
Integer numberOfComments = p.getValue().getCommentsCount();
ReadOnlyObjectWrapper wrapper = null;
if (numberOfComments == 0) {
wrapper = null;
} else if (numberOfComments == 1) {
wrapper = new ReadOnlyObjectWrapper(new ImageView(COMMENT_SINGLE_FLAG_SOURCE));
} else {
wrapper = new ReadOnlyObjectWrapper(new ImageView(COMMENT_DOUBLE_FLAG_SOURCE));
}
return wrapper;
}
});
storeTable.setItems(data);
sortTable(storeTable, missedColumn);
}
#FXML
public void handleTableAction(MouseEvent event) {
if (event.getClickCount() == 2) {
showNewCommentStage();
}
}
private void showNewCommentStage() {
initCommentController();
Store store
= storeTable.getSelectionModel().selectedItemProperty().getValue();
commentController.showNewStage(commentPane, store);
}
It seems like the call-function doesn't get called when the commentpane is closed.
CommentController
public void showNewStage(Pane pane, Store store) {
this.store = store;
initStage(pane);
windowHandler = new WindowHandler(stage);
effectHandler.playEffect(pane);
constructCommentHeaders();
List<Comment> comments;
comments = commentService.listByStoreId(store.getId());
populateCommentTable(comments);
}
Like I said I've tried a lot of the solutions found here on Stackoverflow but with no results. The Tableview doesn't refresh. The Stores and the Comments are in different database tables if that's important
Can someone point me in the right direction?
Thanks!
****EDIT****
The Store.class
public class Store extends CommentEntity {
private String id;
private String chainId;
private String phoneNbr;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getChainId() {
return chainId;
}
public void setChainId(String chainId) {
this.chainId = chainId;
}
public String getPhoneNbr() {
return phoneNbr;
}
public void setPhoneNbr(String phoneNbr) {
this.phoneNbr = phoneNbr;
}
#Override
public String toString() {
return "Store{" + "id=" + id + ", chainId=" + chainId + '}';
}
#Override
public String getCommentIdentifier() {
return id;
}
}
The CommentEntity.Class
public abstract class CommentEntity {
private int commentsCount;
public int getCommentsCount() {
return commentsCount;
}
public void setCommentsCount(int commentsCount) {
this.commentsCount = commentsCount;
}
public abstract String getCommentIdentifier();
}
Thank you for input, I hadn't even reflected over the ImageView / String.
Two issues:
First, you need to distinguish between the data the cells in your column are displaying, and the cells that actually display those data. The cellValueFactory determines the data that are displayed. The PropertyValueFactory is a cellValueFactory implementation that references a JavaFX Property, so when you call
storeNumberColumn.setCellValueFactory(new PropertyValueFactory<Store, String>("id"));
it effectively tells the cells in the storeNumberColumn to call the idProperty() method on the Store object in the current row to get the data for the cell. (If no such method exists, it will try to use getId() as a backup plan.)
By default, you get a cellFactory that displays text resulting from calling toString() on the data generated by the cellValueFactory. In the case where your data are simply Strings, this is usually what you need. In other cases, you often need to provide a cellFactory of your own to get the correct way to display the data.
In your case, the data for the commentColumn are simply the number of comments. You are going to display that by choosing an image based on that numeric value.
So you should have
TableColumn<Store, Number> commentColumn = new TableColumn<>("Comments");
For the cellValueFactory, you can just use
commentColumn.setCellValueFactory(new PropertyValueFactory<>("commentsCount"));
Then you need a cellFactory that displays the appropriate ImageView:
commentColumn.setCellFactory(new Callback<TableColumn<Store, Number>, new TableCell<Store, Number>>() {
#Override
public TableCell<Store, Number>() {
private ImageView imageView = new ImageView();
#Override
public void updateItem(Number numberOfComments, boolean empty) {
super.updateItem(count, empty) ;
if (empty) {
setGraphic(null);
} else {
if (numberOfComments.intValue() == 0) {
setGraphic(null);
} else if (numberOfComments.intValue() == 1) {
imageView.setImage(new Image(COMMENT_SINGLE_FLAG_SOURCE));
setGraphic(imageView);
} else {
imageView.setImage(new Image(COMMENT_DOUBLE_FLAG_SOURCE));
setGraphic(imageView);
}
}
}
}
});
The second issue is actually about the update. A TableView keeps its contents "live" by observing JavaFX properties that are provided by the cellValueFactory as ObservableValues. If the value might change while the table is displayed, you must provide an actual property that can be observed: using a ReadOnlyObjectWrapper is no good (because it's read only, so it's wrapped value will not change). The PropertyValueFactory will also return a ReadOnlyObjectWrapper if you do not have JavaFX property accessor methods (i.e. if it is only using getXXX() methods to access the data). So your model class must provide JavaFX Properties.
You can make an immediate fix to this by updating CommentEntity to use an IntegerProperty:
public abstract class CommentEntity {
private final IntegerProperty commentsCount = new SimpleIntegerProperty();
public final int getCommentsCount() {
return commentsCountProperty().get();
}
public final void setCommentsCount(int commentsCount) {
commentsCountProperty().set(commentsCount);
}
public IntegerProperty commensCountProperty() {
return commentsCount ;
}
public abstract String getCommentIdentifier();
}
I would also strongly recommend updating the Store class to use JavaFX Properties in a similar manner.
I have an object, Supply, that can either be an ElecSupply or GasSupply (see related question).
Regardless of which subclass is being edited, they all have a list of BillingPeriods.
I now need to instantiate N number of BillingPeriodEditors based on the contents of that list, and am pretty baffled as to how I should do it.
I am using GWTP. Here is the code of the SupplyEditor I have just got working:
public class SupplyEditor extends Composite implements ValueAwareEditor<Supply>
{
private static SupplyEditorUiBinder uiBinder = GWT.create(SupplyEditorUiBinder.class);
interface SupplyEditorUiBinder extends UiBinder<Widget, SupplyEditor>
{
}
#Ignore
final ElecSupplyEditor elecSupplyEditor = new ElecSupplyEditor();
#Path("")
final AbstractSubTypeEditor<Supply, ElecSupply, ElecSupplyEditor> elecSupplyEditorWrapper = new AbstractSubTypeEditor<Supply, ElecSupply, ElecSupplyEditor>(
elecSupplyEditor)
{
#Override
public void setValue(final Supply value)
{
setValue(value, value instanceof ElecSupply);
if(!(value instanceof ElecSupply))
{
showGasFields();
}
else
{
showElecFields();
}
}
};
#Ignore
final GasSupplyEditor gasSupplyEditor = new GasSupplyEditor();
#Path("")
final AbstractSubTypeEditor<Supply, GasSupply, GasSupplyEditor> gasSupplyEditorWrapper = new AbstractSubTypeEditor<Supply, GasSupply, GasSupplyEditor>(
gasSupplyEditor)
{
#Override
public void setValue(final Supply value)
{
setValue(value, value instanceof GasSupply);
if(!(value instanceof GasSupply))
{
showElecFields();
}
else
{
showGasFields();
}
}
};
#UiField
Panel elecPanel, gasPanel, unitSection;
public SupplyEditor()
{
initWidget(uiBinder.createAndBindUi(this));
gasPanel.add(gasSupplyEditor);
elecPanel.add(elecSupplyEditor);
}
// functions to show and hide depending on which type...
#Override
public void setValue(Supply value)
{
if(value instanceof ElecSupply)
{
showElecFields();
}
else if(value instanceof GasSupply)
{
showGasFields();
}
else
{
showNeither();
}
}
}
Now, as the list of BillingPeriods is a part of any Supply, I presume the logic for this should be in the SupplyEditor.
I got some really good help on the thread How to access PresenterWidget fields when added dynamically, but that was before I had implemented the Editor Framework at all, so I think the logic is in the wrong places.
Any help greatly appreciated. I can post more code (Presenter and View) but I didn't want to make it too hard to read and all they do is get the Supply from the datastore and call edit() on the View.
I have had a look at some examples of ListEditor but I don't really get it!
You need a ListEditor
It depends of how you want to present them in your actual view, but the same idea apply:
public class BillingPeriodListEditor implements isEditor<ListEditor<BillingPeriod,BillingPeriodEditor>>, HasRequestContext{
private class BillingPeriodEditorSource extends EditorSource<BillingPeriodEditor>{
#Override
public EmailsItemEditor create(final int index) {
// called each time u add or retrive new object on the list
// of the #ManyToOne or #ManyToMany
}
#Override
public void dispose(EmailsItemEditor subEditor) {
// called each time you remove the object from the list
}
#Override
public void setIndex(EmailsItemEditor editor, int index) {
// i would suggest track the index of the subeditor.
}
}
private ListEditor<BillingPeriod, BillingPeriodEditor> listEditor = ListEditor.of(new BillingPeriodEditorSource ());
// on add new one ...
// apply or request factory
// you must implement the HasRequestContext to
// call the create.(Proxy.class)
public void createNewBillingPeriod(){
// create a new one then add to the list
listEditor.getList().add(...)
}
}
public class BillingPeriodEditor implements Editor<BillingPeriod>{
// edit you BillingPeriod object
}
Then in you actual editor edit as is in the path Example getBillingPeriods();
BillingPeriodListEditor billingPeriods = new BillingPeriodListEditor ();
// latter on the clickhandler
billingPeriods.createNewBillingPeriod()
You are done now.