home *** CD-ROM | disk | FTP | other *** search
/ Delphi Anthology / aDELPHI.iso / Runimage / Delphi50 / Source / Vcl / checklst.pas < prev    next >
Pascal/Delphi Source File  |  1999-08-11  |  16KB  |  587 lines

  1.  
  2. {*******************************************************}
  3. {                                                       }
  4. {       Borland Delphi Visual Component Library         }
  5. {                                                       }
  6. {       Copyright (c) 1995,99 Inprise Corporation       }
  7. {                                                       }
  8. {*******************************************************}
  9.  
  10. unit CheckLst;
  11.  
  12. {$T-,H+,X+}
  13.  
  14. interface
  15.  
  16. uses
  17.   Windows, Messages, SysUtils, Classes, Graphics, Controls,
  18.   StdCtrls;
  19.  
  20. type
  21.   TCheckListBox = class(TCustomListBox)
  22.   private
  23.     FAllowGrayed: Boolean;
  24.     FFlat: Boolean;
  25.     FStandardItemHeight: Integer;
  26.     FOnClickCheck: TNotifyEvent;
  27.     FSaveStates: TList;
  28.     procedure ResetItemHeight;
  29.     procedure DrawCheck(R: TRect; AState: TCheckBoxState; AEnabled: Boolean);
  30.     procedure SetChecked(Index: Integer; Checked: Boolean);
  31.     function GetChecked(Index: Integer): Boolean;
  32.     procedure SetState(Index: Integer; AState: TCheckBoxState);
  33.     function GetState(Index: Integer): TCheckBoxState;
  34.     procedure ToggleClickCheck(Index: Integer);
  35.     procedure InvalidateCheck(Index: Integer);
  36.     function CreateWrapper(Index: Integer): TObject;
  37.     function ExtractWrapper(Index: Integer): TObject;
  38.     function GetWrapper(Index: Integer): TObject;
  39.     function HaveWrapper(Index: Integer): Boolean;
  40.     procedure SetFlat(Value: Boolean);
  41.     procedure CNDrawItem(var Message: TWMDrawItem); message CN_DRAWITEM;
  42.     procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;
  43.     procedure WMDestroy(var Msg : TWMDestroy);message WM_DESTROY;
  44.     function GetItemEnabled(Index: Integer): Boolean;
  45.     procedure SetItemEnabled(Index: Integer; const Value: Boolean);
  46.   protected
  47.     procedure DrawItem(Index: Integer; Rect: TRect;
  48.       State: TOwnerDrawState); override;
  49.     function InternalGetItemData(Index: Integer): Longint; override;
  50.     procedure InternalSetItemData(Index: Integer; AData: Longint); override;
  51.     procedure SetItemData(Index: Integer; AData: LongInt); override;
  52.     function GetItemData(Index: Integer): LongInt; override;
  53.     procedure KeyPress(var Key: Char); override;
  54.     procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
  55.       X, Y: Integer); override;
  56.     procedure ResetContent; override;
  57.     procedure DeleteString(Index: Integer); override;
  58.     procedure ClickCheck; dynamic;
  59.     procedure CreateParams(var Params: TCreateParams); override;
  60.     procedure CreateWnd; override;
  61.     procedure DestroyWnd; override;
  62.     function GetCheckWidth: Integer;
  63.   public
  64.     constructor Create(AOwner: TComponent); override;
  65.     destructor Destroy; override;
  66.     property Checked[Index: Integer]: Boolean read GetChecked write SetChecked;
  67.     property ItemEnabled[Index: Integer]: Boolean read GetItemEnabled write SetItemEnabled;
  68.     property State[Index: Integer]: TCheckBoxState read GetState write SetState;
  69.   published
  70.     property OnClickCheck: TNotifyEvent read FOnClickCheck write FOnClickCheck;
  71.     property Align;
  72.     property AllowGrayed: Boolean read FAllowGrayed write FAllowGrayed default False;
  73.     property Anchors;
  74.     property BiDiMode;
  75.     property BorderStyle;
  76.     property Color;
  77.     property Columns;
  78.     property Constraints;
  79.     property Ctl3D;
  80.     property DragCursor;
  81.     property DragKind;
  82.     property DragMode;
  83.     property Enabled;
  84.     property Flat: Boolean read FFlat write SetFlat default True;
  85.     //property ExtendedSelect;
  86.     property Font;
  87.     property ImeMode;
  88.     property ImeName;
  89.     property IntegralHeight;
  90.     property ItemHeight;
  91.     property Items;
  92.     //property MultiSelect;
  93.     property ParentBiDiMode;
  94.     property ParentColor;
  95.     property ParentCtl3D;
  96.     property ParentFont;
  97.     property ParentShowHint;
  98.     property PopupMenu;
  99.     property ShowHint;
  100.     property Sorted;
  101.     property Style;
  102.     property TabOrder;
  103.     property TabStop;
  104.     property TabWidth;
  105.     property Visible;
  106.     property OnClick;
  107.     property OnContextPopup;
  108.     property OnDblClick;
  109.     property OnDragDrop;
  110.     property OnDragOver;
  111.     property OnDrawItem;
  112.     property OnEndDock;
  113.     property OnEndDrag;
  114.     property OnEnter;
  115.     property OnExit;
  116.     property OnKeyDown;
  117.     property OnKeyPress;
  118.     property OnKeyUp;
  119.     property OnMeasureItem;
  120.     property OnMouseDown;
  121.     property OnMouseMove;
  122.     property OnMouseUp;
  123.     property OnStartDock;
  124.     property OnStartDrag;
  125.   end;
  126.  
  127. implementation
  128.  
  129. uses Consts;
  130.  
  131. type
  132.   TCheckListBoxDataWrapper = class
  133.   private
  134.     FData: LongInt;
  135.     FState: TCheckBoxState;
  136.     FDisabled: Boolean;
  137.     procedure SetChecked(Check: Boolean);
  138.     function GetChecked: Boolean;
  139.   public
  140.     class function GetDefaultState: TCheckBoxState;
  141.     property Checked: Boolean read GetChecked write SetChecked;
  142.     property State: TCheckBoxState read FState write FState;
  143.     property Disabled: Boolean read FDisabled write FDisabled;
  144.   end;
  145.  
  146. var
  147.   FCheckWidth, FCheckHeight: Integer;
  148.  
  149. procedure GetCheckSize;
  150. begin
  151.   with TBitmap.Create do
  152.     try
  153.       Handle := LoadBitmap(0, PChar(32759));
  154.       FCheckWidth := Width div 4;
  155.       FCheckHeight := Height div 3;
  156.     finally
  157.       Free;
  158.     end;
  159. end;
  160.  
  161. function MakeSaveState(State: TCheckBoxState; Disabled: Boolean): TObject;
  162. begin
  163.   Result := TObject((Byte(State) shl 16) or Byte(Disabled));
  164. end;
  165.  
  166. function GetSaveState(AObject: TObject): TCheckBoxState;
  167. begin
  168.   Result := TCheckBoxState(Integer(AObject) shr 16);
  169. end;
  170.  
  171. function GetSaveDisabled(AObject: TObject): Boolean;
  172. begin
  173.   Result := Boolean(Integer(AObject) and $FF);
  174. end;
  175.  
  176. { TCheckListBoxDataWrapper }
  177.  
  178. procedure TCheckListBoxDataWrapper.SetChecked(Check: Boolean);
  179. begin
  180.   if Check then FState := cbChecked else FState := cbUnchecked;
  181. end;
  182.  
  183. function TCheckListBoxDataWrapper.GetChecked: Boolean;
  184. begin
  185.   Result := FState = cbChecked;
  186. end;
  187.  
  188. class function TCheckListBoxDataWrapper.GetDefaultState: TCheckBoxState;
  189. begin
  190.   Result := cbUnchecked;
  191. end;
  192.  
  193. { TCheckListBox }
  194.  
  195. constructor TCheckListBox.Create(AOwner: TComponent);
  196. begin
  197.   inherited Create(AOwner);
  198.   FFlat := True;
  199. end;
  200.  
  201. destructor TCheckListBox.Destroy;
  202. begin
  203.   FSaveStates.Free;
  204.   inherited;
  205. end;
  206.  
  207. procedure TCheckListBox.CreateWnd;
  208. begin
  209.   inherited CreateWnd;
  210.   if FSaveStates <> nil then
  211.   begin
  212.     FSaveStates.Free;
  213.     FSaveStates := nil;
  214.   end;
  215.   ResetItemHeight;
  216. end;
  217.     
  218. procedure TCheckListBox.DestroyWnd;
  219. var
  220.   I: Integer;
  221. begin
  222.   if Items.Count > 0 then
  223.   begin
  224.     FSaveStates := TList.Create;
  225.     for I := 0 to Items.Count - 1 do
  226.       FSaveStates.Add(MakeSaveState(State[I], not ItemEnabled[I]));
  227.   end;
  228.   inherited DestroyWnd;
  229. end;
  230.  
  231. procedure TCheckListBox.CreateParams(var Params: TCreateParams);
  232. begin
  233.   inherited;
  234.   with Params do
  235.     if Style and (LBS_OWNERDRAWFIXED or LBS_OWNERDRAWVARIABLE) = 0 then
  236.       Style := Style or LBS_OWNERDRAWFIXED;
  237. end;
  238.     
  239. function TCheckListBox.GetCheckWidth: Integer;
  240. begin
  241.   Result := FCheckWidth + 2;
  242. end;
  243.  
  244. procedure TCheckListBox.CMFontChanged(var Message: TMessage);
  245. begin
  246.   inherited;
  247.   ResetItemHeight;
  248. end;
  249.  
  250. procedure TCheckListBox.ResetItemHeight;
  251. begin
  252.   if HandleAllocated and (Style = lbStandard) then
  253.   begin
  254.     Canvas.Font := Font;
  255.     FStandardItemHeight := Canvas.TextHeight('Wg');
  256.     Perform(LB_SETITEMHEIGHT, 0, FStandardItemHeight);
  257.   end;
  258. end;
  259.     
  260. procedure TCheckListBox.DrawItem(Index: Integer; Rect: TRect;
  261.   State: TOwnerDrawState);
  262. var
  263.   R: TRect;
  264.   SaveEvent: TDrawItemEvent;
  265.   ACheckWidth: Integer;
  266.   Enable: Boolean;
  267. begin
  268.   ACheckWidth := GetCheckWidth;
  269.   if Index < Items.Count then
  270.   begin
  271.     R := Rect;
  272.     if not UseRightToLeftAlignment then
  273.     begin
  274.       R.Right := Rect.Left;
  275.       R.Left := R.Right - ACheckWidth;
  276.     end
  277.     else
  278.     begin
  279.       R.Left := Rect.Right;
  280.       R.Right := R.Left + ACheckWidth;
  281.     end;
  282.     Enable := Self.Enabled and GetItemEnabled(Index);
  283.     DrawCheck(R, GetState(Index), Enable);
  284.     if not Enable then
  285.       Canvas.Font.Color := clGrayText;
  286.   end;
  287.  
  288.   if (Style = lbStandard) and Assigned(OnDrawItem) then
  289.   begin
  290.     { Force lbStandard list to ignore OnDrawItem event. }
  291.     SaveEvent := OnDrawItem;
  292.     OnDrawItem := nil;
  293.     try
  294.       inherited;
  295.     finally
  296.       OnDrawItem := SaveEvent;
  297.     end;
  298.   end
  299.   else
  300.     inherited;
  301. end;
  302.  
  303. procedure TCheckListBox.CNDrawItem(var Message: TWMDrawItem);
  304. begin
  305.   with Message.DrawItemStruct^ do
  306.     if not UseRightToLeftAlignment then
  307.       rcItem.Left := rcItem.Left + GetCheckWidth
  308.     else
  309.       rcItem.Right := rcItem.Right - GetCheckWidth;
  310.   inherited;
  311. end;
  312.  
  313. procedure TCheckListBox.DrawCheck(R: TRect; AState: TCheckBoxState; AEnabled: Boolean);
  314. var
  315.   DrawState: Integer;
  316.   DrawRect: TRect;
  317.   OldBrushColor: TColor;
  318.   OldBrushStyle: TBrushStyle;
  319.   OldPenColor: TColor;
  320.   Rgn, SaveRgn: HRgn;
  321. begin
  322.   SaveRgn := 0;
  323.   DrawRect.Left := R.Left + (R.Right - R.Left - FCheckWidth) div 2;
  324.   DrawRect.Top := R.Top + (R.Bottom - R.Top - FCheckWidth) div 2;
  325.   DrawRect.Right := DrawRect.Left + FCheckWidth;
  326.   DrawRect.Bottom := DrawRect.Top + FCheckHeight;
  327.   case AState of
  328.     cbChecked:
  329.       DrawState := DFCS_BUTTONCHECK or DFCS_CHECKED;
  330.     cbUnchecked:
  331.       DrawState := DFCS_BUTTONCHECK;
  332.     else // cbGrayed
  333.       DrawState := DFCS_BUTTON3STATE or DFCS_CHECKED;
  334.   end;
  335.   if not AEnabled then
  336.     DrawState := DrawState or DFCS_INACTIVE;
  337.   with Canvas do
  338.   begin
  339.     if Flat then
  340.     begin
  341.       { Remember current clipping region }
  342.       SaveRgn := CreateRectRgn(0,0,0,0);
  343.       GetClipRgn(Handle, SaveRgn);
  344.       { Clip 3d-style checkbox to prevent flicker }
  345.       with DrawRect do
  346.         Rgn := CreateRectRgn(Left + 2, Top + 2, Right - 2, Bottom - 2);
  347.       SelectClipRgn(Handle, Rgn);
  348.       DeleteObject(Rgn);
  349.     end;
  350.     DrawFrameControl(Handle, DrawRect, DFC_BUTTON, DrawState);
  351.     if Flat then
  352.     begin
  353.       SelectClipRgn(Handle, SaveRgn);
  354.       DeleteObject(SaveRgn);
  355.       { Draw flat rectangle in-place of clipped 3d checkbox above }
  356.       OldBrushStyle := Brush.Style;
  357.       OldBrushColor := Brush.Color;
  358.       OldPenColor := Pen.Color;
  359.       Brush.Style := bsClear;
  360.       Pen.Color := clBtnShadow;
  361.       with DrawRect do
  362.         Rectangle(Left + 1, Top + 1, Right - 1, Bottom - 1);
  363.       Brush.Style := OldBrushStyle;
  364.       Brush.Color := OldBrushColor;
  365.       Pen.Color := OldPenColor;
  366.     end;
  367.   end;
  368. end;
  369.  
  370. procedure TCheckListBox.SetChecked(Index: Integer; Checked: Boolean);
  371. begin
  372.   if Checked <> GetChecked(Index) then
  373.   begin
  374.     TCheckListBoxDataWrapper(GetWrapper(Index)).SetChecked(Checked);
  375.     InvalidateCheck(Index);
  376.   end;
  377. end;
  378.  
  379. procedure TCheckListBox.SetItemEnabled(Index: Integer; const Value: Boolean);
  380. begin
  381.   if Value <> GetItemEnabled(Index) then
  382.   begin
  383.     TCheckListBoxDataWrapper(GetWrapper(Index)).Disabled := not Value;
  384.     InvalidateCheck(Index);
  385.   end;
  386. end;
  387.  
  388. procedure TCheckListBox.SetState(Index: Integer; AState: TCheckBoxState);
  389. begin
  390.   if AState <> GetState(Index) then
  391.   begin
  392.     TCheckListBoxDataWrapper(GetWrapper(Index)).State := AState;
  393.     InvalidateCheck(Index);
  394.   end;
  395. end;
  396.     
  397. procedure TCheckListBox.InvalidateCheck(Index: Integer);
  398. var
  399.   R: TRect;
  400. begin
  401.   R := ItemRect(Index);
  402.   if not UseRightToLeftAlignment then
  403.     R.Right := R.Left + GetCheckWidth
  404.   else
  405.     R.Left := R.Right - GetCheckWidth;
  406.   InvalidateRect(Handle, @R, not (csOpaque in ControlStyle));
  407.   UpdateWindow(Handle);
  408. end;
  409.     
  410. function TCheckListBox.GetChecked(Index: Integer): Boolean;
  411. begin
  412.   if HaveWrapper(Index) then
  413.     Result := TCheckListBoxDataWrapper(GetWrapper(Index)).GetChecked
  414.   else
  415.     Result := False;
  416. end;
  417.  
  418. function TCheckListBox.GetItemEnabled(Index: Integer): Boolean;
  419. begin
  420.   if HaveWrapper(Index) then
  421.     Result := not TCheckListBoxDataWrapper(GetWrapper(Index)).Disabled
  422.   else
  423.     Result := True;
  424. end;
  425.  
  426. function TCheckListBox.GetState(Index: Integer): TCheckBoxState;
  427. begin
  428.   if HaveWrapper(Index) then
  429.     Result := TCheckListBoxDataWrapper(GetWrapper(Index)).State
  430.   else
  431.     Result := TCheckListBoxDataWrapper.GetDefaultState;
  432. end;
  433.  
  434. procedure TCheckListBox.KeyPress(var Key: Char);
  435. begin
  436.   inherited;
  437.   if (Key = ' ') then ToggleClickCheck(ItemIndex);
  438. end;
  439.     
  440. procedure TCheckListBox.MouseDown(Button: TMouseButton; Shift: TShiftState;
  441.   X, Y: Integer);
  442. var
  443.   Index: Integer;
  444. begin
  445.   inherited;
  446.   if Button = mbLeft then
  447.   begin
  448.     Index := ItemAtPos(Point(X,Y),True);
  449.     if (Index <> -1) and GetItemEnabled(Index) then
  450.       if not UseRightToLeftAlignment then
  451.       begin
  452.         if X - ItemRect(Index).Left < GetCheckWidth then
  453.           ToggleClickCheck(Index)
  454.       end
  455.       else
  456.       begin
  457.         Dec(X, ItemRect(Index).Right - GetCheckWidth);
  458.         if (X > 0) and (X < GetCheckWidth) then
  459.           ToggleClickCheck(Index)
  460.       end;
  461.   end;
  462. end;
  463.  
  464. procedure TCheckListBox.ToggleClickCheck;
  465. var
  466.   State: TCheckBoxState;
  467. begin
  468.   if (Index >= 0) and (Index < Items.Count) and GetItemEnabled(Index) then
  469.   begin
  470.     State := Self.State[Index];
  471.     case State of
  472.       cbUnchecked:
  473.         if AllowGrayed then State := cbGrayed else State := cbChecked;
  474.       cbChecked: State := cbUnchecked;
  475.       cbGrayed: State := cbChecked;
  476.     end;
  477.     Self.State[Index] := State;
  478.     ClickCheck;
  479.   end;
  480. end;
  481.  
  482. procedure TCheckListBox.ClickCheck;
  483. begin
  484.   if Assigned(FOnClickCheck) then FOnClickCheck(Self);
  485. end;
  486.  
  487. function TCheckListBox.GetItemData(Index: Integer): LongInt;
  488. begin
  489.   Result := 0;
  490.   if HaveWrapper(Index) then
  491.     Result := TCheckListBoxDataWrapper(GetWrapper(Index)).FData;
  492. end;
  493.  
  494. function TCheckListBox.GetWrapper(Index: Integer): TObject;
  495. begin
  496.   Result := ExtractWrapper(Index);
  497.   if Result = nil then
  498.     Result := CreateWrapper(Index);
  499. end;
  500.  
  501. function TCheckListBox.ExtractWrapper(Index: Integer): TObject;
  502. begin
  503.   Result := TCheckListBoxDataWrapper(inherited GetItemData(Index));
  504.   if LB_ERR = Integer(Result) then
  505.     raise EListError.CreateResFmt(@SListIndexError, [Index]);
  506.   if (Result <> nil) and (not (Result is TCheckListBoxDataWrapper)) then
  507.     Result := nil;
  508. end;
  509.  
  510. function TCheckListBox.InternalGetItemData(Index: Integer): LongInt;
  511. begin
  512.   Result := inherited GetItemData(Index);
  513. end;
  514.  
  515. procedure TCheckListBox.InternalSetItemData(Index: Integer; AData: LongInt);
  516. begin
  517.   inherited SetItemData(Index, AData);
  518. end;
  519.  
  520. function TCheckListBox.CreateWrapper(Index: Integer): TObject;
  521. begin
  522.   Result := TCheckListBoxDataWrapper.Create;
  523.   inherited SetItemData(Index, LongInt(Result));
  524. end;
  525.  
  526. function TCheckListBox.HaveWrapper(Index: Integer): Boolean;
  527. begin
  528.   Result := ExtractWrapper(Index) <> nil;
  529. end;
  530.  
  531. procedure TCheckListBox.SetItemData(Index: Integer; AData: LongInt);
  532. var
  533.   Wrapper: TCheckListBoxDataWrapper;
  534.   SaveState: TObject;
  535. begin
  536.   Wrapper := TCheckListBoxDataWrapper(GetWrapper(Index));
  537.   Wrapper.FData := AData;
  538.   if FSaveStates <> nil then
  539.     if FSaveStates.Count > 0 then
  540.     begin
  541.       SaveState := FSaveStates[0];
  542.       Wrapper.FState := GetSaveState(SaveState);
  543.       Wrapper.FDisabled := GetSaveDisabled(SaveState);
  544.       FSaveStates.Delete(0);
  545.     end;
  546. end;
  547.  
  548. procedure TCheckListBox.ResetContent;
  549. var
  550.   I: Integer;
  551. begin
  552.   for I := 0 to Items.Count - 1 do
  553.     if HaveWrapper(I) then
  554.       GetWrapper(I).Free;
  555.   inherited;
  556. end;
  557.  
  558. procedure TCheckListBox.DeleteString(Index: Integer);
  559. begin
  560.   if HaveWrapper(Index) then
  561.     GetWrapper(Index).Free;
  562.   inherited;
  563. end;
  564.  
  565. procedure TCheckListBox.SetFlat(Value: Boolean);
  566. begin
  567.   if Value <> FFlat then
  568.   begin
  569.     FFlat := Value;
  570.     Invalidate;
  571.   end;
  572. end;
  573.  
  574. procedure TCheckListBox.WMDestroy(var Msg: TWMDestroy);
  575. var
  576.   i: Integer;
  577. begin
  578.   for i := 0 to Items.Count -1 do
  579.     ExtractWrapper(i).Free;
  580.   inherited;
  581. end;
  582.  
  583. initialization
  584.   GetCheckSize;
  585.  
  586. end.
  587.