喫茶XRAYトップページ


[6991] 仮想リストビューでメモリーリークの原因が不明返信 削除
2013/7/12 (金) 15:09:51 宮 〔HomePage
皆さん暑中お見舞い申し上げます。
環境はWindows7 Delphi6Personalです。
http://mrxray.on.coocan.jp/Delphi/plSamples/790_ListView.htm
を参考に790_仮想リストビューの勉強をしています。
08_仮想リストビューにチェックボックス でつまずいています。
どうしても項目を削除するとメモリーリークします。
場所はFormShowのFItem := TDataListItem.Create;
だと表示しているようなのですが、解決できません。
マスターに変わりどなたか教えてくださいませんか。
イメージ不器用なので○●で代用しています。
コードはコピペしただけですが念のため。(長くなりますがm(_ _)m)
type
  TplDataListItem = class;
  TplDataList = class(TList)
  protected
    function Get(Index: Integer): TplDataListItem;
    procedure Put(Index: Integer; Item: TplDataListItem);
  public
    destructor Destroy; override;
    procedure Clear; virtual;
    function Add(Item: TplDataListItem): Integer;
    function IndexOf(Item: TplDataListItem): Integer;
    function Remove(Item: TplDataListItem): Integer;
    property Items[Index: Integer]: TplDataListItem read Get write Put; default;
  end;
  //プロパティはCaptionとChecedのみとした
  TplDataListItem =class(TObject)
  private
    FCaption : String;
    FChecked : Boolean;
  public
    property Caption : String  read FCaption write FCaption;
    property Checked : Boolean read FChecked write FChecked;
  end;
  TDataListItem = class(TplDataListItem);
  TForm1 = class(TForm)
    ListView1: TListView;
    Panel1: TPanel;
    Button2: TButton;
    ImageList1: TImageList;
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ListView1Data(Sender: TObject; Item: TListItem);
    procedure ListView1MouseDown(Sender: TObject; Button: TMouseButton;Shift: TShiftState; X, Y: Integer);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    DataList  : TplDataList;
  public
    { Public 宣言 }
  end;
var
  Form1: TForm1;
implementation
uses CommCtrl;
{ TplDataList }

//  仮想リストビュー用のデータを格納するTplDataListの関数とメソッド類
destructor TplDataList.Destroy;
var
  i : Integer;
begin
  for i := 0 to Count - 1 do begin
    Items[i].Free;
  end;
  inherited;
end;
//-----------------------------------------------------------------------------
procedure TplDataList.Clear;
var
  i : Integer;
begin
  for i := 0 to Count - 1 do begin
    Items[i].Free;
  end;
  inherited;
end;
//-----------------------------------------------------------------------------
procedure TplDataList.Put(Index: Integer; Item: TplDataListItem);
begin
  inherited Items[Index] := Item;
end;
//-----------------------------------------------------------------------------
function TplDataList.Get(Index: Integer): TplDataListItem;
begin
  Result := TplDataListItem(inherited Items[Index]);
end;
//-----------------------------------------------------------------------------
function TplDataList.Add(Item: TplDataListItem): Integer;
begin
  Result := inherited Add(Item);
end;
//-----------------------------------------------------------------------------
function TplDataList.IndexOf(Item: TplDataListItem): Integer;
begin
    Result := inherited IndexOf(Item);
end;
//-----------------------------------------------------------------------------
function TplDataList.Remove(Item: TplDataListItem): Integer;
begin
  Result := inherited Remove(Item);
end;
{$R *.dfm}

//  開始時にDataListを生成
procedure TForm1.FormCreate(Sender: TObject);
var
  ACol : TListColumn;
begin
  Form1.Caption:=FormCaption;
  DataList := TplDataList.Create;
  //ListViewの設定
  ListView1.ViewStyle   := vsReport;
  ListView1.MultiSelect := True;
  ListView1.OwnerData := True;   //OwnerDataプロパティをTrueにしておく
  ACol := ListView1.Columns.Add;
  ACol.Width   := 150;
  ACol.Caption := '名前';
end;
//=============================================================================
//  フォームを表示したらテスト用データを作成
//=============================================================================
procedure TForm1.FormShow(Sender: TObject);
var
  i     : Integer;
  FItem : TDataListItem;
