Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

In this Discussion

TElXTree: Scrolling one column left or right

As I could not find a way to do this with TElXTree properties and methods, here is the code I use.
It would be better if this were incorporated into the ElPack code.
  ScrollRightOneColumn()
  ScrollLeftOneColumn()
or
  ScrollColumns(const nColumns : Integer); // -ve is scroll left, 

Regards,
Raymond

procedure ScrollColumn(const XTree : TElXTree; const n : Integer);
var
  Indexes         : TArray<Integer>;
  Widths          : TArray<Integer>;
  LeftPositions   : TArray<Integer>;
  LeftColumn      : Integer;

  procedure DetermineWidthsInOrder();
  var
    i : Integer;
  begin
    SetLength(Widths, Length(Indexes));

    with XTree.HeaderSections do
    begin
      for i := 0 to High(Widths) do
      begin
        Widths[i] := Sections[Indexes[i]].Width;
      end;
    end;
  end;

  procedure DetermineLeftPositions(); // NB: Assumes 1 Fixed Column!
  var
    i : Integer;
  begin
    SetLength(LeftPositions, Length(Indexes));

    LeftPositions[1] := 0;

    for i := 2 to High(LeftPositions) do
    begin
      LeftPositions[i] := LeftPositions[i - 1] + Widths[i - 1];
    end;
  end;

  function CalcLeftScrollableColumn() : Integer; // NB: Assumes 1 Fixed Column!
  var
    i : Integer;
  begin
    Result := 1;

    for i := 2 to High(LeftPositions) do
    begin
      with XTree do
      begin
        if    (LeftPosition >= LeftPositions[i] - 10)
          and (LeftPosition <= LeftPositions[i] + Widths[i] - 1 - 10) then
        begin
          Result := i;
          Exit;
        end;
      end;
    end;
  end;

begin
  DetermineVisibleFieldOrder(XTree, Indexes);
  DetermineWidthsInOrder();
  DetermineLeftPositions(); // NB: Assumes 1 Fixed Column!

  LeftColumn := CalcLeftScrollableColumn();

  with XTree do
  begin
    if n < 0 then
    begin
      if LeftColumn > 1 then
      begin
        LeftPosition := LeftPositions[LeftColumn - 1]
      end
      else
      begin
        LeftPosition := LeftPositions[1]
      end;
    end
    else // n > 0
    begin
      if LeftColumn < High(LeftPositions) then
      begin
        LeftPosition := LeftPositions[LeftColumn + 1]
      end;
    end;
  end;
end;

