用Delphi製做仿每行帶按鈕的列表

  Delphi作程序開發在使用到列表控件時,通常是列表放文本內容,在列表之外放操做按鈕,選中列表某項再點按鈕進行操做。如今Web開發作列表的樣式老是列表的每行都有操做按鈕,如微博的列表風格:ide

  

  Web開發經常使用這種風格,一來是用戶找操做按鈕的移動距離近,二來製做上也不麻煩,不過CS程序開發就不多能找到現成的控件可用了。spa

最近正好要作個相似的控件,雖然不是微博風格,但都是在列表上放按鈕放圖片的樣式,作完以後總結了一下感受列表上放神馬已經都不在話下了,分享一下開發經驗。3d

咱們可使用TListBox控件來完成這個需求,由於當TListBox的style屬性設置爲lbOwnerDrawVariable時,能夠在DrawItem事件中對列表元素作徹底的控制,至關於每一個元素都是一張紙,能夠繪製任意的內容。code

研究階段orm

  雖說能夠繪製任意內容,那要是說純粹去繪製複雜的圖形難度仍是很大的,通過研究總結後發現基本能夠下兩種方式繪製內容:對象

  文字方面的,使用TCanvas直接繪製輸出,好比上面的我的描述區域、按鈕的文字;  blog

  

輸出文字的代碼片斷:
ACanvas.TextOut(Rect.Left + 55,  Rect.Top + 4 + FTxtHght * nRows, sln);
繪製按鈕的代碼片斷:
// 繪製邊框,
// EDGE_RAISED是凸起效果可用於表示按鈕通常狀態
// EDGE_ETCHED是凹進效果表示按下狀態
// 至於鼠標通過狀態,沒有合適的線框可用,能夠將邊框擴大1像素InflateRect(rEdge, 1, 1);
DrawEdge(Canvas.Handle, rEdge, EDGE_RAISED, BF_RECT);
Canvas.FillRect(rBtn);
// 繪製文字
SetBkMode(Canvas.Handle, TRANSPARENT);
DrawText(Canvas.Handle, Caption,
    Length(Caption), rBtn, DT_CENTER + DT_SINGLELINE + DT_VCENTER)

   非文字的,都是先作好圖再用TCanvas把copy過來輸出,好比頭像、按鈕圖標,若是按鈕要有背景色也是圖片好些;   事件

繪製圖片的代碼片斷:
// 繪製圖片,若是圖片要自適應大小可使用StrechDraw方法
Canvas.Draw(rEdge.Left, rEdge.Top, NormalPicture.Graphic);

  

能夠將繪製按鈕和圖片封裝成一些類,我封裝了一些TdrawUI系列的類並放到名爲U_DrawUI的單元。圖片

 

         瞭解了以上兩個方式後,剩下的就是在TListBox的事件中寫控制代碼了。開發

咱們須要作的功能能夠列舉以下:

l  列表增長元素時每一個元素顯示頭像和操做按鈕

l  操做按鈕在鼠標通過時、鼠標點擊時有按鈕效果

l  列表每一個元素的文字,名稱用粗體字,附帶我的介紹用非粗體字,文字要自動折行

l   

l  每一個元素之間有分割線,線條兩邊不要頂到邊框

幹活階段

咱們建立一個窗體工程,增長一個TListBox控件命名爲lst1,另外至少包含一個對列表增長元素的Add按鈕

         在lst1的OnDrawItem事件中繪製頭像、按鈕、分割線,另外要在OnMeasureItem事件中計算一下每行的高度。代碼以下:              

procedure TForm1.lst1DrawItem(Control: TWinControl; Index: Integer; Rect: TRect;
  State: TOwnerDrawState);
var
  sTmp,sln:string;
  nRows,I,iPos,iPosEnd,iLen, nWidth:Integer; //,nEnterTimes
  lst: TListBox;
  ACanvas: TCanvas;
  lineRect, iconRect: TRect;
  btn1, btn2: TDrawUIButton;
  hitPoint: TPoint;
  iconHead: TPicture;
