Move Selection to Original Position

General TRichView support forum. Please post your questions here
lkessler
Posts: 112
Joined: Thu Sep 01, 2005 9:00 pm
Location: Winnipeg, Manitoba, Canada
Contact:

Move Selection to Original Position

Post by lkessler »

I want to do the same thing as "Move Caret to Original Position" in http://www.trichview.com/forums/viewtopic.php?t=1463 except I want to do it with the selection, instead of the just the caret position.

It needs to work with tables that are only 1 level deep (i.e. they never have tables in the tables).

This is what I tried and I thought might work:

Code: Select all

  RVE.BeginUpdate;

  RVE.GetSelectionBounds(StartItemNo, StartItemOffs, EndItemNo, EndItemOffs, true);
  if RVE.RVData.GetItemStyle(StartItemNo) = rvsTable then begin
    TableRVE := RVE.TopLevelEditor;
    TableRVE.GetSelectionBounds(TableStartItemNo, TableStartItemOffs, TableEndItemNo, TableEndItemOffs, false);
  end;

  ...

  (Add or remove items before the current item)
  (Update StartItemNo and EndItemNo to reflect the items added or removed)

  ...

  RVE.SetSelectionBounds(StartItemNo, StartItemOffs, EndItemNo, EndItemOffs);
  if RVE.GetCurrentItemEx(TRVTableItemInfo, TableRVE, TableItem) then 
    TableRVE.SetSelectionBounds(TableStartItemNo, TableStartItemOffs, TableEndItemNo, TableEndItemOffs);

  RVE.EndUpdate;
But this does cause an exception when trying to set the selection into a table.

The document is formatted prior to this code.

I have tried adding an RVE.Invalidate just before the RVE.EndUpdate. but that doesn't help.

Any suggestions as to how I can get this to work?

Thanks.

Louis
Sergey Tkachenko
Site Admin
Posts: 17253
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

I am not sure that I understand completely.
But before making a selection inside a table cell, you need to activate its editing.
Use table.EditCell or
RvData := Cell;
RvData := RvData.Edit;
TCustomRVFormattedData(RvData).SetSelectionBounds(...)
lkessler
Posts: 112
Joined: Thu Sep 01, 2005 9:00 pm
Location: Winnipeg, Manitoba, Canada
Contact:

Post by lkessler »

I don't know what it is, Sergey, but I seem to be having a lot of trouble with this.

Let's see if I can explain it in detail.

What I'm trying to do is save a selection in a table, rebuild the RichViewEdit to include exactly the same table, but possibly as a different item, and then restore the selection.

So for example, as in my code above, the Table is in item StartItemNo. Then to save the selection, I do this:

Code: Select all

  var
    TableRVE: TCustomRichViewEdit;
    
  if RVE.RVData.GetItemStyle(StartItemNo) = rvsTable then begin 
    TableRVE := RVE.TopLevelEditor; 
    TableRVE.GetSelectionBounds(TableStartItemNo, TableStartItemOffs, TableEndItemNo, TableEndItemOffs, false); 
  end;

But the GetSelectionBounds I used above does not save the cell Row and Column, so I can't pick the cell to edit.

So I guess you're saying that I have to use the TRVTableItemInfo form of GetSelectionBounds and do this:

Code: Select all

  var
    TableItem: TRVTableItemInfo;
    
  if RVE.RVData.GetItemStyle(StartItemNo) = rvsTable then begin
    TableItem := TRVTableItemInfo(RVE.RVData.GetItem(StartItemNo));
    TableItem.GetSelectionBounds(TableStartRow, TableStartCol, TableRowOffs, TableColOffs);
  end;

Now I recreate my RVE and update StartItemNo to be the item containing the identical table I had before. I want to restore the selection.

But I'm not sure how I do that. If I've got TableStartRow and TableStartCol, then I guess I can select the Cell. Then I can activate its editing. But then how do I get the selection back, since there's only TableRowOffs and TableColOffs? That's only one set of offsets. Not two. What do I put into the SetSelectionBounds?
Sergey Tkachenko
Site Admin
Posts: 17253
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

May be you can use functions from RVLinear.pas

They are sensitive to adding/deleting contents, but you need not worrying about indices of tables and cells.
lkessler
Posts: 112
Joined: Thu Sep 01, 2005 9:00 pm
Location: Winnipeg, Manitoba, Canada
Contact:

Post by lkessler »

