15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту




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

Автор: Maarten de Haan

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

Пример тестировался под WinNT, SP5 и WIN95, SP1.

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

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

              +------+------+-----+------+    ^
              |Курсор|Курсор|нажа-|недос-|    | 
              |на кно|за пре| та  |тупна |  Высота 
              | пке  |делами|     |      |    | 
              +------+------+-----+------+    v
    

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

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

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

Найденные баги
----------
1) Если двигать мышку очень быстро, то кнопка может не вернуться в исходное состояние
2) Если кнопка находится в запрещённом состоянии, то при нажатии на неё, будет наблюдаться неприятное мерцание.

 

Совместимость: Delphi 5.x (или выше)

Скачать компонент: NewButton.dcr

  Собственно сам исходничек:

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.
{====================================================================}