Can we change GWT Bootstrap 3 ButtonCell Icon Dynamically? - java

I have a GWT bootstrap 3 button as a ButtonCell created with IconType and ButtonType:
public abstract class ButtonColumn<T> extends Column<T, String> {
public ButtonColumn(IconType iconType, ButtonType buttonType) {
this(new ButtonCell(buttonType, iconType));
}
}
So when I create the button, I do
new ButtonColumn<Object>(IconType.PLAY, ButtonType.SUCCESS) {
#Override
public void onClick(Object obj) {
doStuff(obj);
}
};
I want to change my button IconType onClick. Is it possible to achieve it?
And can I create a custom IconType extending the GWT IconType Enum? I wanted to put an animated icon (like a loading icon).

Well, you can not change the button's icon in a row, especially when you create the whole column with an icon already specified. But you can redraw() a row and this could be a way to achieve what you want.
I use AbstractCell to render a button and onBrowserEvent:
first create an AbstractCell with ClickEvent in consumedEvents parameter
in the render() method render a button based on the clicked state
in the onBrowserEvent() method change the clicked state and re-render the row
The clicked state is best to be kept in the table's underlying data type so it is available for each row.
Here is a complete working example code:
final CellTable<TableType> table = new CellTable<TableType>();
AbstractCell<TableType> buttonCell = new AbstractCell<ButtonCellTest.TableType>(ClickEvent.getType().getName()) {
#Override
public void render(Context context, TableType value, SafeHtmlBuilder sb) {
Button button = new Button();
button.setType(ButtonType.SUCCESS);
button.setSize(ButtonSize.SMALL);
button.add(new Icon(value.isClicked() ? IconType.CHECK : IconType.TIMES));
sb.append(SafeHtmlUtils.fromTrustedString(button.toString()));
}
#Override
public void onBrowserEvent(Context context, Element parent, TableType value, NativeEvent event, ValueUpdater<TableType> valueUpdater) {
value.setClicked(!value.isClicked());
// ... do stuff...
table.redrawRow(context.getIndex());
}
};
table.addColumn(new Column<TableType, TableType>(buttonCell) {
#Override
public TableType getValue(TableType object) {
return object;
}
});
ArrayList<TableType> rowData = new ArrayList<TableType>();
rowData.add(new TableType("row 1"));
rowData.add(new TableType("row 2"));
...
table.setRowData(rowData);
And example table's data type keeping the clicked state:
public class TableType {
String text;
boolean clicked = false;
public TableType(String text) {
this.text = text;
}
public String getText() {
return text;
}
public boolean isClicked() {
return clicked;
}
public void setClicked(boolean clicked) {
this.clicked = clicked;
}
}
As for extending the IconType enum - no, you can not extend an enum in Java. See this question for example: Can enums be subclassed to add new elements?.
You could try to add your own CSS class but this should be asked as another question to get precise answers.

Related

TreeTableView : setting a row not editable

