我正在构建自定义列表控件,类似于列表视图但更轻。它的每个项目都有ItemWidth
和ItemHeight
属性,这些项目位于TOwnedCollection
。每个项目的大小相同。我还有Margins
和ItemSpacing
的属性来指定每个项目的距离。
问题在于计算每个项目的位置以使其最适合当前控制空间。控件只有垂直滚动,没有水平。因此,我需要识别项目何时无法放入列表并将其带到下一行。
为了使这更加棘手,我还必须能够识别给定点是否在项目的矩形区域内,以便处理鼠标事件。因此,为了解决这个问题,我决定在每个GetRect
项上添加一个函数,该函数将返回控件上该项的Rect
区域。但是我如何使这个函数计算出来呢?
此函数的两个主要实现将位于控件的Paint
中:
for X := 0 to FItems.Count - 1 do begin
Canvas.Rectangle(FItems[X].GetRect);
end;
在确定某个点是否在此项目的区域时:
for X := 0 to FItems.Count - 1 do begin
R:= FItems[X].GetRect;
Result := (P.X > R.Left) and (P.X < R.Right) and (P.Y > R.Top) and (P.Y < R.Bottom);
end;
答案 0 :(得分:4)
了解网格任何单元格的位置不需要计算所有先前单元格的位置。关于电网,这是件好事。每个单元格都有一个可预测的位置。
首先,您需要知道可以在一行中水平排列多少个单元格。使用the first answer中的值,这是由以下等式给出的:
CellsPerRow := (CW - ML - MR + SH) div (IW + SH);
取总客户端宽度,减去边距,然后除以单个单元格的有效宽度,通过将项目宽度与项目间距相加来给出。每行中的一个单元格没有间距(因为它邻接控件的边缘),因此我们假设客户区域实际上宽SH
像素。
现在我们知道一行中有多少项,我们可以计算任何项目所属的(从零开始)行:
ItemRow := Item.Index div CellsPerRow;
该行(列)中的(从零开始)位置也很容易计算:
ItemColumn := Item.Index mod CellsPerRow;
现在我们可以计算出细胞的位置:
LP := ML + ItemColumn * (IW + SH);
TP := MT + ItemRow * (IH + SV);
将所有这些结合在一起,我们得到了这个:
function TMyListItemGrid.GetCellsPerRow: Integer;
begin
Result := (ClientWidth - Margins.Left - Margins.Right + SpacingHorz) div (ItemWidth + SpacingHorz);
end;
function TMyListItem.GetRect: TRect;
var
Row, Col: Integer;
EffectiveWidth, EffectiveHeight: Integer;
begin
EffectiveWidth := Owner.ItemWidth + Owner.SpacingHorz;
EffectiveHeight := Owner.ItemHeight + Owner.SpacingVert;
Row := Index div Owner.CellsPerRow;
Result.Top := Owner.Margins.Top + Row * EffectiveHeight;
Result.Bottom := Result.Top + Owner.ItemHeight;
Col := Index mod Owner.CellsPerRow;
Result.Left := Owner.Margins.Left + Col * EffectiveWidth;
Result.Right := Result.Left + Owner.ItemWidth;
end;
小心让控件变得太窄,或让边距变得太宽。如果发生这种情况,那么CellsPerRow
属性可能会变为零,这将导致所有GetRect
调用的异常。如果CellsPerRow
变为否定,事情可能也会变得奇怪。您需要为控件强制执行某个最小宽度。
答案 1 :(得分:-1)
我已经打破了这个程序来演示如何计算这些位置:
function TMyListItem.GetRect: TRect;
var
I: Integer; //Iterator
LP: Integer; //Left position
TP: Integer; //Top position
CW: Integer; //Client width
CH: Integer; //Client height
IW: Integer; //Item width
IH: Integer; //Item height
SV: Integer; //Vertical spacing
SH: Integer; //Horizontal spacing
ML: Integer; //Margin left
MT: Integer; //Margin top
MR: Integer; //Margin right
MB: Integer; //Margin bottom
R: TRect; //Temp rect
begin //'Owner' = function which returns the control
//Initialize some temporary variables...
CW:= Owner.ClientWidth;
CH:= Owner.ClientHeight;
IW:= Owner.ItemWidth;
IH:= Owner.ItemHeight;
SV:= Owner.SpacingVert;
SH:= Owner.SpacingHorz;
ML:= Owner.Margins.Left;
MT:= Owner.Margins.Top;
MR:= Owner.Margins.Right;
MB:= Owner.Margins.Bottom;
LP:= ML; //Default left position to left margin
TP:= MT; //Default top position to top margin
for I := 0 to Collection.Count - 1 do begin
R:= Rect(LP, TP, LP + IW, TP + IH);
if Self.Index = I then begin
Result:= R;
Break;
end else begin
//Calculate next position
LP:= LP + IW + SV; //move left position by item width + vertical spacing
if (LP + IW + MR) >= CW then begin //Does item fit?
LP:= ML; //reset left position
TP:= TP + IH + SH; //drop down top position to next line
end;
end;
end;
end;
以下是其制作的样本:
应该有更好的表现选择。此过程正在进行计算循环,因此数百个项目的列表可能会显示较慢的结果。