Comments

  • 8 Comments sorted by Votes Date Added
  • Here's the missing procedure.
    If you know an easier way, please tell me!
    Raymond

    procedure DetermineVisibleFieldOrder(const XTree : TElXTree; out Indexes : TArray<Integer>);
    var
      Order    : TArray<Integer>; // The sequence of SectionIndex numbers in the order they appear in the HeaderSection
      i        : Integer;
      j        : Integer;
      n        : Integer;
      nIndexes : Integer;

      procedure DetermineTheOrderFromHeaderSections();
      var
        OrderedSections : TArray<String>;
        i               : Integer;
        SectionData     : TArray<String>;
      begin
        with XTree.HeaderSections do
        begin
          OrderedSections := SectionsOrder.Split([',']);

          SetLength(Order, Length(OrderedSections));

          for i := 0 to High(OrderedSections) do
          begin
            SectionData := OrderedSections[i].Split([':'], 1);

            Order[i] := StrToInt(Copy(SectionData[0], 2, Length(SectionData[0])));
          end;
        end;
      end;

    begin
      DetermineTheOrderFromHeaderSections();

      nIndexes := 0;

      for i := Low(Order) to High(Order) do
      begin
        with XTree.HeaderSections do
        begin
          if Sections[Order[i]].Visible then
          begin
            Inc(nIndexes);
          end;
        end;
      end;

      SetLength(Indexes, nIndexes);

      j := -1;

      for i := Low(Order) to High(Order) do
      begin
        with XTree.HeaderSections do
        begin
          if Sections[Order[i]].Visible then
          begin
            Inc(j);

            n := Order[i];

            Indexes[j] := n;
          end;
        end;
      end;
    end;

  • Methods function GetLeftVisibleColumn() and procedure SetLeftVisibleColumn(const Column : Integer) would also be useful.

    Raymond.
  • 1.) ScrollRightOneColumn() / ScrollLeftOneColumn()
    2.) GetLeftVisibleColumn() / procedure SetLeftVisibleColumn(const Column : Integer)

    1. is rather specific, but setting the left first visible column (like TopIndex) might be a sensible addition. I added suggestion to feature requests.
  • These should depend on how many columns are fixed.

    E.g. SetFirstVisibleColumn(0) should either have no effect if the number of fixed columns is > 0, or set the first visible column to the right of the fixed columns to the first visible column that is not a fixed column.

    I suggest the former be implemented.

    Raymond
  • I have improved the code so that the selected column continues to move right even when the right-most column is visible. This is better than just staying in the same position.

    The same occurs when moving left.

    I use Ctrl+Right and Ctrl+Left on the main form to initiate these actions.

    procedure ScrollColumn(const XTree : TElXTree; const n : Integer);
    var
      Indexes         : TArray<Integer>;
      Widths          : TArray<Integer>;
      LeftPositions   : TArray<Integer>;
      ColumnPositions : TArray<Integer>;
      LeftColumn      : Integer;
      OldLeftPosition : Integer;

      ...

    begin
      DetermineVisibleFieldOrder(XTree, Indexes, ColumnPositions);
      DetermineWidthsInOrder();
      DetermineLeftPositions(); // NB: Assumes 1 Fixed Column!

      LeftColumn := CalcLeftScrollableColumn();

      with XTree do
      begin
        if n < 0 then
        begin
          if LeftColumn > 1 then
          begin
            if ColumnPositions[HitColumn] > LeftColumn then
            begin
              LeftColumn   := ColumnPositions[HitColumn] - 1;

              LeftPosition := LeftPositions[LeftColumn];

              SelectColumn := Indexes[LeftColumn];
            end
            else // ColumnPositions[SelectColumn] <= LeftColumn
            begin
              LeftColumn   := LeftColumn - 1;

              LeftPosition := LeftPositions[LeftColumn];

              SelectColumn := Indexes[LeftColumn];
            end;
          end
          else
          begin
            LeftColumn   := 1;

            LeftPosition := LeftPositions[LeftColumn];

            SelectColumn := Indexes[LeftColumn];
          end;
        end
        else // n > 0
        begin
          LeftColumn := System.Math.Min(LeftColumn + 1, High(LeftPositions));

          OldLeftPosition := LeftPosition;

          LeftPosition := LeftPositions[LeftColumn];

          if LeftPosition = OldLeftPosition then
          begin
            try
              if ColumnPositions[SelectColumn] < High(Indexes) then // Can select right one column, otherwise cannot
              begin
                SelectColumn := Indexes[ColumnPositions[SelectColumn] + 1];
              end;
            except
            end;
          end
          else // LeftPosition <> OldLeftPosition
          begin
            SelectColumn := Indexes[LeftColumn];
          end;
        end;
      end;
    end;

    Also, the procedure DetermineVisibleFieldOrder(XTree, Indexes, ColumnPositions); has had another field added that provides the Column Position for each Header Section. This is used in the new version of the above procedure.

    procedure DetermineVisibleFieldOrder(const XTree : TElXTree; out Indexes : TArray<Integer>; out ColumnPositions : TArray<Integer>);
    var
      Order    : TArray<Integer>; // The sequence of SectionIndex numbers in the order they appear in the HeaderSection
      i        : Integer;
      j        : Integer;
      n        : Integer;
      nIndexes : Integer;

      ...

    begin
      DetermineTheOrderFromHeaderSections();

      nIndexes := 0;

      for i := Low(Order) to High(Order) do
      begin
        with XTree.HeaderSections do
        begin
          if Sections[Order[i]].Visible then
          begin
            Inc(nIndexes);
          end;
        end;
      end;

      SetLength(ColumnPositions, XTree.HeaderSections.Count);

      for i := 0 to High(ColumnPositions) do
      begin
        ColumnPositions[i] := -1;
      end;

      SetLength(Indexes, nIndexes);

      j := -1;

      for i := Low(Order) to High(Order) do
      begin
        with XTree.HeaderSections do
        begin
          if Sections[Order[i]].Visible then
          begin
            Inc(j);

            n := Order[i];

            Indexes[j] := n;

            ColumnPositions[i] := j;
          end;
        end;
      end;
    end;

    Raymond
  • There are several bugs in the above code.

    The following code has now been tested more-thoroughly and is believed to work correctly now.
    It also works for no fixed columns or more than one fixed column, in addition to one fixed column.

    I have emailed the complete demo application to LMD support to assist with requirements for end-users and a demonstration of bugs.

    Regards,
    Raymond

    unit Unit1;

    (*
      Scrolling and Selecting Columns Left/Right in a TElXTree.

      1. Illustration of bugs.

      2. Code to do the scrolling as an end-user would wish.

      All code is Copyright © Dr. Raymond Kennington 2013.

      No warranties are made as to its effectiveness or accuracy and you use it at your own risk.

      This code may be used for free in software by anyone who has a license for LMD Innovative's ElPack,
      as long as recognition is given to the copyright holder.

      This code may not be sold nor placed on any website nor advertised anywhere nor made publicly available
      in any form without the prior express permission of the ocpyright holder.

      This code is placed on the LMD Innovative website for the benefit of its ElPack users.
    *)

    interface

    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Math, ElXPThemedControl, ElTreeInplaceEditors,
      ElXTree, Vcl.ComCtrls, System.Actions, Vcl.ActnList, Vcl.StdCtrls,
      Vcl.ExtCtrls;

    type
      TForm1 = class(TForm)
        XTree: TElXTree;
        SB: TStatusBar;
        ActionList1: TActionList;
        TreeWidthAction: TAction;
        Panel1: TPanel;
        Button1: TButton;
        RadioGroup1: TRadioGroup;
        Memo1: TMemo;
        procedure FormCreate(Sender: TObject);
        procedure XTreeKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
        procedure TreeWidthActionExecute(Sender: TObject);
        procedure Button1Click(Sender: TObject);
        procedure RadioGroup1Click(Sender: TObject);
      private
        { Private declarations }
      public
        { Public declarations }
      end;

    var
      Form1: TForm1;

    implementation

    {$R *.dfm}

    procedure DetermineVisibleFieldOrder(const XTree : TElXTree; out Indexes : TArray<Integer>; out ColumnPositions : TArray<Integer>);
    var
      Order    : TArray<Integer>; // The sequence of SectionIndex numbers in the order they appear in the HeaderSection
      i        : Integer;
      j        : Integer;
      n        : Integer;
      nIndexes : Integer;

      procedure DetermineTheOrderFromHeaderSections();
      var
        OrderedSections : TArray<String>;
        i               : Integer;
        SectionData     : TArray<String>;
      begin
        with XTree.HeaderSections do
        begin
          OrderedSections := SectionsOrder.Split([',']);

          SetLength(Order, Length(OrderedSections));

          for i := 0 to High(OrderedSections) do
          begin
            SectionData := OrderedSections[i].Split([':'], 1);

            Order[i] := StrToInt(Copy(SectionData[0], 2, Length(SectionData[0])));
          end;
        end;
      end;

    begin
      DetermineTheOrderFromHeaderSections();

      nIndexes := 0;

      for i := 0 to High(Order) do
      begin
        with XTree.HeaderSections do
        begin
          if Sections[Order[i]].Visible then
          begin
            Inc(nIndexes);
          end;
        end;
      end;

      SetLength(ColumnPositions, XTree.HeaderSections.Count);

      for i := 0 to High(ColumnPositions) do
      begin
        ColumnPositions[i] := -1;
      end;

      SetLength(Indexes, nIndexes);

      j := -1;

      for i := 0 to High(Order) do
      begin
        with XTree.HeaderSections do
        begin
          if Sections[Order[i]].Visible then
          begin
            Inc(j);

            n := Order[i];

            Indexes[j] := n;

            ColumnPositions[n] := j;
          end;
        end;
      end;
    end;

    procedure ScrollColumn(const XTree : TElXTree; const n : Integer; UpdateLeftPosition : Boolean);
    var
      Indexes           : TArray<Integer>;
      Widths            : TArray<Integer>;
      LeftPositions     : TArray<Integer>;
      RightPositions    : TArray<Integer>;
      ColumnPositions   : TArray<Integer>;
      nFixed            : Integer;         // Number of fixed columns
      HitIndex          : Integer;         // This is used to make the code clearer
                                           // It replaces TElXTree's property HitColumn
      LeftColumn        : Integer;
      OldLeftPosition   : Integer;
      i                 : Integer;
      ScrollWindowWidth : Integer; // Useable width; i.e. remove scrollbar width if visible

      procedure DetermineWidthsInOrder();
      var
        i : Integer;
      begin
        SetLength(Widths, Length(Indexes));

        with XTree.HeaderSections do
        begin
          for i := 0 to High(Widths) do
          begin
            Widths[i] := Sections[Indexes[i]].Width;
          end;
        end;
      end;

      procedure DeterminePositions(); // NB: Assumes nFixed Fixed Column!
      var
        i : Integer;
      begin
        SetLength(LeftPositions,  Length(Indexes));
        SetLength(RightPositions, Length(Indexes));

        LeftPositions [nFixed] := 0;         // Start from the first scrollable column
        RightPositions[nFixed] := Widths[nFixed];

        for i := Succ(nFixed) to High(LeftPositions) do
        begin
          LeftPositions [i] := LeftPositions [i - 1] + Widths[i - 1];
          RightPositions[i] := RightPositions[i - 1] + Widths[i];
        end;
      end;

      function CalcLeftScrollableColumn() : Integer; // NB: Assumes nFixed Fixed Column!
      var
        i : Integer;
      begin
        Result := 0;

        for i := nFixed to High(LeftPositions) do
        begin
          with XTree do
          begin
            if    (LeftPosition >= LeftPositions[i] - 10)
              and (LeftPosition <= LeftPositions[i] + Widths[i] - 1 - 10) then
            begin
              Result := i;
              Exit;
            end;
          end;
        end;
      end;

      procedure CalcUseableTreeWidth();
      var
        i : Integer;
      begin
        with XTree do
        begin
          ScrollWindowWidth := Width - GutterWidth;

          if VertScrollBarVisible then
          begin
            ScrollWindowWidth := ScrollWindowWidth - VScrollBar.Width;
          end;

          for i := 0 to Pred(nFixed) do
          begin
            ScrollWindowWidth := ScrollWindowWidth - Widths[i]; // Subtract the Fixed Column widths
          end;

          // if not all the fixed columns are visible then I don't know what would happen

          ScrollWindowWidth := Max(ScrollWindowWidth, 0);
        end;
      end;

      procedure MakeColumnVisible(const Column : Integer);
      var
        OldLeftPosition : Integer;
      begin
        with XTree do
        begin
          if    (LeftPosition                          < LeftPositions [Column])
            and (RightPositions[Column] - LeftPosition > ScrollWindowWidth     ) then
          begin
            LeftPosition := System.Math.Min(RightPositions[Column] - ScrollWindowWidth,
                                            LeftPositions[Column])
          end;

          if LeftPosition > LeftPositions[Column] then
          begin
            LeftPosition := 0;
            LeftPosition := LeftPositions[Column];
          end;
        end;
      end;

    begin
      nFixed := XTree.FixedColNum;

      DetermineVisibleFieldOrder(XTree, Indexes, ColumnPositions);
      DetermineWidthsInOrder();
      DeterminePositions(); // NB: Assumes 1 Fixed Column!
      CalcUseableTreeWidth();

      LeftColumn := CalcLeftScrollableColumn();

      for i := 0 to Pred(Form1.SB.Panels.Count) do
      begin
        Form1.SB.Panels[i].Text := '';
      end;

      Form1.SB.Panels[1].Text := 'Initial LeftColumn: ' + IntToStr(LeftColumn);

      with XTree do
      begin
        HitIndex := HitColumn; // Columns are Visible Columns, not HeaderSection Index values
                               // The code is easier to follow using HitIndex

        Form1.SB.Panels[0].Text := 'HitIndex: ' + IntToStr(HitIndex);

        if n < 0 then
        begin
          if ColumnPositions[HitIndex] > 1 then // Activate a non-fixed column
          begin
            if ColumnPositions[HitIndex] > LeftColumn then
            begin
              LeftColumn := ColumnPositions[HitIndex] - 1;
            end
            else // ColumnPositions[SelectColumn] <= LeftColumn
            begin
              LeftColumn := LeftColumn - 1;
            end;

            SelectColumn := Indexes[LeftColumn];

            if UpdateLeftPosition then
            begin
              LeftPosition := LeftPositions[LeftColumn];
            end
            else
            begin
              MakeColumnVisible(ColumnPositions[SelectColumn]);
            end;
          end
          else // ColumnPositions[HitIndex] <= 1: Activate a fixed column
          begin
            LeftColumn := Max(ColumnPositions[HitIndex] - 1, 0);

            SelectColumn := Indexes[LeftColumn];

            MakeColumnVisible(ColumnPositions[SelectColumn]);
          end;

          Form1.SB.Panels[2].Text := 'Final LeftColumn: ' + IntToStr(LeftColumn);
        end
        else // n > 0
        begin
          if ColumnPositions[HitIndex] >= nFixed then
          begin
            LeftColumn := System.Math.Min(LeftColumn + 1, High(LeftPositions));

            OldLeftPosition := LeftPosition;

            if UpdateLeftPosition then
            begin
              LeftPosition := LeftPositions[LeftColumn];
            end;

            if LeftPosition = OldLeftPosition then
            begin
              if ColumnPositions[SelectColumn] < High(Indexes) then // Can select right one column, otherwise cannot
              begin
                SelectColumn := Indexes[ColumnPositions[HitIndex] + 1];
              end;
            end
            else // LeftPosition <> OldLeftPosition
            begin
              SelectColumn := Indexes[LeftColumn];
            end;

            MakeColumnVisible(ColumnPositions[SelectColumn]);

            Form1.SB.Panels[2].Text := 'Final LeftColumn: ' + IntToStr(LeftColumn);
          end
          else // ColumnPositions[HitIndex] < 1: so don't scroll the non-fixed columns
          begin
            SelectColumn := Indexes[ColumnPositions[HitIndex] + 1];

            LeftPosition := 0;

    //        MakeColVisible(SelectColumn);

            Form1.SB.Panels[2].Text := 'Final LeftColumn: ' + IntToStr(LeftColumn);
          end;
        end;

        Form1.SB.Panels[3].Text := 'SelectColumn: ' + IntToStr(SelectColumn);
      end;
    end;

    procedure TForm1.Button1Click(Sender: TObject);
    begin
      // The problem this was to illustrate doesn't happen now

      Form1.Width := 263;

      ShowMessage(  'Use the Right-Arrow key to scroll to the right column.' + #13#10#13#10

                  + 'Then use the Down-Arrow key to move down the last column.' + #13#10#13#10

                  + 'Observe how the left position of the last column oscillates between 2 values.');
    end;

    procedure TForm1.FormCreate(Sender: TObject);
    var
      i : Integer;
    begin
      with XTree.Items do
      begin
        for i := 1 to 20 do
        begin
          with Add(Root, 'Item ' + Format('%02d', [i])) do
          begin
            Cells[ 1].Text := FormatDateTime('t', Time());
            Cells[ 2].Text := FormatDateTime('t', Time());
            Cells[ 3].Text := FormatDateTime('t', Time());
            Cells[ 4].Text := 'Some text';
            Cells[ 5].Text := 'More text';
            Cells[ 6].Text := IntToStr(i * (Random(20) + 1));
            Cells[ 7].Text := Format('%0.00m', [6.35 * (Random(50) + 1)]);
            Cells[ 8].Text := FormatDateTime('c', Now());
            Cells[ 9].Text := 'Txt';
            Cells[10].Text := IntToStr(200 + Random(i));
          end;
        end;
      end;

      XTree.ItemFocused := XTree.Items[0];
    end;

    procedure TForm1.RadioGroup1Click(Sender: TObject);
    begin
      inherited;

      XTree.FixedColNum := RadioGroup1.ItemIndex;

      XTree.SetFocus();
    end;

    procedure TForm1.TreeWidthActionExecute(Sender: TObject);
    begin
      ShowMessage('Tree Width: ' + IntToStr(XTree.Width));
    end;

    procedure TForm1.XTreeKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    begin
      if Shift = [ssCtrl] then
      begin
        if Key = VK_Left then
        begin
          Key := 0;

          ScrollColumn(XTree, -1, True);
        end
        else if Key = VK_Right then
        begin
          Key := 0;

          ScrollColumn(XTree, +1, True);
        end;
      end
      else if Shift = [] then
      begin
        if Key = VK_Left then
        begin
          Key := 0;

          ScrollColumn(XTree, -1, False);
        end
        else if Key = VK_Right then
        begin
          Key := 0;

          ScrollColumn(XTree, +1, False);
        end;
      end;
    end;

    end.

  • Oops.
    Change
          if ColumnPositions[HitIndex] > 1 then // Activate a non-fixed column
    to
          if ColumnPositions[HitIndex] > nFixed then // Activate a non-fixed column

    Raymond
Sign In or Register to comment.