2010. május 28., péntek

How to synchronize the scrolling of three TScrollBoxes when only one shows a scrollbar


Problem/Question/Abstract:

I need to synchronize three scrollboxes, only one of which will show the scrollbars. The documentation for TControlScrollBar reads: "If Visible is set to False, the scroll bar is never visible. This is useful, for example, for programmatically controlling the scroll position of a form without allowing the user to control the scroll position." I have been unable to make the scrollbox scroll when the scrollbar is visible. In fact, the moment you set the scrollbar to invisible, the position jumps back to 0.

Answer:

I looked at the VCL source for TScrollbox and TControlScrollbar and found the source of the problem: the TControlscrollbar class has an internal field named FCalcRange. If you try to set the scrollbar position the passed position is clipped to the range 0..FCalcRange. The only problem is that FCalcRange is set to 0 when the scrollbar is set to invisible, so Position will always be set to 0, regardless of what you try to set it to. I see no way around that, so you need to use a different strategy: instead of using the invisible scrollbars for the two slave scrollboxes scroll them directly, using ScrollBy.

The following example uses three scrollboxes of same size and scroll ranges. AutoScroll and Autosize are false for all, the first two have invisible scrollbars, the last has visible scrollbars and controls the other two. Each scrollbox has an edit in it so there is something visible to scroll around.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    ScrollBox1: TScrollBox;
    Edit1: TEdit;
    ScrollBox2: TScrollBox;
    Edit2: TEdit;
    ScrollBox3: TScrollBox;
    Edit3: TEdit;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    FOldProc: TWndMethod;
    procedure NewProc(var msg: TMessage);

  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FoldProc := Scrollbox3.WindowProc;
  Scrollbox3.WindowProc := NewProc;
end;

procedure TForm1.NewProc(var msg: TMessage);
var
  oldpos, newpos: Integer;
begin
  case msg.Msg of
    WM_VSCROLL:
      begin
        oldpos := scrollbox3.VertScrollBar.Position;
        FoldProc(msg);
        newpos := scrollbox3.VertScrollBar.Position;
        if oldpos <> newpos then
        begin
          scrollbox1.ScrollBy(0, oldpos - newpos);
          scrollbox2.ScrollBy(0, oldpos - newpos);
        end;
      end;
    WM_HSCROLL:
      begin
        oldpos := scrollbox3.HorzScrollBar.Position;
        FoldProc(msg);
        newpos := scrollbox3.HorzScrollBar.Position;
        if oldpos <> newpos then
        begin
          scrollbox1.ScrollBy(oldpos - newpos, 0);
          scrollbox2.ScrollBy(oldpos - newpos, 0);
        end;
      end
  else
    FoldProc(msg);
  end;
end;

end.

Nincsenek megjegyzések:

Megjegyzés küldése