Sources
Delphi Russian Knowledge Base
DRKB - это самая большая и удобная в использовании база знаний по Дельфи в рунете, составленная Виталием Невзоровым

Расширяем возможности кнопок в Delphi

01.01.2007

Автор: Maarten de Haan

Пример показывает, как сделать кнопку с тремя состояниями. В обычном состоянии она сливается с формой. При наведении на такую кнопку курсором мышки, она становится выпуклой. Ну и, соотвественно, при нажатии, кнопка становится вогнутой.

Также можно создать до 4-х изображений для индикации состояния кнопки

             <--------- Ширина --------->

             +------+------+-----+------+    ^

             |Курсор|Курсор|нажа-|недос-|    |

             |на кно|за пре| та  |тупна |  Высота

             | пке  |делами|     |      |    |

             +------+------+-----+------+    v

Вы так же можете присвоить кнопке текстовый заголовок. Можно расположить текст и изображение в любом месте кнопки. Для этого в пример добавлены четыре свойства:

TextTop и TextLeft, Для расположения текста заголовка на кнопке,

и:

GlyphTop и GlyphLeft, Для расположения Glyph на кнопке.

Текст заголовка прорисовывается после изображения, потому что они используют одно пространство кнопки, и соответственно заголовок прорисуется поверх изображения. Бэкграунд текста сделан прозрачным. Соответственно мы увидим только текстовые символы поверх изображения.

Найденные баги

----------

1) Если двигать мышку очень быстро, то кнопка может не вернуться в исходное состояние

2) Если кнопка находится в запрещённом состоянии, то при нажатии на неё, будет наблюдаться неприятное мерцание.

Unit NewButton; 
 
Interface 
 
Uses 
  Windows, Messages, SysUtils, Classes, Graphics, Controls, 
  Forms, Dialogs; 
 
Const 
   fShift = 2; // Изменяем изображение и заголовок , когда кнопка нажата.
   fHiColor = $DDDDDD; // Цвет нажатой кнопки (светло серый) 
               // Windows создаёт этот цвет путём смешивания пикселей clSilver и clWhite (50%). 
               // такой цвет хорошо выделяет нажатую и отпущенную кнопки.
 