I want to have control over the styling of some rows of a TreeTableView based on the level in the tree. I used setRowFactory and apply a styling if this row is part of the first level children of the root of the Table. The styling works fine, but I also want to disable clicking on the checkbox for those rows. I am able to setDisable(true) but that also disables the expanding of the TreeItem and SetEditable(false) does not seem to have any effect.
EDIT: What I understand is that the Table must be set editable, then the columns are by default editable. But if I set TreeTableRow.setEditable(true); or TreeTableRow.setEditable(false); I never see any effect. The description seems of setEditable seems exactly what I want but I am unable to use it that way.
void javafx.scene.control.Cell.setEditable(boolean arg0)
setEditable
public final void setEditable(boolean value)
Allows for certain cells to not be able to be edited. This is useful incases >where, say, a List has 'header rows' - it does not make sense forthe header rows >to be editable, so they should have editable set tofalse.
Parameters:value - A boolean representing whether the cell is editable or not.If >true, the cell is editable, and if it is false, the cell can notbe edited.
Main:
public class TreeTableViewRowStyle extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
// create the treeTableView and colums
TreeTableView<Person> ttv = new TreeTableView<Person>();
TreeTableColumn<Person, String> colName = new TreeTableColumn<>("Name");
TreeTableColumn<Person, Boolean> colSelected = new TreeTableColumn<>("Selected");
colName.setPrefWidth(100);
ttv.getColumns().add(colName);
ttv.getColumns().add(colSelected);
ttv.setShowRoot(false);
ttv.setEditable(true);
// set the columns
colName.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
colSelected.setCellFactory(CheckBoxTreeTableCell.forTreeTableColumn(colSelected));
colSelected.setCellValueFactory(new TreeItemPropertyValueFactory<>("selected"));
ttv.setRowFactory(table-> {
return new TreeTableRow<Person>(){
#Override
public void updateItem(Person pers, boolean empty) {
super.updateItem(pers, empty);
boolean isTopLevel = table.getRoot().getChildren().contains(treeItemProperty().get());
if (!isEmpty()) {
if(isTopLevel){
setStyle("-fx-background-color:lightgrey;");
setEditable(false); //THIS DOES NOT SEEM TO WORK AS I WANT
//setDisable(true); //this would disable the checkbox but also the expanding of the tree
}else{
setStyle("-fx-background-color:white;");
}
}
}
};
});
// creating treeItems to populate the treetableview
TreeItem<Person> rootTreeItem = new TreeItem<Person>();
TreeItem<Person> parent1 = new TreeItem<Person>(new Person("Parent 1"));
TreeItem<Person> parent2 = new TreeItem<Person>(new Person("Parent 1"));
parent1.getChildren().add(new TreeItem<Person>(new Person("Child 1")));
parent2.getChildren().add(new TreeItem<Person>(new Person("Child 2")));
rootTreeItem.getChildren().addAll(parent1,parent2);
ttv.setRoot(rootTreeItem);
// build and show the window
Group root = new Group();
root.getChildren().add(ttv);
stage.setScene(new Scene(root, 300, 300));
stage.show();
}
}
Model Person :
public class Person {
private StringProperty name;
private BooleanProperty selected;
public Person(String name) {
this.name = new SimpleStringProperty(name);
selected = new SimpleBooleanProperty(false);
}
public StringProperty nameProperty() {
return name;
}
public BooleanProperty selectedProperty() {
return selected;
}
public void setName(String name){
this.name.set(name);
}
public void setSelected(boolean selected){
this.selected.set(selected);
}
}
The base problem is that none of the editable (nor the pseudo-editable like CheckBoxXX) Tree/Table cells respect the editability of the row they are contained in. Which I consider a bug.
To overcome, you have to extend the (pseudo) editable cells and make them respect the row's editable. The exact implementation is different for pseudo- vs. real editing cells. Below are in-line examples, for frequent usage you would make them top-level and re-use.
CheckBoxTreeTableCell: subclass and override updateItem to re-bind its disabled property like
colSelected.setCellFactory(c -> {
TreeTableCell cell = new CheckBoxTreeTableCell() {
#Override
public void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if (getGraphic() != null) {
getGraphic().disableProperty().bind(Bindings
.not(
getTreeTableView().editableProperty()
.and(getTableColumn().editableProperty())
.and(editableProperty())
.and(getTreeTableRow().editableProperty())
));
}
}
};
return cell;
});
For a real editing cell, f.i. TextFieldTreeTableCell: override startEdit and return without calling super if the row isn't editable
colName.setCellFactory(c -> {
TreeTableCell cell = new TextFieldTreeTableCell() {
#Override
public void startEdit() {
if (getTreeTableRow() != null && !getTreeTableRow().isEditable()) return;
super.startEdit();
}
};
return cell;
});
Now you can toggle the row's editability as you do, changed the logic a bit to guarantee full cleanup in all cases:
ttv.setRowFactory(table-> {
return new TreeTableRow<Person>(){
#Override
public void updateItem(Person pers, boolean empty) {
super.updateItem(pers, empty);
// tbd: check for nulls!
boolean isTopLevel = table.getRoot().getChildren().contains(treeItemProperty().get());
if (!isEmpty() && isTopLevel) {
// if(isTopLevel){
setStyle("-fx-background-color:lightgrey;");
setEditable(false);
}else{
setEditable(true);
setStyle("-fx-background-color:white;");
}
}
};
});
Instead of creating a custom TreeTableCell subclass you can use the following utility method that basically installs a new cell-factory on a column that delegates to the original cell-factory but adds the row-editability binding whenever a cell is created.
public <S, T> void bindCellToRowEditability(TreeTableColumn<S, T> treeTableColumn) {
// Keep a handle on the original cell-factory.
Callback<TreeTableColumn<S, T>, TreeTableCell<S, T>> callback = treeTableColumn.getCellFactory();
// Install a new cell-factory that performs the delegation.
treeTableColumn.setCellFactory(column -> {
TreeTableCell<S, T> cell = callback.call(column);
// Add a listener so that we pick up when a new row is set for the cell.
cell.tableRowProperty().addListener((observable, oldRow, newRow) -> {
// If the new row is non-null, we proceed.
if (newRow != null) {
// We get the cell and row editable-properties.
BooleanProperty cellEditableProperty = cell.editableProperty();
BooleanProperty rowEditableProperty = newRow.editableProperty();
// Bind the cell's editable-property with its row's property.
cellEditableProperty.bind(rowEditableProperty);
}
});
return cell;
});
}
You can then set this for all columns of your TreeTableView as:
List<TreeTableColumn<S, ?>> columns = treeTableView.getColumns();
columns.forEach(this::bindCellToRowEditability);
You still need the custom TreeTableRow that checks whether it is top-level or not so that the editable value is correctly set for the row itself. However, setting the editable value on the row will now ensure that all cells in that row correctly reflects the row's editable-property.
If you want disable a specific Cell then handle the disable logic in the CellFactory rather than in RowFactory. The static method forTreeTableColumn(..) is a convinient method for quick use. But that is not the only way. You can still create your own factory for CheckBoxTreeTableCell.
So instead of
colSelected.setCellFactory(CheckBoxTreeTableCell.forTreeTableColumn(colSelected));
set the cellfactory as below, and this should work for you.
colSelected.setCellFactory(new Callback<TreeTableColumn<Person, Boolean>, TreeTableCell<Person, Boolean>>() {
#Override
public TreeTableCell<Person, Boolean> call(TreeTableColumn<Person, Boolean> column) {
return new CheckBoxTreeTableCell<Person, Boolean>() {
#Override
public void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
boolean isTopLevel = column.getTreeTableView().getRoot().getChildren().contains(getTreeTableRow().getTreeItem());
setEditable(!isTopLevel);
}
};
}
});

