Java FX 2 Cell Editing, Focus and Text Selection - javafx-2

I am testing JavaFX 2.1 and trying to get editable table views to behave the way I would like them to.
I'm using the example from the JavaFX 2 documentation as a base :http://docs.oracle.com/javafx/2/ui_controls/table-view.htm
The example has 2 problems:
The user is forced to click on a cell 3 times in order to edit it, once to select the row, once to select the cell and make it editable and a further click to focus the TextField
The changes are only committed when the enter key is pressed, if the mouse is clicked outside of the cell, then the data entered in the cell is lost.
On the other hand, one feature that does work correctly, is that I can select text, and re-position the caret within the TextField using the mouse as many times as I like.
There are 2 questions here relating to both of these issues individually:
Java FX 2 Table Cell Editing and Focus
and
javafx 2.1 Updating TableView
When the answer to first question is applied on it's own, I only have to click once to edit the cell (after the row has been selected) and I can still select text and move the caret.
When the answer to the second question is applied on it's own, the edit is committed without the enter key being pressed, but I can only re-position the caret or select text once, if I try a second time, then the edit is committed.
When I apply both answers together, focus is applied successfully and edits are committed when the mouse is clicked away, but I lose the ability to re-position the caret or select text entirely. Any mouse click within the cell commits the edit.
My question is how can I fix the original 2 issues without losing the ability to position the caret and select text?

Try jkaufmann's sample app in his answer to his own question TableView - Better Editing through Binding? His binding solution and implementation of TableView editing semantics seems to adequately address all concerns you raise in your question.

You need to modify the GUI component at the correct time in the spirit of the JavaFX framework. i.e. in the controls layoutChildren method. You need to override the layoutChildren method of the custom TableCell and set the cursor position then e.g.
TextField textField = new TextField() {
private boolean first = true;
#Override protected void layoutChildren() {
super.layoutChildren();
// Set cursor caret at end of text (and clear highlighting)
if (first) {
this.end();
first = false;
}
}
};
I also note that Java 1.8.0_241 also contains this problem in the TextFieldTableCell implementation. Worse TextField is completely private to the TextFieldTableCell implementation, so in order to work around that I chose to copy the source of javax.scene.table.cell.TextFieldTableCell and javax.scene.table.cell.CellUtils. TextField is instantiated in CellUtils, so you can fix the cursor positioning there. e.g.
static <T> TextField createTextField(final Cell<T> cell, final StringConverter<T> converter) {
final TextField textField = new TextField(getItemText(cell, converter)) {
private boolean first = true;
#Override protected void layoutChildren() {
super.layoutChildren();
// Set cursor caret at end of text (and clear highlighting)
if (first) {
this.end();
first = false;
}
};
...
...
}

Related

UILabel not wrapping in UITableView until device rotate (iOS8)