Type 
  TNewButton = Class(TCustomControl) 
  Private 
    { Private declarations } 
    fMouseOver,fMouseDown              : Boolean; 
    fEnabled                          : Boolean; 
                                      // То же, что и всех компонент   
    fGlyph                            : TPicture; 
                                      // То же, что и в SpeedButton 
    fGlyphTop,fGlyphLeft              : Integer; 
                                      // Верх и лево Glyph на изображении кнопки
    fTextTop,fTextLeft                : Integer; 
                                      // Верх и лево текста на изображении кнопки 
    fNumGlyphs                        : Integer; 
                                      // То же, что и в SpeedButton 
    fCaption                          : String; 
                                      // Текст на кнопке 
    fFaceColor                        : TColor; 
                                      // Цвет изображения (да-да, вы можете задавать цвет изображения кнопки 
 
    Procedure fLoadGlyph(G : TPicture); 
    Procedure fSetGlyphLeft(I : Integer); 
    Procedure fSetGlyphTop(I : Integer); 
    Procedure fSetCaption(S : String); 
    Procedure fSetTextTop(I : Integer); 
    Procedure fSetTextLeft(I : Integer); 
    Procedure fSetFaceColor(C : TColor); 
    Procedure fSetNumGlyphs(I : Integer); 
    Procedure fSetEnabled(B : Boolean); 
 
  Protected 
    { Protected declarations } 
    Procedure Paint; override; 
    Procedure MouseDown(Button: TMouseButton; Shift: TShiftState; 
      X, Y: Integer); override; 
    Procedure MouseUp(Button: TMouseButton; Shift: TShiftState; 
      X, Y: Integer); override; 
    Procedure WndProc(var Message : TMessage); override; 
    // Таким способом компонент определяет - находится ли курсор мышки на нём или нет
    // Если курсор за пределами кнопки, то она всё равно продолжает принимать сообщения мышки.
    // Так же кнопка будет принимать сообщения, если на родительском окне нет фокуса. 
 
  Public 
    { Public declarations } 
    Constructor Create(AOwner : TComponent); override; 
    Destructor Destroy; override; 
 
  Published 
    { Published declarations } 
    {----- Properties -----} 
    Property Action; 
    // Property AllowUp не поддерживается 
    Property Anchors; 
    Property BiDiMode; 
    Property Caption : String 
       read fCaption write fSetCaption; 
    Property Constraints; 
    Property Cursor; 
    // Property Down не поддерживается 
    Property Enabled : Boolean 
       read fEnabled write fSetEnabled; 
    // Property Flat не поддерживается 
    Property FaceColor : TColor 
       read fFaceColor write fSetFaceColor; 
    Property Font; 
    property Glyph : TPicture // Такой способ позволяет получить серую кнопку, которая сможет
                              //   находиться в трёх положениях. 
                              // После нажатия на кнопку, с помощью редактора картинок Delphi 
                              // можно будет создать картинки для всех положений кнопки.. 
       read fGlyph write fLoadGlyph; 
    // Property GroupIndex не поддерживается 
    Property GlyphLeft : Integer 
       read fGlyphLeft write fSetGlyphLeft; 
    Property GlyphTop : Integer 
       read fGlyphTop write fSetGlyphTop; 
    Property Height; 
    Property Hint; 
    // Property Layout не поддерживается 
    Property Left; 
    // Property Margin не поддерживается 
    Property Name; 
    Property NumGlyphs : Integer 
       read fNumGlyphs write fSetNumGlyphs; 
    Property ParentBiDiMode; 
    Property ParentFont; 
    Property ParentShowHint; 
    // Property PopMenu не поддерживается 
    Property ShowHint; 
    // Property Spacing не поддерживается 
    Property Tag; 
    Property Textleft : Integer 
       read fTextLeft write fSetTextLeft; 
    Property TextTop : Integer 
       read fTextTop write fSetTextTop; 
 
    Property Top; 
    // Property Transparent не поддерживается 
    Property Visible; 
    Property Width; 
    {--- События ---} 
    Property OnClick; 
    Property OnDblClick; 
    Property OnMouseDown; 
    Property OnMouseMove; 
    Property OnMouseUp; 
  end; 
 
Procedure Register; // Hello 
 
Implementation 
 
{--------------------------------------------------------------------} 
Procedure TNewButton.fSetEnabled(B : Boolean); 
 
Begin 
If B <> fEnabled then 
   Begin 
   fEnabled := B; 
   Invalidate; 
   End; 
End; 
{--------------------------------------------------------------------} 
Procedure TNewButton.fSetNumGlyphs(I : Integer); 
 
Begin 
If I > 0 then 
   If I <> fNumGlyphs then 
      Begin 
      fNumGlyphs := I; 
      Invalidate; 
      End; 
End; 
{--------------------------------------------------------------------} 
Procedure TNewButton.fSetFaceColor(C : TColor); 
 
Begin 
If C <> fFaceColor then 
   Begin 
   fFaceColor := C; 
   Invalidate; 
   End; 
End; 
{--------------------------------------------------------------------} 
Procedure TNewButton.fSetTextTop(I : Integer); 
 
Begin 
If I >= 0 then 
   If I <> fTextTop then 
      Begin 
      fTextTop := I; 
      Invalidate; 
      End; 
End; 
{--------------------------------------------------------------------} 
Procedure TNewButton.fSetTextLeft(I : Integer); 
 
Begin 
If I >= 0 then 
   If I <> fTextLeft then 
      Begin 
      fTextLeft := I; 
      Invalidate; 
      End; 
End; 
{--------------------------------------------------------------------} 
Procedure TNewButton.fSetCaption(S : String); 
 
Begin 
If (fCaption <> S) then 
   Begin 
   fCaption := S; 
   SetTextBuf(PChar(S)); 
   Invalidate; 
   End; 
End; 
{--------------------------------------------------------------------} 
Procedure TNewButton.fSetGlyphLeft(I : Integer); 
 
