Detecting single vs multiple selections in Delphi TStringGrid
Asked Answered
M

4

5

This is a follow up to my previous question Delphi TStringGrid multi select, determining selected rows regarding Delphi String Grids. It's a different question.

I was looking more closely at the ONSelectCell Event TSelectCellEvent = procedure (Sender: TObject; ACol, ARow: Longint; var CanSelect: Boolean) of object;

I noticed that the TStringGrid.Selection.Top,Bottom properties are not necessarily accurate (within the event itself). Basically, if someone goes from selecting multiple rows to just one row, the selection.* properties do not get updated, whereas if one selects multiple rows, they do.

The ARow parameter does get updated regardless of whether one or more rows are selected, but this will only help me if I can determine that one and only one row was selected.

Eg, If it's just one row that was selected, then use Arow parameter, if more than one row then use Selection.* properties to determine which row(s) are currently selected.

There must be an easier way....

Thank you!

Mathildemathis answered 17/5, 2011 at 0:29 Comment(3)
I have no idea what you're asking. Is "there must be an easier way" a question?Creamer
...there must be an easier way to know what rows are selected within the onSelectCell Event.Mathildemathis
@Mathildemathis - How did you solved this?Nellanellda
U
6

I think, part of the problem is in terminology used. Until you completely understand what is happening, it must be confusing to find how ‘select’ is used to mean both ‘highlight’ and ‘focus’. In this particular case there should be distinction between the two.

Before I proceed, I'd like you to keep in mind that the focused cell can also be (and actually is) highlighted, but a highlighted cell is not necessarily the focused one.

Now, the OnSelectCell event has to do with focusing. The handler is fired when the cell is clicked or when you are trying to navigate over it with navigation keys. In short, the handler is invoked when there's an attempt to focus a cell. You can prohibit focusing the cell by resetting the CanSelect parameter (which, again, means essentially CanFocus, because the cell can be selected, i.e. highlighted, without being focused, and you can't control that with OnSelectCell).

The goRangeSelect option and TDrawGrid.Selection property, on the other hand, have to do with selecting as highlighting. The former allows you (the user) to highlight more than one cell, while the latter points to the range of those cells highlighted.

Now to my main point. Upon invoking the handler in question, Selection is never accurate, i.e. it is not correlated with the ACol & ARow parameters that are passed to the handler. Selection contains the range of cells that were highlighted just before calling the handler, and it never changes by itself within the handler. Whether one cell or more than one, Selection stays the same until the handler exits. And it is when that happens (the handler exits) that Selection changes (and the result depends on whether you reset CanSelect or not, by the way).

So, in conclusion, you cannot use OnSelectCell to determine the actual Selection as the result of the user's most recent action. Instead I would suggest following @Sam's advice and use the OnMouseUp* event. It also allows you to have control over selection: you can correct the final range if you think the user has selected ‘too much’. In the latter case I would probably consider using OnMouseMove instead, though, as it allows you to respond more smoothly by correcting the range ‘on the fly’.

OnDrawCell seems fine too as long as you need just to determine the selection.


*Following your comment, I must add, that you'd also have to employ OnKeyUp as well, to handle selections made with the keyboard.

Unsegregated answered 18/5, 2011 at 6:15 Comment(2)
Great post! Thanks for the thorough answer. There are actually scenarios when selection is accurate though. The TStringGrid.Selection Property is accurate (in terms of highlighting) in OnSelectCell IFF multiple rows have been highlighted. Using onMouseUp is not a good solution because things can be selected and/or highlighted without the mouse. I agree that onDrawCell is a good way to go, provided flags are used to avoid unnecessary recalculations. I wish there were an onAfterHighlight event. Any thoughts on how I would add this to a descendant class?Mathildemathis
I agree, there are situations when the coordinates that are passed to the handler point to a cell that is already selected. But it's only because that cell has been selected by a previous user action, not by the one the user is performing right now. In my tests Selection always updated after OnSelectCell (after returning from the handler). On the other point, what you've said about OnMouseUp is very true, and so far I don't know other solution (workaround) than defining the OnKeyUp event in addition to OnMouseUp. That makes the entire approach even less elegant, which is a shame.Unsegregated
M
1

I was able to solve this on my own, I wound up using the OnDrawCell Event in conjunction with onSelectCell Event -- which I thought was going to be a mess, but turned out not so bad.

Here's a summary of my solution for others who encounter the same problem. Here are two key facts:

  1. The TStringGrid.Selection Property is ALWAYS accurate in the OnDrawCell.
  2. The TStringGrid.Selection Property is ONLY accurate in OnSelectCell IFF multiple rows have been selected.

public
  previousHighlightCount : integer; //flag to ensure that the necessary code within the onDraw only gets called once per row selection(s).  Initialize to '1' in onFormCreate.


procedure Grid.OnDrawCell(...)
begin
...
SelectionCount := Grid.Bottom - Grid.Top;**
if ((SelectionCount = 1) AND (previousHighlightCount  1)) then  
begin                                                                               
   GridUpdateEdits;  //your routine to update the grid properly for one row.*    
   previousHighlightCount := 1;
end
else
  previousHighlightCount := PrtEdtGrid.SelectionCount;   //the routine for multiply selected rows is in the onSelectCell Event and onSeelctCell works for multiple selections.
....
end;


Thanks to those who responded!!

Mathildemathis answered 17/5, 2011 at 20:18 Comment(0)
R
0
for RowIndex := StringGrid1.Selection.Top to StringGrid1.Selection.Bottom do
begin
  DoSomethingWithRow(RowIndex);
end;

Yes, I see your point. It seems the Selection property is updated after the OnSelectCell event so inside the event you only have the old values (ie. before the select event). The answer to that is to move the code above to the stringgrid's OnMouseUp event. It seems to work fine.

Rowland answered 17/5, 2011 at 0:53 Comment(2)
Thank you, it's a good idea, unfortunately there is no guarantee that the cells will be selected via mouse. My other thought was to track selection.* in the onDrawCell event, where it is always accurate, but I don't like this either because of performance reasons (and because it seems kludgey). Other thoughts?Mathildemathis
ps, I don't mind descending another component if I can get to protected properties that may help. I will dig into the TStringGrid source next.Mathildemathis
E
0

StringGrid1.Selection.Top to StringGrid1.Selection.Bottom is what worked perfectly for me since I'm using the onkeypress event to select/deselect stuff.

Erlin answered 27/8, 2013 at 18:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.