Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Problems with ListGrid canSelectCells and selection mode

    With Pro 3.1d (June 12 build):

    I had a nice standalone example of a simple ListGrid with a dynamic structure that made use of the new canSelectCells(true) behaviors. The table can be redrawn based on different query results with different column structures. When called in standalone mode (making use of the EntryPoint's onModuleLoad()), the single cell selection and range selection works fine upon rebuilds.

    I moved the code into my larger app, where the "canvas" containing the table and other elements is placed into a TabSet. A number of problems came up, starting with the inability to actually call the canSelectCells method
    Code:
    (BaseWidget.java:600) 2012-06-25 19:22:19,435 [ERROR] Uncaught exception
    com.google.gwt.event.shared.UmbrellaException: One or more exceptions caught, see full set in UmbrellaException#getCauses
        at com.google.gwt.event.shared.HandlerManager.fireEvent(HandlerManager.java:129)
        at com.smartgwt.client.widgets.BaseWidget.fireEvent(BaseWidget.java:71)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
    ....
           Caused by: java.lang.IllegalStateException: Cannot change configuration property 'canSelectCells' to false after the component has been created.
    By taking that out, I was able to get the code to render, but not with the correct visualization of the selected range. Then on the second and subsequent renderings, the ListGrid reverts to a rendering mode where all the cells in the row beneath the mouse are highlighted (the typical ListGrid behavior?)

    Here's an example which can be run standalone.

    Code:
    import com.allen_sauer.gwt.log.client.Log;
    import com.google.gwt.core.client.GWT;
    import com.smartgwt.client.data.RecordList;
    import com.smartgwt.client.types.Alignment;
    import com.smartgwt.client.types.ListGridFieldType;
    import com.smartgwt.client.types.SelectionStyle;
    import com.smartgwt.client.util.SC;
    import com.smartgwt.client.widgets.Canvas;
    import com.smartgwt.client.widgets.IButton;
    import com.smartgwt.client.widgets.events.ClickEvent;
    import com.smartgwt.client.widgets.events.ClickHandler;
    import com.smartgwt.client.widgets.form.DynamicForm;
    import com.smartgwt.client.widgets.form.fields.IntegerItem;
    import com.smartgwt.client.widgets.form.fields.TextItem;
    import com.smartgwt.client.widgets.grid.*;
    import com.smartgwt.client.widgets.grid.events.*;
    import com.smartgwt.client.widgets.layout.HLayout;
    import com.smartgwt.client.widgets.layout.VLayout;
    
    public class CellSelectionTester implements EntryPoint
    {
        private ListGrid d_simpleGrid;
    
        private Canvas  d_canvas;
        private VLayout d_vertLayout;
        private HLayout d_placeholderForTable;
        private DynamicForm d_simpleForm;
        private IntegerItem d_numColsIntItem;
        private TextItem d_numColsTextItem;
    
        private boolean d_didRebuild = false;
    
        public void onModuleLoad()
        {
            Log.warn("onModuleLoad was called");
        }
    
        public CellSelectionTester()
        {
            Log.warn("CellSelectionTester()");
    
            GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler()
            {
                public void onUncaughtException(Throwable e)
                {
                    Log.error("Uncaught exception", e);
                }
            });
    
    
            d_canvas = new Canvas();
    
            d_vertLayout = new VLayout();
    
            IButton testButton = new IButton("Test Cell Selection");
            testButton.addClickHandler(new ClickHandler()
            {
                public void onClick(ClickEvent clickEvent)
                {
                    testCellSelection();
                }
            });
    
    
            d_simpleForm = new DynamicForm();
            d_simpleForm.setWidth(225);
            d_simpleForm.setWrapItemTitles(false);
            d_simpleForm.setTitleWidth(150);
            d_numColsIntItem = new IntegerItem("numColsInt","Num Cols As Int");
            d_numColsIntItem.setValue(5);
            d_numColsTextItem = new TextItem("numColsText","Num Cols As Text");
            d_numColsTextItem.setValue("5");
            
            d_simpleForm.setFields(d_numColsIntItem,d_numColsTextItem);
    
            IButton rebuildGrid = new IButton("Rebuild Grid");
            rebuildGrid.addClickHandler(new ClickHandler()
            {
                public void onClick(ClickEvent clickEvent)
                {
                    rebuildGridInteractive();
                    if (!d_didRebuild){
                        SC.warn("*** IF NOT CALLED VIA onModuleLoad() ****\nMove the mouse over the grid to notice that the selection mode is now row-like, not single-cell.\nClick Rebuild Grid again with different form values to see Form error.");
                        d_didRebuild = true;
                    }
                }
            });
    
    
            d_placeholderForTable = new HLayout();
    
            d_simpleGrid = new ListGrid();
            d_placeholderForTable.addMember(d_simpleGrid);
    
            d_vertLayout.addMember(d_placeholderForTable);
            d_vertLayout.addMember(d_simpleForm);
            d_vertLayout.addMember(rebuildGrid);
            d_vertLayout.addMember(testButton);
    
            d_canvas.addChild(d_vertLayout);
    
            rebuildGrid(10);
    
            d_canvas.draw();
    
            SC.warn("Move the mouse over the grid to notice single-cell selection");
    
    
        }
    
        private void rebuildGridInteractive()
        {
            int numCols = 0;
            try{
                Integer valueAsInteger = d_numColsIntItem.getValueAsInteger();
                numCols = valueAsInteger;
            }
            catch (Throwable t){
                // The code above seems to generate this error on the second and subsequent call;
                // com.google.gwt.dev.shell.HostedModeException: invoke arguments: JS value of type string, expected int
                // at com.google.gwt.dev.shell.JsValueGlue.getIntRange(JsValueGlue.java:266)
                //     [ If it only happens in HostedMode... it still makes the development cycle very tedious]
    
                // This Exception seems to only be thrown if you change the original integer value to something else.
    
                SC.warn("Got error message trying to getValueAsInteger");
                Log.error("Error with getValueAsInteger",t);
    
                String numColsText = d_numColsTextItem.getValueAsString();
    
                if (numColsText != null){
                    try{
                        numCols = Integer.parseInt(numColsText);
                    }
                    catch (Throwable t2){
                        SC.warn("Couldn't parse an integer...");
                    }
                }
            }
    
            if (numCols > 0){
                rebuildGrid(numCols);
            }
        }
    
        private void rebuildGrid(int numCols)
        {
        /*
        Exceptions:
        1. With rebuildGrid() being called at the end of the constructor
           a. Caused by: java.lang.IllegalStateException: Cannot change configuration property 'canSelectCells' to false after the component has been created.
           b. Caused by: java.lang.IllegalStateException: Cannot change configuration property 'canRemoveRecords' to false after the component has been created.
           b. Caused by: java.lang.IllegalStateException: Cannot change configuration property 'autoFetchData' to false after the component has been created.
    
         */
    
            if ((d_simpleGrid != null) && (d_placeholderForTable != null)){
                Log.warn("Grid and layout existed already... destroying...");
                d_placeholderForTable.removeMember(d_simpleGrid);
    
                d_simpleGrid.destroy();
            }
    
            d_simpleGrid = new ListGrid();
    
            d_simpleGrid.setID("masterGrid");
            d_simpleGrid.setWidth(400);
            d_simpleGrid.setHeight(300);
    
            // This only works if the code is called via the EntryPoint
            d_simpleGrid.setCanSelectCells(true);
    
            d_simpleGrid.setCanDragSelect(true);
            d_simpleGrid.setCanSort(false);
    
            d_simpleGrid.setHeaderHeight(40);
            d_simpleGrid.setCanEdit(false);
            d_simpleGrid.setShowAllRecords(true);
            d_simpleGrid.setKeepInParentRect(true);
    //        d_simpleGrid.setCanRemoveRecords(false);
            d_simpleGrid.setLeaveScrollbarGap(false);
            d_simpleGrid.setUseAllDataSourceFields(true);
    //        d_simpleGrid.setAutoFetchData(false);
    
    
            d_simpleGrid.addCellClickHandler(new CellClickHandler()
            {
                public void onCellClick(CellClickEvent cellClickEvent)
                {
                    Log.warn("Clicked: " + cellClickEvent.getRowNum() + "," + cellClickEvent.getColNum());
                }
            });
    
    
            d_simpleGrid.addSelectionChangedHandler(new SelectionChangedHandler()
            {
                public void onSelectionChanged(SelectionEvent selectionEvent)
                {
                    Log.warn("selection changed: " + d_simpleGrid.getEventRow() + "," + d_simpleGrid.getEventRow());
                }
            });
    
            d_simpleGrid.addCellMouseDownHandler(new CellMouseDownHandler()
            {
                public void onCellMouseDown(CellMouseDownEvent event)
                {
                    Log.warn("Mouse down at: " + d_simpleGrid.getEventRow() + "," + d_simpleGrid.getEventColumn());
                }
            });
    
            d_simpleGrid.addCellMouseUpHandler(new CellMouseUpHandler()
            {
                public void onCellMouseUp(CellMouseUpEvent event)
                {
                    Log.warn("Mouse up at: " + d_simpleGrid.getEventRow() + "," + d_simpleGrid.getEventColumn());
                }
            });
    
                /*   This is quite noisy...
            d_simpleGrid.addCellSelectionChangedHandler(new CellSelectionChangedHandler()
            {
                public void onCellSelectionChanged(CellSelectionChangedEvent event)
                {
                    dumpSelectedCells(event.getCellList(), "From cell selection changed handler");
                }
            });
            */
    
    
            // Dynamically generate the structures...
            int numRows = 10;
            ListGridRecord[] records = new ListGridRecord[numRows];
            ListGridField[] fields = new ListGridField[numCols];
    
            int col = 0;
            while (col < numCols){
                String key = "col-" + col;
    
                ListGridField field = new ListGridField(key,"Col " + col);
                field.setType(ListGridFieldType.TEXT);
                field.setWidth(50);
                field.setAlign(Alignment.RIGHT);
                fields[col] = field;
    
                int row = 0;
                while (row < numRows){
                    ListGridRecord record = null;
                    if (col == 0){
                        record = new ListGridRecord();
                        records[row] = record;
                    }
                    record = records[row];
    
                    record.setAttribute(key,"r" + row + ", c" + col);
    
                    row++;
                }
    
                col++;
            }
    
            d_simpleGrid.setFields(fields);
            d_simpleGrid.setData(records);
    
            d_placeholderForTable.addMember(d_simpleGrid);
    
            d_canvas.draw();
        }
    
        private void dumpSelectedCells(int[][] cellList, String fromWhere)
        {
            if (cellList != null){
                int length = cellList.length;
                Log.warn("cellList len = " + length + " " + fromWhere);
                int i = 0;
                while (i < length){
                    int[] cols = cellList[i];
                    int c = 0;
                    int numCols = cols.length;
                    if (numCols == 2){
                        Log.warn("r=" + cols[0] + ",c=" + cols[1]);
                    }
                    else{
                        Log.warn("NumCols = " + numCols);
                        // Always in pairs of two, first one is row, second one is column.
                        while (c < numCols){
                            Log.warn("   (" + cellList[i][c] + ")");
                            c++;
                        }
                    }
                    i++;
                }
            }
        }
    
        private void testCellSelection()
        {
            Log.warn("test selection...");
    
            SelectionStyle selectionType = d_simpleGrid.getSelectionType();
    
            RecordList selectedCellData = d_simpleGrid.getSelectedCellData();
    
            ListGridRecord[] selectedRecords = d_simpleGrid.getSelectedRecords();
    
            if (selectedCellData != null){
                Log.warn("selectedCellData.length = " + selectedCellData.getLength());
    
            }
    
            if (selectedRecords != null){
                Log.warn("Num selected records = " + selectedRecords.length);
            }
    
            CellSelection cellSelection = d_simpleGrid.getCellSelection();
    
            int[][] selectedCells = cellSelection.getSelectedCells();
    
            dumpSelectedCells(selectedCells,"Upon Button Click");
    
        }
    
        public Canvas getMainCanvas()
        {
            return d_canvas;
        }
    }
    Here's some additional code which would add a CellSelectionTester to a TabSet:
    Code:
    CellSelectionTester cellSelectionTester = new CellSelectionTester();
    
            Tab reportTab = new Tab("Cell Selection Tester");
            reportTab.setCanClose(true);
            reportTab.setPane(cellSelectionTester.getMainCanvas());
    
            d_mainTabs.addTab(reportTab);
            d_mainTabs.selectTab(reportTab);

    I took the time to demonstrate one other bug I came across recently. In a DynamicForm, an IntegerItem will throw an Exception on a call to getValueAsInteger. Run the code to see.

    Lastly, as I need to be able to treat this code like a "panel class" that I can put into a Tab, when running it that way, I ran into a variety of exceptions when configuring the ListGrid in the constructor, just before the "Tester" would be added to the Tab. Here are snippets of two other exceptions besides the canSelectCells exception mentioned above:

    Code:
           (1)   Caused by: java.lang.IllegalStateException: Cannot change configuration property 'canRemoveRecords' to false after the component has been created.
           (2)  Caused by: java.lang.IllegalStateException: Cannot change configuration property 'autoFetchData' to false after the component has been created.
    I had to remove those method calls to get the code to run. I'm not sure yet if I can leave them out for my final purposes.

    One last item: I was experimenting with the various ListGrid Handlers with the goal being to track and retrieve the selected region. Ultimately, a call to getSelectedCells() does the work...

    When tracking a selection range with canSelectCells(true), the CellMouseUpHandler is not called when the range stops being tracked. The CellMouseDownHandler is always called -- you can see that if you simply click single cells one at a time it different places in the grid. If you click one and then extend a selection rectangle down, upon stopping and releasing the mouse, the UpHandler is not called.

    In summary, four different issues (the isolation of which ate up a good part of my day):
    1. Differences in canSelectCells behavior and rendering between EntryPoint use cases and non-EntryPoint use cases
    2. Inability to call certain ListGrid configuration methods in the non-EntryPoint use case. Ideally with this family of error messages, I'd like more help in knowing what 'component' has been 'created' to determine where the problem is coming from.
    3. Problem with DynamicForm's IntegerItem
    4. Wanting a CellMouseUpEvent when the cell selection range stops changing due to a mouse up.


    Back to my other application logging/configuration issue... http://forums.smartclient.com/showthread.php?t=22601

    #2
    1 & 2 are not bugs and not related to EntryPoint vs non-EntryPoint. Some properties can only be changed before a component has been draw()n or otherwise forced to go through initialization (listGrid.fetchData(), for example, will do it). To avoid this in your code, just avoid immediately calling addMember() on the ListGrid when all you've done is new ListGrid(); configure it first.

    This is not really a meaningful limitation because the pattern you are using is also the worst possible from a performance perspective. In general, if you can setup a widget before asking it to draw or associating it with other widgets in a Layout, do so, otherwise, even if post-draw changes were allowed to these properties, you would create needless churn as the widget reconfigures itself, rebuilding it's appearance and possible requiring other widgets in the Layout to do so as well (if it, for example, gets larger forcing other widgets to shrink).

    We'll address your other concerns in further posts.

    Comment


      #3
      3. Not reproducing this, it could be something that only applied to the June 12th build, or some kind of project issue only happening to you.

      4. MouseUp in general would not necessarily fire when a drag has been initiated. What led you to try to capture this event? We can probably suggest a better approach.

      Comment


        #4
        Re #1 & 2: Thanks for the guidance on the draw() and initialization behavior. Is there additional documentation that describes some of these lower-level drawing consideration issues? As far as the "pattern I'm using", it's very simple: avoid object construction for things that the user has not requested. If that's the "worst possible" one for your product, that would explain the general impedance mismatch I'll admit that I'm having thus far.

        I totally understand your documented recommendation to construct GUI components beforehand, and then use show()/hide() on them, but that assumes that the user will ultimately need them. I'm using that approach where it makes sense. In a complicated app with hundreds or thousands of potential screen elements, many of which may not get used in a given session, constructing everything upfront seems wrong.

        In my example case, I know the user needs a ListGrid immediately, and I'm trying to configure it prior to displaying it, not afterwards. But yes, I have a pattern where many of my table widgets can't know their exact schemas ahead of time... it's a real world, complicated app, what can I say? :-)

        On #3, I can try getting a later build.

        On #4, the simple thought here was to get the two coordinates that define the selected rectangle. The mouse down point is the first coordinate, and the mouse up point is the second one. Given those two, and the attributes (columns) and row indicies of each, I'll be all set in my case. Since the region is contiguous, having all the int[][] pairs is almost too much detail, but it works and I'll move on. Re-read what I wrote -- I'm not requesting a MouseUp on a drag initiation -- simply when the user releases the mouse button (but which will indeed stop the drag action).

        Comment


          #5
          Re: 1&2 the problem you claim to be using - deferred component creation - is the one we recommend in the QuickStart Guide under "Tips" and is correct. The problematic pattern that you're actually using is explained in our previous post - please take another look - this pattern would be a bad idea in any GUI system (examples: Swing, basic DOM/HTML).

          Comment


            #6
            Also, just to note, this had nothing to do with complexity of the app - its a bad pattern at one screen and at 1,000 screens.

            Comment


              #7
              And finally, re:#4, yes we understood you wanted a MouseUp when the mouse goes up. The question was: what is your use case? What are you going to use these coordinates for? Making this clear allows us to potentially suggest a better approach.

              Comment


                #8
                Change in GWT library causing problem with List Grids

                Originally posted by dzarras View Post
                {
                My problem is that after switching libray from
                BuildDate Sun Aug 25 15:28:00 IST 2013
                Version 4.0p
                SCVersionNumber v9.0p_2013-08-25

                to
                BuildDate Wed Mar 05 15:52:00 IST 2014
                Version 4.1p
                SCVersionNumber v9.1p_2014-03-05

                I am facing issues with ListGrid.
                I have made reference to the thread that talks about the same issue and error:-
                http://forums.smartclient.com/showthread.php?t=22607
                The code in this example completely replicates pattern of our application.

                Following are the 4 lines of code that are causing error now where they were working absolutely fine earlier(before switching library):- grid.setCanSelectCells(true);
                grid.setAutoFetchData(true);
                grid.setFilterButtonPrompt("prompt");
                grid.setInitialCriteria(grid.getCriteria());

                Errors i get are like :-Cannot change configuration property 'canSelectCells' to true now that component isc_DataVisualizationGrid_0 has been created.

                I am not able to understand the solution you have provided about fetching data explicitly before adding the member to my canvas. Also, My limitation is that my application can have only autofetch(true).

                Comment


                  #9
                  Your problem produces the same error message as the one above, but it is not necessarily related.

                  setCanSelectCells() may only be called before a ListGrid has been created or drawn by the framework. You are calling some other API, *before calling setCanSelectCells()*, which is forcing creation or drawing.

                  So the problem is in code you haven't shown - you need to look at the other calls before setCanSelectCells() and consider reordering calls so setCanSelectCells() happens sooner.

                  And, as covered above, this is probably going to reveal a bad usage pattern where a component is first being asked to draw, then having many settings changed (which is more expensive to do on a drawn component, in any framework).

                  Comment


                    #10
                    Hi Isomorphic,

                    working with the framework I stumbled over these early initialisation problems once or twice. It would be great to have either a general guidance besides try&error (always do a before b) in the Quick Start Guide or hints in the javadoc for methods that have to come after other methods.

                    Once you had these cases and worked around them, you won't have problems anymore because you know the deal. But if you hit them when working with the framework for the first time they can be really annoying.

                    Best regards,
                    Blama

                    Comment


                      #11
                      APIs that cannot be called after draw are already marked in the JavaDoc with an explicit warning that they will throw an IllegalStateException.

                      Comment


                        #12
                        Thanks Isomorphic.

                        But as raised by Blama, it is not clear even through exception that what needs to be done to avoid that exception.We cannot try and hit multiple times to see what should be the right order of execution of our method calls here.

                        For now i have put these method calls inside setModalEditing(boolean modalEditing) by overriding it.This has fixed my issue and things are working fine now.

                        Comment


                          #13
                          Hi Isomorphic,

                          is that the only rule involved? If so, it would be great, because then it is clearly documented.

                          I remember getting an advice from you once regarding some command order for a databound component (ListGrid?) and setFields().

                          Best regards,
                          Blama

                          Comment


                            #14
                            Blama: yes, only those methods will throw an exception when called after draw. Whatever setFields() issue you had was probably just an ordinary bug, as setFields() does not force a component to draw.

                            mchoudary: whenever you have a chance, you should look more deeply into this. You are most likely drawing a component, then changing several settings on it. This is inefficient with any framework.

                            Comment


                              #15
                              Hello everyone,

                              I just wanted to confirm that Isomorphic is correct with the reply to my question. It was about this post and the two issues mentioned there (set*State() and setSort()) were bugs which were fixed in the meantime.

                              Also note that there is some guideline (regarding *States) on setFields() (in one of the linked threads inside the above post).
                              Originally posted by stonebranch1 View Post
                              "You don't need to wait for draw to call setViewState(), but you do need to do it after you've provided the DataSource and fields if any.
                              Would that be the basic guideline then?"
                              Originally posted by Isomorphic View Post
                              Yes, that's the guideline.
                              The point is that the viewState modifies the fields. So if the fields haven't been configured, there's no way for the ListGrid to meaningfully interpret the viewState.
                              @Isomorphic: I got this far now:
                              1. No call should force early initialisation / initialization
                              2. Initialisation is done via fetchData() or draw().
                              3. Some calls are only possible before initialisation. These are marked with "Throws: java.lang.IllegalStateException - this property cannot be changed after the component has been created" in the docs.
                              4. Some other calls (e.g. the set*State calls) depend on setFields() and therefore must come after setFields().

                              Are there any more rules, especially: Is the list of calls for point 4. longer than I listed?

                              Best regards,
                              Blama

                              Comment

                              Working...
                              X