I have a custom MvxTableViewCell that is associated with an MvxStandardTableViewSource. That Source is then applied to a UITableView. The custom table cell is defined without any Storyboard or NIB. It is laid out in code and uses AutoLayout.
this.searchResultsTable = new UITableView();
this.searchResultsTable.AccessibilityIdentifier = "SearchView_SearchResultsTable";
this.searchResultsTable.TranslatesAutoresizingMaskIntoConstraints = false;
this.searchResultsTable.RowHeight = UITableView.AutomaticDimension;
this.searchResultsTable.EstimatedRowHeight = 44.0f;
this.searchResultsTable.RegisterClassForCellReuse(typeof(CustomerItemCell), new NSString("CustomerItemCell"));
this.searchResultsTable.AllowsMultipleSelectionDuringEditing = true;
this.searchResultsTable.TableFooterView = new UIView();
this.searchResultsTableDataSource = new MvxStandardTableViewSource(this.searchResultsTable, new NSString("CustomerItemCell"));
this.searchResultsTable.Source = this.searchResultsTableDataSource;
The MVxStandardTableViewSource is databound to a ViewModel property of type List
var set = this.CreateBindingSet<SearchView, SearchViewModel>();
set.Bind(this.searchResultsTableDataSource).To(vm => vm.SearchResults);
set.Bind(this.searchBar).For(x => x.Text).To(vm => vm.CurrentSearchCriteria);
set.Apply();
This all works fine until an item in the data source causes some text wrapping in one of the UILabels and consequently a different height to the other cells.
The cell height is mostly correctly calculated but the UILabel within the
cell does not get redrawn until the device is rotated. I am using iOS AutoLayout to layout the various UIViews in the Cell.
Here are some examples of the large cell in my layout, see the
person "THISISAPATIENTWITHA-" (note this is test data not real people's data)
Initial display of cells
Same cells but device has been rotated
Still the same cells with device rotated back to original
How do I get the UILabel to redraw? We only need to support iOS8 and above.
I cannot see an event or method that gets called when the data binding has happened that would allow me to effectively tell the custom cell "You now have your subviews populated with bound data so redraw them"
The table has another issue too that is covered by this question, Implementing cell reuse for varying height cells in UITableView
Simple Repro on Github
https://github.com/munkii/TableCellResizeIssue
UPDATE:
I've forked your GitHub project and submitted a pull request. But here's my updates to your project.
https://github.com/SharpMobileCode/TableCellResizeIssue
First, you're using FluentLayout for your constraints. Nothing wrong with that actually, but that's some good info to tell others. :)
Second, in order for UITableView.AutomaticDimension to work on TableView Cells, there must be enough autolayout constraints defined in order for the cell to calculate the height of the cell. UITableView.AutomaticDimension depends on proper AutoLayout constraints.
Since you were using FluentLayout to abstract iOS AutoLayout constraints, this was not obvious as no warnings were present in the application output window. Though FluentLayout was technically correct, it however wasn't enough for UITableView.AutomaticDimension to automatically calculate each cell height.
So what I did was added a few more constraints. Look in CustomerItemCell.CreateView() in the pull request (or my github link). You can see that I added additional constraints for all the bottom labels so that they add a Bottom Constraint to the ContentView (Just like you did with this.bornLabel). This had to be applied to all the labels on the bottom of the cell. This gives AutoLayout enough information to properly calculate the cell height.
Third, This almost works, but if you rotate to Landscape, you'll notice that the long name cells will be bigger and have extra padding. To fix this, I created another class called AutoLayoutLabel that inherits from UILabel. I overrode the Bounds property so that it changes the PreferredMaxLayoutWidth to the proper width when rotated to Landscape, and back to Portrait. You then will need to use AutoLayoutLabel instead of UILabel. You'll need this for all labels that need to wrap. I'm not sure how to set PreferredMaxLayoutWidth to auto in code, but this is how to do it programmatically (which also works for iOS 7).
public class AutoLayoutLabel : UILabel
{
public override CGRect Bounds
{
get
{
return base.Bounds;
}
set
{
base.Bounds = value;
if(this.Lines == 0 && Bounds.Size.Width != PreferredMaxLayoutWidth)
{
PreferredMaxLayoutWidth = Bounds.Size.Width;
SetNeedsUpdateConstraints();
}
}
}
}
Well, that should do it!
I now have a solution to this part of my issue. Prompted by #SharpMobileCode reference to PreferredMaxLayoutWidth I decided to give that another go. Rather that setting it to Automatic (which seems impossible in code) I am setting it Explicitly, once AutoLayout has done its thing. Like this
/// <summary>
/// Lays out subviews.
/// </summary>
public override void LayoutSubviews()
{
base.LayoutSubviews();
this.nameLabel.PreferredMaxLayoutWidth = this.nameLabel.Frame.Size.Width;
}
I am no longer seeing the Labels not wrap (hurrah!) however I am seeing an issue with what looks like cell reuse. Once I scroll all of the cell off the top of the screen I can scroll it back on and it has reverted to the same height as all the other cells. I can see the label is still wrapping but the cell height is wrong.
The standard table views in MvvmCross date back to iOS4 - while the new UITableViewAutomaticDimension sizing wasn't really added until much more recently (iOS8?)
Most real apps tend to use custom cells rather than the standard ones, but if you do want to use the standard ones, then I'd guess you could try adding some code to the setters in the cell which would trigger resize recalculations - e.g. to setters in https://github.com/MvvmCross/MvvmCross/blob/3.5/Cirrious/Cirrious.MvvmCross.Binding.Touch/Views/MvxStandardTableViewCell.cs#L73
I would guess that judiciously placed calls in there to request layout recalc would cause the parent cell and table to redraw.

GXT live grid not scrolling to bottom