Okay. Finally further investigation has led me to discover something.

It seems that in my code, the GetSelection Bounds, update items, and then SetSelectionBounds is actually working.

What isn't working is when my cursor starts inside a table, I do a cursor movement down, I do my Get-Update-Set procedure, and then I wait for the cursor movement to happen. Then an exception occurs which you have well documented in your Forums as the "limitation" of being unable to cursor out of a table.

You say that we should use PostMessage to solve this. I've looked through your examples, but I don't see any that tell me how to do it for the arrow and page-up/down keys.

I am thinking that the end of my RVEditKeyDown routine will have to look like this:

Code: Select all

  case Key of
    VK_UP: PostMessage(Handle, WM_????, 0, 0);
    VK_DOWN: PostMessage(Handle, WM_????, 0, 0);
    VK_PRIOR: PostMessage(Handle, WM_????, 0, 0);
    VK_NEXT: PostMessage(Handle, WM_????, 0, 0);
  end;
  abort;
What I need to know is what WM_ messages to use for the four keys. I can't find them in the Messages unit. Do you know what I should use here?
Sergey Tkachenko
Site Admin
Posts: 17253
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

I do not understand where is exactly the problem in your code.
Yes, inside OnKeyDown event, you cannot do any operation that destroys the cell inplace editor (for example, moving the caret from this cell to another location).

But do you call your code in OnKeyDown? Why do you need OnKeyDown?
lkessler
Posts: 112
Joined: Thu Sep 01, 2005 9:00 pm
Location: Winnipeg, Manitoba, Canada
Contact:

Post by lkessler »

I have pretty well succeeded in virtualizing my very large TRichView documents so as to allow huge document size, but only loading the page you see as well as a couple of pages before and after that.

So on the key down (RVEditKeyDown) event, if a Page Up, Page Down, Arrow Up or Arrow Down is pressed, then I have to ensure that enough "extra" at the top or bottom is available. When it isn't then I may add to the beginning and end of the virtual document and remove any excess from the other end. Then on exit of my RVEditKeyDown event, I can pass control to TRichView's key down processing to now do the movement for the key.

However, since allowing control to pass directly back to TRichView's key down causes an exception, I have to abort that and instead use the PostMessage method to process the key.
lkessler
Posts: 112
Joined: Thu Sep 01, 2005 9:00 pm
Location: Winnipeg, Manitoba, Canada
Contact:

Post by lkessler »

Oh. Maybe I'm getting it now.

So you are saying that in my OnKeyDown event, for VK_UP, VK_DOWN, VK_PRIOR and VK_NEXT, I should do this:

Code: Select all

    PostMessage(Handle, WM_ExtendView, 0, 0);
and then declare

Code: Select all

const
  WM_ExtendView = WM_USER + 10;
type
  MyForm = class(TForm)
    ...
    procedure WMExtendView(var Msg: TMessage); message WM_ExtendView;
and then call my ExtendView procedure from:

Code: Select all

procedure MyForm.WMExtendView(var Msg: TMessage);
begin
  ExtendView;
end;
Then, it should work with the WMExtendView procedure being called after the RIchView Key handling is completed, instead of before.

I'd have to change my logic to allow it to happen after. I'm not sure if that's easy.
Sergey Tkachenko
Site Admin
Posts: 17253
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

Yes, this is a correct solution. You can pass a key code in parameters of PostMessage.
lkessler
Posts: 112
Joined: Thu Sep 01, 2005 9:00 pm
Location: Winnipeg, Manitoba, Canada
Contact:

Post by lkessler »

We've fixed that little sidetrack problem.

But now I need to get back to the original problem: "Move Selection to Original Position"

I am saving my original position as follows:

Code: Select all

 RVE.GetSelectionBounds(StartItemNo, StartItemOffs, EndItemNo, EndItemOffs, true);
  if RVE.RVData.GetItemStyle(StartItemNo) = rvsTable then begin
    TableRVE := RVE.TopLevelEditor;
    TableRVE.GetSelectionBounds(TableStartRow, TableStartCol, TableRowOffs, TableColOffs, false);
  end;
Then I'm doing something in between, possibly inserting or deleting items before the current selection. While doing that, I update StartItemNo and EndItemNo appropriately and then do a Format.

Now I want to restore the position:

Code: Select all

      RVE.SetSelectionBounds(StartItemNo, StartItemOffs, EndItemNo, EndItemOffs);
      if RVE.GetCurrentItemEx(TRVTableItemInfo, TableRVE, TableItem) then
        TableRVE.SetSelectionBounds(TableStartRow, TableStartCol, TableRowOffs, TableColOffs);
This works perfectly when I'm not in a table. But it doesn't work when I'm in a table.

Do you know what I need to do to get this to set the selection back when the selection is in a table?
Sergey Tkachenko
Site Admin
Posts: 17253
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

In general case, if you cannot store selection as a count of characters from the beginning of the document, you will need to store this information:
[item index of table, row and column]*
StartItemNo, StartItemOffs, EndItemNo, EndItemOffs in the top level editor

*- as many times as many nested tables
Sergey Tkachenko
Site Admin
Posts: 17253
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

Code: Select all

type

  TRVSelection = class
    private
      TableItemNoArr, RowArr, ColArr: array of Integer;
      SelStartNo, SelStartOffs, SelEndNo, SelEndOffs: Integer;
    public
      procedure GetFromRichViewEdit(rve: TCustomRichViewEdit);
      procedure SetToRichViewEdit(rve: TCustomRichViewEdit);
  end;

{ TRVSelection }

procedure TRVSelection.GetFromRichViewEdit(rve: TCustomRichViewEdit);
var rve2: TCustomRichViewEdit;
    Count: Integer;
begin
  Count := 0;
  rve2 := rve;
  while rve2.InplaceEditor<>nil do begin
    rve2 := rve2.InplaceEditor as TCustomRichViewEdit;
    inc(Count);
  end;
  SetLength(TableItemNoArr, Count);
  SetLength(RowArr, Count);
  SetLength(ColArr, Count);

  rve2 := rve;
  Count := 0;
  while rve2.InplaceEditor<>nil do begin
    TableItemNoArr[Count] := TRVTableInplaceEdit(rve2.InplaceEditor).FTableItemNo;
    RowArr[Count] := TRVTableInplaceEdit(rve2.InplaceEditor).FRow;
    ColArr[Count] := TRVTableInplaceEdit(rve2.InplaceEditor).FCol;
    inc(Count);
    rve2 := rve2.InplaceEditor as TCustomRichViewEdit;
  end;
  rve2.RVData.GetSelectionBoundsEx(SelStartNo, SelStartOffs, SelEndNo, SelEndOffs, False);
end;

procedure TRVSelection.SetToRichViewEdit(rve: TCustomRichViewEdit);
var i : Integer;
    RVData: TCustomRVData;
begin
   RVData := rve.RVData;
   for i := 0 to High(TableItemNoArr) do
     RVData := (RVData.GetItem(TableItemNoArr[i]) as TRVTableItemInfo).
       Cells[RowArr[i], ColArr[i]].GetRVData;
   RVData := RVData.Edit;
   TCustomRVFormattedData(RVData).SetSelectionBounds(SelStartNo, SelStartOffs, SelEndNo, SelEndOffs);
   TCustomRVFormattedData(RVData).Invalidate;
end;
lkessler
Posts: 112
Joined: Thu Sep 01, 2005 9:00 pm
Location: Winnipeg, Manitoba, Canada
Contact:

Post by lkessler »

Sergey. Thank you exceptionally much for drafting out the code. Looking at it as a class, and what you are doing in there is something I would never have thought up.

I implemented it like this:

Code: Select all

var
  CurrentSelection: TRVSelection;

...
  CurrentSelection.GetFromRichViewEdit(RVE);
...
  (code that adds and/or deletes items before the selection)
  RVE.Format;
...
  CurrentSelection.SetToRichViewEdit(RVE);  


I tried it in my program and immediately came up with two problems.

First, I got an access violation on the line:

Code: Select all

 SetLength(RowArr, Count);
Since I know I do not have tables nested in my tables, I changed:

Code: Select all

      TableItemNoArr, RowArr, ColArr: array of Integer;
to

Code: Select all

      TableItemNoArr, RowArr, ColArr: array [0..1] of Integer;
and I commented out the 3 SetLength statements:

Code: Select all

//  SetLength(TableItemNoArr, Count);
//  SetLength(RowArr, Count);
//  SetLength(ColArr, Count);
Then I ran it again and got a: "List index out of bounds (53719630)" exception on the line:

Code: Select all

     RVData := (RVData.GetItem(TableItemNoArr[i]) as TRVTableItemInfo).
       Cells[RowArr[i], ColArr[i]].GetRVData;
