Reputation: 1824
I use a TMemo
in TScrollBox
to show some text, and a TLabel
on top as a header info. Sometimes memo is wider than scroll box and of course Horizontal scroll bar
can be used to scroll left and right to see text in memo.
I want to have a label as a header always centered to scroll box visible area. I can do this by setting Label1.Left:= (Scrollbox1.Width div 2) - (Label1.Width div 2);
and it works but it kind of flickers, shakes when scrolling back and forth. Memo moves smoothly, label doesn't.
Here is unit:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TScrollBox=Class(VCL.Forms.TScrollBox)
procedure WMHScroll(var Message: TWMHScroll); message WM_HSCROLL;
private
FOnScrollHorz: TNotifyEvent;
public
Property OnScrollHorz:TNotifyEvent read FOnScrollHorz Write FonScrollHorz;
End;
TForm1 = class(TForm)
ScrollBox1: TScrollBox;
Label1: TLabel;
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure ScrollBox1Resize(Sender: TObject);
private
procedure MyScrollHorz(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TScrollBox.WMHScroll(var Message: TWMHScroll);
begin
inherited;
if Assigned(FOnScrollHorz) then FOnScrollHorz(Self);
end;
procedure TForm1.MyScrollHorz(Sender: TObject);
begin
Label1.Left:= (Scrollbox1.Width div 2) - (Label1.Width div 2);
end;
procedure TForm1.ScrollBox1Resize(Sender: TObject);
begin
Label1.Left:= (Scrollbox1.Width div 2) - (Label1.Width div 2);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ScrollBox1.OnScrollHorz := MyScrollHorz;
end;
end.
and dfm:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 212
ClientWidth = 458
Color = clBtnFace
DoubleBuffered = True
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object ScrollBox1: TScrollBox
Left = 0
Top = 0
Width = 458
Height = 212
HorzScrollBar.Smooth = True
HorzScrollBar.Tracking = True
Align = alClient
BiDiMode = bdLeftToRight
DoubleBuffered = True
ParentBiDiMode = False
ParentDoubleBuffered = False
TabOrder = 0
OnResize = ScrollBox1Resize
ExplicitHeight = 337
object Label1: TLabel
Left = 192
Top = 30
Width = 69
Height = 13
BiDiMode = bdLeftToRight
Caption = 'Details header'
ParentBiDiMode = False
end
object Memo1: TMemo
Left = 24
Top = 70
Width = 700
Height = 89
Lines.Strings = (
'Details...')
TabOrder = 0
end
end
end
I tried using DoubleBuffered
but doesn't help.
Any suggestions how to make Label1 move without flickering/shaking, as smooth as Memo1 does when scrolling?
EDIT:
The design will eventually be that I have 3 or scrollboxes on form and each one contain up to 3 memos with header. And scrolling needs to be by scrollbox as all memos in same scroll box need to be scrolled at the same time. That means I do not see how it would work with putting label on form or panel and then on form, outside scrollboxes:
EDIT 2:
The answers below do provide good solutions, but they do make necessary to place the Labels
, that are centered, out of the Scrollbox
and put on the Form
itself. And then move either by Scrollbox's
scroll bars
or by scroll bars
directly on Form
. This does get desired affect, but it adds a little inconvenience with Labels
not being part of Scrollbox
, anymore.
Upvotes: 0
Views: 2089
Reputation: 8331
Why not use two scroll boxes.
You use one for vertical scrolling. On it you place your label and your second scroll box with memo on it.
This second scroll box will then be used for horizontal scrolling when needed.
Or perhaps even a better solution would be to replace TMemo with some other control like TRichEdit which has its own scrollbars implemented. So you have only one scroll box like now and TRichEdit Will take care of its own scrolling when the text is to wide.
Upvotes: 1
Reputation: 157
You can do it like this:
Instead of ScrollBox put a ScrollBar on your form. Set its alignment to bottom (or set its size and position manually if you wish to have more columns or you can put each one in its own panel). Then set the size of your Memos and place Labels to the center of the Form. After setting the size of the Memos (probably dynamically via code) place this code:
ScrollBar1.Min:=0-Memo1.Left;
ScrollBar1.Max:=Memo1.Width-Form1.ClientWidth+Memo1.Left;
The last thing is to set the ScrollBar OnChange event:
procedure TForm1.ScrollBar1Change(Sender: TObject);
begin
Memo1.Left:=0-ScrollBar1.Position;
Memo2.Left:=0-ScrollBar1.Position;
...
MemoXY.Left:=0-ScrollBar1.Position;
end;
Your form should look something like this:
Done! You have a stable centered Labels and smoothly scrollable Memos.
Edit:
Here is a version with 3 columns each in his own Panel and also with vertical scrollbars:
And the whole source code:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls;
type
TForm1 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
Panel3: TPanel;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Label5: TLabel;
Label6: TLabel;
Memo1: TMemo;
Memo2: TMemo;
Memo3: TMemo;
Memo4: TMemo;
Memo5: TMemo;
Memo6: TMemo;
ScrollBar1: TScrollBar;
ScrollBar2: TScrollBar;
ScrollBar3: TScrollBar;
ScrollBar4: TScrollBar;
ScrollBar5: TScrollBar;
ScrollBar6: TScrollBar;
procedure FormCreate(Sender: TObject);
procedure ScrollBarHChange(Sender: TObject);
procedure ScrollBarVChange(Sender: TObject);
procedure FormResize(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var cycle: Integer;
begin
//GENERATE YOUR COMPONENTS HERE
//sets every components tag to its default top position
//(you can do this in any other way for example using array)
for cycle:=0 to Form1.ComponentCount-1 do
begin
if(Form1.Components[cycle] is TControl)then
Form1.Components[cycle].Tag:=(Form1.Components[cycle] as TControl).Top
end;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
//changes the panels sizes and positions
Panel1.Width:=Form1.ClientWidth div 3;
Panel2.Width:=Form1.ClientWidth div 3;
Panel3.Width:=Form1.ClientWidth div 3;
Panel2.Left:=Panel1.Width+1;
Panel3.Left:=Panel1.Width+Panel2.Width+1;
//if you dont want all scrollbars to reset on window resize, you need to handle the positioning of elements when window (and panels) size is changing
ScrollBar1.Position:=ScrollBar1.Min;
ScrollBar2.Position:=ScrollBar2.Min;
ScrollBar3.Position:=ScrollBar3.Min;
ScrollBar4.Position:=ScrollBar4.Min;
ScrollBar5.Position:=ScrollBar5.Min;
ScrollBar6.Position:=ScrollBar6.Min;
//make these tests on the widest element of each panel (8 is just a margin so the memo has some space on the right)
if((Memo1.Left+Memo1.Width)>(Panel1.ClientWidth-ScrollBar4.Width-8))then
begin
ScrollBar1.Enabled:=true;
ScrollBar1.Max:=Memo1.Width-Panel1.ClientWidth+Memo1.Left+ScrollBar4.Width+8;
end
else
ScrollBar1.Enabled:=false;
if((Memo3.Left+Memo3.Width)>(Panel2.ClientWidth-ScrollBar5.Width-8))then
begin
ScrollBar2.Enabled:=true;
ScrollBar2.Max:=Memo3.Width-Panel1.ClientWidth+Memo3.Left+ScrollBar5.Width+8;
end
else
begin
ScrollBar2.Position:=ScrollBar2.Min;
ScrollBar2.Enabled:=false;
end;
if((Memo5.Left+Memo5.Width)>(Panel3.ClientWidth-ScrollBar6.Width-8))then
begin
ScrollBar3.Enabled:=true;
ScrollBar3.Max:=Memo5.Width-Panel1.ClientWidth+Memo5.Left+ScrollBar6.Width+8;
end
else
ScrollBar3.Enabled:=false;
//make these tests on the bottom element of each panel (16 is just a margin so the memo has some space on the bottom)
if((Memo2.Top+Memo2.Height)>(Panel1.ClientHeight-ScrollBar1.Height-16))then
begin
ScrollBar4.Enabled:=true;
ScrollBar4.Max:=Memo2.Top+Memo2.Height-Panel1.ClientHeight+ScrollBar1.Height+16;
end
else
ScrollBar4.Enabled:=false;
if((Memo4.Top+Memo4.Height)>(Panel2.ClientHeight-ScrollBar2.Height-16))then
begin
ScrollBar5.Enabled:=true;
ScrollBar5.Max:=Memo4.Top+Memo4.Height-Panel2.ClientHeight+ScrollBar2.Height+16;
end
else
ScrollBar5.Enabled:=false;
if((Memo6.Top+Memo6.Height)>(Panel3.ClientHeight-ScrollBar3.Height-16))then
begin
ScrollBar6.Enabled:=true;
ScrollBar6.Max:=Memo6.Top+Memo6.Height-Panel3.ClientHeight+ScrollBar3.Height+16;
end
else
ScrollBar6.Enabled:=false;
end;
procedure TForm1.ScrollBarHChange(Sender: TObject);
var cycle: Integer;
begin
for cycle:=0 to ((Sender as TScrollBar).Parent as TPanel).ControlCount-1 do
begin
if(((Sender as TScrollBar).Parent as TPanel).Controls[cycle] is TMemo)then
(((Sender as TScrollBar).Parent as TPanel).Controls[cycle] as TMemo).Left:=0-(Sender as TScrollBar).Position+8;
end;
end;
procedure TForm1.ScrollBarVChange(Sender: TObject);
var cycle: Integer;
begin
for cycle:=0 to ((Sender as TScrollBar).Parent as TPanel).ControlCount-1 do
begin
if(not (((Sender as TScrollBar).Parent as TPanel).Controls[cycle] is TScrollBar))then
(((Sender as TScrollBar).Parent as TPanel).Controls[cycle] as TControl).Top:=(((Sender as TScrollBar).Parent as TPanel).Controls[cycle] as TControl).Tag-(Sender as TScrollBar).Position;
end;
end;
end.
And the .dfm:
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 473
ClientWidth = 769
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
OnResize = FormResize
DesignSize = (
769
473)
PixelsPerInch = 96
TextHeight = 13
object Panel1: TPanel
Left = 0
Top = 0
Width = 257
Height = 473
Anchors = [akLeft, akTop, akBottom]
BevelOuter = bvNone
BorderStyle = bsSingle
TabOrder = 0
object Label1: TLabel
Left = 104
Top = 16
Width = 31
Height = 13
Caption = 'Label1'
end
object Label2: TLabel
Left = 104
Top = 152
Width = 31
Height = 13
Caption = 'Label2'
end
object Memo1: TMemo
Left = 8
Top = 32
Width = 497
Height = 89
Lines.Strings = (
'Memo1')
TabOrder = 0
end
object Memo2: TMemo
Left = 8
Top = 168
Width = 497
Height = 89
Lines.Strings = (
'Memo2')
TabOrder = 1
end
object ScrollBar1: TScrollBar
AlignWithMargins = True
Left = 0
Top = 452
Width = 236
Height = 17
Margins.Left = 0
Margins.Top = 0
Margins.Right = 17
Margins.Bottom = 0
Align = alBottom
PageSize = 0
TabOrder = 2
OnChange = ScrollBarHChange
ExplicitWidth = 253
end
object ScrollBar4: TScrollBar
Left = 236
Top = 0
Width = 17
Height = 452
Align = alRight
Enabled = False
Kind = sbVertical
PageSize = 0
TabOrder = 3
OnChange = ScrollBarVChange
ExplicitTop = 248
ExplicitHeight = 121
end
end
object Panel2: TPanel
Left = 256
Top = 0
Width = 257
Height = 473
Anchors = [akLeft, akTop, akBottom]
BevelOuter = bvNone
BorderStyle = bsSingle
TabOrder = 1
object Label3: TLabel
Left = 104
Top = 16
Width = 31
Height = 13
Caption = 'Label3'
end
object Label4: TLabel
Left = 104
Top = 152
Width = 31
Height = 13
Caption = 'Label4'
end
object Memo3: TMemo
Left = 8
Top = 32
Width = 497
Height = 89
Lines.Strings = (
'Memo3')
TabOrder = 0
end
object Memo4: TMemo
Left = 8
Top = 168
Width = 497
Height = 89
Lines.Strings = (
'Memo4')
TabOrder = 1
end
object ScrollBar2: TScrollBar
AlignWithMargins = True
Left = 0
Top = 452
Width = 236
Height = 17
Margins.Left = 0
Margins.Top = 0
Margins.Right = 17
Margins.Bottom = 0
Align = alBottom
PageSize = 0
TabOrder = 2
OnChange = ScrollBarHChange
ExplicitWidth = 253
end
object ScrollBar5: TScrollBar
Left = 236
Top = 0
Width = 17
Height = 452
Align = alRight
Enabled = False
Kind = sbVertical
PageSize = 0
TabOrder = 3
OnChange = ScrollBarVChange
ExplicitTop = 248
ExplicitHeight = 121
end
end
object Panel3: TPanel
Left = 512
Top = 0
Width = 257
Height = 473
Anchors = [akLeft, akTop, akBottom]
BevelOuter = bvNone
BorderStyle = bsSingle
TabOrder = 2
object Label5: TLabel
Left = 104
Top = 16
Width = 31
Height = 13
Caption = 'Label5'
end
object Label6: TLabel
Left = 104
Top = 152
Width = 31
Height = 13
Caption = 'Label6'
end
object Memo5: TMemo
Left = 8
Top = 32
Width = 497
Height = 89
Lines.Strings = (
'Memo5')
TabOrder = 0
end
object Memo6: TMemo
Left = 8
Top = 168
Width = 497
Height = 89
Lines.Strings = (
'Memo6')
TabOrder = 1
end
object ScrollBar3: TScrollBar
AlignWithMargins = True
Left = 0
Top = 452
Width = 236
Height = 17
Margins.Left = 0
Margins.Top = 0
Margins.Right = 17
Margins.Bottom = 0
Align = alBottom
PageSize = 0
TabOrder = 2
OnChange = ScrollBarHChange
ExplicitWidth = 253
end
object ScrollBar6: TScrollBar
Left = 236
Top = 0
Width = 17
Height = 452
Align = alRight
Enabled = False
Kind = sbVertical
PageSize = 0
TabOrder = 3
OnChange = ScrollBarVChange
ExplicitTop = 248
ExplicitHeight = 121
end
end
end
Upvotes: 2
Reputation: 54772
-" Memo moves smoothly, label doesn't."
That's because you're trying to prevent it from moving. Detach your OnScrollHorz
handler and the label will move smoothly. But that's not what you want, it will not be centered to the form any more.
The problem is, during the inherited
call (WM_HSCROLL
), the label moves along with the memo. After the default handling, you relocate the label, hence the flicker.
You can expose an additional event handler that will fire before default scrolling (OnBeforeHorzScroll), and hide the label when it fires. While smoothly centered, it will cause a different kind of flicker where the label momentarily disappears. Still may not be satisfactory.
The solution is to use a control that is parented to the form, a sibling to the scroll box. You can't do that with a TLabel
as it is a graphic control, but you can use TStaticText
. The "structure pane" of the IDE may come handy if the static accidentally goes behind the scroll box at design time.
Upvotes: 2