Begin 
If I <> fGlyphLeft then 
   If I >= 0 then 
      Begin 
      fGlyphLeft := I; 
      Invalidate; 
      End; 
End; 
{--------------------------------------------------------------------} 
Procedure TNewButton.fSetGlyphTop(I : Integer); 
 
Begin 
If I <> fGlyphTop then 
   If I >= 0 then 
      Begin 
      fGlyphTop := I; 
      Invalidate; 
      End; 
End; 
{--------------------------------------------------------------------} 
procedure tNewButton.fLoadGlyph(G : TPicture); 
 
Var 
   I      : Integer; 
 
Begin 
fGlyph.Assign(G); 
If fGlyph.Height > 0 then 
   Begin 
   I := fGlyph.Width div fGlyph.Height; 
   If I <> fNumGlyphs then 
      fNumGlyphs := I; 
   End; 
Invalidate; 
End; 
{--------------------------------------------------------------------} 
Procedure Register; // Hello 
 
Begin 
RegisterComponents('Samples', [TNewButton]); 
End; 
{--------------------------------------------------------------------} 
Constructor TNewButton.Create(AOwner : TComponent); 
 
Begin 
Inherited Create(AOwner); 
{ Инициализируем переменные } 
Height := 37; 
Width := 37; 
fMouseOver := False; 
fGlyph := TPicture.Create; 
fMouseDown := False; 
fGlyphLeft := 2; 
fGlyphTop := 2; 
fTextLeft := 2; 
fTextTop := 2; 
fFaceColor := clBtnFace; 
fNumGlyphs := 1; 
fEnabled := True; 
End; 
{--------------------------------------------------------------------} 
Destructor TNewButton.Destroy; 
 
Begin 
If Assigned(fGlyph) then 
   fGlyph.Free; // Освобождаем glyph 
inherited Destroy; 
End; 
{--------------------------------------------------------------------} 
Procedure TNewButton.Paint; 
 
Var 
   fBtnColor,fColor1,fColor2, 
   fTransParentColor            : TColor; 
   Buffer                      : Array[0..127] of Char; 
   I,J                          : Integer; 
   X0,X1,X2,X3,X4,Y0            : Integer; 
   DestRect                    : TRect; 
   TempGlyph                    : TPicture; 
 
Begin 
X0 := 0; 
X1 := fGlyph.Width div fNumGlyphs; 
X2 := X1 + X1; 
X3 := X2 + X1; 
X4 := X3 + X1; 
Y0 := fGlyph.Height; 
TempGlyph := TPicture.Create; 
TempGlyph.Bitmap.Width := X1; 
TempGlyph.Bitmap.Height := Y0; 
DestRect := Rect(0,0,X1,Y0); 
 
GetTextBuf(Buffer,SizeOf(Buffer)); // получаем caption 
If Buffer <> '' then 
   fCaption := Buffer; 
 
If fEnabled = False then 
   fMouseDown := False; // если недоступна, значит и не нажата 
 
If fMouseDown then 
   Begin 
   fBtnColor := fHiColor; // Цвет нажатой кнопки 
   fColor1 := clWhite;    // Правая и нижняя окантовка кнопки, когда на неё нажали мышкой.
   fColor2 := clBlack;    // Верхняя и левая окантовка кнопки, когда на неё нажали мышкой. 
   End 
else 
   Begin 
   fBtnColor := fFaceColor; // fFaceColor мы сами определяем 
   fColor2 := clWhite;     // Цвет левого и верхнего края кнопки, когда на неё находится курсор мышки
   fColor1 := clGray;      // Цвет правого и нижнего края кнопки, когда на неё находится курсор мышки
   End; 
 
// Рисуем лицо кнопки :) 
Canvas.Brush.Color := fBtnColor; 
Canvas.FillRect(Rect(1,1,Width - 2,Height - 2)); 
 