I have a GXT LiveGridView grid - works great, loading fine, but will not scroll all the way to the bottom record using the scroll bar. The only way to see the last record is to select the last visible record and use the down arrow key to force the display down, one record at a time.
By overriding the 'getCalculatedRowHeight' method, since it was returning a wrong value (compared with the Firebug analysis) the issue was resolved.
private class MyLiveGridView<T> extends LiveGridView<T> {
// deal with wrong value of 22 from this method currently.
#Override
protected int getCalculatedRowHeight(){
return 28;
}
}
(A real fix would be to dynamically acquire the correct row height. For now this will suffice since I'm on the hook for a lot of code still).

JavaFX 2.0: What do I have to do to react on a row selection change in a table?

I want to do a very easy and common thing but don't find a way to do it with JavaFX.
I have a TableView with two columns and want to enable a button if at least one row is selected. If no row is selected I want to disable the button again.
For that I need to get a selection changed event or something like that. Shouldn't be there a possibility to set the selectionModel of the tableView on action or add a listener to it?
What do I have to do?
I think what you will want to do is add a ListChangeListener to the selected indices on the selection model of your TableView. It should go something like this...
tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); // just in case you didnt already set the selection model to multiple selection.
tableView.getSelectionModel().getSelectedIndices().addListener(new ListChangeListener<Integer>()
{
#Override
public void onChanged(Change<? extends Integer> change)
{
if (change.getList().size() >= 2)
{
button.setDisable(false);
}
else
{
button.setDisable(true);
}
}
});
I was originally going to suggest binding the disable property of the button to a selected indices property size on the table view being greater than 2, but there doesn't seem to be one provided. If you wanted to, you could create your own SimpleIntegerProperty and update it inside of the list change listener, binding the disable property of the button to the value of your SimpleIntegerProperty being greater than 2. I hope this answer is helpful!
An other solution in one line using the Property :
myButton.disableProperty().bind(myTable.getSelectionModel().selectedItemProperty().isNull());
That way, the button is enable when there is at least one line selected

JavaFX 2.1 TableView that includes WebView cells

I want to create a JavaFX table that, in the cells of one column, allows the user to edit XHTML text. I only need very basic formatting capabilities like bold, italic, striketrough.
I have already managed to implement this by using my own subclass of TableCell and using a WebView for each cell (HTMLEditor would of course have been another choice, but my guess is that for my requirements, WebView should be sufficient).
However, to make editing comfortable for the user, I need the following features:
1. The cell height needs to resize if the user enters multi-line text.
2. A context menu (or, if not possible, some other menu or button) should allow formatting parts of the text in a cell as described above (bold, italic..)
Has anybody been successful in implementing something similar ? I have seen suggestions on the web, but they rarely included code samples.
I have succedded doing something similar.
I figured I can share some of the basic clues that allowed me to achieve it.
Resize the whole WebView. For that, the whole WebView must be an editable html page. You achive that by setting contenteditable to true:
<body contenteditable='true' id='content'></body>
You can have a context menu over a webview. But it is something tricky, as you must first disable the original context menu associated to it.
WebView editView;
...
EventDispatcher originalDispatcher = editView.getEventDispatcher();
editView.setEventDispatcher(new WebmenuEventDispatcher(originalDispatcher));
And this is the event dispatcher class:
public class WebmenuEventDispatcher implements EventDispatcher {
private EventDispatcher originalDispatcher;
public WebmenuEventDispatcher(EventDispatcher originalDispatcher) {
this.originalDispatcher = originalDispatcher;
}
#Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if (event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) event;
if (MouseButton.SECONDARY == mouseEvent.getButton()) {
mouseEvent.consume();
// Show our own menu
cmEdit.show(editView.getScene().getWindow(), mouseEvent.getScreenX(), mouseEvent.getScreenY());
}
}
return originalDispatcher.dispatchEvent(event, tail);
}
}
Now, for setting the font from within that menu, you need a bidirectional Java<->javascript bridge and use some javascript in the webview side.

iPhone alike sliding header, or: how to force immediate redraw on top margin change?