begin
  ListView1.Items.Clear;
  for i := 0 to 80 do begin //  テスト用データの生成
    FItem := TDataListItem.Create;
    FItem.Caption := Format('Item%.3d', [i]);
    FItem.Checked := False;
    DataList.Add(FItem);
  end;
  //ListViewにItems数を通知してListViewの表示を更新
  //OnDataHint,OnDataイベントが発生して必要なItemの描画が行われる
  ListView1.Items.Count := DataList.Count;
end;
//=============================================================================
//  終了時はDataListを解放
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(DataList);
end;
//=============================================================================
//  ListView1のOnDataイベント
//  ListViewのItem.Captionの設定
//  このOnDataイベントで設定しないと無効.OnDataHintでは設定しても無効
//  チェックボックスも同様
//  [備考]ListViewのOnDataHintイベントはスクロールすれば発生する
//=============================================================================
procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
begin
  Item.Caption := DataList[Item.Index].Caption;
  //StateIconを設定
  //1,0はImageList1のアイテム番号(0はチェックなし画像)
  if DataList[Item.Index].Checked then begin
    Item.StateIndex := 1;
  end else begin
    Item.StateIndex := 0;
  end;
end;
//=============================================================================
//  チェックボックス上のマウスダウン(クリック)でチェックボックスのOn,Off切換
//  データリストのCheckedプロパティを変更
//  実際の描画はListViewのRefreshメソッドを実行することで行う
//=============================================================================
procedure TForm1.ListView1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  AItem     : TListItem;
  AHitTests : THitTests;
begin
  AItem := ListView1.GetItemAt(X, Y);
  if AItem = nil then exit;

  //マウス左ボタンの時のみ
  if Button = mbLeft then begin
    //座標(X,Y)がStateIcon(状態アイコンまたはビットマップ)上の場合のみ処理
    AHitTests := ListView1.GetHitTestInfoAt(X, Y);
    if htOnStateIcon in AHitTests then begin
      DataList[AItem.Index].Checked := not DataList[AItem.Index].Checked;
      //ListViewの表示更新(OnDataイベントが発生する)
      ListView1.Refresh;
    end;
  end;
end;
//=============================================================================
//  チェックしてあるアイテムを削除する
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  i : Integer;
begin
  for i := DataList.Count - 1 downto 0 do begin
    if DataList[i].Checked then DataList.Delete(i);
  end;
  ListView1.Items.Count := DataList.Count;
  //ListViewの表示更新(OnDataまたはOnDataHintイベントが発生する)
  ListView1.Refresh;
end;

[6992] Re:仮想リストビューでメモリーリークの原因が不明返信 削除
2013/7/12 (金) 16:46:08 福士
マスターの家出先で聞いたほうがいいような気もしますが。

Button2ClickでDataList.Delete(i)を呼んでいますが、これだけでは
DataList.Items[i]のポインタが削除されるだけでインスタンスが
orphanになってしまいます。

procedure TForm1.Button2Click(Sender: TObject);
var
  i : Integer;
  ListItem: TplDataListItem;
begin
  for i := DataList.Count - 1 downto 0 do begin
    if DataList[i].Checked then
    begin
      ListItem := DataList.Items[i];
      DataList.Delete(i);
      ListItem.Free;  // TplDataListItemのインスタンスを解放
    end;
  end;
  ListView1.Items.Count := DataList.Count;
  //ListViewの表示更新(OnDataまたはOnDataHintイベントが発生する)
  ListView1.Refresh;
end;

のようにするか、TplDataListの継承元をTObjectListにして、
コンストラクタのパラメータをTrueにするといいと思います。

# コードが動くかどうか試していないのではずしていたらすいません。


[6993] Re2:仮想リストビューでメモリーリークの原因が不明返信 削除
2013/7/12 (金) 23:40:03 宮 〔HomePage
▼ 福士さん
> マスターの家出先で聞いたほうがいいような気もしますが。
>
>       ListItem.Free;  // TplDataListItemのインスタンスを解放
>
> # コードが動くかどうか試していないのではずしていたらすいません。


福士さん、早速教えていただきありがとうございます。
ばっちりです。
メモリーリーク発生していません。
>
> のようにするか、TplDataListの継承元をTObjectListにして、
> コンストラクタのパラメータをTrueにするといいと思います。


すごいですね、理論的なことはさっぱりわかりませんが・・


何か応用してできそうな気がしてきました。
ありがとうございました。


INCM/CMT
Cyclamen v3.82