Конференция "Компоненты" » Кнопка на основе картинок.
 
  • Kolan © (01.04.08 09:55) [0]
    Итак, задача — написать свою кнопку-картинку.
    То есть такая кнопка похожа на TImage, но мы задаем не одну картинку, а несколько (для разных состояний кнопки).

    В другой моей ветке:
    http://pda.delphimaster.net/?n=3&id=1206102762

    DVM дал пример такой кнопки, но
    там все сделано на основе TBitmap, я хотел бы иметь возможность загружать картинки разных форматов.

    Требования к кнопке:
    1. Возможность задавать разные картинки для разных состояний:
    Обычная, Не активная, Нажатая, мышь над кнопкой
    2. Возможность использоватеь разные форматы картинок из пункта 1.
    3. Наличие Caption, которое бы работало так же как и в TButton.
    4. Наличие Font.
    5. Наличие Action. Тоже должно работать как в обычной кнопке.

    Вопросы:
    1. Кого выбрать родителем?
    2. Правильно ли я понял, что если я для картинок буду использовать TPicture, то смогу использовать разные форматы?
    3. Action, Caption и Font уже есть у TControl. Если я просто открою к ним доступ, будут лиони работать или придется еще что-то сделать?
    4. Код каких компонентов смотреть, в качестве примера? Я например не очень представляю как рисовать картинки, где искать пример? В TImage?
    5. Любые замечения.

    ЗЫ
    DimaBr — пишу свой компонент :).
  • DimaBr © (01.04.08 10:11) [1]
    1. Если кнопка будет "реальной", то есть иметь окно (её например можно найти WinAPI и нажать с другого приложения) то родитель TCustomControl, если просто рисунок - TGraphicsControl

    2. Правильно, только количество форматов не много (BMP, ICO, EMF, WMF), для изысканных нужно будет подключать библиотеки или свои свои наработки.

    3. Action - да, Сарtion & Font - используя их вы будите отрисовывать свой заголовок на кнопке.

    4. Например TSpeedButton

    5. Замечания
    Можно не писать кнопку с нуля, а взять готовую и дополнить её.
    Например, возьмите TSpeedButton, перекройте
    procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
    procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
    procedure Paint; override;

  • Kolan © (01.04.08 10:20) [2]
    > для изысканных нужно будет подключать библиотеки или свои
    > свои наработки.

    Я так понял, что, чтобы подключить доп формат (готовый) то нужно просто скачать соотв модуль (например TPNGImage с torry) и в TypeLib прописать путь. После этого он доступен для выбора в диалогах загрузки картинки, так? Я просто не оч. понимаю как этот механизм работает.

    1. Если кнопка будет «реальной», то есть иметь окно (её например можно найти WinAPI и нажать с другого приложения) то родитель TCustomControl, если просто рисунок — TGraphicsControl
    Ясно. Наверно окно как раз ненужно.


    > Можно не писать кнопку с нуля, а взять готовую и дополнить
    > её.
    > Например, возьмите TSpeedButton, перекройте

    О, наверно это то, что надо. Я наверно понижу видимость некоторых свойств. Glyph, Flat например. Это оч. плохо? Чем нибудь грозит?
  • DimaBr © (01.04.08 10:50) [3]
    Понизить видимость нельзя
  • Kolan © (01.04.08 10:54) [4]
    > Понизить видимость нельзя

    Как нельзя? перемещю соотв свойства в protected и все.
  • Kolan © (01.04.08 10:57) [5]
    А зачем нужен FDrawing в Paint у TImage?
  • Kolan © (01.04.08 11:11) [6]
    Я вот думаю, может по аналогии с кодом DVM сделать так:

    1. Добавляю поле
    FCurrentImage: TPicture;


    2. Рисование взять из TImage

    var
     Save: Boolean;
    begin
     if csDesigning in ComponentState then
       with inherited Canvas do
       begin
         Pen.Style := psDash;
         Brush.Style := bsClear;
         Rectangle(0, 0, Width, Height);
       end;
     {Save := FDrawing;
     FDrawing := True;}

     try
       with inherited Canvas do
         StretchDraw(DestRect, CurrentImage.Graphic);
     finally
       {FDrawing := Save; }
     end;
    end;



    А вот сам это CurrentImage менять при надобности. Только вот где его менять правильно?

    Или

    Надо проверять все состояния в Paint и рисовать нужную картинку?

    И еще. Как выводит Caption? Я что-то в TSpeedButton ненайду. Просто TextOut?
  • Игорь Шевченко © (01.04.08 11:30) [7]
    Кулибин. Компонентов таких написано море.
  • DimaBr © (01.04.08 11:33) [8]
    Код DVM - я бы сказал не очень.
    Рисовать в MouseUp - извращение.
    ActualBmp.Assign(Bmp); - тоже не ахти...

    Моё предложение.
    Заводим несколько свойства типа TPicture для разных состояний
    TMyButtoState = (mbsUp, mbsHot, mbsDown, mbsDisabled);

    private
     fImages: array[TMyButtoState] of TPicture;
    property ImageHot: TPicture index mbsHot read GetImage write SetImage;



    Заводим переменную - состояние кнопки
    fButtonState: TMyButtoState;
    в зависимости от которого в методе PAINT отрисовываем копку с нужного изображение
  • Игорь Шевченко © (01.04.08 11:34) [9]
    Буквально за час (правда, без Caption)

    unit HSFlatImageButton;

    interface
    uses
     Messages, Classes, Graphics, Controls;

    type
     THSButtonState = (hsbsUp, hsbsDown, hsbsHover, hsbsDisabled);
     THSFlatImageButton = class(TGraphicControl)
     private
       FImages: array[THSButtonState] of TBitmap;
       FState: THSButtonState;
       procedure SetState(const Value: THSButtonState);
       function GetImage(const Index: THSButtonState): TBitmap;
       procedure SetImage(const Index: THSButtonState; const Value: TBitmap);
       function GetDisabledImage: TBitmap;
       function GetDownImage: TBitmap;
       function GetHoverImage: TBitmap;
       function GetUpImage: TBitmap;
       procedure SetDisabledImage(const Value: TBitmap);
       procedure SetDownImage(const Value: TBitmap);
       procedure SetHoverImage(const Value: TBitmap);
       procedure SetUpImage(const Value: TBitmap);
     protected
       procedure Paint; override;
       procedure CMMouseEnter (var Message: TMessage); message CM_MOUSEENTER;
       procedure CMMouseLeave (var Message: TMessage); message CM_MOUSELEAVE;
       procedure MouseDown(Button: TMouseButton;
         Shift: TShiftState; X, Y: Integer); override;
       procedure MouseUp(Button: TMouseButton;
         Shift: TShiftState; X, Y: Integer); override;
     public
       constructor Create (AOwner: TComponent); override;
       destructor Destroy; override;
       //for test purposes only
       property State: THSButtonState read FState write SetState;
       property UpImage: TBitmap read GetUpImage write SetUpImage;
       property DownImage: TBitmap read GetDownImage write SetDownImage;
       property HoverImage: TBitmap read GetHoverImage write SetHoverImage;
       property DisabledImage: TBitmap read GetDisabledImage write SetDisabledImage;
       property OnClick;
     end;

    implementation

    { THSFlatImageButton }

    procedure THSFlatImageButton.CMMouseEnter(var Message: TMessage);
    begin
     if Enabled then
       State := hsbsHover;
    end;

    procedure THSFlatImageButton.CMMouseLeave(var Message: TMessage);
    begin
     inherited;
     if Enabled then
       State := hsbsUp;
    end;

    constructor THSFlatImageButton.Create(AOwner: TComponent);
    var
     I: THSButtonState;
    begin
     inherited;
     Height := 16;
     Width := 16;
     ControlStyle := ControlStyle + [csReplicatable];
     FState := hsbsUp;
     for I := Low(THSButtonState) to High(THSButtonState) do
     begin
       FImages[I] := TBitmap.Create;
       FImages[I].Height := 16;
       FImages[I].Width := 16;
     end;
    end;

    destructor THSFlatImageButton.Destroy;
    var
     I: THSButtonState;
    begin
     for I := Low(THSButtonState) to High(THSButtonState) do
       FImages[I].Free;
     inherited;
    end;

    function THSFlatImageButton.GetDisabledImage: TBitmap;
    begin
     Result := GetImage(hsbsDisabled);
    end;

    function THSFlatImageButton.GetDownImage: TBitmap;
    begin
     Result := GetImage(hsbsDown);
    end;

    function THSFlatImageButton.GetHoverImage: TBitmap;
    begin
     Result := GetImage(hsbsHover);
    end;

    function THSFlatImageButton.GetImage(const Index: THSButtonState): TBitmap;
    begin
     Result := FImages[Index];
    end;

    function THSFlatImageButton.GetUpImage: TBitmap;
    begin
     Result := GetImage(hsbsUp);
    end;

    procedure THSFlatImageButton.MouseDown(Button: TMouseButton; Shift: TShiftState;
     X, Y: Integer);
    begin
     inherited;
     if Enabled then
       State := hsbsDown;
    end;

    procedure THSFlatImageButton.MouseUp(Button: TMouseButton; Shift: TShiftState;
     X, Y: Integer);
    begin
     inherited;
     if Enabled then
       State := hsbsUp;
    end;

    procedure THSFlatImageButton.Paint;
    begin
     Canvas.StretchDraw(ClientRect, FImages[FState]);
    end;

    procedure THSFlatImageButton.SetDisabledImage(const Value: TBitmap);
    begin
     SetImage(hsbsDisabled, Value);
    end;

    procedure THSFlatImageButton.SetDownImage(const Value: TBitmap);
    begin
     SetImage(hsbsDown, Value);
    end;

    procedure THSFlatImageButton.SetHoverImage(const Value: TBitmap);
    begin
     SetImage(hsbsHover, Value);
    end;

    procedure THSFlatImageButton.SetImage(const Index: THSButtonState;
     const Value: TBitmap);
    begin
     FImages[Index].Assign(Value);
     if FState = Index then
       Invalidate;
    end;

    procedure THSFlatImageButton.SetState(const Value: THSButtonState);
    begin
     if FState <> Value then
     begin
       FState := Value;
       Invalidate;
     end;
    end;

    procedure THSFlatImageButton.SetUpImage(const Value: TBitmap);
    begin
     SetImage(hsbsUp, Value);
    end;

    end.

  • DimaBr © (01.04.08 11:34) [10]

    > > Понизить видимость нельзя
    > Как нельзя? перемещю соотв свойства в protected и все.

    Перемешение ничего не даст, можете проверить
  • Kolan © (01.04.08 11:41) [11]
    > Кулибин. Компонентов таких написано море.

    Покажи удовлетворяющий сабжу. Чтобы с Caption и c Action'ом был.


    > Моё предложение.

    Ок. Последую. Только зачем тогда mbsDisabled если можно проверять просто Enabled.

    mbsHot надо ставить и снимать в CMMouseEnter|Leave, так?

    А вот где можно понять что кнопка нажата?

    Так а как рисовать Caption? Нашел это место в TSpeedButton там как-то хитро закручено через
    TButtonGlyph

  • Kolan © (01.04.08 11:43) [12]
    > Перемешение ничего не даст, можете проверить

    Действительно. В результате только CodeInsite не видит свойство, а компиляция идет нормально…

    Может тогда заглушки сделать?
  • Игорь Шевченко © (01.04.08 11:44) [13]
    Kolan ©   (01.04.08 11:41) [11]

    Любой наследник TButton,TSpeedButton,TBitBtn будет иметь Action.
    Перекрытие процедуры Paint дает тебе возможность рисовать хоть черта лысого.

    Накачай компонентов с torry.net - там кнопок с картинками как звезд на небе, посмотри, как устроены, пойми почему так, а не иначе.
  • Kolan © (01.04.08 11:48) [14]
    > Буквально за час


    > Кулибин

    …Еще нет Экшена и TPicture :)

    Благодарю. Теперь понятно где какие состояния выставлять.

    ЗЫ Не увидел где устанавливается Disabled.
  • Игорь Шевченко © (01.04.08 11:55) [15]
    Kolan ©   (01.04.08 11:48) [14]

    TPicture не нужен.


    > ЗЫ Не увидел где устанавливается Disabled.


    Не стал делать, но обычно в обработчике сообщение CM_ENABLEDCHANGED
  • DimaBr © (01.04.08 12:51) [16]

    > Игорь Шевченко ©   (01.04.08 11:34) [9]

    Класс !!!
    Проиндексировав картинки можно сокатить код
  • DimaBr © (01.04.08 12:55) [17]

    if Enabled then State := XXX;


    Можно перенести в SetState тогда

    procedure THSFlatImageButton.SetState(Value: THSButtonState);
    begin
     if not Enabled then Value := hsbsDisabled;
     if fState = Value then Exit;
     fState := Value;
     Invalidate;
    end;

  • Kolan © (01.04.08 13:13) [18]
    Ок.
  • Игорь Шевченко © (01.04.08 13:15) [19]
    DimaBr ©   (01.04.08 12:51) [16]

    У меня не вышло проиндексировать :) Я запамятовал, можно ли индексировать свойства, значением которых является объект
  • Игорь Шевченко © (01.04.08 13:17) [20]
    Кстати, еще одна написанная за час кнопка:

    unit ArgoMenuButton;

    interface
    uses
     Classes, Graphics, Buttons;

    type
     TArgoMenuButton = class(TSpeedButton)
     private
       FStartColor: TColor;
       FEndColor: TColor;
       FUseGradientFill: Boolean;
       procedure SetEndColor(const Value: TColor);
       procedure SetGradientFill(const Value: Boolean);
       procedure SetStartColor(const Value: TColor);
     protected
       procedure Paint; override;
     public
       procedure Assign(Source: TPersistent); override;
     published
       property StartColor: TColor read FStartColor write SetStartColor;
       property EndColor: TColor read FEndColor write SetEndColor;
       property UseGradientFill: Boolean read FUseGradientFill
         write SetGradientFill;
     end;

    implementation
    uses
     Windows, HSGradient;

    { TArgoMenuButton }

    procedure CalcButtonLayout(Canvas: TCanvas; const Client: TRect;
     const Offset: TPoint; const Caption: string; Layout: TButtonLayout; Margin,
     Spacing: Integer; var GlyphPos: TPoint; var TextBounds: TRect;
     BiDiFlags: LongInt);
    var
     TextPos: TPoint;
     ClientSize, GlyphSize, TextSize: TPoint;
     TotalSize: TPoint;
    begin
     if (BiDiFlags and DT_RIGHT) = DT_RIGHT then
       if Layout = blGlyphLeft then Layout := blGlyphRight
       else
         if Layout = blGlyphRight then Layout := blGlyphLeft;
     ClientSize := Point(Client.Right - Client.Left, Client.Bottom -
       Client.Top);

     GlyphSize := Point(0, 0);
       
     if Length(Caption) > 0 then
     begin
       TextBounds := Rect(0, 0, Client.Right - Client.Left, 0);
       DrawText(Canvas.Handle, PChar(Caption), Length(Caption), TextBounds,
         DT_CALCRECT or BiDiFlags);
       TextSize := Point(TextBounds.Right - TextBounds.Left, TextBounds.Bottom -
         TextBounds.Top);
     end
     else
     begin
       TextBounds := Rect(0, 0, 0, 0);
       TextSize := Point(0,0);
     end;
       
     if Layout in [blGlyphLeft, blGlyphRight] then
     begin
       GlyphPos.Y := (ClientSize.Y - GlyphSize.Y + 1) div 2;
       TextPos.Y := (ClientSize.Y - TextSize.Y + 1) div 2;
     end
     else
     begin
       GlyphPos.X := (ClientSize.X - GlyphSize.X + 1) div 2;
       TextPos.X := (ClientSize.X - TextSize.X + 1) div 2;
     end;
       
     if (TextSize.X = 0) or (GlyphSize.X = 0) then
       Spacing := 0;
       
     if Margin = -1 then
     begin
       if Spacing < 0 then
       begin
         TotalSize := Point(GlyphSize.X + TextSize.X, GlyphSize.Y + TextSize.Y);
         if Layout in [blGlyphLeft, blGlyphRight] then
           Margin := (ClientSize.X - TotalSize.X) div 3
         else
           Margin := (ClientSize.Y - TotalSize.Y) div 3;
         Spacing := Margin;
       end
       else
       begin
         TotalSize := Point(GlyphSize.X + Spacing + TextSize.X, GlyphSize.Y +
           Spacing + TextSize.Y);
         if Layout in [blGlyphLeft, blGlyphRight] then
           Margin := (ClientSize.X - TotalSize.X + 1) div 2
         else
           Margin := (ClientSize.Y - TotalSize.Y + 1) div 2;
       end;
     end
     else
     begin
       if Spacing < 0 then
       begin
         TotalSize := Point(ClientSize.X - (Margin + GlyphSize.X), ClientSize.Y -
           (Margin + GlyphSize.Y));
         if Layout in [blGlyphLeft, blGlyphRight] then
           Spacing := (TotalSize.X - TextSize.X) div 2
         else
           Spacing := (TotalSize.Y - TextSize.Y) div 2;
       end;
     end;
       
     case Layout of
       blGlyphLeft:
         begin
           GlyphPos.X := Margin;
           TextPos.X := GlyphPos.X + GlyphSize.X + Spacing;
         end;
       blGlyphRight:
         begin
           GlyphPos.X := ClientSize.X - Margin - GlyphSize.X;
           TextPos.X := GlyphPos.X - Spacing - TextSize.X;
         end;
       blGlyphTop:
         begin
           GlyphPos.Y := Margin;
           TextPos.Y := GlyphPos.Y + GlyphSize.Y + Spacing;
         end;
       blGlyphBottom:
         begin
           GlyphPos.Y := ClientSize.Y - Margin - GlyphSize.Y;
           TextPos.Y := GlyphPos.Y - Spacing - TextSize.Y;
         end;
     end;
       
     with GlyphPos do
     begin
       Inc(X, Client.Left + Offset.X);
       Inc(Y, Client.Top + Offset.Y);
     end;

     OffsetRect(TextBounds, TextPos.X + Client.Left + Offset.X, TextPos.Y + Client.Top + Offset.Y);
    end;

    procedure DrawButtonText(Canvas: TCanvas; const Caption: string;
     TextBounds: TRect; State: TButtonState; Flags: LongInt);
    begin
     with Canvas do
     begin
       Brush.Style := bsClear;
       if State = bsDisabled then
       begin
         OffsetRect(TextBounds, 1, 1);
         Font.Color := clBtnHighlight;
         DrawText(Handle, PChar(Caption), Length(Caption), TextBounds,
           DT_CENTER or DT_VCENTER or Flags);
         OffsetRect(TextBounds, -1, -1);
         Font.Color := clBtnShadow;
         DrawText(Handle, PChar(Caption), Length(Caption), TextBounds,
           DT_CENTER or DT_VCENTER or Flags);
       end else
         DrawText(Handle, PChar(Caption), Length(Caption), TextBounds,
           DT_CENTER or DT_VCENTER or Flags);
     end;
    end;

    function DrawButton (Canvas: TCanvas; const Client: TRect;
     const Offset: TPoint; const Caption: string; Layout: TButtonLayout;
     Margin, Spacing: Integer; State: TButtonState; Transparent: Boolean;
     BiDiFlags: LongInt): TRect;
    var
     GlyphPos: TPoint;
    begin
     CalcButtonLayout(Canvas, Client, Offset, Caption, Layout, Margin, Spacing,
       GlyphPos, Result, BiDiFlags);
     DrawButtonText(Canvas, Caption, Result, State, BiDiFlags);
    end;

    procedure TArgoMenuButton.Assign(Source: TPersistent);
    begin
     if Source is TSpeedButton then
     begin
       BoundsRect := TSpeedButton(Source).BoundsRect;
       Action := TSpeedButton(Source).Action;
       Flat := TSpeedButton(Source).Flat;
       Transparent := TSpeedButton(Source).Transparent;
       Font := TSpeedButton(Source).Font;
       AllowAllUp := TSpeedButton(Source).AllowAllUp;
     end else
       inherited;
    end;

    procedure TArgoMenuButton.Paint;
    var
     PaintRect: TRect;
     GradientFill: THSGradientFill;
    begin
     if (csDesigning in ComponentState) or not
        (Fstate in [bsDown,bsExclusive]) then
       inherited
     else begin
       Canvas.Font := Self.Font;
       Canvas.Font.Color := clYellow;
       Canvas.Brush.Color := $00804000;
       PaintRect := Rect(0, 0, Width, Height);
       if UseGradientFill then
       begin
         GradientFill := THSGradientFill.Create;
         try
           GradientFill.Style := gsVertical;
           GradientFill.StartColor := FStartColor;
           GradientFill.EndColor := FEndColor;
           GradientFill.FillRect(Canvas, PaintRect);
         finally
           GradientFill.Free;
         end;
       end
       else
         Canvas.FillRect(PaintRect);
       DrawButton (Canvas, PaintRect, Point(0,0), Caption, Layout, Margin, Spacing,
         FState, Transparent, DrawTextBiDiModeFlags(0));
     end;
    end;

    procedure TArgoMenuButton.SetEndColor(const Value: TColor);
    begin
     if FEndColor <> Value then
     begin
       FEndColor := Value;
       Invalidate;
     end;
    end;

    procedure TArgoMenuButton.SetGradientFill(const Value: Boolean);
    begin
     if FUseGradientFill <> Value then
     begin
       FUseGradientFill := Value;
       Invalidate;
     end;
    end;

    procedure TArgoMenuButton.SetStartColor(const Value: TColor);
    begin
     if FStartColor <> Value then
     begin
       FStartColor := Value;
       Invalidate;
     end;
    end;

    end.

  • Kolan © (01.04.08 13:29) [21]
    > Класс !!!
    > Проиндексировав картинки можно сокатить код

    Класс — это в смысле плохо или хорошо?

    Что значит проиндексировать?
  • DimaBr © (01.04.08 14:03) [22]

    > Что значит проиндексировать?

    Класс - это всё написано с толком и расстановкой, без лишних излишеств

    Про индексацию я показал в [8], а в [9] полная реализация того о чём я говорил
  • Игорь Шевченко © (01.04.08 14:13) [23]

    > property ImageHot: TPicture index mbsHot read GetImage write
    > SetImage;


    Вот такая конструкция у меня не захотела компилироваться на D2006

     function GetImage (Index: Integer): TBitmap;
     procedure SetImage (Value: TBitmap; Index: Integer);
    ...
     property DisabledImage: TBitmap index 3 read GetImage write SetImage;
  • Kolan © (01.04.08 16:00) [24]
    > Вот такая конструкция у меня не захотела компилироваться
    > на D2006

    Видимо дело в этом:
    An access method for a property with an index specifier must take an extra value parameter of type Integer. For a read function, it must be the last parameter; for a write procedure, it must be the second-to-last parameter (preceding the parameter that specifies the property value). When a program accesses the property, the property&#146;s integer constant is automatically passed to the access method.



    Вот что получилось:

    unit KMultiImageButton;

    interface

    uses
     SysUtils, Classes, Controls, Buttons, Graphics, Types, Messages;

    type
     TMultiImageButtonStates = (mibsNormal, mibsDown, mibsHover, mibsDisabled);

     TKMultiImageButton = class(TGraphicControl)
     private
       FImages: array [TMultiImageButtonStates] of TPicture;
       FButtonState: TMultiImageButtonStates;
       { Private declarations }
     protected
       { Protected declarations }
       function GetImage(const Index: TMultiImageButtonStates): TPicture;
       procedure SetImage(const Index: TMultiImageButtonStates; const Value: TPicture);
       procedure SetButtonState(const AState: TMultiImageButtonStates);

       procedure Paint; override;
       procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
       procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
       procedure MouseDown(Button: TMouseButton;
        Shift: TShiftState; X, Y: Integer); override;
       procedure MouseUp(Button: TMouseButton;
        Shift: TShiftState; X, Y: Integer); override;  
     public
       { Public declarations }
       constructor Create(AOwner: TComponent); override;
       destructor Destroy; override;
     published
       { Published declarations }
       property Caption;
       property Action;

       property ImageNormal: TPicture index mibsNormal read GetImage write SetImage;
       property ImageDisabled: TPicture index mibsDisabled read GetImage write SetImage;
       property ImageDown: TPicture index mibsDown read GetImage write SetImage;
       property ImageHover: TPicture index mibsHover read GetImage write SetImage;

       property OnClick;
       property OnDblClick;
       property OnMouseDown;
       property OnMouseEnter;
       property OnMouseLeave;
       property OnMouseMove;
       property OnMouseUp;
       property OnMouseWheel;
       property OnMouseWheelDown;
       property OnMouseWheelUp;

     end;

    procedure Register;

    implementation

    procedure Register;
    begin
     RegisterComponents('KSoftWare', [TKMultiImageButton]);
    end;

    { TKMultiImageButton }

    procedure TKMultiImageButton.CMMouseEnter(var Message: TMessage);
    begin
     SetButtonState(mibsHover);
    end;

    procedure TKMultiImageButton.CMMouseLeave(var Message: TMessage);
    begin
     SetButtonState(mibsNormal);
    end;

    constructor TKMultiImageButton.Create(AOwner: TComponent);
    var
     I: TMultiImageButtonStates;
    begin
     inherited;
     for I := Low(TMultiImageButtonStates) to High(TMultiImageButtonStates) do
       FImages[I] := TPicture.Create;
    end;

    destructor TKMultiImageButton.Destroy;
    var
     I: TMultiImageButtonStates;
    begin
     for I := Low(TMultiImageButtonStates) to High(TMultiImageButtonStates) do
       FImages[I].Free;
     inherited;
    end;

    function TKMultiImageButton.GetImage(const Index: TMultiImageButtonStates): TPicture;
    begin
     Result := FImages[Index];
    end;

    procedure TKMultiImageButton.MouseDown(Button: TMouseButton; Shift: TShiftState;
     X, Y: Integer);
    begin
     inherited;
     SetButtonState(mibsDown);
    end;

    procedure TKMultiImageButton.MouseUp(Button: TMouseButton; Shift: TShiftState;
     X, Y: Integer);
    begin
     inherited;
     SetButtonState(mibsNormal);
    end;

    procedure TKMultiImageButton.Paint;
    begin
     if csDesigning in ComponentState then
       with Canvas do
       begin
         Pen.Style := psDash;
         Brush.Style := bsClear;
         Rectangle(0, 0, Width, Height);
       end;

     Canvas.Font := Self.Font;
     with GetImage(FButtonState) do
       Canvas.StretchDraw(ClientRect, Graphic);
     Canvas.TextOut(0, 0, Caption);
    end;

    procedure TKMultiImageButton.SetButtonState(
     const AState: TMultiImageButtonStates);
    begin
     if not Enabled then
     begin
       FButtonState := mibsDisabled;
       Exit;
     end
     else
       if FButtonState <> AState then
         FButtonState := AState;
     Invalidate;    
    end;

    procedure TKMultiImageButton.SetImage(const Index: TMultiImageButtonStates;
     const Value: TPicture);
    begin
     FImages[Index].Assign(Value);
     if FButtonState = Index then
      Invalidate;
    end;

    end.



    Еще с Enable/Disabled не разобрался, а так вроде работает :)
  • Kolan © (01.04.08 16:01) [25]
    А, забыл, как правильно координаты теста вычислять? где подсмотреть?
  • Kolan © (01.04.08 16:15) [26]
    Так Enabled то же победил:
    procedure TKMultiImageButton.CMEnabledChanged(var Message: TMessage);
    begin
     if Enabled then
       SetButtonState(mibsNormal)
     else
       SetButtonState(mibsDisabled);
     inherited;
    end;


    Нормально?

    Кроме кординат текста незнаю как сделать его фон прозраным.
  • Игорь Шевченко © (01.04.08 16:21) [27]

    > Кроме кординат текста незнаю как сделать его фон прозраным.


    Ввести свойство Transaparent ?

    При рисовании текста сказать Canvas.Brush.Style := bsClear, а потом собстна текст рисовать ?
  • Kolan © (01.04.08 16:29) [28]
    > При рисовании текста сказать Canvas.Brush.Style := bsClear,
    > а потом собстна текст рисовать ?

    Да, получилось.

    А что с координатами? Можно конечно текст по центру делать, но интересно понять как делается в кнопке, а я там капался так и не понял :(
  • Kolan © (01.04.08 16:31) [29]
    Кстати DoubleClick работает только ели не назначен просто Click. Наверно надо доп. обработку делать… Опять же где бы посмотреть пример?
  • DimaBr © (01.04.08 16:40) [30]

    procedure TKMultiImageButton.CMEnabledChanged(var Message: TMessage);
    const AState: array[boolean] of TMultiImageButtonStates = (mibsDisabled,mibsNormal);
    begin
     inherited;
    SetButtonState(AState[Enabled]);
    end;

    procedure TKMultiImageButton.SetButtonState(AState: TMultiImageButtonStates);
    var P: TPoint;
    begin
    if not Enabled then AState := mibsDisabled;
    if fButtonState = AState then Exit;
    if FButtonState = mibsDisabled then begin
     GetCursorPos(P);
     P := ScrennToClient(P);
     if PtInRect(P,ClientRect) then
       if csLButtonDown in ControlState
         then AState := mibsDown
         else AState := mibsHover
       else AState := mibsNormal;
    FButtonState := AState;
    Invalidate;    
    end;

  • Игорь Шевченко © (01.04.08 16:51) [31]

    > А что с координатами? Можно конечно текст по центру делать,
    >  но интересно понять как делается в кнопке


    RTFS: Buttons.pas - там все подробно написано. С картинками
  • Игорь Шевченко © (01.04.08 16:53) [32]
    DimaBr ©   (01.04.08 16:40) [30]


    > if FButtonState = mibsDisabled then begin
    >  GetCursorPos(P);
    >  P := ScrennToClient(P);
    >  if PtInRect(P,ClientRect) then
    >    if csLButtonDown in ControlState
    >      then AState := mibsDown
    >      else AState := mibsHover
    >    else AState := mibsNormal;
    > FButtonState := AState;
    > Invalidate;    
    > end;


    Непонятно, зачем при недоступной кнопке менять ее State ?
  • Kolan © (01.04.08 16:54) [33]
    > DimaBr ©   (01.04.08 16:40)


    SetButtonState(AState[Enabled]);
    Не понял. Это типа если True, то 1, а если False, то 0?

    Но у меня же
    (mibsNormal, mibsDown, mibsHover, mibsDisabled);



    SetButtonState


    Не понял, зачем манипуляции с координатами курсора?
  • Kolan © (01.04.08 16:56) [34]
    Кстати а при Disabled надо же еще и цвет Caption'a менять? На какой? Или может это сделано где —то в предке?
  • Игорь Шевченко © (01.04.08 16:59) [35]

    > Кстати а при Disabled надо же еще и цвет Caption'a менять?
    >  На какой? Или может это сделано где —то в предке?


    Предок у тебя TGraphicControl - он про текст ничего не знает.
    При рисовании текста проверяй State, в зависимости от него устанавливай Canvas.Font.Color - собственно, в Buttons.pas все это подробно расписано.
  • Kolan © (01.04.08 17:01) [36]
    > С картинками

    CalcButtonLayout
    ?

    Мне кажется это слишком сложно.

    Сделал так:
     
    ControlMiddle.X := Width div 2;
     ControlMiddle.Y := Height div 2;

     TextMiddle.X := Canvas.TextWidth(Caption) div 2;
     TextMiddle.Y := Canvas.TextHeight(Caption) div 2;

     Canvas.Brush.Style := bsClear;
     Canvas.TextOut(ControlMiddle.X &#151; TextMiddle.X, ControlMiddle.Y &#151; TextMiddle.Y, Caption);



    + может Offset добавлю.

    Глиф мне не нужен и писать задом наперед тоже. Нормально?
  • DimaBr © (01.04.08 17:03) [37]
    Если кнопка БЫЛА недоступной и вдруг стала доступной, при этом мышка находится на ней, то она должна быть mibsHover
  • Игорь Шевченко © (01.04.08 17:11) [38]

    > Если кнопка БЫЛА недоступной и вдруг стала доступной, при
    > этом мышка находится на ней, то она должна быть mibsHover


    Не могу представить ситуацию
  • {RASkov} © (01.04.08 17:17) [39]
    > [38] Игорь Шевченко ©   (01.04.08 17:11)

    Форма, прогрессбар и кнопка. Идет какой либо процесс (прогрессбар бежит) и одна кнопка Ок, Которая не доступна пока идет процесс, а пользователь мышь держит на ней и ждет....
    :о)
  • {RASkov} © (01.04.08 17:17) [40]
    ...по окончанию процесса кнопка становится доступной...
  • Kolan © (01.04.08 17:21) [41]
    > DimaBr

    Правда не понял зачем эти извращения с курсором. Вроде и так ([24]) состояния правильно работают.
  • Kolan © (01.04.08 17:23) [42]
    Сделал так вывод кэпшена:

     ControlMiddle.X := Width div 2;
     ControlMiddle.Y := Height div 2;

     TextMiddle.X := Canvas.TextWidth(Caption) div 2;
     TextMiddle.Y := Canvas.TextHeight(Caption) div 2;

     Canvas.Brush.Style := bsClear;
     if FButtonState = mibsDisabled then
     begin
       Canvas.Font.Color := clBtnHighlight;  
       Canvas.TextOut((ControlMiddle.X &#151; TextMiddle.X) + CaptionXOffset + 1,
         (ControlMiddle.Y &#151; TextMiddle.Y) + CaptionYOffset + 1, Caption);
         
       Canvas.Font.Color := clBtnShadow;
       Canvas.TextOut((ControlMiddle.X &#151; TextMiddle.X) + CaptionXOffset,
         (ControlMiddle.Y &#151; TextMiddle.Y) + CaptionYOffset, Caption);
     end
     else
       Canvas.TextOut((ControlMiddle.X &#151; TextMiddle.X) + CaptionXOffset,
         (ControlMiddle.Y &#151; TextMiddle.Y) + CaptionYOffset, Caption);



    Повиду все нормально работает.
  • Игорь Шевченко © (01.04.08 17:31) [43]
    {RASkov} ©   (01.04.08 17:17) [39]

    Я бы для подобных извратов все равно сделал бы по образу и подобию TSpeedButton.UpdateTracking
  • Kolan © (01.04.08 17:57) [44]
    Хотелось бы, чтобы если картинка не задана, то рисовалась бы обычная…

    Изменил код так:
    TempPicture := GetImage(FButtonState);
     if not Assigned(TempPicture.Graphic) or (TempPicture.Graphic.Empty) then
       TempPicture := GetImage(mibsNormal);
     Canvas.StretchDraw(ClientRect, TempPicture.Graphic);



    Но почему-то все равно ничего не рисуется…
  • Kolan © (01.04.08 18:02) [45]
    Удалено модератором
    Примечание: Флудить завязываем
  • Kolan © (01.04.08 18:07) [46]
    А и еще. А как реагировать на изменение Caption? Написать сеттер&
  • Kolan © (01.04.08 18:15) [47]
    И все-таки все это дело мерцает :(. Как быть?
  • Игорь Шевченко © (01.04.08 19:52) [48]

    > А как реагировать на изменение Caption? Написать сеттер&


    Обрабатывать CM_TEXTCHANGED

    Ты когда-нибудь откроешь исходники VCL или так всю жизнь и будешь ключи подавать ? Тогда тебе в "Начинающие", навечно
  • Kolan © (02.04.08 10:38) [49]
    > Обрабатывать CM_TEXTCHANGED

    Я сомневаюсь в себе :). Получилось.

    А с мельканием что делать?
  • DimaBr © (02.04.08 11:10) [50]
    С мельканием - воевать
    ControlStyle := ControlStyle + [ csOpaque ] ;
  • Игорь Шевченко © (02.04.08 11:18) [51]

    > А с мельканием что делать?


    Скачай уже готовый компонент и не парься
  • DVM © (02.04.08 11:28) [52]

    > DimaBr ©   (01.04.08 11:33) [8]
    > Код DVM - я бы сказал не очень.

    Это не мой код, я там написал же, что код не очень. Просто под руку попался.
  • Kolan © (02.04.08 11:38) [53]
    > ControlStyle := ControlStyle + [ csOpaque ] ;

    Угу. Помогло.
    Игорь, Dima, благодарю. Все что хотель все сделал + про index узнал.  :)
  • DimaBr © (02.04.08 12:06) [54]

    > Игорь Шевченко ©   (02.04.08 11:18) [51]
    > Скачай уже готовый компонент и не парься

    В принципе - не верно.
    "Лучше один раз увидеть, чем сто раз услышать"
  • Игорь Шевченко © (02.04.08 12:30) [55]
    DimaBr ©   (02.04.08 12:06) [54]

    Раз пошел оффтопик - во всех нюансах компонентостроения лучше разбираться на действующих примерах, так как нюансы, они общие для многих компонентов. А не понимая этих нюансов и задавая на каждый чих вопрос в форуме - так всю жизнь и будешь ключи подавать и по форумам клянчить.
  • Kolan © (02.04.08 12:33) [56]
    > [55] Игорь Шевченко ©   (02.04.08 12:30)

    К сожалению к компонентам скачанным с нета у меня нет доверия. А незная нормальных решений я не могу понять хорошо написано или нет…

    ЗЫ
     До задания вопроса я скачал 3 штуки и посмотрел. Смысл везде один и тот же.
  • Игорь Шевченко © (02.04.08 12:37) [57]
    Kolan ©   (02.04.08 12:33) [56]

    Какая разница "хорошо написано" или нет - главное, чтобы работало, как надо.
 
Конференция "Компоненты" » Кнопка на основе картинок.
Есть новые Нет новых   [134464   +62][b:0.001][p:0.012]