JavaFX EventHandler - new Alert if boolean equals true

When the user has reached the end of this Tetris game I want a new alert to be opened.
In the model class I have a boolean that will switch to true if a new Tetris block cannot be spawned.
I'm working with model view presenter, so in the model is the boolean + getter and in the presenter a new alert will be created if the boolean returns true.
The question is how do I add this to the eventHandlers() in the presenter?
public Presenter(Model model, View view) {
this.model = model;
this.view = view;
addEventHandlers();
}
private void addEventHandlers() {
//view.setOnKeyPressed... this is for rotating the blocks to give you an example
}
JavaFX implements observable properties, which are extensions of the Java Bean pattern that support notification for invalidation and for changes to the underlying value. These are fundamental to the JavaFX library: all controls in JavaFX make use of these. So, for example, if you want to respond to changes to the text in a text field, you would do
myTextField.textProperty().addListener((observable, oldText, newText) -> {
// ... do something with newText (and perhaps oldText) here...
});
So you can just achieve this with a BooleanProperty (or similar) in your model class:
private final BooleanProperty gameEnded = new SimpleBooleanProperty();
public BooleanProperty gameEndedProperty() {
return gameEnded ;
}
public final boolean isGameEnded() {
return gameEndedProperty().get();
}
public final void setGameEnded(boolean gameEnded) {
gameEndedProperty().set(gameEnded);
}
Then you can do:
model.gameEndedProperty().addListener((obs, gameWasEnded, gameIsNowEnded) -> {
if (gameIsNowEnded) {
// show alert, etc...
}
});
See "Properties and Bindings" in the Oracle tutorial for more details, including bindings, etc. You might also consider a ReadOnlyBooleanWrapper if you don't want the property to be changed from outside the class it is defined in.
public void setEind() {
boolean oldValue = this.eind;
eind = true;
System.out.println(eind);
firePropertyChange("eind",oldValue,eind);
}
private final List<PropertyChangeListener> listeners = new ArrayList<>();
public void addPropertyChangeListener(PropertyChangeListener listener) {
listeners.add(listener);
}
public void firePropertyChange(String property, Object oldValue, Object newValue) {
for (PropertyChangeListener listener : listeners) {
listener.propertyChange(new PropertyChangeEvent(this,property,oldValue,newValue));
}
}
spel.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
val.stop();
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setHeaderText("Game over!");
alert.setContentText("Enter your name in the next window for the highscores.");
alert.setTitle("End");
alert.show();
}
});

