首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 更多程式設計知識

Delphi OwnerDraw 完全指南:為 ComboBox 與 ListBox 繪製自定義圖文項目

     編輯:更多程式設計知識

標準 TComboBox 與 TListBox 僅支援純文字顯示。若需在下拉項目前加圖示、變更背景、實現分組標題等豐富視覺效果,必須啟用 OwnerDraw (自繪) 機制。本文以完整可編譯範例,詳細講解 csOwnerDrawVariable / lbOwnerDrawVariable 模式下的核心事件處理、資源管理與繪圖技巧。

核心概念:Variable vs Fixed 高度

  • Fixed (csOwnerDrawFixed / lbOwnerDrawFixed):所有項目高度相同,僅需處理 OnDrawItem。
  • Variable (csOwnerDrawVariable / lbOwnerDrawVariable):項目高度可異,必須同時實作 OnMeasureItem 回傳高度,再由 OnDrawItem 繪製。

本範例採用 Variable 模式,展示最高彈性。

完整實作步驟與代碼架構

1. 表單設計與屬性設置

object Form1: TForm1
  object ComboBox1: TComboBox
    Style = csOwnerDrawVariable
    OnDrawItem = ComboBox1DrawItem
    OnMeasureItem = ComboBox1MeasureItem
  end
  object ListBox1: TListBox
    Style = lbOwnerDrawVariable
    OnDrawItem = ListBox1DrawItem
    OnMeasureItem = ListBox1MeasureItem
  end
end

2. 全域圖片資源管理 (FormCreate / FormClose)

var
  TheBitmap1..5: TBitmap;  // 全域變數,避免重複載入

procedure TForm1.FormCreate(Sender: TObject);
begin
  TheBitmap1 := TBitmap.Create; TheBitmap1.LoadFromFile('C:\Delphi\Images\Buttons\globe.bmp');
  TheBitmap2 := TBitmap.Create; TheBitmap2.LoadFromFile('C:\Delphi\Images\Buttons\video.bmp');
  TheBitmap3 := TBitmap.Create; TheBitmap3.LoadFromFile('C:\Delphi\Images\Buttons\gears.bmp');
  TheBitmap4 := TBitmap.Create; TheBitmap4.LoadFromFile('C:\Delphi\Images\Buttons\key.bmp');
  TheBitmap5 := TBitmap.Create; TheBitmap5.LoadFromFile('C:\Delphi\Images\Buttons\tools.bmp');

  // 關鍵:用 Items.AddObject 將圖片物件與文字綁定
  ComboBox1.Items.AddObject('Bitmap1: Globe', TheBitmap1);
  ComboBox1.Items.AddObject('Bitmap2: Video', TheBitmap2);
  ComboBox1.Items.AddObject('Bitmap3: Gears', TheBitmap3);
  ComboBox1.Items.AddObject('Bitmap4: Key', TheBitmap4);
  ComboBox1.Items.AddObject('Bitmap5: Tools', TheBitmap5);

  ListBox1.Items.Assign(ComboBox1.Items);  // 共享同一組資料
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  TheBitmap1.Free; TheBitmap2.Free; TheBitmap3.Free; TheBitmap4.Free; TheBitmap5.Free;
end;

3. 測量項目高度 (OnMeasureItem)

procedure TForm1.ComboBox1MeasureItem(Control: TWinControl; Index: Integer; var Height: Integer);
begin
  Height := 20;  // 統一高度,亦可根據 Index 動態計算
end;

procedure TForm1.ListBox1MeasureItem(Control: TWinControl; Index: Integer; var Height: Integer);
begin
  Height := 20;
end;

4. 核心繪圖邏輯 (OnDrawItem) —— 通用模板

procedure TForm1.ComboBox1DrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
var
  Bmp: TBitmap;
  Offset: Integer;
begin
  with (Control as TComboBox).Canvas do
  begin
    // 1. 清除背景 (處理選中/焦點狀態)
    FillRect(Rect);

    // 2. 取得綁定的圖片物件
    Bmp := TBitmap(ComboBox1.Items.Objects[Index]);
    if Bmp <> nil then
    begin
      // 3. 繪製圖片 (左側留 2px 邊距)
      BrushCopy(
        Bounds(Rect.Left + 2, Rect.Top + 2, Bmp.Width, Bmp.Height),
        Bmp,
        Bounds(0, 0, Bmp.Width, Bmp.Height),
        clRed  // 透明色,視圖片實際情況調整
      );
      Offset := Bmp.Width + 8;  // 文字起始 X 偏移 = 圖寬 + 間距
    end
    else
      Offset := 4;

    // 4. 繪製文字 (垂直置中需自行計算 Top)
    TextOut(Rect.Left + Offset, Rect.Top, ComboBox1.Items[Index]);
  end;
end;

// ListBox 邏輯完全相同,僅將 Control 轉型為 TListBox
procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
var
  Bmp: TBitmap; Offset: Integer;
begin
  with (Control as TListBox).Canvas do
  begin
    FillRect(Rect);
    Bmp := TBitmap(ListBox1.Items.Objects[Index]);
    if Bmp <> nil then
    begin
      BrushCopy(Bounds(Rect.Left+2, Rect.Top+2, Bmp.Width, Bmp.Height), Bmp, Bounds(0,0,Bmp.Width,Bmp.Height), clRed);
      Offset := Bmp.Width + 8;
    end else Offset := 4;
    TextOut(Rect.Left + Offset, Rect.Top, ListBox1.Items[Index]);
  end;
end;

關鍵技術細節解析

Canvas 狀態處理

State: TOwnerDrawState 包含 odSelected、odFocused、odHotLight 等標誌。專業繪製應根據狀態切換筆刷/筆顏色:

if odSelected in State then
  Brush.Color := clHighlight
else
  Brush.Color := clWindow;
FillRect(Rect);

透明色與 BrushCopy

BrushCopy 最後一個參數指定透明色。若圖片已含 Alpha 通道 (32-bit BMP/PNG),建議改用 Draw 或 StretchDraw,或使用 TImageList 統一管理圖片資源。

高 DPI 適配

現代 Delphi (10.3+) 應啟用 PerMonitorV2 DPI 感知。繪圖座標需乘以 Screen.PixelsPerInch / 96,或使用 MulDiv 縮放尺寸。

資源釋放最佳實踐

  • 圖片物件建立於 FormCreate,釋放於 FormClose (Action := caFree 時)
  • 若項目動態增刪,考慮用 TObjectList 統一擁有權管理
  • 避免在 OnDrawItem 內 Create/Free 圖片,嚴重影響效能

進階擴展方向

  1. 多欄位佈局:在 OnDrawItem 內計算多個 TextOut X 座標,實現類報表列表
  2. 漸層背景/圓角:搭配 GradientFill、RoundRect 打造現代 UI
  3. 虛擬模式:海量資料 (10萬+) 時改用 lbVirtual / csVirtual,按需繪製
  4. 動畫/懸停效果:結合 OnMouseMove 追蹤 ItemIndex 重繪熱軌項目

完整單元代碼下載

本文範例包含 Ownerdrw.pas、Ownerdrw.dfm 完整源碼,可直接編入專案編譯運行。建議以此為基礎,封裝為可複用的 TOwnerDrawComboBox / TOwnerDrawListBox 元件庫,提升團隊開發效率。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved