Page 1 of 1

Moving cursor into and out of inserted components

Posted: Tue Aug 08, 2006 8:30 am
by Kern
Hi all

How can i move the caret (focus) with the arrow keys into an inserted edit field?
And how can i "leave" the field pressing the rigth/left arrow key at the end/beginning of the text?


Thanks

Posted: Tue Aug 08, 2006 10:31 am
by Sergey Tkachenko
Use Control.SetFocus to focus Control.

As for moving focus out of the control when pressing Left and Right keys:

1) This function returns the control position in TRichView

Code: Select all

function FindControlLocation(RVData: TCustomRVData; ctrl: TControl;
                              var CtrlRVData: TCustomRVData;
                              var CtrlItemNo: Integer): Boolean;
var i,r,c: Integer;
    table: TRVTableItemInfo;
begin
  Result := False;
  for i := 0 to RVData.Items.Count-1 do
    case RVData.GetItemStyle(i) of
      rvsComponent:
        begin
          Result := TRVControlItemInfo(RVData.GetItem(i)).Control=ctrl;
          if Result then begin
            CtrlRVData := RVData.GetSourceRVData;
            CtrlItemNo := i;
            exit;
          end;
        end;
      rvsTable:
        begin
          table := TRVTableItemInfo(RVData.GetItem(i));
          for r := 0 to table.Rows.Count-1 do
            for c := 0 to table.Rows[r].Count-1 do
              if table.Cells[r,c]<>nil then begin
                Result := FindControlLocation(table.Cells[r,c].GetRVData, ctrl, CtrlRVData, CtrlItemNo);
                if Result then
                  exit;
              end;
        end;
    end;
end;
2) This is an event code for inserted TEdit:

Code: Select all

procedure TForm1.DoEditKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var RVData: TCustomRVData;
    ItemNo, Offs: Integer;
    Edit: TEdit;
begin
  Edit := Sender as TEdit;
  case Key of
    VK_LEFT:
      begin
        if Edit.SelStart<>0 then
          exit;
        Offs := 0;
      end;
    VK_RIGHT:
      begin
        if Edit.SelStart+Edit.SelLength<>Length(Edit.Text) then
          exit;
        Offs := 1;
      end;
    else
      exit;
  end;
  if not FindControlLocation(RichViewEdit1.RVData, Sender as TControl,
    RVData, ItemNo) then exit;
  RVData := RVData.Edit;
  TCustomRVFormattedData(RVData).SetSelectionBounds(ItemNo, Offs, ItemNo, Offs);
  TCustomRVFormattedData(RVData).Invalidate;
  TCustomRVFormattedData(RVData).GetParentControl.SetFocus;
end;
3) When creating TEdit, assign this event to OnKeyDown:

Code: Select all

edt.OnKeyDown := DoEditKeyDown;
4) Events of controls are not stored in RVF files. You need to reassign them on loading, using OnControlAction event:

Code: Select all

procedure TForm1.RichViewEdit1ControlAction(Sender: TCustomRichView;
  ControlAction: TRVControlAction; ItemNo: Integer; var ctrl: TControl);
begin
  if ControlAction=rvcaAfterRVFLoad then begin
    if ctrl is TEdit then
      TEdit(ctrl).OnKeyDown := DoEditKeyDown;
  end;
end;

Posted: Tue Aug 08, 2006 3:31 pm
by Kern
Hi Sergey

Thanks for your answer.
The second part (returns the control position in TRichView) works fine.

But
Sergey Tkachenko wrote:Use Control.SetFocus to focus Control.
at the moment i have no idea when or what event i have to use to set the focus to the control. How do i know the cursor is before or behind a control?


Thanks again

Posted: Tue Aug 08, 2006 4:17 pm
by Sergey Tkachenko
RichViewEdit1.OnKeyDown:

Code: Select all

procedure TForm1.RichViewEdit1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var rve: TCustomRichViewEdit;
    ItemNo, Offs: Integer;
    Name: String;
    VAlign: TRVVAlign;
    Tag: Integer;
    Control: TControl;
begin
  rve := RichViewEdit1.TopLevelEditor;
  ItemNo := rve.CurItemNo;
  Offs := rve.OffsetInCurItem;
  case Key of
    VK_RIGHT:
      begin
        if (Offs<=rve.GetOffsBeforeItem(ItemNo)) and
           (rve.GetItemStyle(ItemNo)=rvsComponent) then
          ItemNo := ItemNo
        else if (Offs>=rve.GetOffsAfterItem(ItemNo)) and
          (ItemNo+1<rve.ItemCount) and
          (rve.GetItemStyle(ItemNo+1)=rvsComponent) then
          inc(ItemNo)
        else
          exit;
        rve.GetControlInfo(ItemNo, Name, Control, VAlign, Tag);
        if not (Control is TEdit) then
          exit;
        TEdit(Control).SetFocus;
        TEdit(Control).SelLength := 0;        
        TEdit(Control).SelStart := 0;
        Key := 0;
      end;
    VK_LEFT:
      begin
        if (Offs>=rve.GetOffsAfterItem(ItemNo)) and
           (rve.GetItemStyle(ItemNo)=rvsComponent) then
          ItemNo := ItemNo
        else if (Offs<=rve.GetOffsBeforeItem(ItemNo)) and
          (ItemNo-1>=0) and
          (rve.GetItemStyle(ItemNo-1)=rvsComponent) then
          dec(ItemNo)
        else
          exit;
        rve.GetControlInfo(ItemNo, Name, Control, VAlign, Tag);
        if not (Control is TEdit) then
          exit;
        TEdit(Control).SetFocus;
        TEdit(Control).SelLength := 0;        
        TEdit(Control).SelStart := Length(TEdit(Control).Text);
        Key := 0;
      end;
  end;