How to listen to selection changes on TableViewer?

I'm working on an Eclipse RCP application and I'm trying to update an expression value which is provided by MySourceProvider according to selection changes on a TableViewer in MyEditorPart.
MyEditorPart instance defines a TableViewer like this:
public class MyEditorPart extends EditorPart {
#Override
public void createPartControl(Composite parent) {
TableViewer tableviewer = new TableViewer(parent, SWT.CHECK);
tableviewer.setContentProvider(ArrayContentProvider.getInstance());
getSite().setSelectionProvider(tableViewer);
...
MySourceProvider have some expression values like this:
public class MySourceProvider extends AbstractSourceProvider {
public static final String EXPR = "org.xyz.isEntrySelected";
// other expressions
#Override
public String[] getProvidedSourceNames() {
return new String[] { EXPR,
// other expressions
};
}
#Override
public Map getCurrentState() {
HashMap<String, Object> map = new HashMap<String, Object>(1);
map.put(EXPR, expr_value); // expr_value calculated by the listener
// other expressions
return map;
}
I want to change expr_value according to selection changes on TableViewer.
I registered the listener like this:
window.getSelectionService().addPostSelectionListener(MyEditorPartId, selectionListener);
private final ISelectionListener selectionListener = new SelectionListener() {
#Override
public void selectionChanged(IWorkbenchPart part, ISelection selection) {
handleEvent();
}
};
The listener registers successfully but gets notified only once if I clicked somewhere on MyEditorPart (not just TableViewer but the whole editor). To get notified again, I have to click on some other view (or editor) part to lose focus and then click again on MyEditorPart.
1. Why does the listener gets notified only once when MyEditorPart re-gains focus?
2. How to listen only to selection changes to TableViewer rows?
What am I missing here? What is the proper way to listen to selection changes?
Thanks in advance.
What you need is not a SelectionListener, but a SelectionChangedListener.
With this you can write the following code:
viewer.addSelectionChangedListener(new ISelectionChangedListener() {
#Override
public void selectionChanged(SelectionChangedEvent event) {
IStructuredSelection selection = viewer.getStructuredSelection();
Object firstElement = selection.getFirstElement();
// do something with it
}
});
It does appear that this form of addPostSelectionListener only fires when the part becomes active. Use the:
addPostSelectionListener(ISelectionListener listener)
form of the listener which is called for every selection change.
You can then test the IWorkbenchPart id in the listener:
#Override
public void selectionChanged(final IWorkbenchPart part, final ISelection selection)
{
if (MyEditorPartId.equals(part.getSite().getId()))
{
// your code
}
}

Why super.updateItem(item, empty) Called in listview.cellFacoty method in javafx?

Code:
mpcListView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> param){
return new XCell();
}
});
public class XCell extends ListCell<String>{
HBox hbox = new HBox();
Label label = new Label();
Button button = new Button("",getImage());
String lastItem;
public XCell(){
super();
hbox.setSpacing(120);
hbox.getChildren().addAll(label,button);
button.setOnAction(new EventHandler<ActionEvent>(){
#Override
public void handle(ActionEvent event){
mpcListView.getItems().remove(lastItem);
}
});
}
#Override
protected void updateItem(String item, boolean empty){
super.updateItem(item, empty);
if (empty){
lastItem = null;
setGraphic(null);
}else{
lastItem = item;
label.setText(item);
setGraphic(hbox);
}
}
}
Why super.updateItem(item, empty) is called?
ListCell updateItem(...) implementation is really important as it calls Cell updateItem(...) implementation which checks if it is empty or not and calls the correct method: setItem(...) or setEmpty(...).
If you don't call super.updateItem(...) then Cell.updateItem(...) is not called and setItem(...) is not called then... nothing is drawn or the value is not updated! ListCell just adds some check before using Cell updateItem implementation, so you get two choices:
You can call super.updateItem(...) in your custom ListCell implementation
You can call setItem(...) and setEmpty(...) in your updateItem implementation and "by-pass" ListCell implementation on checks and edits stuff
Note that ListCell it is not the "base" implementation used by a ListView. Refer to the class TextFieldListCell instead, it is a great example of how it actually works, get the sources from mercurial and read, it is always the best way.
For instance, TextFieldListCell uses super.updateItem(...) to call Cell.updateItem implementation to check if it is empty or not (by using setItem or setEmpty) and then uses javafx.scene.control.cell.CellUtils.updateItem(...). This method gets the item setted in the current cell and uses after that a converter on the item to show a String in a Label.
The default implementation of ListCell does some standard housekeeping. For example, it registers a mouse listener that updates the ListView's selection model appropriately.
The super.updateItem(...) call invokes the superclass implementation of the method. If you omit it, selection won't work, and probably a bunch of other functionality won't work either.
In your code, the lastItem field is redundant. There's already an item property defined in the ListCell class: another job the default implementation of updateItem(...) does is to update this property. So you can omit that field and just call getItem() when you need to get the item.
It's pretty easy to forget to call super.updateItem(...). For this reason I often use an alternative approach:
mpcListView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> param){
return createXCell();
}
});
public ListCell<String> createXCell() {
final ListCell<String> cell = new ListCell<String>();
final HBox hbox = new HBox();
final Label label = new Label();
final Button button = new Button("",getImage());
hbox.setSpacing(120);
hbox.getChildren().addAll(label,button);
button.setOnAction(new EventHandler<ActionEvent>(){
#Override
public void handle(ActionEvent event){
mpcListView.getItems().remove(cell.getItem());
}
});
cell.itemProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> obs, String oldValue, String newValue) {
if (newValue == null) {
cell.setText(null);
cell.setGraphic(null);
} else {
cell.setText(newValue);
cell.setGraphic(hbox);
}
}
});
return cell ;
}
Note that I don't subclass ListCell at all here: I just use the default implementation and update the text and graphic using a listener on the itemProperty. This is more in the spirit of "Prefer aggregation over inheritance".

