본문 바로가기

Delphi/끄적이기

[TPageControl] Tab 수동으로 그리기

기본 컴포넌트만 써야하는 상황에서

PageControl의 Tab 모양에 변화를 주기위해 Custom으로 그려보았습니다.

 

uses
  .., Vcl.ComCtrls, CommCtrl;

 

type
  TCheckBoxInfo = record
    Pos: TPoint;
    Width: Integer;
    Height: Integer;
    Checked: Boolean;
  end;

  //Developist Custom PageContorl
  TPageControl = class(Vcl.ComCtrls.TPageControl)
  private
    FCheckBoxInfo: TCheckBoxInfo;
  protected
    //TabControl BackGround
    procedure PaintWindow(DC: HDC); override;
    //Tab을 그리는 Message를 받아서 처리
    procedure CNDrawitem(var Message: TWMDrawItem); message CN_DRAWITEM;
    //Border를 그리는 Message를 받아서 처리
    procedure TCMAdjustRect(var Msg: TMessage); message TCM_ADJUSTRECT;
  public
    constructor Create(AOwner: TComponent); override;
  end;
  
type
  TForm1 = class(TForm)
    PageControl1: TPageControl;
    TabSheet1: TTabSheet;
    TabSheet2: TTabSheet;
    TabSheet3: TTabSheet;
    procedure PageControl1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  
implementation

 

{ Custom TPageControl }

procedure TPageControl.PaintWindow(DC: HDC);
begin
  inherited PaintWindow(DC);

  //Custom이 아니면 기본 Style 적용
  if not OwnerDraw then Exit;

  //TabControl의 Color를 흰색으로 변경(Tab Color가 아님)
  FCanvas.Handle := DC;
  FCanvas.Brush.Color := clWhite;
  FCanvas.FillRect(Self.ClientRect);
end;

procedure TPageControl.CNDrawitem(var Message: TWMDrawItem);
var
  Points: array of TPoint;
  Rect: TRect;
  Rgn: HRGN;
begin
  //Tab 영역 가져오기
  SelectClipRgn(Message.DrawItemStruct.hDC, 0);

  Rect := Message.DrawItemStruct.rcItem;

  //Default Tab 그리기(다각형 모양)
  SetLength(Points, 0);
  SetLength(Points, 4);

  Points[0] := Rect.TopLeft;
  Points[1].X := Points[0].X + Rect.Width - 10;
  Points[0].X := Points[0].X + 10;
  Points[1].Y := Points[0].Y;
  Points[2] := Rect.BottomRight;
  Points[3].X := Points[2].X - Rect.Width;
  Points[3].Y := Points[2].Y;

  SetDCBrushColor(Message.DrawItemStruct.hDC, clSilver);

  Rgn := CreatePolygonRgn(Points[0], 4, ALTERNATE);
  FillRgn(Message.DrawItemStruct.hDC, Rgn, GetStockObject(DC_BRUSH));
  
  //Default Tab 그리기(기본 사각형 모양)
  //FillRect(Message.DrawItemStruct.hDC, Rect, GetStockObject(DC_BRUSH));  

  //선택된 Tab은 Highlight 처리
  if Bool(Message.DrawItemStruct.itemState and ODS_SELECTED) then begin
    SetDCBrushColor(Message.DrawItemStruct.hDC, clWhite);

    SetROP2(Message.DrawItemStruct.hDC, R2_COPYPEN);

    SetLength(Points, 0);
    SetLength(Points, 4);

    Rect.TopLeft.X := Rect.TopLeft.X + 1;
    Rect.TopLeft.Y := Rect.TopLeft.Y + 1;
    Rect.BottomRight.X := Rect.BottomRight.X - 1;
    Rect.BottomRight.Y := Rect.BottomRight.Y;

    Points[0].X := Rect.Left + 10;
    Points[0].Y := Rect.Top;
    Points[1].X := Rect.Right - 10;
    Points[1].Y := Rect.Top;
    Points[2] := Rect.BottomRight;
    Points[3].X := Points[2].X - Rect.Width;
    Points[3].Y := Points[2].Y;

    Rgn := CreatePolygonRgn(Points[0], 4, ALTERNATE);
    FillRgn(Message.DrawItemStruct.hDC, Rgn, GetStockObject(DC_BRUSH));
  end;

  //Tab Caption 그리기
  if Self.Pages[Message.DrawItemStruct.itemID].Tag = 1 then begin
    if Bool(Message.DrawItemStruct.itemState and ODS_SELECTED) then
      Rect.Top := Rect.Top + 6
    else
      Rect.Top := Rect.Top + 5
  end else begin
    if Bool(Message.DrawItemStruct.itemState and ODS_SELECTED) then
      Rect.Top := (Rect.Height + Canvas.Font.Height) div 2
    else
      Rect.Top := (Rect.Height + 5 + Canvas.Font.Height) div 2;
  end;

  SetBkMode(Message.DrawItemStruct.hDC, TRANSPARENT);
  DrawText(
    Message.DrawItemStruct.hDC,
    Self.Pages[Message.DrawItemStruct.itemID].Caption,
    Length(Self.Pages[Message.DrawItemStruct.itemID].Caption),
    Rect,
    DT_CENTER
  );