begin
  lst := TListBox(Control);
  ACanvas := TListBox(Control).Canvas;

  nWidth := lst.Width - 170;
  ACanvas.FillRect(Rect);
  // 每一個元素之間畫一條分割線
  lineRect := Classes.Rect(Rect.Left, Rect.Bottom - 1, Rect.Right, Rect.Bottom);
  lst.Canvas.Pen.Width := 1;
  lst.Canvas.Pen.Color := $F5F2F2;
  lst.Canvas.MoveTo(lineRect.Left + 10, lineRect.Top);
  lst.Canvas.LineTo(lineRect.Right - 10, lineRect.Top);

  iconHead := TPicture.Create;
  if index mod 2 = 0 then
    IconHead.LoadFromFile(ExtractFilePath(Application.ExeName) + 'butt_png\I_like_buttons_022.png')
  else
    IconHead.LoadFromFile(ExtractFilePath(Application.ExeName) + 'butt_png\I_like_buttons_023.png');
  SetBkMode(lst.Canvas.Handle, TRANSPARENT);
  iconRect := Classes.Rect(Rect.Left + 2, Rect.Top + 10, Rect.Left + 50, Rect.Top + 58);
  lst.Canvas.StretchDraw(iconRect, iconHead.Graphic);
  iconHead.Free;
  nRows := 0;

  // 輸出標題
  sln := '我是一個用戶';
  ACanvas.Font.Name := '微軟雅黑';
  ACanvas.Font.Size := 10;
  ACanvas.Font.Style := ACanvas.Font.Style + [fsBold];
  ACanvas.TextOut(Rect.Left + 55,  Rect.Top + 4 + FTxtHght * nRows, sln);
  ACanvas.Font.Style := ACanvas.Font.Style - [fsBold];
  Inc(nRows);

  // 輸出內容
  sTmp:=WrapText(ACanvas, lst.Items[index], nWidth);
  ACanvas.Font.Size := 9;
  while true do
  begin
    I := Pos(#10,sTmp);
    if I <> 0 then
    begin
      sln := Copy(sTmp,1,I-1);
      sTmp := Copy(sTmp,I+1,Length(sTmp));
      ACanvas.TextOut(Rect.Left + 55,  Rect.Top + 8 + FTxtHght * nRows, sln);
      Inc(nRows);
    end
    else begin
      if Length(sTmp) <> 0 then
      begin
        ACanvas.TextOut(Rect.Left + 55,  Rect.Top + 8 + FTxtHght * nRows, sln);
        Inc(nRows);
      end;
      System.Break;
    end;
  end;

  hitPoint := lst.ScreenToClient(Mouse.CursorPos);

  // add button1
  btn1 := TDrawUIButton.Create(Self);
  btn1.Left := Rect.Right - 120;
  btn1.Top := Rect.Top + 20;
  btn1.Width := 68;
  btn1.Height := 20;
  btn1.Caption := '關注';
  btn1.Color := clWhite;
  btn1.Font.Color := clBlack;
  btn1.Icon.LoadFromFile(ExtractFilePath(Application.ExeName) + 'butt_png\check.png');
  FBtns.AddObject(Format('%d_%d', [index, 1]), btn1);
  btn1.Draw(lst.Canvas, BUTTON_DRAW_NORMAL);

  // add button 2
  btn2 := TDrawUIButton.Create(Self);
  btn2.Left := Rect.Right - 120 + btn1.Width + 3;
  btn2.Top := Rect.Top + 20;
  btn2.Width := 36;
  btn2.Height := 20;
  btn2.Caption := '更多';
  btn2.Color := clWhite;
  btn2.Font.Color := clBlack;
  FBtns.AddObject(Format('%d_%d', [index, 2]), btn2);
  btn2.Draw(lst.Canvas, BUTTON_DRAW_NORMAL);
end;


procedure TForm1.lst1MeasureItem(Control: TWinControl; Index: Integer;
  var Height: Integer);
var
  sTmp:string;
  nRows, nWidth: Integer;
  lst: TListBox;
begin
  lst := TListBox(Control);
  nWidth := lst.Width - 170;
  nRows := 0;
  sTmp:=WrapText(lst.Canvas, lst.Items[index], nWidth);
  nRows := nRows + GetLineCount(sTmp);
  Height:= FTxtHght*nRows + 30;
end;

  

  在OnDrawItem畫出的東西就已經具有咱們需求中的模樣了,只是按鈕在鼠標操做時不會有變化,咱們須要讓按鈕在鼠標通過、鼠標點擊時候按鈕樣式有變化,且要能響應點擊事件。

      在OnMouseDown事件中將按鈕重繪爲按下狀態

  

procedure TForm1.lst1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  btn, btnHit: TDrawUIButton;
  btnRect: TRect;
  pt: TPoint;
  lst: TListBox;
  lstIndex, btnIndex: Integer;
begin
  lst := TListBox(Sender);
  pt := Classes.Point(X, Y);
  lstIndex := lst.ItemAtPos(pt, True);
  if lstIndex = -1 then
    Exit;

  btnHit := nil;
  btnIndex := FBtns.IndexOf(Format('%d_%d', [lstIndex, 1]));
  btn := TDrawUIButton(FBtns.Objects[btnIndex]);
  btnRect := btn.EdgeRect;
  // 點了第一個button
  if PtInRect(btnRect, pt) then begin
    btnHit := btn;
  end;

  if not Assigned(btnHit) then begin
    btnIndex := FBtns.IndexOf(Format('%d_%d', [lstIndex, 2]));
    btn := TDrawUIButton(FBtns.Objects[btnIndex]);
    btnRect := btn.EdgeRect;
    // 點了第二個button
    if PtInRect(btnRect, pt) then begin
      btnHit := btn;
    end;
  end;

  // 鼠標按下效果
  if Assigned(btnHit) then
    btnHit.Draw(lst.Canvas, BUTTON_DRAW_CLICK);
end;

  在OnMouseUp事件繪製按鈕彈起效果,並觸發點擊事件,點擊事件要在初始化按鈕的時候賦值,代碼以下:

  

省略掉判斷鼠標所在按鈕的代碼。。。
 // 鼠標彈起效果
  if Assigned(btnHit) then begin
    btnHit.Draw(lst.Canvas, BUTTON_DRAW_NORMAL);
    // 在鼠標按鍵放開時觸發點擊事件
    if Assigned(btnHit.OnClick) then begin
      btnHit.OnClick(btnHit);
    end;
  end;

  還有,在OnMouseMove事件繪製鼠標變亮的效果,

  

省略掉判斷鼠標所在按鈕的代碼。。。
  // 通過第一個button,第二個button的代碼也省略,實際上每行應維護一個按鈕List,示例代碼略過。
  if PtInRect(btnRect, pt) then begin
    btn.Draw(lst.Canvas, BUTTON_DRAW_HOVER)
  end else begin
    btn.Draw(lst.Canvas, BUTTON_DRAW_NORMAL)
  end;

  運行效果如圖:

  

  繪製工做大體到這裏,要繼續美化樣式,最好按鈕也使用圖片來畫,好比關注按鈕的圖片自帶對號會更好。

 

  附:

     U_DrawUI.pas代碼

   

unit U_DrawUI;

{ 用於在界面繪製控件UI時的數據對象
  author: edhn
}

interface

uses
  Generics.Collections, Windows, Forms, ComCtrls, Controls, Classes,
  Types, Messages, Graphics, ExtCtrls, SysUtils, StdCtrls, Buttons;

const
  BUTTON_DRAW_NORMAL = 1;
  BUTTON_DRAW_HOVER = 2;
  BUTTON_DRAW_CLICK = 3;

type
  TDrawUIBaseControl = class
  private
    FOwner: TObject;
    FLeft: Integer;
    FTop: Integer;
    FWidth: Integer;
    FHeight: Integer;
    FColor: TColor;
    FHint: String;
    FEnabled: Boolean;
    FVisbile: Boolean;

    function GetBrushRect: TRect;
    function GetEdgeRect: TRect;
    procedure SetEdgeRect(value: TRect);
  protected
    procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
  public
    property Owner: TObject read FOwner write FOwner;
    property Left: Integer read FLeft write FLeft;
    property Top: Integer read FTop write FTop;
    property Width: Integer read FWidth write FWidth;
    property Height: Integer read FHeight write FHeight;
    property Color: TColor read FColor write FColor;
    property Hint: String read FHint write FHint;
    property Enabled: Boolean read FEnabled write FEnabled;
    property Visbile: Boolean read FVisbile write FVisbile;
    property BrushRect: TRect read GetBrushRect;
    property EdgeRect: TRect read GetEdgeRect write SetEdgeRect;

    constructor Create();overload; virtual;
    constructor Create(Owner: TObject);overload; virtual;
    destructor Destroy();override;

    procedure Draw(Canvas: TCanvas; param: Integer);virtual; abstract;
  end;

  { TDrawUIButton }
  TDrawUIButton = class(TDrawUIBaseControl)
  private
    FCaption: String;
    FFont: TFont;
    FEnabled: Boolean;
    FOnClick: TNotifyEvent;
    FNormalPicture: TPicture;
    FHoverPicture: TPicture;
    FClickPicture: TPicture;
    FDisablePicture: TPicture;
    FDrawState: TButtonState;
    FIcon: TPicture;
  public
    MouseOnButton: Boolean;
    property Caption: String read FCaption write FCaption;
    property Font: TFont read FFont write FFont;
    property Enabled: Boolean read FEnabled write FEnabled;
    property Icon: TPicture read FIcon write FIcon;
    property NormalPicture: TPicture read FNormalPicture;
    property HoverPicture: TPicture read FHoverPicture;
    property ClickPicture: TPicture read FClickPicture;
    property DisablePicture: TPicture read FDisablePicture;
    property DrawState: TButtonState read FDrawState;
    property OnClick: TNotifyEvent read FOnClick write FOnClick;

    constructor Create(Owner: TObject);override;
    destructor Destroy();override;

    procedure Draw(Canvas: TCanvas; param: Integer);override;
  end;

  { TDrawUIImage }
  TDrawUIImage = class(TDrawUIBaseControl)
  private
    FImage: TImage;
  public
    property Image: TImage read FImage write FImage;

    constructor Create(Owner: TObject);override;
    destructor Destroy();override;

    procedure Draw(Canvas: TCanvas; param: Integer);override;
  end;

implementation

{ TDrawBaseControl }

constructor TDrawUIBaseControl.Create(Owner: TObject);
begin
  FOwner := Owner;
  FEnabled := True;
  FVisbile := True;
end;

constructor TDrawUIBaseControl.Create;
begin
  FEnabled := True;
  FVisbile := True;
end;

destructor TDrawUIBaseControl.Destroy;
begin

  inherited;
end;

function TDrawUIBaseControl.GetBrushRect: TRect;
begin
  Result.Left := Left + 1;
  Result.Top := Top + 1;
  Result.Right := Left + Width - 1;
  Result.Bottom := Top + Height - 1;
end;

function TDrawUIBaseControl.GetEdgeRect: TRect;
begin
  Result.Left := Left;
  Result.Top := Top;
  Result.Right := Left + Width;
  Result.Bottom := Top + Height;
end;

procedure TDrawUIBaseControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
  FLeft := ALeft;
  FTop := ATop;
  FWidth := AWidth;
  FHeight := AHeight;
end;

procedure TDrawUIBaseControl.SetEdgeRect(value: TRect);
begin
  with value do
    SetBounds(Left, Top, Right - Left, Bottom - Top);
end;

{ TDrawButton }

constructor TDrawUIButton.Create(Owner: TObject);
begin
  inherited Create(Owner);
  FFont := TFont.Create;
  FEnabled := True;
  FIcon := TPicture.Create;
  FNormalPicture := TPicture.Create;
  FHoverPicture := TPicture.Create;
  FClickPicture := TPicture.Create;
  FDisablePicture := TPicture.Create;
end;

destructor TDrawUIButton.Destroy;
begin
  FFont.Free;
  FIcon.Free;
  FNormalPicture.Free;
  FHoverPicture.Free;
  FClickPicture.Free;
  FDisablePicture.Free;
  inherited;
end;

procedure TDrawUIButton.Draw(Canvas: TCanvas; param: Integer);
var
  rBtn, rEdge, iconRect: TRect;
begin
  rBtn := BrushRect;
  rEdge := Self.EdgeRect;
  iconRect := Classes.Rect(0, 0, 0, 0);
  if Assigned(FIcon.Graphic) and (not FIcon.Graphic.Empty) then begin
    iconRect := Classes.Rect(rEdge.Left + 2, rEdge.Top + 1,
      rEdge.Left + Self.Height - 2, rEdge.Top + Self.Height - 1);
    Canvas.StretchDraw(iconRect, FIcon.Graphic);
  end;

  rBtn.Left := rBtn.Left + RectWidth(iconRect);
  if not Enabled then begin
    Canvas.Brush.Color := $F4F4F4;
    if Assigned(DisablePicture.Graphic) and (not DisablePicture.Graphic.Empty) then begin
      Canvas.Draw(rEdge.Left, rEdge.Top, DisablePicture.Graphic);
    end else begin
      DrawEdge(Canvas.Handle, rEdge, EDGE_RAISED, BF_RECT);
      Canvas.FillRect(rBtn);
    end;
  end else begin
     Canvas.Brush.Color := Color;
     if param = BUTTON_DRAW_CLICK then begin
      if Assigned(ClickPicture.Graphic) and (not ClickPicture.Graphic.Empty) then begin
        Canvas.Draw(rEdge.Left, rEdge.Top, ClickPicture.Graphic);
      end else begin
        DrawEdge(Canvas.Handle, rEdge, EDGE_ETCHED, BF_RECT);
        Canvas.FillRect(rBtn);
      end;
    end else if param = BUTTON_DRAW_HOVER then begin
      if Assigned(HoverPicture.Graphic) and (not HoverPicture.Graphic.Empty) then begin
        Canvas.Draw(rEdge.Left, rEdge.Top, HoverPicture.Graphic);
      end else begin
        InflateRect(rEdge, 1, 1);
        DrawEdge(Canvas.Handle, rEdge, EDGE_RAISED, BF_RECT);
        Canvas.FillRect(rBtn);
      end;
    end else begin
      if Assigned(NormalPicture.Graphic) and (not NormalPicture.Graphic.Empty) then begin
        Canvas.Draw(rEdge.Left, rEdge.Top, NormalPicture.Graphic);
      end else begin
        DrawEdge(Canvas.Handle, rEdge, EDGE_RAISED, BF_RECT);
        Canvas.FillRect(rBtn);
      end;
    end;
  end;

  if Enabled then
    Canvas.Font.Color := Self.Font.Color
  else
    Canvas.Font.Color := clGrayText;
  Canvas.Font.Name := '微軟雅黑';
  Canvas.Font.Size := 9;
  SetBkMode(Canvas.Handle, TRANSPARENT);
  DrawText(Canvas.Handle, Caption,
    Length(Caption), rBtn, DT_CENTER + DT_SINGLELINE + DT_VCENTER);
end;

{ TDrawImage }

constructor TDrawUIImage.Create(Owner: TObject);
begin
  inherited Create(Owner);
  FImage := TImage.Create(nil);
end;

destructor TDrawUIImage.Destroy;
begin
  FImage.Free;
  inherited;
end;

procedure TDrawUIImage.Draw(Canvas: TCanvas; param: Integer);
begin
  Canvas.Draw(Left, Top, Image.Picture.Bitmap);
end;

end.
相關文章
相關標籤/搜索