본문 바로가기
delphi

TListView에서 TListItem의 위치를 변경하는 방법

by 천지조율 2024. 8. 16.

TListView와 TListItem 간의 관계

TListView는 Delphi와 같은 RAD(빠른 애플리케이션 개발) 환경에서 주로 사용되는 컴포넌트로, 리스트 형태의 데이터를 쉽게 관리할 수 있는 도구입니다. TListItem은 TListView 내에서 각각의 항목을 나타내는 객체입니다. TListView는 여러 개의 TListItem을 포함할 수 있으며, 이들 간의 순서를 변경하는 기능은 사용자 경험(UX) 향상에 중요한 요소가 될 수 있습니다.

TListItem의 이동 및 교환

TListItem 간의 위치를 변경하는 가장 일반적인 방법은 두 항목의 데이터를 서로 교환하는 것입니다. 이 과정에서 주의할 점은 TListView의 업데이트 상태를 제어하는 것입니다. BeginUpdate와 EndUpdate 메소드를 사용하여 UI 갱신을 일시 중지하고, 모든 변경 작업이 완료된 후에 UI를 다시 갱신하는 것이 중요합니다. 이렇게 하면 불필요한 리소스 소모와 성능 저하를 방지할 수 있습니다.

procedure ExchangeListItems(ListView: TListView; const Idx1, Idx2: Integer);
var
  tempLI: TListItem;
begin
  ListView.Items.BeginUpdate;
  try
    tempLI := TListItem.Create(ListView.Items);
    tempLI.Assign(ListView.Items.Item[Idx1]);
    ListView.Items.Item[Idx1].Assign(ListView.Items.Item[Idx2]);
    ListView.Items.Item[Idx2].Assign(tempLI);
    ListView.Selected := ListView.Items[Idx2];
    tempLI.Free;
  finally
    ListView.Items.EndUpdate;
  end;
end;

이 코드는 두 개의 TListItem의 위치를 교환합니다. ListView.Items.BeginUpdate와 ListView.Items.EndUpdate 사이에서 TListItem을 생성하고, 이를 통해 항목 간 데이터를 임시로 저장하여 서로 교환합니다.

 

TListView와 객체 목록 관리

객체 목록의 중요성

애플리케이션 개발에서 객체의 목록을 관리하는 것은 매우 중요한 부분입니다. 이때 객체는 다양한 속성과 메소드를 가지며, 이를 효과적으로 관리하기 위해서는 객체 지향적인 접근이 필요합니다. Delphi에서는 이러한 객체를 TObjectList를 통해 쉽게 관리할 수 있습니다.

TObjectList는 객체를 포함한 리스트를 관리하는데 최적화된 클래스입니다. 특히 객체가 포함된 리스트가 해제될 때, 리스트에 담긴 객체들도 자동으로 해제 처리되기 때문에 메모리 관리 측면에서 매우 유용합니다.

TSite 클래스와 TSiteList 클래스

여기서는 예제로 TSite라는 클래스를 만들어 보겠습니다. 이 클래스는 웹사이트에 대한 정보를 담고 있으며, 이를 목록으로 관리하기 위해 TSiteList라는 클래스를 생성합니다.

type
  TSite = class
  private
    FSiteName: String;
    FURL: String;
    FCeo: String;
    FAddress: String;
  end;

  TSiteList = class(TObjectList)
  private
    function _GetSite(nIdx: Integer): TSite;
    property Items[nIdx: Integer]: TSite read _GetSite;
  end;

TSite 클래스는 사이트명, URL, 대표자, 주소를 속성으로 가집니다. TSiteList 클래스는 이러한 TSite 객체들을 관리하는 리스트이며, 객체를 안전하게 관리하고 접근할 수 있도록 메소드를 제공합니다.

TListView와 객체 연동

객체 목록과 TListView를 연동하는 것은 매우 유용합니다. 사용자는 리스트 뷰를 통해 객체의 정보를 시각적으로 확인하고, 객체의 상태를 직접 조작할 수 있습니다. 이를 위해 DrawItems 메소드와 DrawListItem 메소드를 사용하여 TListView에 객체 정보를 표시합니다.

procedure TfrmExchangeListItem.DrawItems;
var
  i: Integer;
  lt: TListItem;
  siteObj: TSite;
begin
  lvListView.Items.Clear;
  for i := 0 to SiteList.Count - 1 do
  begin
    siteObj := SiteList.Items[i];
    lt := lvListView.Items.Add;
    DrawListItem(lt, siteObj);
  end;
end;

