I really apologize if this question doesn't make much sense in your perspective but in my perspective, this is really important to write cleaner and more maintainable code.
I have A.fxml, AController.java (controller class for A.fxml). I have a TableView with custom cell factories defined. I prefer defining my cell factories in a separate class so that I can reuse them if needed. I prefer writing all my event handling code in my controller class. But if I use custom cell factories then I am forced to write the event handling in the cell factory class itself.
Is there a way i can handle the custom cell factory events in my controller class itself? or atleast just throw the event from the custom cell factory class to my controller class and handle?
Thanks in Advance.
You can pass a object to the factory that determines when an context menu should be opened and that prepares the menu.
Example:
public interface CellContextMenuProvider<S, T> {
/**
* Prepares the context menu for opening.
* #param cell the cell the menu was requested for
* #param menu the menu to prepare
*/
public void prepareContextMenu(TableCell<S, T> cell, ContextMenu menu);
/**
* Checks, if a cell continaing a certain item should have an active context
* menu.
* #param empty if the cell is empty
* #param item the item of the cell
* #return {#literal true} iff the context menu should be enabled.
*/
public boolean enableContextMenu(boolean empty, T item);
/**
* Prepares the intial menu. This menu must not be empty, otherwise it won't
* be shown when it's requested for the first time.
* #param menu the menu to prepare
*/
public void prepareInitialContextMenu(ContextMenu menu);
}
public class CellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
private final CellContextMenuProvider<S, T> menuProvider;
private final ContextMenu contextMenu;
public CellFactory(#NamedArg("menuProvider") CellContextMenuProvider<S, T> menuProvider) {
this.menuProvider = menuProvider;
if (menuProvider == null) {
this.contextMenu = null;
} else {
this.contextMenu = new ContextMenu();
menuProvider.prepareInitialContextMenu(contextMenu);
}
this.menuEventHandler = evt -> {
if (this.contextMenu != null) {
TableCell<S, T> source = (TableCell<S, T>) evt.getSource();
this.menuProvider.prepareContextMenu(source, this.contextMenu);
}
};
}
public CellFactory() {
this(null);
}
private final EventHandler<ContextMenuEvent> menuEventHandler;
#Override
public TableCell<S, T> call(TableColumn<S, T> param) {
TableCell<S, T> result = new TableCell<S, T>() {
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
setText(Objects.toString(item, ""));
setContextMenu(menuProvider != null && menuProvider.enableContextMenu(empty, item) ? contextMenu : null);
}
};
result.setOnContextMenuRequested(menuEventHandler);
if (menuProvider != null && menuProvider.enableContextMenu(true, null)) {
result.setContextMenu(contextMenu);
}
return result;
}
}
public class AController {
#FXML
private TableView<Item<Integer>> table;
public void initialize() {
for (int i = 0; i < 100; i++) {
table.getItems().add(new Item<>(i));
}
}
public CellContextMenuProvider<Item<Integer>, Integer> getMenuProvider() {
return new CellContextMenuProvider<Item<Integer>, Integer>() {
private final MenuItem item = new MenuItem("Say Hello World");
{
item.setOnAction(evt -> System.out.println("Hello World"));
}
#Override
public void prepareContextMenu(TableCell<Item<Integer>, Integer> cell, ContextMenu menu) {
}
#Override
public void prepareInitialContextMenu(ContextMenu menu) {
menu.getItems().setAll(item);
}
#Override
public boolean enableContextMenu(boolean empty, Integer item) {
// only for odd items
return !empty && (item % 2) != 0;
}
};
}
}
A.fxml
<TableView fx:id="table" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxml.table.AController">
<columns>
<TableColumn prefWidth="159.0" text="C1">
<cellValueFactory>
<PropertyValueFactory property="value" />
</cellValueFactory>
<cellFactory>
<CellFactory menuProvider="$controller.menuProvider"/>
</cellFactory>
</TableColumn>
</columns>
</TableView>
Note: if the context menu is always the same you can also add EventHandler properties to the factory and use them like e.g. onAction attributes for buttons, which would allow you to pass event handlers of the controller, wich would result in shorter/simpler code.
Wouldnt it be possible to give your cell factory a FunctionalInterface as parameter with your event handling?(not sure if a good idea tho)
I imagine your code as follow:
Controller:
myTableView.setCellFactory(new MyOwnCellFactory<>(() -> {
// event handling
}));
MyOwnCellFactory:
public MyOwnCellFactory(MyFunctionalInterface myInterface) {
functionalInterface = myInterface;
}
// something something
functionalInterface.handleEvent();
FunctionalInterface:
#FunctionalInterface
public interface MyFunctionalInterface {
public void handleEvent();
}
Not sure if I understand your idea correct. Didnt test the code, just wrote it out of my head.
Related
I created a RadioButtonCell with this article but now i want to bind the selectedPropeties of my RadioButton with the properties contained in the ObservableList linked to this TableView. The observableList contains object type of "Risk", and the Model is containing:
final BooleanProperty isDefaultRiskProperty;
My own TableCell implementation is:
package utils;
import Model.databaseModels.Risk;
import controllers.risks.ModifyRisksAvailableController;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TableCell;
import javafx.scene.control.ToggleGroup;
public class RadioButtonCell extends TableCell<Risk, Boolean> {
ToggleGroup toggleGroup;
ModifyRisksAvailableController modifyRisksAvailableController;
public RadioButtonCell(ToggleGroup group){
toggleGroup = group;
}
#Override
public void startEdit() {
if (!isEmpty()) {
super.startEdit();
}
}
#Override
protected void updateItem(Boolean item, boolean empty){
super.updateItem(item, empty);
System.out.println(item);
if(!empty && item != null){
RadioButton radioButton = new RadioButton();
radioButton.setToggleGroup(this.toggleGroup);
radioButton.setSelected(item);
setGraphic(radioButton);
}else{
setGraphic(null);
}
}
}
My TableView contains 3 columns:
#FXML
TableColumn<Risk,Boolean> ColumnCheckBox;
#FXML
TableColumn<Risk,Number> ColumnRiskValue;
#FXML
TableColumn<Risk, Boolean> ColumnIsDefaultRisk;
And I initialize the TableView like this:
//Colonne -> Checbkox / sélection pour suppression
ColumnCheckBox.setCellValueFactory(cellData -> cellData.getValue().checkProperty());
ColumnCheckBox.setCellFactory(column -> new CheckBoxTableCell<>());
ColumnCheckBox.setEditable(true);
ColumnCheckBox.setVisible(false);
ColumnCheckBox.setPrefWidth(24.0);
//Colonne -> Checkbox / risque par défaut
ColumnIsDefaultRisk.setCellValueFactory(cellData -> cellData.getValue().isDefaultRiskProperty());
ColumnIsDefaultRisk.setCellFactory(column -> new RadioButtonCell(toggleGroup,this));
ColumnIsDefaultRisk.setEditable(true);
//Colonne -> TextField / % de risque
ColumnRiskValue.setCellValueFactory(cellData -> cellData.getValue().riskValueProperty());
ColumnRiskValue.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter()));
ColumnRiskValue.setEditable(true);
The property i want to bind with the radioButton is ".isDefaultRiskProperty()" of the "ColumnIsDefaultRisk" column. I giving my datas to the column with setCellValueFactory but i can't get the SimpleBooleanProperty in my CellFactory.
The param "item" that i get in the updateItem's method is a Boolean, (it converting BooleanProperty to Boolean), but i want a ObservableValue.
Thanks you very much.
The problem is that table cells aren’t guaranteed to exist all the time; they are for painting and editing only. So, a ToggleGroup isn’t really of any use here.
But you can do the work of a ToggleGroup yourself fairly easily:
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
RadioButton radioButton = new RadioButton();
radioButton.setToggleGroup(this.toggleGroup);
radioButton.setSelected(item);
setGraphic(radioButton);
radioButton.selectedProperty().addListener(
(o, old, selected) -> {
if (selected) {
Risk cellRisk = getTableRow().getItem();
for (Risk risk : getTableView().getItems()) {
risk.setDefaultRisk(risk == cellRisk);
}
}
});
} else {
setGraphic(null);
}
}
If you have a lot of table items (like, thousands), the for-loop could become a performance issue. So, alternatively, you can implement radio-button-like behavior by defining a Risk field or property independent of the RadioButtonCell class which keeps track of the previous selection:
ObjectProperty<Risk> previousDefaultRisk = new SimpleObjectProperty<>();
columnIsDefaultRisk.setCellFactory(
c -> new RadioButtonCell(previousDefaultRisk));
And your RadioButtonCell class would change to look like this:
public class RadioButtonCell extends TableCell<Risk, Boolean> {
private final ObjectProperty<Risk> previousDefaultRisk;
public RadioButtonCell(ObjectProperty<Risk> previousDefaultRisk) {
this.previousDefaultRisk = previousDefaultRisk;
}
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
RadioButton radioButton = new RadioButton();
radioButton.setSelected(item);
setGraphic(radioButton);
radioButton.selectedProperty().addListener(
(o, old, selected) -> {
if (selected) {
if (previousDefaultRisk.get() != null) {
previousDefaultRisk.get().setDefaultRisk(false);
}
Risk risk = getTableRow().getItem();
risk.setIsDefaultRisk(true);
previousDefaultRisk.set(risk);
}
});
} else {
setGraphic(null);
}
}
}
Note: It is Java convention that non-static field names should always start with a lowercase letter. Following these conventions will make your code easier for others to read, including Stack Overflow readers.
I'm trying to make a TableView with an embedded ListView, but I'm not exactly sure what to do, I've been researching how to embed Buttons into a TableColumn and I've seen that I should create a custom class that extends TableColumn and overrides updateItem().
I have a:
#FXML
private TableColumn<FoodModel, ObservableSet<Food>> storeFood;
for the tableColumn, set by the FXML editor. It's value is set by this.storeFood.setCellValueFactory(val -> val.getValue().getFood); and this.storeFood.setCellFactory(value -> new ListViewCell<>()); upon initialization.
I've been encountering a problem in which the list on screen is not being populated. Can I have a checklist of things to do to embed a list into a TableColumn?
Cell:
private static final class ListViewCell<T, V> extends TableCell<T, V> {
private ListView<T> list;
ListViewCell() {
this.list = new ListView<>();
this.list.setPrefHeight(60);
}
#Override
protected void updateItem(V item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(this.list);
}
}
}
Context
I'm building a plugin for eclipse 3.4 and more.
I have a view with id mrp.view with a menuContribution set to toolbar:mrp.view.
This menuContribution has some command, and I have this one:
<handler
class="mrp.handlers.export"
commandId="mrp.commands.export">
</handler>
<command
commandId="mrp.commands.export"
label="My command"
style="push">
</command>
My handler, mrp.handlers.export has a dynamic ìsEnabled()` method, looking like that :
#Override
public boolean isEnabled() {
return !getMySelection().isEmpty();
}
Question
How can I refresh the button on my toolbar when data changed ?
(refresh is done automatically if I click anothr button of the toolbar, but if I don't...)
I tried..
ICommandService service = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class);
service.refreshElements("mrp.commands.export", null);
But it doesn't seems to do anything.
Also this one:
public class Export extends AbstractHandler implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
setBaseEnabled(!getSelection().isEmpty());
}
// ....
}
It is called, but the icon on my view's menu is not refreshed (on eclipse 3.7).
Did I do something wrong ?
Your handler must fire an event when it's enablement changes. If you have the change update your handler using org.eclipse.core.commands.AbstractHandler.setBaseEnabled(boolean) it will fire the required event.
Thanls to Paul Webster's answer, I got it working.
public class Export extends AbstractHandler implements PropertyChangeListener {
public Export() {
Activator.getDefault().AddListener(this);
setBaseEnabled(!getMySelection().isEmpty());
}
#Override
public Object execute(ExecutionEvent event) throws ExecutionException {
// My handler
return null;
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(Activator.EVENT_SELECTION_CHANGED)) {
boolean before = isEnabled();
boolean after = !getMySelection().isEmpty();
if (after != before) {
setBaseEnabled(after);
}
}
}
}
I have an Osmdroid MapView. Even though I have set
mapView.setClickable(false);
mapView.setFocusable(false);
the map can still be moved around. Is there a simple way to disable all interactions with the map view?
A simple solution is to do like #Schrieveslaach but with the mapView:
mapView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
My solution is similar to #schrieveslaach and #sagix, but I just extend base MapView class and add new functionality:
class DisabledMapView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : MapView(context, attrs) {
private var isUserInteractionEnabled = true
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
if (isUserInteractionEnabled.not()) {
return false
}
return super.dispatchTouchEvent(event)
}
fun setUserInteractionEnabled(isUserInteractionEnabled: Boolean) {
this.isUserInteractionEnabled = isUserInteractionEnabled
}
}
I've found a solution. You need to handle the touch events directly by setting a OnTouchListener. For example,
public class MapViewLayout extends RelativeLayout {
private MapView mapView;
/**
* #see #setDetachedMode(boolean)
*/
private boolean detachedMode;
// implement initialization of your layout...
private void setUpMapView() {
mapView.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (detachedMode) {
if (event.getAction() == MotionEvent.ACTION_UP) {
// if you want to fire another event
}
// Is detached mode is active all other touch handler
// should not be invoked, so just return true
return true;
}
return false;
}
});
}
/**
* Sets the detached mode. In detached mode no interactions will be passed to the map, the map
* will be static (no movement, no zooming, etc).
*
* #param detachedMode
*/
public void setDetachedMode(boolean detachedMode) {
this.detachedMode = detachedMode;
}
}
You could try:
mapView.setEnabled(false);
Which should disable all interactions with the map view
I created a ButtonCell and a Column for it:
ButtonCell previewButton = new ButtonCell();
Column<Auction,String> preview = new Column<Auction,String>(previewButton) {
public String getValue(Auction object) {
return "Preview";
}
};
How do I now add a click handler (e.g. ClickHandler) for this ButtonCell?
The Cell Sampler example includes use of clickable ButtonCells. Clicks on ButtonCells are handled by setting the FieldUpdater for the Column:
preview.setFieldUpdater(new FieldUpdater<Auction, String>() {
#Override
public void update(int index, Auction object, String value) {
// The user clicked on the button for the passed auction.
}
});
//Prevent mouse events for table cell
CellPreviewEvent.Handler<Auction > manager = DefaultSelectionEventManager.createBlacklistManager(4);//column number
cellTable.setSelectionModel(selectionModel, manager);
new Column<Auction , String>(new ButtonCell()){
#Override
public String getValue(Auction object) {
return "Preview";
}
#Override
public void onBrowserEvent(Cell.Context context, Element elem, Auction object, NativeEvent event) {
event.preventDefault();
//TODO implement event handling
}
}