ColumnMove bug with multi-line headers?

Posted by: zenrhapsody on 8 September 2017, 1:06 pm EST

  • Posted 8 September 2017, 1:06 pm EST

    I think I have found a bug with Spread 3.

    I have multi-line column headers.  The first row has custom text; the 2nd row is the standard A,B,C.

    If I enable ColumnMove and move column 4 to column 6, data in my custom header does not move correctly.

    For example, before column move I have these headers:

    zero   one   two   three   four   five   six    seven   eight

    A         B   C      D         E      F      G      H         I


    After insert, I have

    zero   one   two   three   five six   <empty> four   eight

    A         B   C      D         E      F      G         H         I


    Where the header row 1 for columns G and H are incorrect. (G should be four, H should be seven). 

    I am using a custom data model for the main sheet data, and this data looks fine. 


    Also, the cell types are moved one cell to much to the right also.  (Column G has no celltype, whereas column H's cell type is overwritten by what should have been in G)


  • Replied 8 September 2017, 1:06 pm EST


    When moving columns, the columns are moved only in the viewport by default, but you are probably using model indexes in your custom code and the numbers are not matching. You would need ot use the GetModelColumnFromViewColumn and/or GetViewColumnFromModelColumn methods to get the correct column indexes. Or you can catch the ColumnDragMove event and set the e.MoveContent parameter to True to have the Model reorder it's indexes instead of the view. This will be slower but would not cause any other code (possible) headaches.

  • Replied 8 September 2017, 1:06 pm EST


    Thanks for the reply.  MoveContent already does equal true in ColumnDragMove event.

    If the problem was only with my DataModel, why would the column headers (which is still completely in the control of FpSpread code) as well as cell types not be moving correctly?  Also my tracking calls to my data model's IRangeSupport interface, FpSpread does not seem to be remapping columns between viewport and datamodel.  FpSpread specifically instructs the data model to move columns around.

    My test is moving Column E (#4) to between Columns G and H. 

    If I step into disassembly code, I see FpSpread code calling a move column method (not sure of exact name) with fromColumn=4 and toColumn=6.  This is what I expect.

    The calls to my data model's IRangeSupport implementation are as follows:

    IRangeSupport.AddColumns(int column=7, int columnCount=1) (conflicts with documentation that says insert AFTER column).

    IRangeSupport.Move(int fromRow=-1, int fromColumn=4, int toRow=-1, int toColumn=7, int rowCount=-1, int columnCount=1)

    IRangeSupport.Move(int fromRow=-1, int fromColumn=5, int toRow=-1, int toColumn=4, int rowCount=-1, int columnCount=2)

    IRangeSupport.RemoveColumns(int column=6, int columnCount=1)

    Is the problem with the first IRangeSupport.AddColumns()?  According to documentation, I should be inserting AFTER column 7, not before.  That may be what your code is doing.  If your insert added after column 7, then deleted column 6, I would see an empty header / non celltype formatted column when all was said and done.  I cannot insert after column 7 (a new column 8) in my example, because I would never copy anything new into it according to the parameters in IRangeSupport.Move() calls.


    Here is my class inheritance:

    <FONT size=2>

    </FONT><FONT color=#0000ff size=2>class</FONT><FONT size=2> </FONT><FONT color=#008080 size=2>DataModelZoom</FONT><FONT size=2> : </FONT><FONT color=#008080 size=2>BaseSheetDataModel</FONT><FONT size=2>, </FONT><FONT color=#008080 size=2>IRangeSupport</FONT><FONT size=2>, </FONT><FONT color=#008080 size=2>INonEmptyCells, </FONT><FONT color=#008080 size=2>ISpreadTranspose </FONT>

    <FONT color=#008080 size=2>ISpreadTranspose is a special interface for my app..

  • Replied 8 September 2017, 1:06 pm EST


    Based on what you have said, it sounds like my response is the correct response. If it is not working for you, could you post a small zipped project reproducing the issue you are seeing for us to debug?

  • Replied 8 September 2017, 1:06 pm EST

    Application posted..

    Here's what I found and you can reproduce all 3 scenarios:

    1) No custom data model - works fine.

    2) Custom data model derives from BaseSheetDataModel - works fine; only viewport columns move as you stated.  Data in new view column 5 still asks data model for data column 2.

    3) Custom data model derives from BaseSheetDataModel AND implements IRangeSupport - reproduce error with custom data headers; does remap data model.

    Set scenario variable to 1, 2, or 3 in form load code to select scenario to test.

    I don't have any code in the IRangeSupport methods in this test project.  In my production code, I 'move' data be fixing my mapping pointers.  Regardless, the primary problem is with the headers and formats. BTW, the header color of the effected column is lost as well.


  • Replied 8 September 2017, 1:06 pm EST

    In my app code, I DO have code in the 3 IRangeSupport methods you mention.  My custom data DOES move fine.  What does not work is the column header and cell types that my custom data model has no control over to start with. (Just as you see in the demo app). 

    I must not be understanding all the detailed requirements of implementing IRangeSupport methods in my custom data model to keep data sync'd up with other models.  All code in my IRangeSupport only moved internal pointers; I assumed that since the view was telling me to change in response to user action, that I didn't need to call the datamodel's OnChanged() method to fire more events.

    Where can I get more info on these interfaces and models?



  • Replied 8 September 2017, 1:06 pm EST

    Here is how SheetView.MoveColumn/MoveRow works:

      If MoveContent is true (this is the default behavior), and the data model implements the IRangeSupport interface (also the default, since DefaultSheetDataModel implements this interface), then the column or row is moved using a three step process:

      A new column or row is inserted at the correct location in the sheet.

      The Move method is called to move the column or row to the new location.

      If the new location is after the original location, the Move method is called again to move each intervening column or row up by one.

      The extra column or row is removed with the Remove method.

    SheetView.Move will perform the move operation using the IRangeSupport methods on each of the sheet models (SheetView.Models) necessary to perform the operation, if those methods are implemented and if the sheet does not currently have any row or column index mapping from a sort operation or from a move operation where MoveContent was false.  If there is a row or column index mapping, or if one or more of the models does not implement IRangeSupport, then the values in the model(s) are moved one at a time.  SheetView.Move prefers to use the IRangeSupport methods when available in the sheet models and there is no index mapping, since it is faster to do it that way.

      If MoveContent is false, or if the sheet's data model (SheetView.Models.Data) does not implement the IRangeSupport interface, then the column or row is moved by creating or modifying the index mapping of SheetView's column or row indexes to the models' column or row indexes.  This is not the default behavior, since this operation is not persistable, so you must either put code in the SheetView.ColumnDragMove/RowDragMove event to set e.MoveContent = false, or you must supply a custom data model that does not implement IRangeSupport, to get this behavior.  The operation is performed as described above, except that instead of actually moving the contents of the sheet's models, the index mappings are updated to effect the move using the methods for getting and setting the index mapping (SheetView.GetModelRowFromViewRow, SheetView.GetModelColumnFromViewColumn, SheetView.GetViewRowFromModelRow, SheetView.GetViewColumnFromModelColumn, SheetView.DocumentModels.SetViewRowIndex, SheetView.DocumentModel.SetViewColumnIndex).

    If you want the best performance and do not want to have to implement IRangeSupport, and you do not need to save changes to the ordering of the data resulting from sorting or moving columns or rows, then it is best to use MoveContent = false and let the SheetView rearrange the row or column index mappings.  If you need to persist changes that include sorting or moving columns or rows, then you must use MoveContent = true to force the values to be moved in the models, but this requires IRangeSupport to be implement on the sheet's main data model (SheetView.Models.Data).  The index mappings created by sorting the sheet with SheetView.SortRows, SheetView.SortColumns, or by SheetView.MoveColumn or SheetView.MoveRow with MoveContent = false, are not saved as part of the sheet state when the control is saved to XML.
  • Replied 8 September 2017, 1:06 pm EST


    Thanks for the detailed information.  It is helpful.  However, you still have not addressed my original problem with the spread control.  If you look above in this thread (and find the sample project that reproduces the issue) you can see the problem that I am experiencing with multi-line row headers and with cell types not moving as expected. 

    I have a data model derived from BaseSheetDataModel that explicitly implements IRangeSupport.  In my production app, I move the data pointers in AddColumns, Move, adn RemoveColumns methods of IRangeSupport.  The app I sent that reproduces the error implements IRangeSupport interface (though the methods are empty).  I left them empty because my production app does nothing other than move internal pointers.  If you could look at this app and tell me what's wrong with IRangeSupport, it would be extremely helpful.  The 'bug' only occurs if I implement IRangeSupport with BaseSheetDataModel as my base class.


    I am using Spread Win v3 not v4.

  • Replied 8 September 2017, 1:06 pm EST

    The main data model for the sheet (SheetView.Models.Data) is the controlling model for the other sheet models, and SheetView uses the Changed event on the main data model to keep the other sheet models in sync with changes made to the data model.  When columns or rows are added or removed in the main data model, the Changed event handler in SheetView calls the IRangeSupport methods in the other models to update them and keep them in sync.  This is why the IRangeSupport methods are implement as public virtual methods on DefaultSheetDataModel, but as explicit interface implementations on the other model classes.  It will work to call methods like AddRows or RemoveColumns either on SheetView or directly on the main data model, but it is a very bad idea to make calls to those methods on any of the other models, because it will make the row or column counts in the other models get out of sync with the main data model and cause exceptions in the paint code.

    Your data model with IRangeSupport implemented with empty methods is not telling the SheetView of the changes made to it through the IRangeSupport methods, and that is causing the other models to get out of sync with the sheet data model.

    If you add code to your sample that calls FireChanged with the appropriate arguments in each of the IRangeSupport methods, then your sample works beautifully.
  • Replied 8 September 2017, 1:06 pm EST

    Thank you!  It's working correctly now.

    I had tried a similar approach, but I called base.OnChanged() instead of FireChanged, thinking that base.OnChanged would fire the event corectly.  Possibly it did, but I could have had the arguments incorrect.

    Thanks for the detailed explanation of your architecture. 


  • Replied 8 September 2017, 1:06 pm EST


    The MoveContent parameter actually defaults to True. When this is the case, the Spread source is using the IRangeSupport to handle the moving of the columns to sync all models (data, style, axis, etc). In your example of moving column 4 to column 6, the AddColumns method is called and then 2 MoveColumns and finally a RemoveColumns. Since the project you sent me had no code in these methods, the moving of the column did not work.

    You can map the ColumnDragMove event and set the MoveContent parameter to False and this code will work. Otherwise you need to write the code for the 3 methods in IRangeSupport that I mentioned.

Need extra support?

Upgrade your support plan and get personal unlimited phone support with our customer engagement team

Learn More

Forum Channels