procedure TfrmExchangeListItem.DrawListItem(Item: TListItem; ASite: TSite);
begin
  if Item = nil then
    exit;
  if Item.SubItems.Count = 0 then
  begin
    Item.SubItems.Add('');
    Item.SubItems.Add('');
    Item.SubItems.Add('');
    Item.SubItems.Add('');
  end;
  Item.SubItems[0] := ASite.FSiteName;
  Item.SubItems[1] := ASite.FURL;
  Item.SubItems[2] := ASite.FCeo;
  Item.SubItems[3] := ASite.FAddress;
  Item.Data := ASite;
end;

DrawItems 메소드는 SiteList에 포함된 각 TSite 객체를 TListView에 표시합니다. DrawListItem 메소드는 개별 TListItem에 TSite 객체의 속성들을 할당하여 사용자가 UI를 통해 확인할 수 있도록 합니다.

TListView와 객체 연동 알아보기

TListView의 커스터마이징

아이템의 커스터마이징

TListView는 기본적으로 여러 가지 커스터마이징 옵션을 제공합니다. 예를 들어, 특정 조건에 따라 아이템의 색상이나 글꼴을 변경하는 것이 가능합니다. 이를 통해 사용자 경험을 더욱 향상시킬 수 있습니다.

procedure TfrmExchangeListItem.lvListViewCustomDrawItem(Sender: TCustomListView; Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
begin
  if Item = nil then
    exit;
  Item.Caption := IntToStr(Item.Index + 1);
end;

이 메소드는 각 아이템의 인덱스를 Caption으로 설정하는 예제입니다. 필요에 따라 조건문을 추가하여 색상, 글꼴 스타일 등을 변경할 수 있습니다.

드래그 앤 드롭을 통한 TListItem 이동

TListItem 간의 이동은 드래그 앤 드롭 기능을 통해 쉽게 구현할 수 있습니다. 사용자는 리스트 내에서 아이템을 드래그하여 원하는 위치로 이동할 수 있습니다. 이를 위해 OnDragOver와 OnDragDrop 이벤트를 활용합니다.

procedure TfrmExchangeListItem.lvListViewDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
begin
  Accept := Source = Sender;
end;

procedure TfrmExchangeListItem.lvListViewDragDrop(Sender, Source: TObject; X, Y: Integer);
var
  pt: TPoint;
  DropItem: TListItem;
begin
  pt := Point(X, Y);
  DropItem := lvListView.GetItemAt(pt.X, pt.Y);
  if Assigned(DropItem) then
  begin
    ExchangeListItems(lvListView, lvListView.Selected.Index, DropItem.Index);
  end;
end;

위 코드는 드래그된 아이템이 드롭될 위치를 결정하고, 해당 위치에 아이템을 이동시키는 로직을 구현한 것입니다.

 

프로젝트 예제: TListView에서 TListItem의 순서 변경하기

프로젝트 개요

이 프로젝트에서는 TListView에서 TListItem 간의 순서를 변경하는 방법을 실제로 구현해보겠습니다. 이를 통해 TListView와 객체 간의 연동, 커스터마이징, 드래그 앤 드롭 기능을 종합적으로 배울 수 있습니다.

프로젝트 소스 코드

아래는 프로젝트 전체의 소스 코드입니다. TListItem 간의 순서 변경 외에도 리스트뷰와 객체를 어떻게 효율적으로 연동해서 사용하는지 배울 수 있으니, 코드를 살펴보시기 바랍니다.

 
unit uDemoExchangeListItem;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, contnrs, Vcl.ComCtrls, Vcl.StdCtrls;

type
  TSite = class
  private
    FSiteName: String;
    FURL: String;
    FCeo: String;
    FAddress: String;
  end;

  TSiteList = class(TObjectList)
  private
    function _GetSite(nIdx: Integer): TSite;
    property Items[nIdx: Integer]: TSite read _GetSite;
  end;

  TfrmExchangeListItem = class(TForm)
    lvListView: TListView;
    btnUp: TButton;
    btnDown: TButton;
    procedure btnUpClick(Sender: TObject);
    procedure btnDownClick(Sender: TObject);
    procedure lvListViewCustomDrawItem(Sender: TCustomListView; Item: TListItem;
      State: TCustomDrawState; var DefaultDraw: Boolean);
  private
    FSiteList: TSiteList;
    procedure InitSampleData;
    procedure DrawListItem(Item: TListItem; ASite: TSite);
    procedure DrawItems;
  public
    property SiteList: TSiteList read FSiteList write FSiteList;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

var
  frmExchangeListItem: TfrmExchangeListItem;

implementation

{$R *.dfm}

procedure ExchangeListItems(ListView: TListView; const Idx1, Idx2: Integer);
var
  tempLI: TListItem;
begin
  ListView.Items.BeginUpdate;
  try
    tempLI := TListItem.Create(ListView.Items);
    tempLI.Assign(ListView.Items.Item[Idx1]);
    ListView.Items.Item[Idx1].Assign(ListView.Items.Item[Idx2]);
    ListView.Items.Item[Idx2].Assign(tempLI);
    ListView.Selected := ListView.Items[Idx2];
    tempLI.Free;
  finally
    ListView.Items.EndUpdate;
  end;
end;

procedure TfrmExchangeListItem.btnDownClick(Sender: TObject);
begin
  if (lvListView.Selected <> nil) and (lvListView.Selected.Index < lvListView.Items.Count - 1) then
    ExchangeListItems(lvListView, lvListView.Selected.Index, lvListView.Selected.Index + 1);
end;

procedure TfrmExchangeListItem.btnUpClick(Sender: TObject);
begin
  if (lvListView.Selected <> nil) and (lvListView.Selected.Index > 0) then
    ExchangeListItems(lvListView, lvListView.Selected.Index, lvListView.Selected.Index - 1);
end;

constructor TfrmExchangeListItem.Create(AOwner: TComponent);
begin
  inherited;
  FSiteList := TSiteList.Create;
  InitSampleData;
  DrawItems;
end;

destructor TfrmExchangeListItem.Destroy;
begin
  FSiteList.Free;
  inherited;
end;

procedure TfrmExchangeListItem.DrawItems;
var
  i: Integer;
  lt: TListItem;
  siteObj: TSite;
begin
  lvListView.Items.Clear;
  for i := 0 to SiteList.Count - 1 do
  begin
    siteObj := SiteList.Items[i];
    lt := lvListView.Items.Add;
    DrawListItem(lt, siteObj);
  end;
end;

procedure TfrmExchangeListItem.DrawListItem(Item: TListItem; ASite: TSite);
begin
  if Item = nil then
    exit;
  if Item.SubItems.Count = 0 then
  begin
    Item.SubItems.Add('');
    Item.SubItems.Add('');
    Item.SubItems.Add('');
    Item.SubItems.Add('');
  end;
  Item.SubItems[0] := ASite.FSiteName;
  Item.SubItems[1] := ASite.FURL;
  Item.SubItems[2] := ASite.FCeo;
  Item.SubItems[3] := ASite.FAddress;
  Item.Data := ASite;
end;

procedure TfrmExchangeListItem.InitSampleData;
var
  site: TSite;
begin
  SiteList.Clear;
  site := TSite.Create;
  site.FSiteName := '네이버';
  site.FUrl := 'http://www.naver.com';
  site.FCeo := '한성숙';
  site.FAddress := '경기도 성남시 분당구 불정로 6 NAVER그린팩토리';
  SiteList.Add(site);

  site := TSite.Create;
  site.FSiteName := '다음';
  site.FUrl := 'http://www.daum.net';
  site.FCeo := '여민수';
  site.FAddress := '제주특별자치도 제주시 첨단로 242 ';
  SiteList.Add(site);

  site := TSite.Create;
  site.FSiteName := '조선일보';
  site.FUrl := 'http://www.chosun.com/';
  site.FCeo := '방우영';
  site.FAddress := '서울특별시 중구 세종대로21길 30';
  SiteList.Add(site);

  site := TSite.Create;
  site.FSiteName := '경향신문';
  site.FUrl := 'http://www.khan.co.kr';
  site.FCeo := '이동현';
  site.FAddress := '서울시 중구 정동길 3 경향신문사';
  SiteList.Add(site);

  site := TSite.Create;
  site.FSiteName := '한겨레신문';
  site.FUrl := 'http://www.hani.co.kr/';
  site.FCeo := '양상우';
  site.FAddress := '서울특별시 마포구 효창목길 6';
  SiteList.Add(site);
end;

procedure TfrmExchangeListItem.lvListViewCustomDrawItem(Sender: TCustomListView; Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
begin
  if Item = nil then
    exit;
  Item.Caption := IntToStr(Item.Index + 1);
end;

function TSiteList._GetSite(nIdx: Integer): TSite;
begin
  result := TSite(Get(nIdx));
end;

end.

결론

TListView와 TListItem을 효과적으로 관리하고, 객체 간의 데이터를 효율적으로 처리하는 방법을 학습하는 것은 Delphi와 같은 RAD 환경에서 매우 중요합니다. 본 가이드를 통해 기본적인 사용법뿐만 아니라, TListItem의 위치 변경, 객체와의 연동, 커스터마이징, 드래그 앤 드롭 기능을 종합적으로 배울 수 있었습니다. 이러한 기술을 숙달함으로써 더욱 유연하고 사용자 친화적인 애플리케이션을 개발할 수 있을 것입니다.