I am trying to implement a similar effect like the iPhone-alike sliding header in the iPhone contact app (the sliding header that group the contacts by it's starting letter).
This is the screen of my app, and what I want to achieve is the following:
I have a 'guide header' and three 'tabs' for sorting the list. When the user scrolls the list up, I want everything to scroll up (guide header, tabs, list). However, when the tabs reach the top of the screen (and the guide header will just be gone off the screen), I want the tabs to stop and stay there (remain as "sticky header"), and only the list items scroll as in any regular list view.
I have a view group (guide header) above a list view.
First of all, I want to have the guide header adjust it's position depending on the scrolling position of the list view.
First approach:
My idea was to set an onScrollListener to the list view and change the top margin of the guide header to whatever the scroll position of the first item in the list view is (which would be a negative value).
The logic is correct, but the problem I'm facing is that the guide header view doesn't get redrawn fast enough while I'm scrolling in the list view. The guide header view only updates (to my changed top margin value) when the list view fling comes to an end. Even slow scrolling doesn't work. Invalidating (invalidate()) the guide header view or it's parent also doesn't help, since it would just put an invalidation request to the queue, but the invalidation and redrawing doesn't happen immediately, but only when the UI thread becomes idle, which doesn't seem to happen while the user still has his fingers on the scroll list view. Seems that flinging the list view blocks the whole UI thread or keeps it busy for itself.
So the main problem is: changing the margin of the guide header view doesn't become visible immediately while the user is scrolling the list view. The code I'm using it this:
#Override
public void onScroll(final AbsListView view, final int firstVisibleItem,
final int visibleItemCount, final int totalItemCount) {
// Get the first list item and check it's scroll position. This will be the value (top), that we also
// use the scroll the header parallel.
View v = mainList.getChildAt(0);
final int top = (v==null)?0:v.getTop();
// This logs the current scroll position of the first list item element/view group.
Log.d("onScroll", "onScroll: " + top);
// Here we finally change the margin (setting a negative margin) to the header element.
((LinearLayout.LayoutParams)(findViewById(R.id.header_container).getLayoutParams())).setMargins(0, top, 0, 0);
// was just a test: invalidating the outer container/view group, doesn't help
// findViewById(R.id.ll_container).invalidate();
}
I do see the "onScroll:" log output I inserted in the code above in the logcat, but the following adjustment of the top margin just doesn't become visible.
My second approach: is to use a scrollview for the guide header + tabs and work with those. Scrolling the guide header (which is then a scroll view) from code with scrollView.scrollTo(0,Math.abs(Math.abs(top)) from the onScroll method of the list view does work and almost immediately shows on the screen, however, it's not very accurate/stable when the user flings the list view very fast - meaning it jumps in intervals and doesn't look smooth; it's only accurate/stable when scrolling slowly.
My question is now: is there any best practice to accomplish such a sliding header effect, and more concrete: is there a way to force the guide header view to be redrawn while the user is still scrolling the list view (in my first mentioned approach).
For this you should use some tricks (afaik there is no ready-to-use implementation of such a feature).
For instance, you could detect gestures on your view, and
if the current gesture matches a
scroll down, and the first list item
is visible, animate-shrink the
header's size to 0, the tab view's
size to match_parent. Start scrolling
the list only when the header is not
present anymore.
if the current gesture matches scroll
up, and the first is already visible,
animate-expand the header to it's
original size.
So using Animation on the header view might be your solution.
Update
An other workaround would be to extend your List (the value array of your adapter):
Inster a new (dummy) item at the top for the header representation, and modify your ListAdapter's getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
if (position == 0)
{
convertView = inflater.inflate(R.layout.sliding_header, parent,
false);
return convertView;
}
//TODO: your original method body comes here
}
where the xml referenced by R.layout.sliding_header should contain the header layout of your list.
A custom OnScrollListener implementation applied to the ListView would make unnoticeable that the header actually is an item of the list, since it would hide the scrollbar.
You should add this listener to your listView in the activity's onCreate method:
listView.setOnScrollListener(new MyScrollListener());
where MyScrollListener is:
/**
* Custom OnScrollListener
*/
private final class MyScrollListener implements OnScrollListener
{
#Override
public void onScrollStateChanged(AbsListView view, int scrollState)
{}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount)
{
if (view.getFirstVisiblePosition() == 0)
view.setVerticalScrollBarEnabled(false);
else if (!view.isVerticalScrollBarEnabled())
view.setVerticalScrollBarEnabled(true);
}
}
I think you can also try and use my ExpandAnimation for that.
http://udinic.wordpress.com/2011/09/03/expanding-listview-items/
Just pass the animation class that "guide header" view, and let the animation do the work for you, no scrolling is needed in that case, and it's smooth.

Resources