I am developing a simple golf scoring app as a learning exercise for android devices. After the score for a hole is entered I want to display the progress score as a popup over the main display which displays the results for each hole in a recyclerview. The data in the recycler view updates as expected but, when the popup shows, the totals are one hole behind. It appears as if, perhaps, the queries for summations from the database are being done before the database update with the new score is complete.
I have read the Android developers sections on room and searched widely for similar problems and worked through a number of tutorials but nothing seems to match this situation. I have seen a suggestion that running the queries as transactions might help but my efforts at that have made no difference.
In the Dao there are a number of the following type of query
#Transaction
#Query("SELECT SUM(strokes) AS frontstrokes FROM score_table WHERE hole <= 9")
LiveData<Integer> getStrokesFront();
In the ViewModel the corresponding code is
public LiveData<Integer> getStrokesFront(){return strokesFront;}
The Repository contains
public LiveData<Integer> getStrokesFront() {
return strokesFront;
}
In MainActivity inside the onCreate we have
scoringViewModel.getStrokesFront().observe(this, new Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
frontstrokes = integer;
}
});
Inside onActivityResult we have (after return from the score entry activity)
scoringViewModel.update(scoring);
Toast.makeText(this, "Score added", Toast.LENGTH_LONG).show();
loadData();
showProgress();
And in showProgress we have
LayoutInflater inflater = (LayoutInflater) MainActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.scoring_header, null);
((TextView) layout.findViewById(R.id.comp_name)).setText(comp);
((TextView) layout.findViewById(R.id.strokes_value)).setText(String.format("%d", frontstrokes));
The reason that I think it is a 'timing' thing is that if I return to the score entry activity and make no changes but press the save again, the update of the totals is now correct.
The following does appear in the Logcat
2019-06-05 14:30:13.906 31492-31492/org.ivanhoegc.ivanhoegolfscoringapp E/WindowManager: android.view.WindowLeaked: Activity org.ivanhoegc.ivanhoegolfscoringapp.MainActivity has leaked window android.widget.PopupWindow$PopupDecorView{3e7185f V.E...... R....... 0,0-800,570} that was originally added here
at android.view.ViewRootImpl.<init>(ViewRootImpl.java:627)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:377)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:95)
at android.widget.PopupWindow.invokePopup(PopupWindow.java:1573)
at android.widget.PopupWindow.showAtLocation(PopupWindow.java:1339)
at android.widget.PopupWindow.showAtLocation(PopupWindow.java:1305)
at org.ivanhoegc.ivanhoegolfscoringapp.MainActivity$override.showProgress(MainActivity.java:297)
at org.ivanhoegc.ivanhoegolfscoringapp.MainActivity$override.onActivityResult(MainActivity.java:208)
at org.ivanhoegc.ivanhoegolfscoringapp.MainActivity$override.access$dispatch(Unknown Source:92)
at org.ivanhoegc.ivanhoegolfscoringapp.MainActivity.onActivityResult(Unknown Source:39)
at android.app.Activity.dispatchActivityResult(Activity.java:8006)
at android.app.ActivityThread.deliverResults(ActivityThread.java:4638)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4687)
at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1935)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7116)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:925)
EDIT:
I have been doing some poking around with debug.
Here is the code for my pop-up.
private void showProgress(){
LayoutInflater inflater = (LayoutInflater) MainActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.scoring_header, null);
float density = MainActivity.this.getResources().getDisplayMetrics().density;
final PopupWindow pw = new PopupWindow(layout, (int)density * 400, (int)density * 285,true);
pw.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
scoringViewModel = ViewModelProviders.of(this).get(ScoringViewModel.class);
scoringViewModel.getStrokesFront().observe(this, new Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
frontstrokes = integer;
}
});
scoringViewModel.getPointsFront().observe(this, new Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
frontpoints = integer;
}
});
scoringViewModel.getStrokesBack().observe(this, new Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
backstrokes = integer;
}
});
scoringViewModel.getPointsBack().observe(this, new Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
backpoints = integer;
}
});
scoringViewModel.getStrokesTotal().observe(this, new Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
totalstrokes = integer;
}
});
scoringViewModel.getPointsTotal().observe(this, new Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
totalpoints = integer;
}
});
((TextView) layout.findViewById(R.id.comp_name)).setText(comp);
((TextView) layout.findViewById(R.id.strokes_value)).setText(String.format("%d", frontstrokes));
((TextView) layout.findViewById(R.id.points_value)).setText(String.format("%d", frontpoints));
((TextView) layout.findViewById(R.id.bstrokes_value)).setText(String.format("%d", backstrokes));
((TextView) layout.findViewById(R.id.bpoints_value)).setText(String.format("%d", backpoints));
((TextView) layout.findViewById(R.id.rstrokes_value)).setText(String.format("%d", totalstrokes));
((TextView) layout.findViewById(R.id.rpoints_value)).setText(String.format("%d", totalpoints));
pw.setTouchInterceptor(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_OUTSIDE){
pw.dismiss();
return true;
}
return false;
}
});
pw.setOutsideTouchable(true);
pw.showAtLocation(layout, Gravity.CENTER, 0, 0);
}
Looking at the values that I want to display, they are not updated until after the pop-up is displayed. This is despite the new individual score and points values being clearly displayed in the recycler view beneath the pop-up. A break at the setTouchInterceptor shows the old values while a break at the action check 'if statement' reveals the new values. Rearranging the order of the prior code has no effect on the result.
Any ideas please?
Related
I am using iText7 to build a table of contents for my document. I know all the section names before I start, but don't know what the page numbers will be. My current process is to create a table on the first page and create all the Link objects with generic text "GO!". Then as I add sections I add through the link objects and update the text with the page numbers that I figured out as I created the document.
However, at the end, what gets written out for the link is "GO!", not the updated page number values I set as I was creating the rest of the document.
I did set the immediateFlush flag to false when I created the Document.
public class UpdateLinkTest {
PdfDocument pdfDocument = null;
List<Link>links = null;
Color hyperlinkColor = new DeviceRgb(0, 102, 204);
public static void main(String[] args) throws Exception {
List<String[]>notes = new ArrayList<>();
notes.add(new String[] {"me", "title", "this is my text" });
notes.add(new String[] {"me2", "title2", "this is my text 2" });
new UpdateLinkTest().exportPdf(notes, new File("./test2.pdf"));
}
public void exportPdf(List<String[]> notes, File selectedFile) throws Exception {
PdfWriter pdfWriter = new PdfWriter(selectedFile);
pdfDocument = new PdfDocument(pdfWriter);
Document document = new Document(pdfDocument, PageSize.A4, false);
// add the table of contents table
addSummaryTable(notes, document);
// add a page break
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
// add the body of the document
addNotesText(notes, document);
document.close();
}
private void addSummaryTable(List<String[]> notes, Document document) {
links = new ArrayList<>();
Table table = new Table(3);
float pageWidth = PageSize.A4.getWidth();
table.setWidth(pageWidth-document.getLeftMargin()*2);
// add header
addCell("Author", table, true);
addCell("Title", table, true);
addCell("Page", table, true);
int count = 0;
for (String[] note : notes) {
addCell(note[0], table, false);
addCell(note[1], table, false);
Link link = new Link("Go!", PdfAction.createGoTo(""+ (count+1)));
links.add(link);
addCell(link, hyperlinkColor, table, false);
count++;
}
document.add(table);
}
private void addNotesText(List<String[]> notes, Document document)
throws Exception {
int count = 0;
for (String[] note : notes) {
int numberOfPages = pdfDocument.getNumberOfPages();
Link link = links.get(count);
link.setText(""+(numberOfPages+1));
Paragraph noteText = new Paragraph(note[2]);
document.add(noteText);
noteText.setDestination(++count+"");
if (note != notes.get(notes.size()-1))
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
}
private static void addCell(String text, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
table.addCell(c1);
}
private static void addCell(Link text, Color backgroundColor, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
text.setUnderline();
text.setFontColor(backgroundColor);
table.addCell(c1);
}
}
Quite more work needs to be done compared to the code you have now because the changes to the elements don't take any effect once you've added them to the document. Immediate flush set to false allows you to relayout the elements, but that does not happen automatically. The way you calculate the current page the paragraph will be placed on (int numberOfPages = pdfDocument.getNumberOfPages();) is not bulletproof because in some cases pages might be added in advance, even if the content is not going to be placed on them immediately.
There is a very low level way to achieve your goal but with the recent version of iText (7.1.15) there is a simpler way as well, which still requires some work though. Basically your use case is very similar to target-counter concept in CSS, with page counter being the target one in your case. To support target counters in pdfHTML add-on we added new capabilities to our layout module which are possible to use directly as well.
To start off, we are going to tie our Link elements to the corresponding Paragraph elements that they will point to. We are going to do it with ID property in layout:
link.setProperty(Property.ID, String.valueOf(count));
noteText.setProperty(Property.ID, String.valueOf(count));
Next up, we are going to create custom renderers for our Link elements and Paragraph elements. Those custom renderers will interact with TargetCounterHandler which is the new capability in layout module I mentioned in the introduction. The idea is that during layout operation the paragraph will remember the page on which it was placed and then the corresponding link element (remember, link elements are connected to paragraph elements) will ask TargetCounterHandler during layout process of that link element which page the corresponding paragraph was planed on. So in a way, TargetCounterHandler is a connector.
Code for custom renderers:
private static class CustomParagraphRenderer extends ParagraphRenderer {
public CustomParagraphRenderer(Paragraph modelElement) {
super(modelElement);
}
#Override
public IRenderer getNextRenderer() {
return new CustomParagraphRenderer((Paragraph) modelElement);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
LayoutResult result = super.layout(layoutContext);
TargetCounterHandler.addPageByID(this);
return result;
}
}
private static class CustomLinkRenderer extends LinkRenderer {
public CustomLinkRenderer(Link link) {
super(link);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
Integer targetPageNumber = TargetCounterHandler.getPageByID(this, getProperty(Property.ID));
if (targetPageNumber != null) {
setText(String.valueOf(targetPageNumber));
}
return super.layout(layoutContext);
}
#Override
public IRenderer getNextRenderer() {
return new CustomLinkRenderer((Link) getModelElement());
}
}
Don't forget to assign the custom renderers to their elements:
link.setNextRenderer(new CustomLinkRenderer(link));
noteText.setNextRenderer(new CustomParagraphRenderer(noteText));
Now, the other thing we need to do it relayout. You already set immediateFlush to false and this is needed for relayout to work. Relayout is needed because on the first layout loop we will not know all the positions of the paragraphs, but we will already have placed the links on the pages by the time we know those positions. So we need the second pass to use the information about page numbers the paragraphs will reside on and set that information to the links.
Relayout is pretty straightforward - once you've put all the content you just need to call a single dedicated method:
// For now we have to prepare the handler for relayout manually, this is going to be improved
// in future iText versions
((DocumentRenderer)document.getRenderer()).getTargetCounterHandler().prepareHandlerToRelayout();
document.relayout();
One caveat is that for now you also need to subclass the DocumentRenderer since there is an additional operation that needs to be done that is not performed under the hood - propagation of the target counter handler to the root renderer we will be using for the second layout operation:
// For now we have to create a custom renderer for the root document to propagate the
// target counter handler to the renderer that will be used on the second layout process
// This is going to be improved in future iText versions
private static class CustomDocumentRenderer extends DocumentRenderer {
public CustomDocumentRenderer(Document document, boolean immediateFlush) {
super(document, immediateFlush);
}
#Override
public IRenderer getNextRenderer() {
CustomDocumentRenderer renderer = new CustomDocumentRenderer(document, immediateFlush);
renderer.targetCounterHandler = new TargetCounterHandler(targetCounterHandler);
return renderer;
}
}
document.setRenderer(new CustomDocumentRenderer(document, false));
And now we are done. Here is our visual result:
Complete code looks as follows:
public class UpdateLinkTest {
PdfDocument pdfDocument = null;
Color hyperlinkColor = new DeviceRgb(0, 102, 204);
public static void main(String[] args) throws Exception {
List<String[]> notes = new ArrayList<>();
notes.add(new String[] {"me", "title", "this is my text" });
notes.add(new String[] {"me2", "title2", "this is my text 2" });
new UpdateLinkTest().exportPdf(notes, new File("./test2.pdf"));
}
public void exportPdf(List<String[]> notes, File selectedFile) throws Exception {
PdfWriter pdfWriter = new PdfWriter(selectedFile);
pdfDocument = new PdfDocument(pdfWriter);
Document document = new Document(pdfDocument, PageSize.A4, false);
document.setRenderer(new CustomDocumentRenderer(document, false));
// add the table of contents table
addSummaryTable(notes, document);
// add a page break
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
// add the body of the document
addNotesText(notes, document);
// For now we have to prepare the handler for relayout manually, this is going to be improved
// in future iText versions
((DocumentRenderer)document.getRenderer()).getTargetCounterHandler().prepareHandlerToRelayout();
document.relayout();
document.close();
}
private void addSummaryTable(List<String[]> notes, Document document) {
Table table = new Table(3);
float pageWidth = PageSize.A4.getWidth();
table.setWidth(pageWidth-document.getLeftMargin()*2);
// add header
addCell("Author", table, true);
addCell("Title", table, true);
addCell("Page", table, true);
int count = 0;
for (String[] note : notes) {
addCell(note[0], table, false);
addCell(note[1], table, false);
Link link = new Link("Go!", PdfAction.createGoTo(""+ (count+1)));
link.setProperty(Property.ID, String.valueOf(count));
link.setNextRenderer(new CustomLinkRenderer(link));
addCell(link, hyperlinkColor, table, false);
count++;
}
document.add(table);
}
private void addNotesText(List<String[]> notes, Document document) {
int count = 0;
for (String[] note : notes) {
Paragraph noteText = new Paragraph(note[2]);
noteText.setProperty(Property.ID, String.valueOf(count));
noteText.setNextRenderer(new CustomParagraphRenderer(noteText));
document.add(noteText);
noteText.setDestination(++count+"");
if (note != notes.get(notes.size()-1))
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
}
private static void addCell(String text, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
table.addCell(c1);
}
private static void addCell(Link text, Color backgroundColor, Table table, boolean b) {
Cell c1 = new Cell().add(new Paragraph(text));
text.setUnderline();
text.setFontColor(backgroundColor);
table.addCell(c1);
}
private static class CustomLinkRenderer extends LinkRenderer {
public CustomLinkRenderer(Link link) {
super(link);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
Integer targetPageNumber = TargetCounterHandler.getPageByID(this, getProperty(Property.ID));
if (targetPageNumber != null) {
setText(String.valueOf(targetPageNumber));
}
return super.layout(layoutContext);
}
#Override
public IRenderer getNextRenderer() {
return new CustomLinkRenderer((Link) getModelElement());
}
}
private static class CustomParagraphRenderer extends ParagraphRenderer {
public CustomParagraphRenderer(Paragraph modelElement) {
super(modelElement);
}
#Override
public IRenderer getNextRenderer() {
return new CustomParagraphRenderer((Paragraph) modelElement);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
LayoutResult result = super.layout(layoutContext);
TargetCounterHandler.addPageByID(this);
return result;
}
}
// For now we have to create a custom renderer for the root document to propagate the
// target counter handler to the renderer that will be used on the second layout process
// This is going to be improved in future iText versions
private static class CustomDocumentRenderer extends DocumentRenderer {
public CustomDocumentRenderer(Document document, boolean immediateFlush) {
super(document, immediateFlush);
}
#Override
public IRenderer getNextRenderer() {
CustomDocumentRenderer renderer = new CustomDocumentRenderer(document, immediateFlush);
renderer.targetCounterHandler = new TargetCounterHandler(targetCounterHandler);
return renderer;
}
}
}
I have a Composite containing a number of Text controls. I have attached MouseListeners to each control.
What surprises me is that sometimes, when I click on a control, I get a MouseDown event from its neighbour. The Event position is outside of the control's boundary and I get no event from the other control which I thought I had clicked on.
What can cause this to happen?
SNIPPET
Run. Press Esc to close the MessageBox. Click in field BBBB. Press Esc to close MessageBox. Click in field AAAA. The event is generated from field BBBB.
public class Test
{
public class MyListener implements MouseListener, FocusListener
{
private boolean active;
#Override
public void focusGained(FocusEvent e)
{
System.out.println(e);
message((Text) e.widget, "FocusGained");
}
#Override
public void focusLost(FocusEvent e)
{
System.out.println(e);
}
#Override
public void mouseDoubleClick(MouseEvent e)
{
}
#Override
public void mouseDown(MouseEvent e)
{
System.out.println(e);
}
private void message(final Text t, final String m)
{
if (active == false)
{
active = true;
MessageBox mb = new MessageBox(t.getShell());
mb.setText(m);
mb.setMessage(t.getMessage() + "\n\n" + m);
mb.open();
active = false;
}
}
#Override
public void mouseUp(MouseEvent e)
{
System.out.println(e);
message((Text) e.widget, e.toString());
}
}
private MyListener listener = null;
public static void main(String[] args)
{
Display display = new Display();
Shell shell = new Shell(display);
new Test(shell);
shell.open();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
public Test(Composite parent)
{
listener = new MyListener();
create(parent);
}
private void create(Composite parent)
{
parent.setLayout(new GridLayout(2, true));
createText(parent, "AAAA");
createText(parent, "BBBB");
parent.layout(true);
}
private Text createText(Composite parent, String message)
{
Text t = new Text(parent, SWT.NONE);
t.setMessage(message);
GridData gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
t.setLayoutData(gd);
t.addFocusListener(listener);
t.addMouseListener(listener);
return t;
}
}
This is indeed strange. When traversing from B to A, the MouseUp event is marked as sent from field B.
If you replace the MessageBox with something non-interrupting i.e. System.out, the mouse event senders are the right ones.
To me, this seems more of a theoretical corner case. Decent applications would not interrupt the users field traversal with a modal window. However, if this is relevant for your, I'd report a bug to SWT.
I managed to get this working by de-coupling the pop-up window from the events. The message method now looks like this:
private void message(final Text t, final String m)
{
//NB active is declared as 'volatile'
if (active == false)
{
active = true;
t.getDisplay().asyncExec(new Runnable()
{
#Override
public void run()
{
MessageBox mb = new MessageBox(t.getShell());
mb.setText(m);
mb.setMessage(t.getMessage() + "\n\n" + m);
mb.open();
active = false;
}
});
}
else
{
System.out.println("Already active: " + t.getMessage());
}
}
This seems to give the events the opportunity to continue uninterrupted by the pop-up window. The pop-up will be activated a short while later. So far it works ok.
I've an combo box which is composed of a text field and a popup with a CellTable showing the suggestion items. The text field has a change handler that updates the CellTable's selection.
When typing a character and clicking an already selected suggestion, the first click is swallowed. The second click works and triggers the selection via the CellTable.addDomHandler(...).
Any idea why first click is swallowed?
Example code:
private static class SuggestFieldTextAndPopupSandbox extends SimplePanel {
private final TextField mText;
private CellTable<Handle<String>> mTable;
private SingleSelectionModel<Handle<String>> mTableSelection;
private SingleSelectionModel<Handle<String>> mSelection;
private ProvidesKey<Handle<String>> mKeyProvider = new SimpleKeyProvider<Handle<String>>();
private PopupPanel mPopup;
private List<Handle<String>> mData;
public SuggestFieldTextAndPopupSandbox() {
mData = Lists.newArrayList(new Handle<String>("AAA"), new Handle<String>("AAB"), new Handle<String>("ABB"));
mSelection = new SingleSelectionModel<Handle<String>>();
mText = new TextField();
mText.addKeyPressHandler(new KeyPressHandler() {
#Override
public void onKeyPress(KeyPressEvent pEvent) {
mPopup.showRelativeTo(mText);
}
});
mText.addBlurHandler(new BlurHandler() {
#Override
public void onBlur(BlurEvent pEvent) {
mTableSelection.setSelected(startsWith(mText.getValue()), true);
}
});
mText.addChangeHandler(new ChangeHandler() {
#Override
public void onChange(ChangeEvent pEvent) {
mText.setText(mText.getText().toUpperCase());
}
});
mTable = new CellTable<Handle<String>>(0, GWT.<TableResources>create(TableResources.class));
mTable.setTableLayoutFixed(false);
mTableSelection = new SingleSelectionModel<Handle<String>>(mKeyProvider);
mTable.setSelectionModel(mTableSelection);
mTable.addDomHandler(new ClickHandler() {
#Override
public void onClick(final ClickEvent pEvent) {
Scheduler.get().scheduleFinally(new ScheduledCommand() {
#Override
public void execute() {
mSelection.setSelected(mTableSelection.getSelectedObject(), true);
mText.setFocus(true);
mPopup.hide();
}
});
}
}, ClickEvent.getType());
mTable.addColumn(new TextColumn<Handle<String>>() {
#Override
public String getValue(Handle<String> pObject) {
return pObject.get();
}
});
mTable.setRowData(mData);
mPopup = new PopupPanel();
mPopup.setAutoHideEnabled(true);
mPopup.setWidget(mTable);
mPopup.setWidth("200px");
mPopup.setHeight("200px");
VerticalPanel p = new VerticalPanel();
p.add(mText);
setWidget(p);
}
private Handle<String> startsWith(final String pValue) {
final String val = nullToEmpty(pValue).toLowerCase();
int i = 0;
for (Handle<String> item : mData) {
String value = item.get();
if (value != null && value.toLowerCase().startsWith(val)) {
return item;
}
i++;
}
return null;
}
}
I reproduced your issue and here is the problem:
when you click on the suggestions the following is happening:
The text field is loosing focus which causes the corresponding ChangeEvent to be dealt with followed by the BlurEvent.
The click causes the popup to get the focus now which is why it is swallowed.
If you remove the ChangeHandler and the BlurHandler of the text field the issue disappears. But I think I found another solution
Try replacing the DOM handler of the mTable with a selection handler relative to the mTableSelection as follows:
mTableSelection.addSelectionChangeHandler(new Handler(){
#Override
public void onSelectionChange(SelectionChangeEvent event) {
Scheduler.get().scheduleFinally(new ScheduledCommand() {
#Override
public void execute() {
mSelection.setSelected(mTableSelection.getSelectedObject(), true);
mText.setFocus(true);
mPopup.hide();
}
});
}
});
Found a way how to properly solve this.
Skipping the blur handler when user hovers the suggestion list area seemed to fix that issue, at least from the tests that were done didn't see any more issues.
This was necessary because just before the user clicks a suggestion item, the text is blurred and it fires a selection change. This in turn cancels the selection made when user clicks an item.
I have set up an AsyncDataProvider for my CellTable and added it to a SimplePager. I have hooked up a ListHandler to take care of sorting based on a column.
When I click the header of that column, the data doesn't change but on going to the next/previous page within the pager the data is then sorted. Also before the column is clicked there is no visual indicator on the column that would indicate that it is meant to be sortable.
How can I get the data to update when I click the header of the Column?
Here's my code snippet
service.getHosts(environment, new AsyncCallback<Set<String>>() {
#Override
public void onSuccess(final Set<String> hosts) {
final List<String> hostList = new ArrayList<String>(hosts);
//Populate the table
CellTable<String> hostTable = new CellTable<String>();
TextColumn<String> hostNameColumn = new TextColumn<String>(){
#Override
public String getValue(String string){
return string;
}
};
NumberCell numberCell = new NumberCell();
Column<String, Number> lengthColumn = new Column<String, Number>(numberCell){
#Override
public Number getValue(String string) {
return new Integer(string.length());
}
};
AsyncDataProvider<String> dataProvider = new AsyncDataProvider<String>() {
#Override
protected void onRangeChanged(HasData<String> data) {
int start = data.getVisibleRange().getStart();
int end = start + data.getVisibleRange().getLength();
List<String> subList = hostList.subList(start, end);
updateRowData(start, subList);
}
};
// Hooking up sorting
ListHandler<String> columnSortHandler = new ListHandler<String>(hostList);
columnSortHandler.setComparator(lengthColumn, new Comparator<String>(){
#Override
public int compare(String arg0, String arg1) {
return new Integer(arg0.length()).compareTo(arg1.length());
}
});
hostTable.setPageSize(10);
hostTable.addColumnSortHandler(columnSortHandler);
hostTable.addColumn(hostNameColumn,"Host Name");
lengthColumn.setSortable(true);
hostTable.addColumn(lengthColumn, "Length");
VerticalPanel verticalPanel = new VerticalPanel();
SimplePager pager = new SimplePager();
pager.setDisplay(hostTable);
dataProvider.addDataDisplay(hostTable);
dataProvider.updateRowCount(hosts.size(), true);
verticalPanel.add(hostTable);
verticalPanel.add(pager);
RootPanel.get().add(verticalPanel);
}
#Override
public void onFailure(Throwable throwable) {
Window.alert(throwable.getMessage());
}
});
I'm not sure how to make sure that the list is shared by both the table and the Pager. Before adding the pager I was using
ListDataProvider<String> dataProvider = new ListDataProvider<String>();
ListHandler<String> columnSortHandler = new ListHandler<String>(dataProvider.getList());
The AsyncDataProvider doesn't have the method getList.
To summarize I want the data to be sorted as soon as the column is clicked and not after I move forward/backward with the pager controls.
As per the suggestion I have changed the code for the AsyncDataProvider to
AsyncDataProvider<String> dataProvider = new AsyncDataProvider<String>() {
#Override
protected void onRangeChanged(HasData<String> data) {
int start = data.getVisibleRange().getStart();
int end = start + data.getVisibleRange().getLength();
List<String> subList = hostList.subList(start, end);
// Hooking up sorting
ListHandler<String> columnSortHandler = new ListHandler<String>(hostList);
hostTable.addColumnSortHandler(columnSortHandler);
columnSortHandler.setComparator(lengthColumn, new Comparator<String>(){
#Override
public int compare(String v0, String v1) {
return new Integer(v0.length).compareTo(v1.length);
}
});
updateRowData(start, subList);
}
};
But there is no change in the behavior even after that. Can someone please explain the process. The GWT showcase app seems to have this functionality but how they've done it isn't all that clear.
When using an AsyncDataProvider both pagination and sorting are meant to be done on the server side. You will need an AsyncHandler to go with your AsyncDataProvider:
AsyncHandler columnSortHandler = new AsyncHandler(dataGrid) {
#Override
public void onColumnSort(ColumnSortEvent event) {
#SuppressWarnings("unchecked")
int sortIndex = dataGrid.getColumnIndex((Column<Entry, ?>) event.getColumn());
boolean isAscending = event.isSortAscending();
service.getPage(0, sortIndex, isAscending, new AsyncCallback<List<Entry>>() {
public void onFailure(Throwable caught) {
}
public void onSuccess(List<Entry> result) {
pager.setPage(0);
provider.updateRowData(0, result);
}
});
}
};
dataGrid.addColumnSortHandler(columnSortHandler);
Clicking on a column header will then fire a columnSortEvent. Then you have to get the column clicked. I am overloading my servlet to provide both sorting and pagination, so I pass a -1 for the column index when only pagination is desired.
provider = new AsyncDataProvider<Entry>() {
#Override
protected void onRangeChanged(HasData<Entry> display) {
final int start = display.getVisibleRange().getStart();
service.getPage(start, -1, true, new AsyncCallback<List<Entry>>() {
#Override
public void onFailure(Throwable caught) {
}
#Override
public void onSuccess(List<Entry> result) {
provider.updateRowData(start, result);
}
});
}
};
provider.addDataDisplay(dataGrid);
provider.updateRowCount(0, true);
Then your servlet implementation of getPage performs the sorting and pagination. The whole thing is much easier to follow with separate event handlers.
I think the problem is with the ListHandler initialization. You are passing hostList as a parameter to List Handler and in onRangeChange method you are calling updateRowData with a different list (sublist).
Make sure you use the same list in both the places.
or
Move your ListHander initialization and cellTable.addColumnSortHandler method call to onRangeChange method after updateRowData call.
I am trying to get a proper method for days to select multiple cells in a flextable's column.
So far i only managed to do it with clicks which works well, but a drag selection would be much better. I have been reading docs and searching, but all the stuff i found was based on deprecated code. I use GWT 2.0 .
I know i need some event handler which would run when drag selection mouse gesture occurs, and that handler needs to know the cell's index where the selection start and of course the cell's index where the selection ends.
Any advice || code would be much appreciated.
This needs to be improved but it should give you the basic idea. First you need to create a CustomTable that listens to MouseEvents. You can do this by extending composite to wrap a focuspanel and a flextable as such :
public class CustomTable extends Composite implements MouseDownHandler, MouseMoveHandler, MouseUpHandler{
List<CellWidget> widgets = new ArrayList<CellWidget>();
FlexTable table = new FlexTable();
FocusPanel focusPanel = new FocusPanel();
boolean selecting= false;
Point selectStart,selectEnd;
public CustomTable(){
focusPanel.setWidget(table);
focusPanel.addMouseDownHandler(this);
focusPanel.addMouseMoveHandler(this);
focusPanel.addMouseUpHandler(this);
initWidget(focusPanel);
}
public void setWidget(int row, int column, CellWidget widget){
widgets.add(widget);
table.setWidget(row, column, widget);
}
#Override
public void onMouseUp(MouseUpEvent event) {
event.preventDefault();
if (selecting){
selecting=false;
DOM.releaseCapture(this.getElement());
selectEnd = new Point(event.getClientX(),event.getClientY());
for (CellWidget widget : widgets){
if (widget.isIn(selectStart,selectEnd))
widget.say();
}
selectStart = selectEnd = null;
}
}
#Override
public void onMouseMove(MouseMoveEvent event) {
event.preventDefault();
if (selecting){
//do some fancy layout
}
}
#Override
public void onMouseDown(MouseDownEvent event) {
event.preventDefault();
selecting = true;
DOM.setCapture(this.getElement());
selectStart = new Point(event.getClientX(),event.getClientY());
}
}
Next you define a CellWidget which basically encapsulates what you would like to add to your cells. When added to DOM, CellWidget calculates and stores its position later to determine if it is in the selected area :
public class CellWidget extends Composite{
Widget content;
Point topLeft,topRight,bottomLeft,bottomRight;
public CellWidget(Widget w){
this.content = w;
initWidget(w);
}
#Override
protected void onLoad() {
topLeft = new Point(getAbsoluteLeft(),getAbsoluteTop());
topRight = new Point(getAbsoluteLeft()+getOffsetWidth(),getAbsoluteTop());
bottomLeft = new Point(getAbsoluteLeft(),getAbsoluteTop()+getOffsetHeight());
bottomRight = new Point(getAbsoluteLeft()+getOffsetWidth(),getAbsoluteTop()+getOffsetHeight());
}
public void say(){
Window.alert(content + " is selected!");
}
public boolean isIn(Point start, Point end){
if (topLeft.isBetween(start, end) || topRight.isBetween(start, end)
|| bottomLeft.isBetween(start, end) || bottomRight.isBetween(start, end))
return true;
else
return false;
}
}
A simple point implementation to make things easier :
public class Point {
int x,y;
public Point(int x,int y){
this.x=x;
this.y=y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
#Override
public String toString() {
return x+","+y;
}
public boolean isBetween(Point p1,Point p2){
if (p1.getX() < x && p2.getX() > x && p1.getY() < y && p2.getY() > y)
return true;
return false;
}
}
Finally at your EntryPoint module you wrap things up by :
public void onModuleLoad() {
RootPanel rootPanel = RootPanel.get();
CustomTable table = new CustomTable();
table.setWidget(0, 0, new CellWidget(new Label("hello 0,0")));
table.setWidget(0, 1, new CellWidget(new Label("hello 0,1")));
table.setWidget(1, 0, new CellWidget(new Label("hello 1,0")));
table.setWidget(1, 1, new CellWidget(new Label("hello 1,1")));
rootPanel.add(table);
}
I know that the actual logic to determine if the widgets fall within the selected area is incomplete and needs to be improved but i think this solution is clear enough to give the basic idea. Cheers