end;

Posted: Thu Aug 10, 2006 8:12 pm
by Kern
Hi Sergey

Once again thanks for your excelent answer.

With an edit field, your code works but i have also TCheckBoxes and TRadioGroups in the RichView.
When i call TheCheckbox.SetFocus, RichView draws the "RichView selection border" arround the embedded TCheckBox and the focus rectangle. Then, if i press the arrow keys, the cursor jumps to the prev/next embedded component instead to the prec/following text. I also never get the OnKeyDown message. Either the RichViev.KeyDown nor the TCheckBox.OnKeyDown.
The focussing of the TRadioGroup draws also the border. But there is no focus-rectangle arround the ItemIndex-item and it is not possible to select another RadioButton by keyboard. The impossibility to select the text arround the TRadioGroup is the same as the TCheckBox.


Any idea?

Posted: Tue Aug 15, 2006 11:29 am
by Sergey Tkachenko
Please tell me how can I reproduce the problems with visualisation of controls.
TRichViewEdit never draws a focusing rectangle.
TRichView can draw it around hypelinks (text or images), but never draws it around controls.
Drawing of controls themselves should not be modifed. I just created a demo with TRadioGroup: http://www.trichview.com/forums/viewtopic.php?t=1131
As you can see, the selection rectangle is shown properly.

Posted: Tue Aug 15, 2006 11:59 am
by Sergey Tkachenko
As for the checkboxes.
There is one problem: TCheckBox does not have OnKeyDown event. So you cannot use it. But you can create your own component inherited from TCheckBox:

Code: Select all

type
  TMyCheckBox = class (TCheckBox)
    private
      procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
    published
      property OnKeyDown;
  end;

procedure TMyCheckBox.WMGetDlgCode(var Message: TWMGetDlgCode);
begin
  Message.Result := DLGC_WANTARROWS;
end;
Do not forget to register this class: RegisterClass(TMyCheckBox).

Unlike TEdit, checkbox does not have caret position, so its OnKeyDown is more simple:

Code: Select all

procedure TForm1.DoControlKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var RVData: TCustomRVData;
    ItemNo, Offs: Integer;
begin
  case Key of
    VK_LEFT:
      Offs := 0;
    VK_RIGHT:
      Offs := 1;
    else
      exit;
  end;
  if not FindControlLocation(RichViewEdit1.RVData, Sender as TControl,
    RVData, ItemNo) then exit;
  RVData := RVData.Edit;
  TCustomRVFormattedData(RVData).SetSelectionBounds(ItemNo, Offs, ItemNo, Offs);
  TCustomRVFormattedData(RVData).Invalidate;
  TCustomRVFormattedData(RVData).GetParentControl.SetFocus;
end;
TRichViewEdit's OnKeyDown should be modified to work with all types of controls:

Code: Select all

procedure TForm1.RichViewEdit1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var rve: TCustomRichViewEdit;
    ItemNo, Offs: Integer;
    Name: String;
    VAlign: TRVVAlign;
    Tag: Integer;
    Control: TControl;
begin
  rve := RichViewEdit1.TopLevelEditor;
  ItemNo := rve.CurItemNo;
  Offs := rve.OffsetInCurItem;
  case Key of
    VK_RIGHT:
      begin
        if (Offs<=rve.GetOffsBeforeItem(ItemNo)) and
           (rve.GetItemStyle(ItemNo)=rvsComponent) then
          ItemNo := ItemNo
        else if (Offs>=rve.GetOffsAfterItem(ItemNo)) and
          (ItemNo+1<rve.ItemCount) and
          (rve.GetItemStyle(ItemNo+1)=rvsComponent) then
          inc(ItemNo)
        else
          exit;
        rve.GetControlInfo(ItemNo, Name, Control, VAlign, Tag);
        if Control is TWinControl then
          TWinControl(Control).SetFocus;
        if Control is TEdit then begin
          TEdit(Control).SelLength := 0;
          TEdit(Control).SelStart := 0;
        end;
        Key := 0;
      end;
    VK_LEFT:
      begin
        if (Offs>=rve.GetOffsAfterItem(ItemNo)) and
           (rve.GetItemStyle(ItemNo)=rvsComponent) then
          ItemNo := ItemNo
        else if (Offs<=rve.GetOffsBeforeItem(ItemNo)) and
          (ItemNo-1>=0) and
          (rve.GetItemStyle(ItemNo-1)=rvsComponent) then
          dec(ItemNo)
        else
          exit;
        rve.GetControlInfo(ItemNo, Name, Control, VAlign, Tag);
        if Control is TWinControl then
          TWinControl(Control).SetFocus;
        if Control is TEdit then begin
          TEdit(Control).SelLength := 0;
          TEdit(Control).SelStart := Length(TEdit(Control).Text);
        end;
        Key := 0;
      end;
  end;
end;

Posted: Tue Aug 15, 2006 12:01 pm
by Sergey Tkachenko
As for TRadioGroup, I did not test it, but probably you should do it like with TCheckBox.

Posted: Tue Aug 15, 2006 4:52 pm
by Kern
Sergey Tkachenko wrote:As for TRadioGroup, I did not test it, but probably you should do it like with TCheckBox.
Hi Sergey

Your code works great for TCheckBox.
To navigate into and out of a TRadioGroup is with DLGC_WANTARROWS partially possible. Partially, because the whole TRadioGroup receives/looses the focus instead of one of the Items (ItemIndex). So it's not possible to change the ItemIndex by keypress.

Does anybody knows, how to set the focus INTO the RadioGroup?


Thanks
Patrik