delphifocusdelphi-xe7tscrollbox

How to keep controls in a Scroll Box from jumping into focus?


I have a TScrollBox which scrolls vertically, and the horizontal scroll bar is disabled. Inside this scroll box, there's a single large panel aligned to the top, and its height is dynamically calculated based on its contents. Its contents are two panels, one aligned to the left and the other aligned client, with a splitter in-between. Inside each of these two panels, are a series of user-chosen panels aligned to the top, which may dynamically resize also, thus extending/reducing the height of the main panel within the scroll box.

procedure TfrmDashboard.ResizePanels;
var
  X: Integer;
  H1, H2: Integer;
  H: Integer;
begin
  H1:= 0;
  H2:= 0;
  //Calculate height of left panel
  for X := 0 to p1.ControlCount-1 do
    H1:= H1 + p1.Controls[X].Height;
  //Calculate height of right panel
  for X := 0 to p2.ControlCount-1 do
    H2:= H2 + p2.Controls[X].Height;
  //Check which panel is larger
  H:= H1;
  if H2 > H then
    H:= H2;
  //Adjust scrolling height
  pMain.Height:= H + 10;
  SB.VertScrollBar.Range:= pMain.Height;
  SB.VertScrollBar.Size:= pMain.Height;
end;

At the same time, most of these smaller sub-panels of content also have controls which receive focus, and need to allow getting focus. Everything works fine with scrolling currently.

The problem arises when the user clicks on one of such focusable controls within the scroll box. If that control happened to be partially hidden (The top of the control extended above the top of the scrolled position), the whole scroll box jumps up to position this control at the top of the scroll box.

This seems like a "feature", but a feature I would like to disable. It's quite annoying in this situation. How do I prevent the scroll box from jumping positions when one of its children receives focus?


Solution

  • This is the default behaviour of the TScrollBox, and there is no property to turn off this feature.
    But you can change this behaviour by subclassing TScrollBox and use this new class instead:

    TModifiedScrollBox=class(TScrollBox)
    protected
        procedure AutoScrollInView(AControl:TControl); override;
    end;
    
    procedure TModifiedScrollBox.AutoScrollInView(AControl:TControl);
    begin
      // empty body
    end;
    

    The drawback of this code is AutoScrollInView stop working if you using it in your own code. But if you still need this method you can create additional method to "save" it:

    TModifiedScrollBox=class(TScrollBox)
    protected
        procedure AutoScrollInView(AControl:TControl); override;
        procedure AutoScrollInViewSave(AControl:TControl);
    end;
    
    procedure TModifiedScrollBox.AutoScrollInViewSave(AControl:TControl);
    begin
      // forward to base implementation
      inherited AutoScrollInView(AControl);
    end;
    

    And then in your code you should replace all AutoScrollInView calls to AutoScrollInViewSave for this new class.

    PS Under the hood, every time new control is focused, the TCustomForm calls AutoScrollInView for all focused control's parents.