I'm using a TreeViewer with a list of customers. I added a DoubleClickListener to the TreeViewer.
this.treeViewer.addDoubleClickListener(new IDoubleClickListener() {
#Override
public void doubleClick(DoubleClickEvent event) {
IStructuredSelection thisSelection = (IStructuredSelection) event
.getSelection();
Object selectedNode = thisSelection.getFirstElement();
if (selectedNode instanceof ICustomer) {
ICustomer customer = (ICustomer) selectedNode;
selectionService.setSelection(customer);
perspective = (MPerspective) modelService
.find("de.checkpoint.rinteln.carlofon.perspective.customer",
app);
}
if (perspective != null) {
partService.switchPerspective(perspective);
}
}
});
In the Customer-Perspective, I've got 2 Views, which use the selected customer to load his data(orders and reminders) from the DB.
In the Customer-View, everything works just fine. But once i move on the next view(Reminder or Order) the selection list Null, which I don't get.
#Inject
void setSelection(
#Optional #Named(IServiceConstants.ACTIVE_SELECTION) ICustomer customer) {
if (customer != null) {
idText.setText("" + customer.getCustomerId());
customerNameText
.setText(customer.getFirstname() + " " + customer.getLastname());
steetText.setText(customer.getStreet());
cityText.setText(customer.getCity());
steetCodeText.setText("" + customer.getCityCode());
} else {
// TODO Clear View!
}
}
And in the Reminder-View (in the same perspective as the Customer-View), the selected Customer is Null
#Inject
void setSelection(
#Optional #Named(IServiceConstants.ACTIVE_SELECTION) ICustomer customer) {
if (customer != null) {
super.treeViewer.setInput(service.loadAll());
} else {
// TODO Clear View!
}
}
Which lead to my question, do i miss something? Am I not supposed to use the same selection in different views?
I must add, that both views extend an AbstractView in which the IDoubleClickListener is implemented.
Related
I am building am application where I need to have a tree view alongside a pane where data is presented. When someone selects an item in the tree view, the application must identify what they have selected and seek the correct data from a database, then present it in the format I've chosen. It must not matter how the user selects the item in the tree view (mouse click, tabbing, arrow keys, etc), just that when an item gets focus, a method is triggered to present the data to the user.
I got this working perfectly for mouse clicks only in the following way:
// Application thread method to build the tree map, used in the generateTree
// method.
public void treeBuilder(TreeMap<ModelSites, ArrayList<ModelPlants>> map) {
TreeMap<ModelSites, ArrayList<ModelPlants>> treeMap = map;
final TreeItemProperties<String, String> rootTreeItem = new TreeItemProperties<String, String>("EMT", null);
TreeItemProperties<String, Integer> site = null;
TreeItemProperties<String, Integer> plant = null;
for (Map.Entry<ModelSites, ArrayList<ModelPlants>> entry : treeMap.entrySet()) {
site = new TreeItemProperties<String, Integer>(entry.getKey().getLongName(), entry.getKey().getPrimaryKey());
rootTreeItem.getChildren().add(site);
if (site.getValue().equalsIgnoreCase("test item")) {
site.setExpanded(true);
}
for (int i = 0; i < entry.getValue().size(); i++) {
plant = new TreeItemProperties<String, Integer>(entry.getValue().get(i).getSitePlantId() + " " + entry.getValue().get(i).getShortName(), entry.getValue().get(i).getPrimaryKey());
site.getChildren().add(plant);
}
}
//Cell Factory is used to effectively turn the tree items into nodes, which they are not natively.
//This is necessary to have actions linked to the tree items (eg. double click an item to open an edit window).
emtTree.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {
#Override
public TreeCell<String> call(TreeView<String> param) {
FactoryTreeCell<String> cell = new FactoryTreeCell<String>();
cell.setOnMouseClicked(event -> {
if (!cell.isEmpty()) {
#SuppressWarnings("unchecked")
TreeItemProperties<String, Integer> treeItem = (TreeItemProperties<String, Integer>) cell.getTreeItem();
generateEquipmentPanes(treeItem.getPropertyValue());
}
});
return cell;
}
});
rootTreeItem.setExpanded(true);
emtTree.setRoot(rootTreeItem);
}
// Populate the main screen with all equipment items in the selected plant.
public void generateEquipmentPanes(int plantId) {
int plant = plantId;
Task<LinkedList<ModelEquipment>> task = new Task<LinkedList<ModelEquipment>>() {
#Override
public LinkedList<ModelEquipment> call() {
LinkedList<ModelEquipment> equipmentList = DAOEquipment.listEquipmentByPlant(plant);
return equipmentList;
}
};
// When list is built successfully, send the results back to the application
// thread to load the equipment panes in the GUI.
task.setOnSucceeded(e -> equipmentPaneBuilder(task.getValue()));
task.setOnFailed(e -> task.getException().printStackTrace());
task.setOnCancelled(null);
String methodName = new Object() {}.getClass().getEnclosingMethod().getName();
Thread thread = new Thread(task);
thread.setName(methodName);
//System.out.println("Thread ID: " + thread.getId() + ", Thread Name: " + thread.getName());
thread.setDaemon(true);
thread.start();
}
// Application thread method to build the equipment panes, used in the
// generateEquipmentPanes method.
public void equipmentPaneBuilder(LinkedList<ModelEquipment> list) {
LinkedList<ModelEquipment> equipmentList = list;
EquipmentPanels.getChildren().clear();
for (int i = 0; i < equipmentList.size(); i++) {
ModelEquipment item = equipmentList.get(i);
try {
PaneEquipment equipmentPane = new PaneEquipment();
equipmentPane.updateFields(item.getTechId(), item.getShortName(), item.getLongDesc()); equipmentPane.setId("equipPane" + i);
EquipmentPanels.getChildren().add(equipmentPane);
} catch (Exception e) {
e.printStackTrace();
}
}
}
I've done a tonne of searching and I sort of figured out how to implement listeners instead of handlers as this seemed to be the way to do what I want - put a listener on a property of the cell. But when replace the event handler with the listener, like the below two examples, I get many issues.
emtTree.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
#SuppressWarnings("unchecked")
TreeItemProperties<String, Integer> treeItem = (TreeItemProperties<String, Integer>) cell.getTreeItem();
generateEquipmentPanes(treeItem.getPropertyValue());
}
});
cell.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (!cell.isEmpty()) {
#SuppressWarnings("unchecked")
TreeItemProperties<String, Integer> treeItem = (TreeItemProperties<String, Integer>) cell.getTreeItem();
generateEquipmentPanes(treeItem.getPropertyValue());
}
}
});
For a start I get nullpointerexceptions every time I click on a tree item, which comes from the line generateEquipmentPanes(treeItem.getPropertyValue());. Secondly, it tends to pick data from the wrong item, not the one I selected. Then after a few clicks it seems to break down altogether and do nothing except provide more nullpointerexceptions.
From what I can understand, I think that the issue is the location of the listener relative to the variables that need to be passed to the method generateEquipmentPanes. And something about removing listeners at a certain point and re-adding them later.
Should I somehow be putting the listener into the cell factory? At the moment it just looks like this:
import javafx.scene.control.TreeCell;
public class FactoryTreeCell<T> extends TreeCell<T> {
public FactoryTreeCell() {
}
/*
* The update item method simply displays the cells in place of the tree items (which disappear when setCellFactory is set.
* This can be used for many more things (such as customising appearance) not implemented here.
*/
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null); //Note that a graphic can be many things, not just an image. Refer openJFX website for more details.
} else {
setText(item.toString());
}
}
}
Another thing I've noticed is that there are other ways to implement the Callback, that might work better with listeners, but I don't know how to do that.
I've been stuck on this for ages so a breakthrough would be great.
You should not be using a tree cell to examine the selected value. Your ChangeListener already receives the new value directly:
emtTree.getSelectionModel().selectedItemProperty().addListener(
(observable, oldSelection, newSelection) -> {
if (newSelection != null) {
TreeItemProperties<String, Integer> treeItem = newSelection;
generateEquipmentPanes(treeItem.getPropertyValue());
}
});
I have a Table View pinTable in FXMLand two Table Columns i.e. latColumn, longColumn. I have followed the following method to populate the table :
https://docs.oracle.com/javafx/2/ui_controls/table-view.htm
Snippets are as follows:
FXML Controller Class:
#FXML
public TableView pinTable;
#FXML
public TableColumn latColumn, longColumn;
public final ObservableList<PinList> dataSource = new FXMLCollections.observableArrayList();
#Override
public void initialize(URL url, ResourceBundle rb)
{
initTable();
}
private void initTable()
{
latColumn.setCellValueFactory(new PropertyValueFactory<PinList,String>("latPin"));
longColumn.setCellValueFactory(new PropertyValueFactory<PinList,String>("longPin"));
pinTable.setItems(dataSource);
}
#FXML
private void addButtonClicked(MouseEvent event)
{
if(latText.getText().equals(""))
{
System.out.println("Lat Empty");
}
else if(longText.getText().equals(""))
{
System.out.println("Long Empty");
}
else
{
latVal = Double.parseDouble(latText.getText());
longVal = Double.parseDouble(longText.getText());
dataSource.add(new PinList(latText.getText(),longText.getText(),descriptionText.getText()));
pinTable.getItems().clear();
pinTable.getItems().addAll(dataSource);
for(PinList p: dataSource)
{
System.out.print(p.getLatPin() + " " + p.getLongPin() + " " + p.getDescriptionPin() + "\n");
}
}
}
I have a PinList class which is as follows:
public class PinList
{
private final SimpleStringProperty latPin;
private final SimpleStringProperty longPin;
private String descriptionPin;
public PinList(String _latPin, String _longPin, String _descriptionPin)
{
this.latPin = new SimpleStringProperty(_latPin);
this.longPin = new SimpleStringProperty(_longPin);
this.descriptionPin = _descriptionPin;
}
public String getLatPin()
{
return latPin.getValue();
}
public StringProperty latPinProperty()
{
return latPin;
}
public String getLongPin()
{
return longPin.getValue();
}
public StringProperty longPinProperty()
{
return longPin;
}
public String getDescriptionPin()
{
return descriptionPin;
}
}
All these seem to be fine. But, when I click Add button, nothing happens. No row is created in the table and the println inside the addButtonClicked event handler doesn't execute, or it is executed with no data in the dataSource whatsoever. Any help will be appreciated.
The problem is you confused the Application class with your FXML controller class. Even though this FXML class extends Application the overridden start() method is not being invoked anywhere in your code. Put some printlns to verify this. The FXML controller class can optionally have a initialize() (and additionally can implement Initializable but not mandatory). This method will be invoked by FXMLLoader after the fxml file is loaded by it. So the correct code should be:
public void initialize( URL url, ResourceBundle rb ) {
initTable();
}
and delete start() method and remove extending from Application.
In your initTable() method you do
pinTable.setItems(dataSource);
So now the list held internally by pinTable in its items property is the very same list as dataSource (they are the identical by reference).
Now in your event handler method you do
dataSource.add(new PinList(...));
which adds a new item to dataSource (which is the same list as the table's items)
and then
pinTable.getItems().clear();
which removes all elements from the table's items list. So the table's items list is now empty (has no elements). Of course, since this is the very same list as dataSource, you have also removed all items from dataSource: it is the same (now empty) list as pinTable.getItems().
and now you do
pinTable.getItems().addAll(dataSource);
which copies all the items in dataSource (there are none) into pinTable.getItems() (which is the same list). So this actually duplicates all items in the list, but since you emptied the list, you still have an empty list.
Just remove the lines
pinTable.getItems().clear();
pinTable.getItems().addAll(dataSource);
All you want here is:
#FXML
private void addButtonClicked(MouseEvent event)
{
if(latText.getText().equals(""))
{
System.out.println("Lat Empty");
}
else if(longText.getText().equals(""))
{
System.out.println("Long Empty");
}
else
{
latVal = Double.parseDouble(latText.getText());
longVal = Double.parseDouble(longText.getText());
dataSource.add(new PinList(latText.getText(),longText.getText(),descriptionText.getText()));
for(PinList p: dataSource)
{
System.out.print(p.getLatPin() + " " + p.getLongPin() + " " + p.getDescriptionPin() + "\n");
}
}
}
I'm trying to fill a listview with the artist and title of songs using the open() method.
To achieve this I created the artist and title ArrayLists and merged them using the create() method.
The problem is, when I try to run create() inside open() nothing happens. However, if I assign the create() method to a different button and click it after using the filechooser everything works fine.
So, I would like to know if it is possible to run the create() method after the open() method using only one button via fxml or regular java code.
public class PLController implements Initializable {
#Override
public void initialize(URL location, ResourceBundle resources) {
list.setItems(visibleList);
}
List<File> filelist = new ArrayList<File>();
ArrayList<String> title = new ArrayList<String>();
ArrayList<String> artist = new ArrayList<String>();
ObservableList<String> visibleList = FXCollections.observableArrayList();
#FXML
ListView<String> list;
#FXML
Button impButton;
public void create(){
for(int i = 0; i < title.size(); i++){
visibleList.add(artist.get(i) +" - " +title.get(i));
Collections.sort(visibleList);
}
}
public void handleMetadata(String key, Object value){
if (key.equals("title")){
title.add(value.toString());
}
if (key.equals("artist")){
artist.add(value.toString());
}
}
public void open(){
FileChooser chooser = new FileChooser();
filelist = chooser.showOpenMultipleDialog(impButton.getScene().getWindow());
for(File f:filelist){
try {
Media media = new Media(f.toURI().toURL().toString());
media.getMetadata().addListener(new MapChangeListener<String, Object>(){
#Override
public void onChanged(Change<? extends String, ? extends Object> change) {
if(change.wasAdded()) {
handleMetadata(change.getKey(), change.getValueAdded());
}
}
});
} catch (MalformedURLException e) {
e.printStackTrace();
}
}create(); //Nothing happens
}
As others have pointed out, the Media object does not have its metadata initialized immediately. (It needs to read data from the URL and populate those metadata as it receives them.) That is why the metadata are exposed as an ObservableMap. When you reach the end of your open() method, it is highly unlikely that the metadata will have been initialized, so your create() method will not see any data at that point.
What you need to do is observe the map, and update the ListView once both the artist and title are available. The best way to do this, in my opinion, is to encapsulate the information you want into a separate class:
public class Video {
private final Media media ;
private final ReadOnlyStringWrapper artist = new ReadOnlyStringWrapper("Unknown");
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper("Title");
public Video(File file) {
try {
this.media = new Media(file.toURI().toURL().toExternalForm());
artist.bind(Bindings.createStringBinding(() -> {
Object a = media.getMetadata().get("artist");
return a == null ? "Unknown" : a.toString();
}, media.getMetadata()));
title.bind(Bindings.createStringBinding(() -> {
Object t = media.getMetadata().get("title");
return t == null ? "Unknown" : t.toString();
}, media.getMetadata()));
}
catch (Exception e) {
throw new RuntimeException("Could not create Video for "+file, e);
}
}
public ReadOnlyStringProperty titleProperty() {
return title.getReadOnlyProperty();
}
public ReadOnlyStringProperty artistProperty() {
return artist.getReadOnlyProperty();
}
public final String getTitle() {
return title.get();
}
public final String getArtist() {
return artist.get();
}
public final Media getMedia() {
return media ;
}
}
Now you can create a ListView<Video> to display the videos. Use a cell factory to display the artist and the title in the format you want. You can make sure that the observable list fires updates when either the artist or title properties change, and you can keep it sorted via a SortedList.
#FXML
private ListView<Video> list ;
private ObservableList<Video> visibleList ;
public void initialize() {
visibleList = FXCollections.observableArrayList(
// make list fire updates when artist or title change:
v -> new Observable[] {v.artistProperty(), v.titleProperty()});
list.setItems(new SortedList<>(list, Comparator.comparing(this::formatVideo)));
list.setCellFactory(lv -> new ListCell<Video>() {
#Override
public void updateItem(Video item, boolean empty) {
super.updateItem(item, empty) ;
setText(formatVideo(item));
}
});
}
#FXML
private void open() {
FileChooser chooser = new FileChooser();
List<File> fileList = chooser.showOpenMultipleDialog(impButton.getScene().getWindow());
if (fileList != null) {
fileList.stream()
.map(Video::new)
.forEach(visibleList::add);
}
}
private String formatVideo(Video v) {
if (v == null) return "" ;
return String.format("%s - %s", v.getArtist(), v.getTitle());
}
Simply creating a Media object and assigning a listener to it won't fire the code in the listener. So the title list in your code remains empty. The create() method is called, but since you are iterating over an empty list, nothing actually happens.
Use a debugger or add some logging information in such cases.
Also, you should sort the list after the for loop, not every time you add an item.
I would like to print out an appointment's summary and description when a user clicks on a particular appointment retrieved from the database.
I'm trying to implement this with something like:
lAgenda.selectedAppointments().addListener(new ListChangeListener<Appointment>() {
#Override
public void onChanged(Change<? extends Appointment> c) {
System.out.println(c.toString());
}
});
I however, only get this:
com.sun.javafx.collections.NonIterableChange$GenericAddRemoveChange#1ef0f08
com.sun.javafx.collections.NonIterableChange$SimpleAddChange#1c3e48b
com.sun.javafx.collections.NonIterableChange$GenericAddRemoveChange#d57e70
com.sun.javafx.collections.NonIterableChange$SimpleAddChange#6022e2
com.sun.javafx.collections.NonIterableChange$GenericAddRemoveChange#54ddc1
How can I retrieve other items, like the ID of the row in the database row the appointment is being retrieved from? Thank you all.
You are using the right property to be notified of the selection change.
You received a ListChangeListener.Change. As described in the javadoc, a change should be used in the this way :
lAgenda.selectedAppointments().addListener(new ListChangeListener< Appointment >() {
public void onChanged(Change<? extends Appointment> c) {
while (c.next()) {
if (c.wasPermutated()) {
for (int i = c.getFrom(); i < c.getTo(); ++i) {
//permutate
}
} else if (c.wasUpdated()) {
//update item
} else {
for (Appointment a : c.getRemoved()) {
}
for (Appointment a : c.getAddedSubList()) {
printAppointment(a);
}
}
}
}
});
Now, you could print out appointment summary and description :
private void printAppointment(Appointment a) {
System.out(a.getSummary());
System.out(a.getDescription());
}
If you need some specific properties on the appointment object (like a database id), you could create your appointment class by extending AppointmentImpl or by implementing Appointment
I'm developing a Vaadin application. Now, I have a trouble with a BeanItemContainer. I have a few items inside my container.
private void populateTable() {
tableContainer.removeAllItems();
for(MyBean myBean : beans){
tableContainer.addItem(myBean);
}
}
When I select the item in the table, I bind the item selected with the binder and I fill the form automatically
table.addItemClickListener(new ItemClickListener() {
public void itemClick(ItemClickEvent event) {
myBean = ((BeanItem<MyBean>) event.getItem()).getBean();
//BeanFieldGroup<MyBean>
binder.setItemDataSource(myBean);
}
});
private Component makeForm() {
formLayout = new FormLayout();
binder.bind(comboBoxModPag,"modPagamento");
binder.bind(fieldInizioVal, "id.dInizioVal");
formLayout.addComponent(comboBoxModPag);
formLayout.addComponent(fieldInizioVal);
formLayout.addComponent(binder.buildAndBind(getI18NMessage("dValidoAl"), "dValidoAl", DateField.class));
return formLayout;
}
Now, I have to manage the user interactions in a different way. For example, if the user modify the value inside the combobox, I have to add a new Bean in the container, while if the users modify the value of the field fieldInizioVal I have to update the current Bean.
insertOrUpdateButton.addClickListener(new ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
tableContainer.addItem(myBean));
}
});
But, when add a new Item, the container adds the new item correctly but modify also the old item selected.
How can I do?
I solved in this way
comboBoxModPag.addValueChangeListener(new ValueChangeListener() {
public void valueChange(ValueChangeEvent event) {
MyBean oldValue = (MyBean) comboBoxModPag.getOldValue();
MyBean newValue = (MyBean) comboBoxModPag.getValue();
if( oldValue!=null && newValue!=null && !oldValue.equals(newValue) ){
insertMode = true;
}
else{
insertMode = false;
}
}
}
});
protected void saveOrUpdateModPagContrattoSito() {
if(insertMode){
MyBean newMyBean = new MyBean(myBean);
//Do somethings to restore myBean statuse
//....
//....
tableContainer.addBean(newMyBean);
}
else{
tableContainer.addBean(myBean);
}
table.refreshRowCache();
}
But I don't know if this is the correct way.