If fMouseOver then 
   Begin 
   Canvas.MoveTo(Width,0); 
   Canvas.Pen.Color := fColor2; 
   Canvas.LineTo(0,0); 
   Canvas.LineTo(0,Height - 1); 
   Canvas.Pen.Color := fColor1; 
   Canvas.LineTo(Width - 1,Height - 1); 
   Canvas.LineTo(Width - 1, - 1); 
   End; 
 
If Assigned(fGlyph) then  // Bitmap загружен? 
   Begin 
   If fEnabled then       // Кнопка разрешена? 
      Begin 
      If fMouseDown then  // Мышка нажата? 
         Begin 
         // Mouse down on the button so show Glyph 3 on the face 
         If (fNumGlyphs >= 3) then 
            TempGlyph.Bitmap.Canvas.CopyRect(DestRect, 
               fGlyph.Bitmap.Canvas,Rect(X2,0,X3,Y0)); 
 
         If (fNumGlyphs < 3) and (fNumGlyphs > 1)then 
            TempGlyph.Bitmap.Canvas.CopyRect(DestRect, 
               fGlyph.Bitmap.Canvas,Rect(X0,0,X1,Y0)); 
 
         If (fNumGlyphs = 1) then 
            TempGlyph.Assign(fGlyph); 
 
         // Извините, лучшего способа не придумал... 
         // Glyph.Bitmap.Прозрачность цвета не работает, если Вы выберете в качестве
         // прозрачного цвета clWhite... 
         fTransParentColor := TempGlyph.Bitmap.Canvas.Pixels[0,Y0-1]; 
         For I := 0 to X1 - 1 do 
            For J := 0 to Y0 - 1 do 
               If TempGlyph.Bitmap.Canvas.Pixels[I,J] = 
                  fTransParentColor then 
                  TempGlyph.Bitmap.Canvas.Pixels[I,J] := fBtnColor; 
         //Рисуем саму кнопку
         Canvas.Draw(fGlyphLeft + 2,fGlyphTop + 2,TempGlyph.Graphic); 
         End 
      else 
         Begin 
         If fMouseOver then 
            Begin 
            // Курсор на кнопке, но не нажат, показываем Glyph 1 на морде кнопки 
            // (если существует) 
            If (fNumGlyphs > 1) then 
               TempGlyph.Bitmap.Canvas.CopyRect(DestRect, 
                  fGlyph.Bitmap.Canvas,Rect(0,0,X1,Y0)); 
            If (fNumGlyphs = 1) then 
               TempGlyph.Assign(fGlyph); 
            End 
         else 
            Begin 
            // Курсор за пределами кнопки, показываем Glyph 2 на морде кнопки (если есть) 
            If (fNumGlyphs > 1) then 
               TempGlyph.Bitmap.Canvas.CopyRect(DestRect, 
                  fGlyph.Bitmap.Canvas,Rect(X1,0,X2,Y0)); 
            If (fNumGlyphs = 1) then 
               TempGlyph.Assign(fGlyph); 
            End; 
         // Извиняюсь, лучшего способа не нашёл... 
         fTransParentColor := TempGlyph.Bitmap.Canvas.Pixels[0,Y0-1]; 
         For I := 0 to X1 - 1 do 
            For J := 0 to Y0 - 1 do 
               If TempGlyph.Bitmap.Canvas.Pixels[I,J] = 
                  fTransParentColor then 
                  TempGlyph.Bitmap.Canvas.Pixels[I,J] := fBtnColor; 
         //Рисуем bitmap на морде кнопки 
         Canvas.Draw(fGlyphLeft,fGlyphTop,TempGlyph.Graphic); 
         End; 
      End 
   else 
      Begin 
      // Кнопка не доступна (disabled), показываем Glyph 4 на морде кнопки (если существует) 
      If (fNumGlyphs = 4) then 
         TempGlyph.Bitmap.Canvas.CopyRect(DestRect, 
            fGlyph.Bitmap.Canvas,Rect(X3,0,X4,Y0)) 
      else 
         TempGlyph.Bitmap.Canvas.CopyRect(DestRect, 
            fGlyph.Bitmap.Canvas,Rect(0,0,X1,Y0)); 
      If (fNumGlyphs = 1) then 
         TempGlyph.Assign(fGlyph.Graphic); 
 
      // Извините, лучшего способа не нашлось... 
      fTransParentColor := TempGlyph.Bitmap.Canvas.Pixels[0,Y0-1]; 
      For I := 0 to X1 - 1 do 
         For J := 0 to Y0 - 1 do 
            If TempGlyph.Bitmap.Canvas.Pixels[I,J] = 
               fTransParentColor then 
               TempGlyph.Bitmap.Canvas.Pixels[I,J] := fBtnColor; 
      //Рисуем изображение кнопки 
      Canvas.Draw(fGlyphLeft,fGlyphTop,TempGlyph.Graphic); 
      End; 
   End; 
 