//  TextOut(
//    Message.DrawItemStruct.hDC,
//    (Rect.Left - Length(Self.Pages[Message.DrawItemStruct.itemID].Caption)) div 2,
//    Rect.Top + 5,
//    PWideChar(Self.Pages[Message.DrawItemStruct.itemID].Caption),
//    Length(Self.Pages[Message.DrawItemStruct.itemID].Caption)
//  );

  //TabSheet의 Tag가 1이면 CheckBox 그리기
  if Self.Pages[Message.DrawItemStruct.itemID].Tag = 1 then begin
    Rect.Top := Rect.Top + 15;

    if Bool(Message.DrawItemStruct.itemState and ODS_SELECTED) then
      Rect.Left := Message.DrawItemStruct.rcItem.Left + 18
    else
      Rect.Left := Message.DrawItemStruct.rcItem.Left + 14;

    Rect.Width := 14;
    Rect.Height := 14;

    FCheckBoxInfo.Pos := Rect.TopLeft;
    FCheckBoxInfo.Width := Rect.Width;
    FCheckBoxInfo.Height := Rect.Height;

    if FCheckBoxInfo.Checked then
      DrawFrameControl(
        Message.DrawItemStruct.hDC, 
        Rect, 
        DFC_BUTTON, 
        DFCS_BUTTONCHECK or DFCS_CHECKED
      )
    else
      DrawFrameControl(
        Message.DrawItemStruct.hDC, 
        Rect, 
        DFC_BUTTON, 
        DFCS_BUTTONCHECK
      );

    Rect.Left := Rect.Left + 18;
    Rect.Width := Message.DrawItemStruct.rcItem.Right - 10;
    DrawText(
      Message.DrawItemStruct.hDC,
      'CheckBoxCaption',
      Length('CheckBoxCaption'),
      Rect,
      DT_LEFT
    );
  end;

  //더 이상 컨트롤하지 못하게 삭제처리
  Rgn := CreateRectRgn(0, 0, 0, 0);
  SelectClipRgn(Message.DrawItemStruct.hDC, Rgn);
  DeleteObject(Rgn);

  Message.Result := 1;

  inherited;
end;

constructor TPageControl.Create(AOwner: TComponent);
begin
  inherited;

  //CheckBox 정보
  FCheckBoxInfo.Pos := Point(0, 0);
  FCheckBoxInfo.Width := 14;
  FCheckBoxInfo.Height := 14;
  FCheckBoxInfo.Checked := False;
end;

procedure TPageControl.TCMAdjustRect(var Msg: TMessage);
begin
  inherited;

  //Custom이 아니면 기본 Style 적용
  if not OwnerDraw then Exit;

  //Border
  if Msg.WParam = 0 then begin
    with PRect(Msg.LParam)^ do begin
      Left := 0;
      Right := ClientWidth;
      Top := Top - 4;
      Bottom := ClientHeight;
    end;
  end;
end;

 

procedure TForm1.FormCreate(Sender: TObject);
begin
  //OwnerDraw가 True일 때만 적용됨.
  PageControl1.OwnerDraw := True;
end;

procedure TForm1.PageControl1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  //CheckBox 영역 확인
  if ((X >= TPageControl(Sender).FCheckBoxInfo.Pos.X) and
      (X <= TPageControl(Sender).FCheckBoxInfo.Pos.X + TPageControl(Sender).FCheckBoxInfo.Width)) and
     ((Y >= TPageControl(Sender).FCheckBoxInfo.Pos.Y) and
      (Y <= TPageControl(Sender).FCheckBoxInfo.Pos.Y + TPageControl(Sender).FCheckBoxInfo.Height))
  then begin
    TPageControl(Sender).FCheckBoxInfo.Checked := not TPageControl(Sender).FCheckBoxInfo.Checked;
    TPageControl(Sender).Refresh;
  end;
end;

 

반응형