I did check and High(TableItemNoArr) is 1.

I think the problem is that the particular selection I had was not in a table, so the while loop in the GetFromRichViewEdit routine was not executed and the TableItemNoArr was not set.

Could you please help me fix this to handle the "selection not in a table case".

And actually aren't there 3 different possible selections that need to be handled?
1. Outside of tables
2. One or more full cells in a table
3. Within a cell of a table

Thanks.

Louis
Sergey Tkachenko
Site Admin
Posts: 17253
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

It's strange, in my tests this code worked ok for selection outside table.
Of course, if you set a fixed number of elements in the array (2 in your case), this will correspond to two levels of nested tables (because the cycle is to High(array)), so it will fail with another nesting level.

Yes, my code ignores the case of multicell selection.

I'll create a fixed version.
Sergey Tkachenko
Site Admin
Posts: 17253
Joined: Sat Aug 27, 2005 10:28 am
Contact:

Post by Sergey Tkachenko »

This is a modified version.
For any case, it does not use dynamic arrays, max nesting level is 12 (I know you need only 1, but I wanted to create an universal procedure).
Now it supports multicell selections.

Code: Select all

const MAXNESTINGLEVEL = 12;

type

  TRVSelection = class
    private
      Count: Integer;
      TableItemNoArr, RowArr, ColArr: array [0..MAXNESTINGLEVEL-1] of Integer;
      SelStartNo, SelStartOffs, SelEndNo, SelEndOffs: Integer;
      MultiCell: Boolean;
      StartRow, StartCol, RowOffs, ColOffs: Integer;
    public
      procedure GetFromRichViewEdit(rve: TCustomRichViewEdit);
      procedure SetToRichViewEdit(rve: TCustomRichViewEdit);
  end;

{ TRVSelection }

procedure TRVSelection.GetFromRichViewEdit(rve: TCustomRichViewEdit);
var rve2: TCustomRichViewEdit;
begin
  Count := 0;
  rve2 := rve;
  while (rve2.InplaceEditor<>nil) and (Count<MAXNESTINGLEVEL) do begin
    rve2 := rve2.InplaceEditor as TCustomRichViewEdit;
    inc(Count);
  end;
  rve2 := rve;
  Count := 0;
  while (rve2.InplaceEditor<>nil) and (Count<MAXNESTINGLEVEL) do begin
    TableItemNoArr[Count] := TRVTableInplaceEdit(rve2.InplaceEditor).FTableItemNo;
    RowArr[Count] := TRVTableInplaceEdit(rve2.InplaceEditor).FRow;
    ColArr[Count] := TRVTableInplaceEdit(rve2.InplaceEditor).FCol;
    inc(Count);
    rve2 := rve2.InplaceEditor as TCustomRichViewEdit;
  end;
  MultiCell := (rve2.RVData.PartialSelectedItem<>nil) and
    (rve2.RVData.PartialSelectedItem is TRVTableItemInfo);
  if MultiCell then begin
    TRVTableItemInfo(rve2.RVData.PartialSelectedItem).GetSelectionBounds(
      StartRow, StartCol, RowOffs, ColOffs);
    SelStartNo := TRVTableItemInfo(rve2.RVData.PartialSelectedItem).GetMyItemNo;
    end
  else
    rve2.RVData.GetSelectionBoundsEx(SelStartNo, SelStartOffs, SelEndNo, SelEndOffs, False);
end;

procedure TRVSelection.SetToRichViewEdit(rve: TCustomRichViewEdit);
var i : Integer;
    RVData: TCustomRVData;
begin
   RVData := rve.RVData;
   for i := 0 to Count-1 do
     RVData := (RVData.GetItem(TableItemNoArr[i]) as TRVTableItemInfo).
       Cells[RowArr[i], ColArr[i]].GetRVData;
   RVData := RVData.Edit;
   if MultiCell then begin
     TCustomRVFormattedData(RVData).SetSelectionBounds(SelStartNo,1,SelStartNo,1); 
     (TCustomRVFormattedData(RVData).GetItem(SelStartNo) as TRVTableItemInfo).
       Select(StartRow, StartCol, RowOffs, ColOffs)
     end
   else
     TCustomRVFormattedData(RVData).SetSelectionBounds(SelStartNo, SelStartOffs, SelEndNo, SelEndOffs);
   TCustomRVFormattedData(RVData).Invalidate;
end;
Post Reply