// Рисуем caption 
If fCaption <> '' then 
   Begin 
   Canvas.Pen.Color := Font.Color; 
   Canvas.Font.Name := Font.Name; 
   Canvas.Brush.Style := bsClear; 
   //Canvas.Brush.Color := fBtnColor; 
   Canvas.Font.Color := Font.Color; 
   Canvas.Font.Size := Font.Size; 
   Canvas.Font.Style := Font.Style; 
 
   If fMouseDown then 
      Canvas.TextOut(fShift + fTextLeft,fShift + fTextTop,fCaption) 
   else 
      Canvas.TextOut(fTextLeft,fTextTop,fCaption); 
   End; 
 
TempGlyph.Free; // Освобождаем временный glyph 
End; 
{--------------------------------------------------------------------} 
// Нажата клавиша мышки на кнопке ? 
Procedure TNewButton.MouseDown(Button: TMouseButton; 
   Shift: TShiftState;X, Y: Integer); 
 
Var 
   ffMouseDown,ffMouseOver : Boolean; 
 
Begin 
ffMouseDown := True; 
ffMouseOver := True; 
If (ffMouseDown <> fMouseDown) or (ffMouseOver <> fMouseOver) then 
   Begin 
   fMouseDown := ffMouseDown; 
   fMouseOver := ffMouseOver; 
   Invalidate; // не перерисовываем кнопку без необходимости.
   End; 
Inherited MouseDown(Button,Shift,X,Y);; 
End; 
{--------------------------------------------------------------------} 
// Отпущена клавиша мышки на кнопке ?
Procedure TNewButton.MouseUp(Button: TMouseButton; Shift: TShiftState; 
      X, Y: Integer); 
 
Var 
   ffMouseDown,ffMouseOver : Boolean; 
 
Begin 
ffMouseDown := False; 
ffMouseOver := True; 
If (ffMouseDown <> fMouseDown) or (ffMouseOver <> fMouseOver) then 
   Begin 
   fMouseDown := ffMouseDown; 
   fMouseOver := ffMouseOver; 
   Invalidate; // не перерисовываем кнопку без необходимости. 
   End; 
Inherited MouseUp(Button,Shift,X,Y); 
End; 
{--------------------------------------------------------------------} 
// Эта процедура перехватывает события мышки, если она даже за пределами кнопки 
// Перехватываем оконные сообщения 
Procedure TNewButton.WndProc(var Message : TMessage); 
 
Var 
   P1,P2 : TPoint; 
   Bo    : Boolean; 
 
Begin 
If Parent <> nil then 
   Begin 
   GetCursorPos(P1); // Получаем координаты курсона на экране 
   P2 := Self.ScreenToClient(P1); // Преобразуем их в координаты относительно кнопки
   If (P2.X > 0) and (P2.X < Width) and 
      (P2.Y > 0) and (P2.Y < Height) then 
      Bo := True // Курсор мышки в области кнопки 
   else 
      Bo := False; // Курсор мышки за пределами кнопки 
 
   If Bo <> fMouseOver then // не перерисовываем кнопку без необходимости. 
      Begin 
      fMouseOver := Bo; 
      Invalidate; 
      End; 
   End; 
inherited WndProc(Message); // отправляем сообщение остальным получателям 
End; 
{--------------------------------------------------------------------} 
End. 
{====================================================================}

Взято из https://forum.sources.ru