I'm trying to implement an Excel like functionality where a cell may have a comment, so on hover I want a pop up to appear containing the desired text. All is well until the pop up appears. The problem I'm facing is that when the pop up appears all the background events are disabled. When hovering at any other part of the grid, nothing happens if the pop up is on display (e.g. the header is unreachable). Although I specifically state dialog.setGlassEnabled(false);. As I've seen by chrome dev tools, the glass div that acts as a background is indeed not present. So why is this strange behavior? Is there something else? Below is the whole test case (in order to reproduce easily) and on separate parts is the code that I believe is of importance.
I'm using GWT 2.6 and GXT 3.1, but the component I'm using is the native component DialogBox of GWT, so I believe GXT is irrelevant here.
mainGrid.addHandler(new GridHoverPopUpHandler()
{
#Override
public void onHover(Element element)
{
dialog.hide();
if(element == null) return;
Element cell = mainGrid.getView().findCell(element);
if(cell != null && cell.hasClassName("comment-indicator"))
{
Label label = new Label("mpazingka " + (String)cell.getInnerText());
dialog.setText("Title of comment");
dialog.setWidget(label);
dialog.setPopupPosition(absoluteX, absoluteY);
dialog.show();
}
}
}, MouseMoveEvent.getType());
abstract class GridHoverPopUpHandler implements MouseOutHandler, MouseMoveHandler
{
protected int absoluteX;
protected int absoluteY;
private Element lastHoveredElement = null;
protected DialogBox dialog;
public abstract void onHover(Element element);
GridHoverPopUpHandler()
{
dialog = new DialogBox();
dialog.setGlassEnabled(false);
}
#Override
public void onMouseMove(MouseMoveEvent event)
{
Element curHoveredElement = null;
NativeEvent natev = event.getNativeEvent();
if (Element.is(natev.getEventTarget()))
{
curHoveredElement = Element.as(natev.getEventTarget());
}
if(lastHoveredElement == curHoveredElement)
{
return;
}
absoluteX = event.getClientX();
absoluteY = event.getClientY();
lastHoveredElement = curHoveredElement;
onHover(curHoveredElement);
}
#Override
public void onMouseOut(MouseOutEvent event)
{
//TODO: Pendig implementation
}
}
package com.test.client;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.google.gwt.cell.client.DateCell;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.widget.core.client.ContentPanel;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.grid.GridView;
import com.sencha.gxt.widget.core.client.grid.GridViewConfig;
import com.sencha.gxt.widget.core.client.grid.GroupSummaryView;
import com.sencha.gxt.widget.core.client.grid.SummaryColumnConfig;
public class GridHoverExample implements IsWidget, EntryPoint {
private static final StockProperties props = GWT.create(StockProperties.class);
private ContentPanel root;
private ArrayList<String> comments;
private void rootInit() {
root = new ContentPanel();
root.setHeadingText("Locked Grid Sample");
root.setPixelSize(400, 300);
comments = new ArrayList();
comments.add("Stack_2");
}
#Override
public Widget asWidget() {
if (root == null) {
rootInit();
ColumnConfig<Stock, String> nameCol = new SummaryColumnConfig<Stock, String>(props.name(), 100, SafeHtmlUtils.fromTrustedString("<b>Company</b>"));
ColumnConfig<Stock, String> symbolCol = new SummaryColumnConfig<Stock, String>(props.symbol(), 100, "Symbol");
ColumnConfig<Stock, Double> changeCol = new SummaryColumnConfig<Stock, Double>(props.change(), 100, "Change");
ColumnConfig<Stock, String> industryCol = new SummaryColumnConfig<Stock, String>(props.industry(), 100, "Industry");
ColumnConfig<Stock, Date> dateCol = new SummaryColumnConfig<Stock, Date>(props.date(), 100, "Date");
dateCol.setCell(new DateCell(DateTimeFormat.getFormat("MM/dd/yyyy")));
List<ColumnConfig<Stock, ?>> ccFree = new ArrayList<ColumnConfig<Stock, ?>>();
ccFree.add(nameCol);
ccFree.add(symbolCol);
ccFree.add(changeCol);
ccFree.add(dateCol);
ccFree.add(industryCol);
ColumnModel<Stock> cm = new ColumnModel<Stock>(ccFree);
ListStore<Stock> store = new ListStore<Stock>(props.key());
for (int i = 1; i <= 100; i++)
store.add(new Stock("Stack_"+i, "S_"+i, 2, 2, new Date()));
final Grid<Stock> mainGrid = new Grid<Stock>(store, cm);
mainGrid.addHandler(new GridHoverPopUpHandler()
{
#Override
public void onHover(Element element)
{
dialog.hide();
if(element == null) return;
Element cell = mainGrid.getView().findCell(element);
if(cell != null && cell.hasClassName("comment-indicator"))
{
Label label = new Label("mpazingka " + (String)cell.getInnerText());
dialog.setText("Title of comment");
dialog.setWidget(label);
dialog.setPopupPosition(absoluteX, absoluteY);
dialog.show();
}
}
}, MouseMoveEvent.getType());
mainGrid.setView(createGridView());
root.setWidget(mainGrid);
}
return root;
}
#Override
public void onModuleLoad() {
RootPanel.get("nameFieldContainer").add(asWidget());
}
private GridView<Stock> createGridView()
{
final GroupSummaryView<Stock> view = new GroupSummaryView<Stock>();
view.setShowGroupedColumn(false);
view.setStripeRows(true);
view.setColumnLines(true);
view.setSortingEnabled(false);
view.setShowDirtyCells(false);
view.setViewConfig(new GridViewConfig<Stock>()
{
#Override
public String getRowStyle(Stock model, int rowIndex)
{
return "";
}
#Override
public String getColStyle(Stock model, ValueProvider<? super Stock, ?> valueProvider, int rowIndex, int colIndex)
{
String style = "";
if(model.getName().equals(comments.get(0)) && colIndex == 0)
{
style += "comment-indicator ";
}
style += "columnWidth";
return style;
}
});
return view;
}
}
abstract class GridHoverPopUpHandler implements MouseOutHandler, MouseMoveHandler
{
protected int absoluteX;
protected int absoluteY;
private Element lastHoveredElement = null;
protected DialogBox dialog;
public abstract void onHover(Element element);
GridHoverPopUpHandler()
{
dialog = new DialogBox();
dialog.setGlassEnabled(false);
}
#Override
public void onMouseMove(MouseMoveEvent event)
{
Element curHoveredElement = null;
NativeEvent natev = event.getNativeEvent();
if (Element.is(natev.getEventTarget()))
{
curHoveredElement = Element.as(natev.getEventTarget());
}
if(lastHoveredElement == curHoveredElement)
{
return;
}
absoluteX = event.getClientX();
absoluteY = event.getClientY();
lastHoveredElement = curHoveredElement;
onHover(curHoveredElement);
}
#Override
public void onMouseOut(MouseOutEvent event)
{
//TODO: Pendig implementation
}
}
Update
Ok I found it. This widget in order to manipulate the DOM for making the component dragable captures some events (mouse events). I didn't follow through because of time restrictions, so I used DecoratedPopupPanel which is the parent class of DialogBox and doesn't capture all those events that I need. I'll be waiting for a day or two, for an answer about the part that does this event capturing, and how I can disable it. If not provided I'm posting this as an answer to close the topic.
I think you may have done it overkill by using the dialog to done the functionality of tool tip. IMHO, What you should do was add a tooltip to the cell that you want to have the "excel comment" functionality. So whenever, user hover to that cell, it will show the tooltip loaded with data from your comment. Take a look at the Basic Grid example at here: http://www.sencha.com/examples/#ExamplePlace:basicgrid
Try to hover your mouse to Change cell, it will pop you a tool tip. The key point was to create your own grid cell implementation, like this :
ColumnConfig<Stock, Double> changeCol = new ColumnConfig<Stock, Double>(props.change(), 100, "Change");
changeCol.setCell(new AbstractCell<Double>() {
#Override
public void render(Context context, Double value, SafeHtmlBuilder sb) {
String style = "style='color: " + (value < 0 ? "red" : "green") + "'";
String v = number.format(value);
sb.appendHtmlConstant("<span " + style + " qtitle='Change' qtip='" + v + "'>" + v + "</span>");
}
});
If you need to use different type of tooltip, take a look at the tooltips example at here: http://www.sencha.com/examples/#ExamplePlace:tooltips
Hope this could help you.
Related
We try to achieve the following:
When a node gets selected in a JavaFX TreeTableView, also "the path to the root", i.e., the parent, the grandparent, and so on should get selected. Selected in this case means highlighted with a different background color, see the image (in the example, the node on Level 2 has been clicked by the user).
Is there a built-in function how to achieve this?
We tried using CSS but did not succeed.
There's no "built-in function" to do this. Use a row factory on the tree table view to create rows that observe the selected item, and set a pseudoclass on the row accordingly.
For example:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableRow;
import javafx.scene.control.TreeTableView;
import javafx.stage.Stage;
public class TreeTableViewHighlightSelectionPath extends Application {
#Override
public void start(Stage primaryStage) {
TreeTableView<Item> table = new TreeTableView<Item>();
PseudoClass ancestorOfSelection = PseudoClass.getPseudoClass("ancestor-of-selection");
table.setRowFactory(ttv -> new TreeTableRow<Item>() {
{
table.getSelectionModel().selectedItemProperty().addListener(
(obs, oldSelection, newSelection) -> updateStyleClass());
}
#Override
protected void updateItem(Item item, boolean empty) {
super.updateItem(item, empty);
updateStyleClass();
}
private void updateStyleClass() {
pseudoClassStateChanged(ancestorOfSelection, false);
TreeItem<Item> treeItem = table.getSelectionModel().getSelectedItem();
if (treeItem != null) {
for (TreeItem<Item> parent = treeItem.getParent() ; parent != null ; parent = parent.getParent()) {
if (parent == getTreeItem()) {
pseudoClassStateChanged(ancestorOfSelection, true);
break ;
}
}
}
}
});
TreeTableColumn<Item, String> itemCol = new TreeTableColumn<>("Item");
itemCol.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getValue().getName()));
table.getColumns().add(itemCol);
TreeTableColumn<Item, Number> valueCol = new TreeTableColumn<>("Value");
valueCol.setCellValueFactory(cellData -> cellData.getValue().getValue().valueProperty());
table.getColumns().add(valueCol);
table.setRoot(createRandomTree());
Scene scene = new Scene(table);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private TreeItem<Item> createRandomTree() {
TreeItem<Item> root = new TreeItem<>(new Item("Item 1", 0));
Random rng = new Random();
List<TreeItem<Item>> items = new ArrayList<>();
items.add(root);
for (int i = 2 ; i <= 20 ; i++) {
TreeItem<Item> item = new TreeItem<>(new Item("Item "+i, rng.nextInt(1000)));
items.get(rng.nextInt(items.size())).getChildren().add(item);
items.add(item);
}
return root ;
}
public static class Item {
private final String name ;
private final IntegerProperty value = new SimpleIntegerProperty();
public Item(String name, int value) {
this.name = name ;
setValue(value);
}
public String getName() {
return name ;
}
public IntegerProperty valueProperty() {
return value ;
}
public final int getValue() {
return valueProperty().get();
}
public final void setValue(int value) {
valueProperty().set(value);
}
}
public static void main(String[] args) {
launch(args);
}
}
Now you can just style the "ancestor of a selected node" in CSS:
File style.css:
.tree-table-row-cell:ancestor-of-selection {
-fx-background: -fx-selection-bar;
-fx-table-cell-border-color: derive(-fx-selection-bar, 20%);
}
(You may want to modify the CSS to get better control, e.g. set different colors for selected rows in a non-focused table, etc. See the default stylesheet for details on the default style.)
Here's a screenshot of the above test app:
This is the first time I've had to work with JavaFX (and hopefully the last) so I don't exactly understand how everything works. I'll try to sum where I am briefly
I am trying to make my table highlight duplicate cells on a specific column
I need editable cells and no TableCell extensions I've come across work, I've been spending most of today trying to fix their bugs to no avail. I've given up on that approach.
I found TextFieldTableCell but that does not allow me to extend and override functions like updateItem. At this point I have no interest in re-implementing any of this functionality.
Currently what I do is the following:
CollectionName.setCellValueFactory(new PropertyValueFactory<>("CollectionName"));
CollectionName.setCellFactory(EditingCell.<Item>forTableColumn(this)); //At the moment this just passes though TextFieldTableCell, the parameter is totally inconsequential
CollectionName.setOnEditCommit((CellEditEvent<Item, String> t) ->
{
((Item) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setCollectionName(t.getNewValue());
System.out.println("Set on edit commit");
if(isDuplicateName(t.getNewValue()))
{
t.getTableView().getColumns().get(t.getTablePosition().getColumn()).getStyleClass().add("duplicate-cell");
System.out.println("Duplicate");
}
else
{
t.getTableView().getColumns().get(t.getTablePosition().getColumn()).getStyleClass().remove("duplicate-cell");
System.out.println("Not duplicate");
}
});
This functions as intended but highlights the entire column. I need it to highlight only the specific cell. I wish there was a way to simply call myTable.getCell(x,y).getStyleClass().add("duplicate-cell") or something. I mean it is a table after all...
The solution to any problem involving changing the appearance of table cells based on certain state of the cell's item, and other data, is always to use a cell factory which returns a cell that updates its appearance accordingly.
The problem with the approach you are trying is that you are overlooking the fact that the table view reuses cells. For example, if the table contains a large amount of data and the user scrolls, new cells will not be created but cells that are scrolled out of view will be reused for the new items that scroll into view. Since you don't update the style of the cell when this happens, scrolling will make the wrong cells highlighted.
Here the logic is a little tricky as each cell essentially has to observe all values in the column (whether they are currently displayed or not). I think the easiest solution here is to independently maintain an ObservableSet that keeps a list of duplicate entries, and have the cell observe that. Here's an implementation. You can probably factor this out into a separate class for the cell factory (or something convenient) to make it more elegant and reusable.
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class HighlightDuplicateTableCells extends Application {
// create an observable list that fires events if the dataProperty of any elements change:
private final ObservableList<Item> items =
FXCollections.observableArrayList(item -> new Observable[]{item.dataProperty()});
// collection of strings that are duplicated in the data properties of all the items:
private final ObservableSet<String> duplicateData = FXCollections.observableSet();
private static final PseudoClass DUPLICATE_PC = PseudoClass.getPseudoClass("duplicate");
private final StringConverter<String> identityStringConverter = new StringConverter<String>() {
#Override
public String toString(String object) {
return object;
}
#Override
public String fromString(String string) {
return string;
}
};
#Override
public void start(Stage primaryStage) {
// listener to maintain collection of duplicates:
items.addListener((Change<? extends Item> change) -> updateDuplicateData());
TableView<Item> table = new TableView<>();
table.setEditable(true);
table.setItems(items);
TableColumn<Item, Number> idColumn = new TableColumn<>("Id");
idColumn.setCellValueFactory(cellData -> new SimpleIntegerProperty(cellData.getValue().getId()));
TableColumn<Item, String> dataColumn = new TableColumn<>("Data");
dataColumn.setCellValueFactory(cellData -> cellData.getValue().dataProperty());
dataColumn.setCellFactory(tc -> {
TextFieldTableCell<Item, String> cell = new TextFieldTableCell<Item, String>(identityStringConverter) {
// boolean binding that indicates if the current item is contained in the duplicateData set:
private BooleanBinding duplicate = Bindings.createBooleanBinding(
() -> duplicateData.contains(getItem()),
duplicateData, itemProperty());
// anonymous constructor just updates CSS pseudoclass if above binding changes:
{
duplicate.addListener((obs, wasDuplicate, isNowDuplicate) ->
pseudoClassStateChanged(DUPLICATE_PC, isNowDuplicate));
}
};
return cell ;
});
table.getColumns().add(idColumn);
table.getColumns().add(dataColumn);
// note best to minimize changes to items.
// creating a temp list and using items.setAll(...) achieves this:
List<Item> tmp = new ArrayList<>();
for (int i = 1 ; i <= 70; i++) {
char c = (char)('#' + (i % 60));
String data = Character.toString(c) ;
tmp.add(new Item(i, data));
}
items.setAll(tmp);
Scene scene = new Scene(table, 600, 600);
scene.getStylesheets().add("duplicate-cell-example.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private void updateDuplicateData() {
// TODO: may not be most efficient implementation
// all data:
List<String> data = items.stream().map(Item::getData).collect(Collectors.toList());
// unique data:
Set<String> uniqueData = new HashSet<>(data);
// remove unique values from data:
uniqueData.forEach(data::remove);
// remaining values are duplicates: replace contents of duplicateData with these:
duplicateData.clear();
duplicateData.addAll(data);
}
public static class Item {
private final int id ;
private final StringProperty data = new SimpleStringProperty();
public Item(int id, String data) {
this.id = id ;
setData(data);
}
public final StringProperty dataProperty() {
return this.data;
}
public final String getData() {
return this.dataProperty().get();
}
public final void setData(final String data) {
this.dataProperty().set(data);
}
public int getId() {
return id ;
}
}
public static void main(String[] args) {
launch(args);
}
}
and the duplicate-cell-example.css:
.table-cell:duplicate {
-fx-background-color: -fx-background ;
-fx-background: red ;
}
This is basically James_D's approach, but it improves the time required for updates from Ω(n²) worst case (n = list size) to O(m) where m is the number of changes (1 for updates of a property; the number of elements added/removed on a list update).
This performance is achieved by storing the number of occurances in a ObservableMap<String, Integer>:
private final ObservableMap<String, Integer> valueOccuranceCounts = FXCollections.observableHashMap();
private final ChangeListener<String> changeListener = (observable, oldValue, newValue) -> {
valueOccuranceCounts.computeIfPresent(oldValue, REMOVE_UPDATER);
valueOccuranceCounts.merge(newValue, 1, ADD_MERGER);
};
private static final BiFunction<Integer, Integer, Integer> ADD_MERGER = (oldValue, newValue) -> oldValue + 1;
private static final BiFunction<String, Integer, Integer> REMOVE_UPDATER = (key, value) -> {
int newCount = value - 1;
// remove mapping, if the value would become 0
return newCount == 0 ? null : newCount;
};
private final ListChangeListener<Item> listChangeListener = (ListChangeListener.Change<? extends Item> c) -> {
while (c.next()) {
if (c.wasRemoved()) {
for (Item r : c.getRemoved()) {
// decrease count and remove listener
this.valueOccuranceCounts.computeIfPresent(r.getData(), REMOVE_UPDATER);
r.dataProperty().removeListener(this.changeListener);
}
}
if (c.wasAdded()) {
for (Item a : c.getAddedSubList()) {
// increase count and add listener
this.valueOccuranceCounts.merge(a.getData(), 1, ADD_MERGER);
a.dataProperty().addListener(this.changeListener);
}
}
}
};
private final ObservableList<Item> items;
{
items = FXCollections.observableArrayList();
items.addListener(listChangeListener);
}
private static final PseudoClass DUPLICATE = PseudoClass.getPseudoClass("duplicate");
private static final String FIRST_COLUMN_CLASS = "first-column";
#Override
public void start(Stage primaryStage) throws Exception {
TableView<Item> tableView = new TableView<>(items);
// tableView.getSelectionModel().setCellSelectionEnabled(true);
tableView.setEditable(true);
TableColumn<Item, String> column = new TableColumn<>("data");
column.setCellValueFactory(cellData -> cellData.getValue().dataProperty());
column.setCellFactory(col -> new TextFieldTableCell<Item, String>() {
// boolean binding that indicates if the current item is contained in the duplicateData set:
private final BooleanBinding duplicate = Bindings.createBooleanBinding(
() -> valueOccuranceCounts.getOrDefault(getItem(), 1) >= 2,
valueOccuranceCounts, itemProperty());
// anonymous constructor just updates CSS pseudoclass if above binding changes:
{
duplicate.addListener((observable, oldValue, newValue)
-> pseudoClassStateChanged(DUPLICATE, newValue));
}
});
TableColumn<Item, Number> idColumn = new TableColumn<>("id");
idColumn.setCellValueFactory(cellData -> new SimpleIntegerProperty(cellData.getValue().getId()));
tableView.getColumns().addAll(idColumn, column);
tableView.getColumns().addListener((Observable observable) -> {
// keep style class marking the cells of the column as
// belonging to the first column up to date
if (tableView.getColumns().get(0) == column) {
if (!column.getStyleClass().contains(FIRST_COLUMN_CLASS)) {
column.getStyleClass().add(FIRST_COLUMN_CLASS);
}
} else {
column.getStyleClass().remove(FIRST_COLUMN_CLASS);
}
});
// note best to minimize changes to items.
// creating a temp list and using items.setAll(...) achieves this:
final int count = 70;
List<Item> tmp = Arrays.asList(new Item[count]);
for (int i = 0; i < count; i++) {
tmp.set(i, new Item(Integer.toString(i % 60)));
}
items.setAll(tmp);
Scene scene = new Scene(tableView);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
public static class Item {
private static int counter = 0;
private final StringProperty data;
private final int id = counter++;
public Item(String data) {
this.data = new SimpleStringProperty(data);
}
public final StringProperty dataProperty() {
return this.data;
}
public final String getData() {
return this.dataProperty().get();
}
public final void setData(final String data) {
this.dataProperty().set(data);
}
public int getId() {
return id ;
}
}
style.css
.table-row-cell:filled .table-cell:duplicate {
-fx-background: yellow;
-fx-background-color: -fx-table-cell-border-color, -fx-background;
}
.table-view:focused .table-row-cell:filled .table-cell:duplicate:focused {
-fx-background-color: -fx-background, -fx-cell-focus-inner-border, -fx-background;
}
/* keep use the same background colors normally used for focused table rows */
.table-view:focused .table-row-cell:filled:focused .table-cell:duplicate {
-fx-background-color: -fx-background, -fx-cell-focus-inner-border, -fx-background;
/* frame only at top & bottom sides */
-fx-background-insets: 0, 1 0 1 0, 2 0 2 0;
}
.table-view:focused .table-row-cell:filled:focused .table-cell.first-column:duplicate {
/* frame only for top, left and bottom sides*/
-fx-background-insets: 0, 1 0 1 1, 2 0 2 2;
}
.table-row-cell:filled .table-cell:duplicate:selected,
.table-row-cell:filled:selected .table-cell:duplicate {
-fx-background: turquoise;
}
Note that some parts (creating & filling the table, creating the column) are copied from #James_D's answer, since it's simply best practice to do it this way.
I took the sample code from here How to implement freeze column in GXT 3.x? and I came up with the code below. I've managed to make the columns dynamically move from locked grid to unlocked grid and vice versa. My problem is with the sizing of the grids. The original code used a fixed width. I can't have that. I need the grids (locked and unlocked) to fill as much space as the children columns need. The columns have a fixed width (let's say 50px;).
The part that interests me is here
HorizontalLayoutContainer gridWrapper = new HorizontalLayoutContainer();
root.setWidget(gridWrapper);
// add locked column, only 300px wide (in this example, use layouts
// to change how this works
HorizontalLayoutData lockedColumnLayoutData = new HorizontalLayoutData(300, 1.0);
// this is optional - without this, you get a little offset issue at
// the very bottom of the non-locked grid
lockedColumnLayoutData.setMargins(new Margins(0, 0, XDOM.getScrollBarWidth(), 0));
gridWrapper.add(lockedGrid, lockedColumnLayoutData);
// add non-locked section, taking up all remaining width
gridWrapper.add(mainGrid, new HorizontalLayoutData(1.0, 1.0));
and maybe here
final Grid<Stock> lockedGrid = new Grid<Stock>(store, lockedCm) {
#Override
protected Size adjustSize(Size size) {
// this is a tricky part - convince the grid to draw just
// slightly too wide
// and so push the scrollbar out of sight
Window.alert("" + (size.getWidth() + XDOM.getScrollBarWidth() - 1));
return new Size(size.getWidth() + XDOM.getScrollBarWidth() - 1, size.getHeight());
}
};
I am a newbie in GXT and in GWT in general so I used the same components as the original
answer. If what I need to accomplish can be solved easier with something other than HorizontalLayoutContainer, then feel free to change it.
package com.test.client;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import com.google.gwt.cell.client.DateCell;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sencha.gxt.core.client.Style.ScrollDirection;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.core.client.dom.XDOM;
import com.sencha.gxt.core.client.util.Margins;
import com.sencha.gxt.core.client.util.Size;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.widget.core.client.ContentPanel;
import com.sencha.gxt.widget.core.client.Resizable;
import com.sencha.gxt.widget.core.client.Resizable.Dir;
import com.sencha.gxt.widget.core.client.container.HorizontalLayoutContainer;
import com.sencha.gxt.widget.core.client.container.HorizontalLayoutContainer.HorizontalLayoutData;
import com.sencha.gxt.widget.core.client.event.BodyScrollEvent;
import com.sencha.gxt.widget.core.client.event.BodyScrollEvent.BodyScrollHandler;
import com.sencha.gxt.widget.core.client.event.CollapseEvent;
import com.sencha.gxt.widget.core.client.event.CollapseEvent.CollapseHandler;
import com.sencha.gxt.widget.core.client.event.ExpandEvent;
import com.sencha.gxt.widget.core.client.event.ExpandEvent.ExpandHandler;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.grid.GridView;
import com.sencha.gxt.widget.core.client.grid.GridViewConfig;
import com.sencha.gxt.widget.core.client.grid.GroupSummaryView;
import com.sencha.gxt.widget.core.client.grid.SummaryColumnConfig;
import com.sencha.gxt.widget.core.client.grid.filters.GridFilters;
import com.sencha.gxt.widget.core.client.grid.filters.StringFilter;
import com.sencha.gxt.widget.core.client.menu.Item;
import com.sencha.gxt.widget.core.client.menu.Menu;
import com.sencha.gxt.widget.core.client.menu.MenuItem;
public class GridExample implements IsWidget, EntryPoint {
private static final StockProperties props = GWT.create(StockProperties.class);
private ContentPanel root;
private void rootInit() {
root = new ContentPanel();
root.setHeadingText("Locked Grid Sample");
root.setPixelSize(600, 300);
final Resizable resizable = new Resizable(root, Dir.E, Dir.SE, Dir.S);
root.addExpandHandler(new ExpandHandler() {
#Override
public void onExpand(ExpandEvent event) {
resizable.setEnabled(true);
}
});
root.addCollapseHandler(new CollapseHandler() {
#Override
public void onCollapse(CollapseEvent event) {
resizable.setEnabled(false);
}
});
}
#Override
public Widget asWidget() {
if (root == null) {
rootInit();
ColumnConfig<Stock, String> nameCol = new SummaryColumnConfig<Stock, String>(props.name(), 50, SafeHtmlUtils.fromTrustedString("<b>Company</b>"));
ColumnConfig<Stock, String> symbolCol = new SummaryColumnConfig<Stock, String>(props.symbol(), 100, "Symbol");
ColumnConfig<Stock, Double> lastCol = new SummaryColumnConfig<Stock, Double>(props.last(), 75, "Last");
ColumnConfig<Stock, Double> changeCol = new SummaryColumnConfig<Stock, Double>(props.change(), 100, "Change");
ColumnConfig<Stock, Date> lastTransCol = new SummaryColumnConfig<Stock, Date>(props.lastTrans(), 100, "Last Updated");
lastTransCol.setCell(new DateCell(DateTimeFormat.getFormat("MM/dd/yyyy")));
List<ColumnConfig<Stock, ?>> l = new ArrayList<ColumnConfig<Stock, ?>>();
//l.add(nameCol);
l.add(symbolCol);
l.add(lastCol);
l.add(changeCol);
l.add(lastTransCol);
// create two column models, one for the locked section
ColumnModel<Stock> lockedCm = new ColumnModel<Stock>(Collections.<ColumnConfig<Stock, ?>> singletonList(nameCol));
ColumnModel<Stock> cm = new ColumnModel<Stock>(l);
ListStore<Stock> store = new ListStore<Stock>(props.key());
for (int i = 0; i < 30; i++)
store.add(new Stock("Stackoverflow" + i, "StackoverflowPosts"+i, 0, 2, new Date()));
// locked grid
final Grid<Stock> mainGrid = new Grid<Stock>(store, cm);
final Grid<Stock> lockedGrid = new Grid<Stock>(store, lockedCm) {
#Override
protected Size adjustSize(Size size) {
// this is a tricky part - convince the grid to draw just
// slightly too wide
// and so push the scrollbar out of sight
return new Size(size.getWidth() + XDOM.getScrollBarWidth() - 1, size.getHeight());
}
};
GridFilters<Stock> filters = new GridFilters<Stock>();
filters.setLocal(true);
filters.initPlugin(mainGrid);
filters.initPlugin(lockedGrid);
StringFilter<Stock> nameFilter = new StringFilter<Stock>(props.name());
filters.addFilter(nameFilter);
lockedGrid.setView(createGridView(mainGrid, "Unfreeze", true));
mainGrid.setView(createGridView(lockedGrid, "Freeze", false));
// link scrolling
lockedGrid.addBodyScrollHandler(new BodyScrollHandler() {
#Override
public void onBodyScroll(BodyScrollEvent event) {
mainGrid.getView()
.getScroller()
.scrollTo(ScrollDirection.TOP, event.getScrollTop());
}
});
mainGrid.addBodyScrollHandler(new BodyScrollHandler() {
#Override
public void onBodyScroll(BodyScrollEvent event) {
lockedGrid
.getView()
.getScroller()
.scrollTo(ScrollDirection.TOP, event.getScrollTop());
}
});
HorizontalLayoutContainer gridWrapper = new HorizontalLayoutContainer();
root.setWidget(gridWrapper);
// add locked column, only 300px wide (in this example, use layouts
// to change how this works
HorizontalLayoutData lockedColumnLayoutData = new HorizontalLayoutData();
// this is optional - without this, you get a little offset issue at
// the very bottom of the non-locked grid
lockedColumnLayoutData.setMargins(new Margins(0, 0, XDOM.getScrollBarWidth(), 0));
gridWrapper.add(lockedGrid, lockedColumnLayoutData);
// add non-locked section, taking up all remaining width
gridWrapper.add(mainGrid, new HorizontalLayoutData(1.0, 1.0));
}
return root;
}
#Override
public void onModuleLoad() {
RootPanel.get().add(asWidget());
}
private GridView<Stock> createGridView(final Grid<Stock> targetGrid, final String menuText, final boolean isLocked)
{
final GroupSummaryView<Stock> view = new GroupSummaryView<Stock>()
{
{
if(isLocked)
scrollOffset = 0;
}
protected Menu createContextMenu(final int colIndex)
{
final Menu createContextMenu = super.createContextMenu(colIndex);
MenuItem lockItem = new MenuItem();
lockItem.setText(menuText);
lockItem.addSelectionHandler(new SelectionHandler<Item>()
{
#Override
public void onSelection(SelectionEvent<Item> event) {
//I'm making new column models since getColumns() can't be modified
ColumnConfig<Stock, ?> column = grid.getColumnModel().getColumn(colIndex);
List<ColumnConfig<Stock, ?>> newCm = new ArrayList<>(cm.getColumns());
newCm.remove(colIndex);
grid.reconfigure(grid.getStore(), new ColumnModel<>(newCm));
List<ColumnConfig<Stock, ?>> newTargetCm = new ArrayList<>(targetGrid.getColumnModel().getColumns());
newTargetCm.add(column);
targetGrid.reconfigure(targetGrid.getStore(), new ColumnModel<>(newTargetCm));
grid.getView().refresh(true);
targetGrid.getView().refresh(true);
}
});
createContextMenu.add(lockItem);
return createContextMenu;
}
};
view.setShowGroupedColumn(false);
view.setForceFit(false);
view.setStripeRows(true);
view.setColumnLines(true);
view.setViewConfig(new GridViewConfig<Stock>()
{
#Override
public String getRowStyle(Stock model, int rowIndex)
{
return "";
}
#Override
public String getColStyle(Stock model, ValueProvider<? super Stock, ?> valueProvider, int rowIndex, int colIndex)
{
return "";
}
});
return view;
}
}
I think you should do something along these lines:
Calculate the width of the locked table according the columns, then set it to lockedColumnLayoutData
and force gridWrapper to layout.
#Override
public void onSelection(SelectionEvent<Item> event)
{
....
double lockedTableWidth = 0;//calculate
lockedColumnLayoutData.setWidth(lockedTableWidth);
gridWrapper.forceLayout();
}
I had some problems with freezing SWING GUIs when re-rendering a JTable with a custom cell renderer in Java. So I asked the question "Why does a JTable view update block the entire GUI?". The answers pointed to the fact, that a JList without modifying JTable and overwriting doLayout might be a better choice. So I implemented the example with a JList and ran into the same problem: while generating data, everything works fine and the progress bar moves. But when the view is updated, the program freezes and the progress bar stops moving.
Please note, that the sleep statement is there only to let the generation take a longer, more realistic time (reading thousands of data sets via JDBC and create objects out of them takes a lot time). One could remove it and increment the number of generated items. But you can clearly see, that the HTML rendering is quite slow. But I need this colors and the two lines (if not necessarily so many different colors).
So could you please tell me, where my mistake is? I think, that EDT and other work is separated through separate threads and I cannot see any mistke.
Update: I looked around at SO and found this question "https://stackoverflow.com/a/20813122/2429611". There is said:
The more interesting question would be how to avoid that UI blocking, but I don't think that's possible with just Swing, you'll have to implement some lazy loading, or rendering in batches.
This would mean, that I cannot solve my problem. Is this correct?
package example;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.AbstractListModel;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
public class ListExample extends AbstractListModel {
static List<DemoObject> internalList = new ArrayList<>();
#Override
public int getSize() {
return internalList.size();
}
#Override
public DemoObject getElementAt(int index) {
return internalList.get(index);
}
public void fireContentsChanged() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
fireContentsChanged(this, 0, -1);
}
});
}
static class MyCellRenderer extends JLabel implements ListCellRenderer<ListExample.DemoObject> {
public MyCellRenderer() {
setOpaque(true);
}
#Override
public Component getListCellRendererComponent(JList<? extends ListExample.DemoObject> list,
ListExample.DemoObject value,
int index,
boolean isSelected,
boolean cellHasFocus) {
setText("<html>" + value.toString()
+ "<br/>"
+ "<span bgcolor=\"#ff0000\">Line 2; Color = " + value.c + "</span>");
Color background;
Color foreground;
// check if this cell represents the current DnD drop location
JList.DropLocation dropLocation = list.getDropLocation();
if (dropLocation != null
&& !dropLocation.isInsert()
&& dropLocation.getIndex() == index) {
background = Color.BLUE;
foreground = Color.WHITE;
// check if this cell is selected
} else if (isSelected) {
background = Color.RED;
foreground = Color.WHITE;
// unselected, and not the DnD drop location
} else {
background = value.c; //Color.WHITE;
foreground = Color.BLACK;
};
setBackground(background);
setForeground(foreground);
return this;
}
}
static class DemoObject {
String str;
Color c;
public DemoObject(String str, int color) {
this.str = str;
this.c = new Color(color);
}
#Override
public String toString() {
return str;
}
}
static JPanel overlay;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Example");
frame.setLayout(new BorderLayout(4, 4));
// Add JTable
final ListExample model = new ListExample();
JList list = new JList(model);
list.setCellRenderer(new MyCellRenderer());
frame.add(new JScrollPane(list), BorderLayout.CENTER);
// Add button
Box hBox = Box.createHorizontalBox();
hBox.add(new JButton(new AbstractAction("Load data") {
#Override
public void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {
#Override
public void run() {
overlay.setVisible(true);
internalList.clear();
System.out.println("Generating data ...");
SecureRandom sr = new SecureRandom();
for (int i = 0; i < 10000; i++) {
internalList.add(
new DemoObject(
"String: " + i + " (" + sr.nextFloat() + ")",
sr.nextInt(0xffffff)
)
);
// To create the illusion, that data are
// fetched via JDBC (which takes a little
// while), this sleep statement is embedded
// here. In a real world scenario, this wait
// time is caused by talking to the database
// via network
if (i%10 == 0) {
try {
Thread.sleep(1);
} catch (Exception e) {
}
}
}
System.out.println("Updating view ...");
model.fireContentsChanged();
overlay.setVisible(false);
System.out.println("Finished.");
}
}).start();
}
}));
hBox.add(Box.createHorizontalGlue());
frame.add(hBox, BorderLayout.NORTH);
// Create loading overlay
overlay = new JPanel(new FlowLayout(FlowLayout.CENTER)) {
#Override
protected void paintComponent(Graphics g) {
g.setColor(new Color(0, 0, 0, 125));
g.fillRect(0, 0, getWidth(), getHeight());
super.paintComponent(g);
}
};
overlay.setOpaque(false);
overlay.setBackground(new Color(0, 0, 0, 125));
JProgressBar bar = new JProgressBar();
bar.setIndeterminate(true);
overlay.add(bar);
frame.setGlassPane(overlay);
frame.getGlassPane().setVisible(false);
// Create frame
frame.setSize(600, 400);
frame.setVisible(true);
}
});
}
}
there are three problems (recreating, reseting the model, and custom Renderer stoped to works)
JList (JComboBox hasn't) has an issue by removing more than 999 items, you have to set a new model to JList
see important for ComboBoxModel extends AbstractListModel implements MutableComboBoxModel for setElementAt(to hold current selection)
usage of public void fireContentsChanged() { is wrong, don't see reason to use this way, again is about to replace current, reset the model
. e.g. with success atr runtime and by recrusive testing for/if event (fired)
setModel(new DefaultListModel(list.toArray()) {
protected void fireContentsChanged(Object obj, int i, int j) {
if (!isFired)
super.fireContentsChanged(obj, i, j);
}
});
I'm using GXT 3 Grid with InlineEdit mode following (more or less) the example code on their site. I don't think there is a way to get the check box cell to fire the 'EditComplete' event and if so, I'm not sure how I would, upon receiving it, disable the date cell on that same row. Just look for the comment: "// not firing for checkbox:" in the code below.
The following code works in an Eclipse web application project - you just need to use it in your 'onModuleLoad' method as demonstrated here:
public void onModuleLoad() {
GridInlineEditingTest j = new GridInlineEditingTest();
}
Here's the code:
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.google.gwt.cell.client.DateCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.editor.client.Editor.Path;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sencha.gxt.cell.core.client.form.CheckBoxCell;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.data.shared.PropertyAccess;
import com.sencha.gxt.data.shared.Store;
import com.sencha.gxt.widget.core.client.FramedPanel;
import com.sencha.gxt.widget.core.client.button.TextButton;
import com.sencha.gxt.widget.core.client.container.BoxLayoutContainer.BoxLayoutPack;
import com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer;
import com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer.VerticalLayoutData;
import com.sencha.gxt.widget.core.client.container.Viewport;
import com.sencha.gxt.widget.core.client.event.CompleteEditEvent;
import com.sencha.gxt.widget.core.client.event.CompleteEditEvent.CompleteEditHandler;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.event.SelectEvent.SelectHandler;
import com.sencha.gxt.widget.core.client.form.CheckBox;
import com.sencha.gxt.widget.core.client.form.DateField;
import com.sencha.gxt.widget.core.client.form.DateTimePropertyEditor;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.grid.Grid.GridCell;
import com.sencha.gxt.widget.core.client.grid.GridView;
import com.sencha.gxt.widget.core.client.grid.editing.GridEditing;
import com.sencha.gxt.widget.core.client.grid.editing.GridInlineEditing;
public class GridInlineEditingTest {
public GridInlineEditingTest() {
VerticalLayoutContainer vlc = new VerticalLayoutContainer();
vlc.add(createGrid(), new VerticalLayoutData(1, 1));
Viewport vp = new Viewport();
vp.add(vlc);
RootPanel.get().add(vp);
}
interface PlaceProperties extends PropertyAccess<Plant> {
ValueProvider<Plant, Date> available();
#Path("id")
ModelKeyProvider<Plant> key();
ValueProvider<Plant, String> name();
ValueProvider<Plant, Boolean> indoor();
}
private static final PlaceProperties properties = GWT.create(PlaceProperties.class);
protected Grid<Plant> grid;
private FramedPanel panel;
private ListStore<Plant> store;
private DateField dateField;
public Widget createGrid() {
if (panel == null) {
ColumnConfig<Plant, String> nameCol = new ColumnConfig<Plant, String>( properties.name(), 220, "Name" );
ColumnConfig<Plant, Date> dateCol = new ColumnConfig<Plant, Date>( properties.available(), 95, "Date" );
ColumnConfig<Plant, Boolean> indorCol = new ColumnConfig<Plant, Boolean>( properties.indoor(), 55, "Indoor");
// display formatting
DateCell dateCell = new DateCell(DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT));
dateCol.setCell(dateCell);
// display a checkbox in the gridview
indorCol.setCell(new CheckBoxCell());
List<ColumnConfig<Plant, ?>> l = new ArrayList<ColumnConfig<Plant, ?>>();
l.add(nameCol);
l.add(dateCol);
l.add(indorCol);
ColumnModel<Plant> columns = new ColumnModel<Plant>(l);
store = new ListStore<Plant>(properties.key());
store.setAutoCommit(false);
store.addAll(getPlants());
GridView<Plant> gridView = new GridView<Plant>();
grid = new Grid<Plant>(store, columns, gridView);
grid.getView().setAutoExpandColumn(nameCol);
// EDITING//
final GridEditing<Plant> editing = new GridInlineEditing<Plant>(grid);
dateField = new DateField(new DateTimePropertyEditor(DateTimeFormat.getFormat(PredefinedFormat.DATE_SHORT)));
dateField.setClearValueOnParseError(false);
editing.addEditor(dateCol, dateField);
CheckBox checkField = new CheckBox();
editing.addEditor(indorCol, checkField);
editing.addCompleteEditHandler( new CompleteEditHandler<Plant>(){
// not firing for checkbox:
#Override
public void onCompleteEdit(CompleteEditEvent<Plant> event) {
GridCell cell = event.getEditCell();
int row = cell.getRow();
int col = cell.getCol();
System.out.println("got here. row "+row+", col "+col);
}
});
panel = new FramedPanel();
panel.setHeadingText("Editable Grid Example");
panel.setPixelSize(600, 400);
panel.addStyleName("margin-10");
VerticalLayoutContainer con = new VerticalLayoutContainer();
con.setBorders(true);
con.add(grid, new VerticalLayoutData(1, 1));
panel.setWidget(con);
panel.setButtonAlign(BoxLayoutPack.CENTER);
panel.addButton(new TextButton("Reset", new SelectHandler() {
#Override
public void onSelect(SelectEvent event) {
store.rejectChanges();
}
}));
panel.addButton(new TextButton("Save", new SelectHandler() {
#Override
public void onSelect(SelectEvent event) {
store.commitChanges();
}
}));
}
return panel;
}
private static int AUTO_ID = 0;
public class Plant {
private DateTimeFormat df = DateTimeFormat.getFormat("MM/dd/y");
private int id;
private String name;
private String light;
private double price;
private Date available;
private boolean indoor;
private String color;
private int difficulty;
private double progress;
public Plant() {
id = AUTO_ID++;
difficulty = (int) (Math.random() * 100);
progress = Math.random();
}
public Plant(String name, String light, double price, String available, boolean indoor) {
this();
setName(name);
setLight(light);
setPrice(price);
setAvailable(df.parse(available));
setIndoor(indoor);
}
public int getId() { return id; }
public double getProgress() { return progress; }
public String getColor() { return color; }
public int getDifficulty() { return difficulty; }
public Date getAvailable() { return available; }
public String getLight() { return light; }
public String getName() { return name; }
public double getPrice() { return price; }
public boolean isIndoor() { return indoor; }
public void setId(int id) { this.id = id; }
public void setProgress(double progress) { this.progress = progress; }
public void setAvailable(Date available) { this.available = available; }
public void setDifficulty(int difficulty) { this.difficulty = difficulty; }
public void setColor(String color) { this.color = color; }
public void setIndoor(boolean indoor) { this.indoor = indoor; }
public void setLight(String light) { this.light = light; }
public void setName(String name) { this.name = name; }
public void setPrice(double price) { this.price = price; }
#Override
public String toString() {
return name != null ? name : super.toString();
}
}
public List<Plant> getPlants() {
List<Plant> plants = new ArrayList<Plant>();
plants.add(new Plant("Bloodroot", "Mostly Shady", 2.44, "03/15/2006", true));
plants.add(new Plant("Columbine", "Shade", 9.37, "03/15/2006", true));
plants.add(new Plant("Marsh Marigold", "Mostly Sunny", 6.81, "05/17/2006", false));
plants.add(new Plant("Cowslip", "Mostly Shady", 9.90, "03/06/2006", true));
plants.add(new Plant("Dutchman's-Breeches", "Mostly Shady", 6.44, "01/20/2006", true));
plants.add(new Plant("Ginger, Wild", "Mostly Shady", 9.03, "04/18/2006", true));
return plants;
}
}
thanks. and have a great day!!
You are setting a checkbox cell in the column, and then also attaching a field as an inline editor for the column. So if the user clicks the checkbox (cell), you are expecting that click to be ignored, but instead a checkbox (field) to show up over it, which the user may then click?
Instead what is happening is that the checkbox (cell) is reporting that it is using that click event to do something useful - it is changing its value. As a result, the grid editing mechanism ignores the click, so the checkbox (field) never goes into edit mode, and so of course it doesn't complete edit mode.
What are you trying to achieve by making it the purpose of two different checkboxes to be drawn in the same place, and function differently? If you are trying to use the CheckBoxCell instance as a way to always draw the checkbox symbol in the grid cell, there are two main choices:
Skip the CheckBox field in the inline editing, and just let the cell take care of it. It will not fire the editing events, but it will still directly interact with the store. You can listen to the cell's events if you need to, or just to the record change events from the store, or you can subclass the cell to modify behavior.
Removing the event handing guts of the CheckBoxCell to prevent it from handling the event - this may be as simple as overriding onBrowserEvent to do nothing, though I suspect that you actually will want to prevent its check changing behavior entirely so that the Inline Editing version takes care of it
Finally, remember that the purpose of inline editing is to keep the grid from being a mass of fields, and to make it only draw those fields when the user actually interacts with it. This means that the user must first click a field to get something like a checkbox to show up, then interface with the field to change it. Looking one more time at the CheckBox field in an inline editable grid (though this time with a custom cell) at http://www.sencha.com/examples/#ExamplePlace:inlineeditablegrid you'll see that this means two clicks to change a value and get the CompleteEditing event (as well as the various other field change events) that you are after - is this really what you have in mind?
As per the Source Code of CheckBoxCell#isEditing() that says:
A checkbox is never in "edit mode". There is no intermediate state between checked and unchecked.
Find the alternate solution here How to get the row index of selected checkbox on grid GXT.
Please have a look at GXT checkbox in grid