GWT: Building widgets with separated mvp

I'm trying to build a gwt widget with separated model, view and presenter.
I'm using just one class for all of these components so far:
A compact example:
public class MyCellSelectableTable extends Composite {
private WhatEverRepresentation selectedCell;
public MyCellSelectableTable() {
Grid myTable = new Grid(2,2);
/*
* Some code to realize a table with cell selection
* ...
*/
initWidget(myTable);
}
}
In my appreciation the information "selectedCell" (and in my project many other data) should be stored in a separate model.
How can I implement this structurally correct, so it still is a widget but with an encapsulated mvp architecture?
In one of my projects I was asked to design a spinner item which is dressed to look good on a mobile web app. Then we realized we actually need another view for our spinner which is 'thinner' as well. So I have tried to aplly the MVP approach to my widget implementation which worked well for me. This one below is a very simple example not for production use just for the sake of demonstration
Define a Presenter and a View interface as in MVP pattern. The point here is to make the view implementations dumb so they can be switched without a hassle. Note that Spinner is not actually a Widget but it implements IsWidget which means it will be treated like a widget but in fact we will be passing the reference of our view implementation.
public class Spinner implements IsWidget, SpinnerPresenter{
interface View{
Widget asWidget();
void stepUp(int step);
void stepDown(int step);
void setValue(int value);
void setPixelSize(int width,int height);
}
View view;
int value;
public Spinner() {
view = new SpinnerImpl(this);
view.setValue(0);
}
public int getValue() {
return value;
}
public void setValue(int value) {
if (value == this.value)
return;
this.value = value;
view.setValue(value);
}
public void setPixelSize(int width, int height){
view.setPixelSize(width,height);
}
#Override
public void downButtonClicked() {
value--;
view.stepDown(1);
}
#Override
public void upButtonClicked() {
value++;
view.stepUp(1);
}
#Override
public Widget asWidget() {
return view.asWidget();
}
}
And the view Implementation itself which implements the view interface defined in Spinner class. Notice how we delegate user events to Spinner class to be handled over the SpinnerPresenter interface.
public class SpinnerImpl extends Composite implements Spinner.View{
private TextBox txtBox;
private Button upButton,downButton;
private HorizontalPanel panel;
private SpinnerPresenter presenter;
public SpinnerImpl(SpinnerPresenter presenter){
this.presenter = presenter;
upButton = new Button("up");
downButton = new Button("down");
txtBox = new TextBox();
txtBox.setEnabled(false);
panel = new HorizontalPanel();
panel.add(upButton);
panel.add(txtBox);
panel.add(downButton);
addHandlers();
initWidget(panel);
}
private void addHandlers() {
upButton.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
presenter.upButtonClicked();
}
});
downButton.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
presenter.downButtonClicked();
}
});
}
#Override
public void stepDown(int step) {
txtBox.setValue(Integer.parseInt(txtBox.getValue())-1+"");
}
#Override
public void stepUp(int step) {
txtBox.setValue(Integer.parseInt(txtBox.getValue())+1+"");
}
#Override
public void setValue(int value) {
txtBox.setValue(0+"");
}
}
SpinnerPresenter interface :
public interface SpinnerPresenter {
void upButtonClicked();
void downButtonClicked();
}
Finally to add my widget to rootpanel. Notice I can add spinner class as if it was a widget
Spinner s = new Spinner();
RootPanel.get().add(s);
Now if i wanted to change the way my spinner item looks, change orientation, maybe add a fancy animation for spinning etc, I need only to change my View Implementation. Last but not least when it comes to testing my widget this approach will help since I can easily mockup my view.
FYI, the cell widgets in GWT are built with MVP internally. A CellList (for instance) is the "view" part and instantiates an internal presenter.
Also, this session from Google I/O 2010 was eye opening for me: http://www.google.com/events/io/2010/sessions/gwt-continuous-build-testing.html
I recommend you read the following MVP article if you haven't already (and forgive me if you have): http://code.google.com/webtoolkit/articles/mvp-architecture.html http://www.gwtproject.org/articles/mvp-architecture.html
Believe me it's worth it to go ahead and separate out your model, view, and presenter. The problem always seems simple enough to have in one class, but when I've tried that I always end up separating things out. You'll appreciate having a better separation of concerns and the flexibility it offers.

Categories