2014年8月28日 星期四

Delphi 用 for..in 迴圈釋放物件的注意事項 (XE5, XE6)

從 Delphi 2009 開始, 新增了不少原本 Object Pascal 沒有支援的語法, 包含了我還蠻常用的 for-each 迴圈. (在 Perl, obj-c 我都還蠻常用這個功能的)

為了搭配動態產生元件, 用 for-each 迴圈來處理釋放動態新增的物件, 在 Delphi XE5 分外重要,因為在 XE5 當中,我常用 TVertScrollBox 這個元件來處理需要動態新增、高度不確定的 Contents.

舉個大家最常見的例子來說,即時通訊軟體的對話內容,內容會有多少?誰也說不準,可能跟朋友A的對話內容有一千則,但跟不熟的路人甲則只有三則。

這時候,要用什麼元件來顯示這些對話內容呢? 從 XE2 開始,大多數的範例程式都是用 TListBox 或者 TListView 來載入這些資料,TListBox 跟 TListView 也很好,但這兩個元件的內容呈現比較制式,用來顯示通訊錄裡面的聯絡人非常適合,但要用來顯示對話內容的話,就不很合適了。

請回憶一下,現在大家都常用的通訊軟體,Line, What's app, iMessage 等等,都是以左邊顯示對方傳來的訊息,右邊顯示我方傳過去的訊息,底下會用個氣泡圖片來襯底,我們看一下 iMessage 的畫面:


要在 Delphi XE5, XE6 裡面製作完全相同的效果的話, 要怎麼做?

透過 TListView, 很抱歉, 我一時半刻創意不足, 想不出來, TListBox 倒是可以。

因為TListBox 的 Items 是 TListItem,建立TListItem 出來以後,可以用 AddObject 這個方法來把圖片 (TImage), 文字 (TLabel) 加到 TListItem 裡,所以也可以製作出像上圖的畫面,但如果要在上圖的畫面上顯示圖片呢?現在即時通訊軟體都流行貼圖,這種功能要怎麼在 TListBox 做出來呢?

抱歉,我又創意不足,一時技窮了。

這時候,大概也只有 TVertScrollBox 可以用來製作這樣的畫面效果了。

TVertScrollBox 跟 THorzScrollBox 都是從 TScrollBox 繼承而來的,從名稱來看,TVertScrollBox 是垂直方向的捲動視窗,而 THorzScrollBox 則是水平方向的捲動視窗,所以要製作上圖的畫面的話,就可以用 TVertScrollBox 來製作。

讓我寫一段簡單的 procedure 來示範怎麼把一個 TStrings 裡面的文字放進 TVertScrollBox 裡面去:

procedure addTextToScrollBox(info : TStringList);
var
   oneLine : string;
   currentY : Single;
   idx : integer;
   newLabel : TLabel;
begin
    currentY := 0.0;

    for idx := 0 to info.Count -1 do begin
         oneLine := info.Strings[idx];
         newLabel := TLabel.Create(self);
         newLabel.Position.X := 10;
         newLabel.Position.Y := currentY;
         newLabel.Text := oneLine;

         vScrollBox.AddObject(newLabel);

         currentY := currentY + 10.0 + newLabel.Height;  
    end;
end;

以上這段程式就可以把傳進去的 TStrings 的內容一行行顯示在名為 vScrollBox 的 TVertScrollBox 裡面去了,但這範例會有個小困擾,就是在對話內容要釋放,或者刷新的時候,需要把所有的資料全部清除,在 XE6 底下,可以用以下這個 procedure 來達成:

procedure clearScrollBox;
var
    obj : TFMXObject;
begin
    for obj in vScrollBox.Children do begin
         vScrollBox.removeObject(obj);
         obj.DisposeOf;
    end;
end;

在行動平台中,請記得除了透過 vScrollBox.removeObject(obj); 把 obj 從 vScrollBox 的可視範圍移除以外,還要呼叫 obj.DisposeOf; 這樣 obj 才會實際消失,不然的話,雖然obj 當時是看不見的,但卻也還存在記憶體當中沒有沒事放掉。

視覺效果上,我們會看到 vScrollBox 內容空無一物,但捲動軸卻不會消失,這就會讓使用者覺得很納悶了,所以記得,除了RemoveObj之外,還要呼叫 DisposeOf 把物件是放掉。

如果是 XE5 的話, TVertScrollBox 的 Children 就不只存放我們透過 addObject 放進去的物件,我實測的結果,會連同捲動軸也列名在 Children 裡面,所以得先判斷其 Class 是否是我們要釋放的,因此要加入 Delphi 的 RTTI 功能,改為:

procedure clearScrollBox;
var
    obj : TFMXObject;
begin
    for obj in vScrollBox.Children do begin
         if (obj is TLabel) then begin
             vScrollBox.removeObject(obj);
             obj.DisposeOf;
         end;
    end;
end;

不然也可能會把捲動軸弄消失,但這一點在 XE6 以後的版本應該不用再擔心了,真是太好了。

沒有留言:

張貼留言