2012-06-04 26 views
0

我正在構建一個自定義列表控件,類似於列表視圖,但更輕。它的物品分別爲ItemWidthItemHeight,物品在TOwnedCollection。每個項目是相同的大小。我還有MarginsItemSpacing的屬性來指定相隔多遠來定位每個項目。如何識別自動排列列表中的列表項的rect?

問題在於何時計算每個項目的位置以使其最適合當前控制空間。該控件只有垂直滾動,並且沒有水平。因此,我需要識別某個項目何時無法放入列表中並將其帶到下一行。

爲了使這更加棘手,我還必須能夠識別給定點是否位於項目的矩形區域內,以便處理鼠標事件。所以爲了解決這個問題,我決定在每個項目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; 
+2

如果您不再將其視爲列表視圖控件,並開始將其視爲網格控件,它可能會幫助您理解事物。 –

回答

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

我打破這個過程下來演示瞭如何計算這樣的位置:

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; 

這裏是它生產的樣品:

enter image description here

應該有一個更好的執行方案。此過程正在進行一系列計算,因此數百個項目的列表可能會顯示較慢的結果。

+0

你有'SV'和'SH'混在一起,不是嗎?將垂直間距添加到項目的水平位置是沒有意義的。 –

+0

@RobKennedy取決於你如何看待它 - 我想象的是一條垂直線,它將每個項目彼此隔開。 –

+1

穿過水平間距的垂直線。間距影響物品的水平位置。它可能是一條垂直線的距離,但不會以任何方式影響垂直空間。 –