介绍
我正在编写一个自定义控件,它是从TScrollBox
派生的,但是我遇到了一些困难,似乎应该很容易解决。
该控件将用于在顶部显示静态的标题栏(即当滚动框滚动时不会移动),然后在标题栏下面,我将在其自己的列中绘制一些值,如行号等。
这就是目前的控件的外观,以便更好地理解(非常早期的工作进展):
闪烁问题
我面临的问题是闪烁,我没有找到消除它的简单方法。我有一种感觉,闪烁是因为我试图在我的标题栏下面进行绘制,当闪烁发生时,您实际上可以看到在标题栏下面绘制的值,尽管我的假设可能完全错误。
所有的绘图都是在一个TGraphicControl
上完成的,它是滚动框的子元素,当快速滚动时,闪烁发生得非常频繁,当使用滚动条按钮时,它仍然会闪烁,但不会像快速滚动时那样频繁。
我无法捕捉到闪烁并在此处显示图像,但是通过下面的代码,您可以构建并安装到一个新包中进行测试:
unit MyGrid;
interface
uses
Winapi.Windows,
Winapi.Messages,
System.Classes,
System.SysUtils,
Vcl.Controls,
Vcl.Dialogs,
Vcl.Forms,
Vcl.Graphics;
type
TMyCustomGrid = class(TGraphicControl)
private
FFont: TFont;
FRowNumbers: TStringList;
FRowCount: Integer;
FCaptionBarRect: TRect;
FRowNumbersBackgroundRect: TRect;
FValuesBackgroundRect: TRect;
procedure CalculateNewHeight;
function GetMousePosition: TPoint;
function RowIndexToMousePosition(ARowIndex: Integer): Integer;
function GetRowHeight: Integer;
function RowExists(ARowIndex: Integer): Boolean;
function GetRowNumberRect(ARowIndex: Integer): TRect;
function GetRowNumberTextRect(ARowIndex: Integer): TRect;
function GetValueRect(ARowIndex: Integer): TRect;
function GetValueTextRect(ARowIndex: Integer): TRect;
function GetFirstVisibleRow: Integer;
function GetLastVisibleRow: Integer;
protected
procedure Paint; override;
procedure DrawCaptionBar;
procedure DrawRowNumbers;
procedure DrawValues;
procedure DrawColumnLines;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
TMyGrid = class(TScrollBox)
private
FGrid: TMyCustomGrid;
protected
procedure Loaded; override;
procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
const
FCaptionBarHeight = 20;
FRowNumbersWidth = 85;
FValuesWidth = 175;
FTextSpacing = 5;
implementation
constructor TMyCustomGrid.Create(AOwner: TComponent);
var
I: Integer;
begin
inherited Create(AOwner);
FFont := TFont.Create;
FFont.Color := clBlack;
FFont.Name := 'Tahoma';
FFont.Size := 10;
FFont.Style := [];
FRowNumbers := TStringList.Create;
//FOR TEST PURPOSES
for I := 0 to 1000 do
begin
FRowNumbers.Add(IntToStr(I));
end;
Canvas.Font.Assign(FFont);
end;
destructor TMyCustomGrid.Destroy;
begin
FFont.Free;
FRowNumbers.Free;
inherited Destroy;
end;
procedure TMyCustomGrid.Paint;
begin
FCaptionBarRect := Rect(0, 0, Self.Width, GetRowHeight + TMyGrid(Self.Parent).VertScrollBar.Position + 2);
FRowCount := FRowNumbers.Count;
DrawRowNumbers;
DrawValues;
DrawCaptionBar;
DrawColumnLines;
end;
procedure TMyCustomGrid.DrawCaptionBar;
var
R: TRect;
S: string;
begin
{background}
Canvas.Brush.Color := clSkyBlue;
Canvas.Brush.Style := bsSolid;
Canvas.FillRect(FCaptionBarRect);
{text}
Canvas.Brush.Style := bsClear;
R := Rect(FTextSpacing, FCaptionBarRect.Top + TMyGrid(Self.Parent).VertScrollBar.Position, FRowNumbersWidth - FTextSpacing, FCaptionBarRect.Bottom);
S := 'Row No.';
DrawText(Canvas.Handle, PChar(S), Length(S), R, DT_LEFT or DT_SINGLELINE or DT_VCENTER);
R := Rect(FTextSpacing + FRowNumbersWidth, FCaptionBarRect.Top + TMyGrid(Self.Parent).VertScrollBar.Position, FValuesWidth - FTextSpacing, FCaptionBarRect.Bottom);
S := 'Item No.';
DrawText(Canvas.Handle, PChar(S), Length(S), R, DT_LEFT or DT_SINGLELINE or DT_VCENTER);
end;
procedure TMyCustomGrid.DrawRowNumbers;
var
I, Y: Integer;
R: TRect;
S: string;
begin
{background}
FRowNumbersBackgroundRect := Rect(0, FCaptionBarRect.Bottom, FRowNumbersWidth, FCaptionBarRect.Height + GetLastVisibleRow * GetRowHeight + 1);
Canvas.Brush.Color := clCream;
Canvas.Brush.Style := bsSolid;
Canvas.FillRect(FRowNumbersBackgroundRect);
{text}
Y := 0;
// a bit of optimization here, instead of iterating every item in FRowNumbers
// which would be slow - instead determine the the top and last visible row
// and paint only that area.
for I := GetFirstVisibleRow to GetLastVisibleRow do
begin
if RowExists(I) then
begin
R := GetRowNumberTextRect(I);
S := FRowNumbers.Strings[I];
DrawText(Canvas.Handle, PChar(S), Length(S), R, DT_LEFT or DT_SINGLELINE or DT_VCENTER);
Inc(Y, GetRowHeight);
end;
end;
end;
procedure TMyCustomGrid.DrawValues;
var
I, Y: Integer;
R: TRect;
S: string;
begin
{background}
FValuesBackgroundRect := Rect(FRowNumbersBackgroundRect.Width, FCaptionBarRect.Bottom, FValuesWidth + FRowNumbersWidth, FCaptionBarRect.Height + GetLastVisibleRow * GetRowHeight + 1);
Canvas.Brush.Color := clMoneyGreen;
Canvas.Brush.Style := bsSolid;
Canvas.FillRect(FValuesBackgroundRect);
{text}
Y := 0;
// a bit of optimization here, instead of iterating every item in FRowNumbers
// which would be slow - instead determine the the top and last visible row
// and paint only that area.
for I := GetFirstVisibleRow to GetLastVisibleRow do
begin
if RowExists(I) then
begin
R := GetValueTextRect(I);
S := 'This is item number ' + FRowNumbers.Strings[I];
DrawText(Canvas.Handle, PChar(S), Length(S), R, DT_LEFT or DT_SINGLELINE or DT_VCENTER);
Inc(Y, GetRowHeight);
end;
end;
end;
procedure TMyCustomGrid.DrawColumnLines;
begin
Canvas.Brush.Style := bsClear;
Canvas.Pen.Color := clBlack;
{row numbers column}
Canvas.MoveTo(FRowNumbersBackgroundRect.Right, FCaptionBarRect.Top);
Canvas.LineTo(FRowNumbersBackgroundRect.Right, FCaptionBarRect.Height + GetLastVisibleRow * GetRowHeight + 1);
{values column}
Canvas.MoveTo(FValuesBackgroundRect.Right, FCaptionBarRect.Top);
Canvas.LineTo(FValuesBackgroundRect.Right, FCaptionBarRect.Height + GetLastVisibleRow * GetRowHeight + 1);
end;
procedure TMyCustomGrid.CalculateNewHeight;
var
I, Y: Integer;
begin
FRowCount := FRowNumbers.Count;
Y := 0;
for I := 0 to FRowCount -1 do
begin
Inc(Y, GetRowHeight);
end;
if Self.Height <> Y then
Self.Height := Y + FCaptionBarHeight + 1;
end;
function TMyCustomGrid.GetMousePosition: TPoint;
var
P: TPoint;
begin
Winapi.Windows.GetCursorPos(P);
Winapi.Windows.ScreenToClient(Self.Parent.Handle, P);
Result := P;
end;
function TMyCustomGrid.RowIndexToMousePosition(
ARowIndex: Integer): Integer;
begin
if RowExists(ARowIndex) then
Result := ARowIndex * GetRowHeight;
end;
function TMyCustomGrid.GetRowHeight: Integer;
begin
Result := 18;
end;
function TMyCustomGrid.RowExists(ARowIndex: Integer): Boolean;
var
I: Integer;
Y: Integer;
begin
Result := False;
Y := 0;
for I := GetFirstVisibleRow to GetLastVisibleRow -1 do
begin
if ARowIndex = I then
begin
Result := True;
Break;
end;
Inc(Y, GetRowHeight);
end;
end;
function TMyCustomGrid.GetRowNumberRect(ARowIndex: Integer): TRect;
begin
Result.Bottom := RowIndexToMousePosition(ARowIndex) + FCaptionBarHeight + GetRowHeight;
Result.Left := 0;
Result.Right := FRowNumbersWidth;
Result.Top := RowIndexToMousePosition(ARowIndex) + FCaptionBarHeight + 1;
end;
function TMyCustomGrid.GetRowNumberTextRect(ARowIndex: Integer): TRect;
begin
Result := GetRowNumberRect(ARowIndex);
Result.Inflate(-FTextSpacing, 0);
end;
function TMyCustomGrid.GetValueRect(ARowIndex: Integer): TRect;
begin
Result.Bottom := RowIndexToMousePosition(ARowIndex) + FCaptionBarHeight + GetRowHeight;
Result.Left := FRowNumbersWidth;
Result.Right := FValuesBackgroundRect.Right;
Result.Top := RowIndexToMousePosition(ARowIndex) + FCaptionBarHeight + 1;
end;
function TMyCustomGrid.GetValueTextRect(ARowIndex: Integer): TRect;
begin
Result := GetValueRect(ARowIndex);
Result.Inflate(-FTextSpacing, 0);
end;
function TMyCustomGrid.GetFirstVisibleRow: Integer;
begin
Result := TMyGrid(Self.Parent).VertScrollBar.Position div GetRowHeight;
end;
function TMyCustomGrid.GetLastVisibleRow: Integer;
begin
Result := GetFirstVisibleRow + TMyGrid(Self.Parent).Height div GetRowHeight -1;
end;
constructor TMyGrid.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Self.DoubleBuffered := True;
Self.Height := 150;
Self.HorzScrollBar.Visible := False;
Self.TabStop := True;
Self.Width := 250;
FGrid := TMyCustomGrid.Create(Self);
FGrid.Align := alTop;
FGrid.Parent := Self;
FGrid.CalculateNewHeight;
Self.VertScrollBar.Smooth := False;
Self.VertScrollBar.Increment := FGrid.GetRowHeight;
Self.VertScrollBar.Tracking := True;
end;
destructor TMyGrid.Destroy;
begin
FGrid.Free;
inherited Destroy;
end;
procedure TMyGrid.Loaded;
begin
inherited Loaded;
Self.VertScrollBar.Range := FGrid.Height - FGrid.FCaptionBarRect.Height;
end;
procedure TMyGrid.WMVScroll(var Message: TWMVScroll);
begin
inherited;
Self.Invalidate;
end;
end.
问题
为了解决闪烁问题,我需要做出哪些改变?
似乎将滚动框的DoubleBuffered
设置为True没有什么效果。我尝试使用WM_ERASEBACKGROUND
消息进行实验,但这只会使滚动框变成黑色。
我还尝试在滚动框上实现画布,并直接在其上绘制标题栏,然后将滚动框的填充设置为我的标题栏高度,并在我的TGraphicControl
上绘制其余部分,但这导致闪烁更严重。此时,我不知道究竟是什么导致了闪烁,也不知道如何消除它。
最后一个问题是,当使用滚动条拖块滚动时,如何使滚动条以固定增量滚动?我将垂直滚动条增量设置为相当于行高的值,这在按下滚动条按钮时可以正常工作,但在使用滚动条拖块向上和向下滚动时,它并不是以固定增量滚动。我正在尝试让滚动条以固定增量滚动而不是松散滚动。