Конференция "Компоненты" » Странности компонента [D5, WinXP]
 
  • harisma © (24.03.09 11:13) [0]
    Достался мне в наследство от предыдущих разработчиков компонент TFlatButton. Он порожден от дельфийского TButton.

    И вроде все бы хорошо, если бы ни одна странность его поведения, а именно:

    - берем новый проект.
    - Кладем на форму поле ввода TEdit и устанавливаем для него [B]жирный[/B] шрифт. Текст в поле компонента не стираем.
    - Кладем на форму эту кнопку (TFlatButton).
    - Запускаем проект - фокус естественно стоит на TEdit.
    - Жмем клавишу Tab - фокус перемешается на кнопку - пока все нормально.
    - перемещаем мышью фокус обратно на поле ввода - и вот тут начинаются чудеса - шрифт в поле ввода меняет свой размер :(

    Примечание: данный эффект проявляется не с первого раза, а после нескольких серий такого перещелкивания, причем курсор в TEdit надо позиционировать не в конец текста, а куда нибудь в середину (чтоб быстрее проявился данный эффект).

    В чем проблема - никак не пойму.

    Вот исходник самого компонента:


    unit FlatBtn;

    interface

    uses
     Windows, Messages, SysUtils, Classes, Graphics, Controls,
     StdCtrls, Extctrls;

    type
     TCustomFlatButton = class(TButton)
     private
       FCanvas: TCanvas;
       FFlat: Boolean;
       fbMouseIn: Boolean;
       procedure SetFlat(const Value: Boolean);
     protected
       procedure CreateParams(var Params: TCreateParams); override;
       procedure SetButtonStyle(ADefault: Boolean); override;
       procedure CNDrawItem(var Message: TWMDrawItem); message CN_DRAWITEM;
       procedure CMMouseEnter(var Msg: TMessage); message CM_MOUSEENTER;
       procedure CMMouseLeave(var Msg: TMessage); message CM_MOUSELEAVE;
     public
       constructor Create(AOwner: TComponent); override;
       destructor Destroy; override;
       property  Canvas: TCanvas read FCanvas;
       property  Flat: Boolean read FFlat write SetFlat default True;
     end;

     TFlatButton = class(TCustomFlatButton)
     published
       property  Flat;
     end;

    procedure Register;

    implementation

    constructor TCustomFlatButton.Create(AOwner: TComponent);
    begin
     inherited Create(AOwner);
     FCanvas := TControlCanvas.Create;
     FFlat := True;
     fbMouseIn := False;
    end;

    procedure TCustomFlatButton.CreateParams(var Params: TCreateParams);
    begin
     inherited;
     if FFlat and (not (csDesigning in ComponentState)) then
       Params.Style := Params.Style or BS_OWNERDRAW;
    end;

    destructor TCustomFlatButton.Destroy;
    begin
     FreeAndNil(FCanvas);
     inherited Destroy;
    end;

    procedure TCustomFlatButton.SetButtonStyle(ADefault: Boolean);
    const
     BS_MASK = $000F;
    var
     Style: Word;
    begin
     if FFlat and (not (csDesigning in ComponentState)) then begin
       if HandleAllocated then
       begin
         Style := BS_OWNERDRAW;
         if GetWindowLong(Handle, GWL_STYLE) and BS_MASK <> Style then
           SendMessage(Handle, BM_SETSTYLE, Style, 1);
       end;
     end
     else
       inherited;
    end;

    procedure TCustomFlatButton.CNDrawItem(var Message: TWMDrawItem);
    var
     State: TOwnerDrawState;
     Flags: Integer;
     Buf: array[0..255] of Char;
     TR: TRect;
    begin
     with Message.DrawItemStruct^ do
     begin
       State := TOwnerDrawState(LongRec(itemState).Lo);
       FCanvas.Handle := hDC;
       FCanvas.Font.Assign(Font);
       FCanvas.Brush.Assign(Brush);
       FCanvas.Pen.Color := clNone;
       SetBkMode(FCanvas.Handle, TRANSPARENT);
       TR := rcItem;
       FCanvas.FillRect(TR);
       if odSelected in State then
         Frame3D(FCanvas, TR, clBtnShadow, clBtnHighlight, 1)
       else
         if fbMouseIn or (odFocused in State) then
           Frame3D(FCanvas, TR, clBtnHighlight, clBtnShadow, 1);
       TR := rcItem;
       if odFocused in State then
       begin
         InflateRect(TR, -2, -2);
         DrawFocusRect(hDC, TR);
         TR := rcItem;
       end;
       StrPLCopy(Buf, Caption, 255);
       Flags := DT_CENTER or DT_VCENTER or DT_SINGLELINE or DT_NOPREFIX;
       if Enabled then
       begin
         if odSelected in State then
           OffsetRect(TR, 1, 1);
         DrawText(FCanvas.Handle, Buf, StrLen(Buf), TR, Flags);
       end
       else
       begin
         {use shadow text for inactive folder text}
         OffsetRect(TR, 1, 1);
         FCanvas.Font.Color := clBtnHighlight;
         DrawText(FCanvas.Handle, Buf, -1, TR, Flags);
         OffsetRect(TR, -1, -1);
         FCanvas.Font.Color := clBtnShadow;
         DrawText(FCanvas.Handle, Buf, -1, TR, Flags);
         FCanvas.Handle := 0;
       end;
     end;
    end;

    procedure TCustomFlatButton.CMMouseEnter(var Msg: TMessage);
    begin
     fbMouseIn := True;
     Invalidate;
     inherited;
    end;

    procedure TCustomFlatButton.CMMouseLeave(var Msg: TMessage);
    begin
     fbMouseIn := False;
     Invalidate;
     inherited;
    end;

    procedure TCustomFlatButton.SetFlat(const Value: Boolean);
    begin
     FFlat := Value;
     if not (csDesigning in ComponentState) then
       SetButtonStyle(Default);
    end;

    procedure Register;
    begin
     RegisterComponents('Sample', [TFlatButton]);
    end;

    end.




    Прошу помощи !!!
  • Игорь Шевченко © (24.03.09 19:03) [1]

    > В чем проблема - никак не пойму.
    >
    > Вот исходник самого компонента:


    по тексту вроде криминала не видно, если только в какой-то момент кнопка не начинает работать не со своим DC\Canvas.

    А если обычную кнопку положить, таких явлений не наблюдается ? :)
  • harisma © (25.03.09 14:02) [2]
    Для обычной кнопки не наблюдается.
    Но я уже нашел проблему. Все дело в процедуре
    procedure TCustomFlatButton.CNDrawItem(var Message: TWMDrawItem);



    Там надо сначала сохранить hDC, потом залочить канву компонента, потом выполнить все описанные действия, а в конце разлочить канву и вернуть hDC его значение. Вот.

    Если сделать все так - то работает :)
  • Игорь Шевченко © (25.03.09 14:36) [3]

    > Но я уже нашел проблему. Все дело в процедуре
    > procedure TCustomFlatButton.CNDrawItem(var Message: TWMDrawItem);
    >
    >
    > Там надо сначала сохранить hDC, потом залочить канву компонента,
    >  потом выполнить все описанные действия, а в конце разлочить
    > канву и вернуть hDC его значение. Вот.


    Вот я смотрю исходный текст TBitBtn.DrawItem (вызывается непосредственно из обработчика CNDrawItem)

    procedure TBitBtn.DrawItem(const DrawItemStruct: TDrawItemStruct);
    const
     WordBreakFlag: array[Boolean] of Integer = (0, DT_WORDBREAK);
    var
     IsDown, IsDefault: Boolean;
     State: TButtonState;
     R: TRect;
     Flags: Longint;
     Details: TThemedElementDetails;
     Button: TThemedButton;
     Offset: TPoint;
    begin
     FCanvas.Handle := DrawItemStruct.hDC;
     R := ClientRect;

     with DrawItemStruct do
     begin
       FCanvas.Handle := hDC;
       FCanvas.Font := Self.Font;
       IsDown := itemState and ODS_SELECTED <> 0;
       IsDefault := itemState and ODS_FOCUS <> 0;

       if not Enabled then State := bsDisabled
       else if IsDown then State := bsDown
       else State := bsUp;
     end;

     if ThemeServices.ThemesEnabled then
     begin
       if not Enabled then
         Button := tbPushButtonDisabled
       else
         if IsDown then
           Button := tbPushButtonPressed
         else
           if FMouseInControl then
             Button := tbPushButtonHot
           else
             if IsFocused or IsDefault then
               Button := tbPushButtonDefaulted
             else
               Button := tbPushButtonNormal;

       Details := ThemeServices.GetElementDetails(Button);
       // Parent background.
       ThemeServices.DrawParentBackground(Handle, DrawItemStruct.hDC, @Details, True);
       // Button shape.
       ThemeServices.DrawElement(DrawItemStruct.hDC, Details, DrawItemStruct.rcItem);
       R := ThemeServices.ContentRect(FCanvas.Handle, Details, DrawItemStruct.rcItem);

       if Button = tbPushButtonPressed then
         Offset := Point(1, 0)
       else
         Offset := Point(0, 0);
       TButtonGlyph(FGlyph).Draw(FCanvas, R, Offset, Caption, FLayout, FMargin, FSpacing, State, False,
         DrawTextBiDiModeFlags(0) or WordBreakFlag[WordWrap]);

       if IsFocused and IsDefault then
       begin
         FCanvas.Pen.Color := clWindowFrame;
         FCanvas.Brush.Color := clBtnFace;
         DrawFocusRect(FCanvas.Handle, R);
       end;
     end
     else
     begin
       R := ClientRect;

       Flags := DFCS_BUTTONPUSH or DFCS_ADJUSTRECT;
       if IsDown then Flags := Flags or DFCS_PUSHED;
       if DrawItemStruct.itemState and ODS_DISABLED <> 0 then
         Flags := Flags or DFCS_INACTIVE;

       { DrawFrameControl doesn't allow for drawing a button as the
           default button, so it must be done here. }

       if IsFocused or IsDefault then
       begin
         FCanvas.Pen.Color := clWindowFrame;
         FCanvas.Pen.Width := 1;
         FCanvas.Brush.Style := bsClear;
         FCanvas.Rectangle(R.Left, R.Top, R.Right, R.Bottom);

         { DrawFrameControl must draw within this border }
         InflateRect(R, -1, -1);
       end;

       { DrawFrameControl does not draw a pressed button correctly }
       if IsDown then
       begin
         FCanvas.Pen.Color := clBtnShadow;
         FCanvas.Pen.Width := 1;
         FCanvas.Brush.Color := clBtnFace;
         FCanvas.Rectangle(R.Left, R.Top, R.Right, R.Bottom);
         InflateRect(R, -1, -1);
       end
       else
         DrawFrameControl(DrawItemStruct.hDC, R, DFC_BUTTON, Flags);

       if IsFocused then
       begin
         R := ClientRect;
         InflateRect(R, -1, -1);
       end;

       FCanvas.Font := Self.Font;
       if IsDown then
         OffsetRect(R, 1, 1);
       TButtonGlyph(FGlyph).Draw(FCanvas, R, Point(0,0), Caption, FLayout, FMargin,
         FSpacing, State, False, DrawTextBiDiModeFlags(0) or WordBreakFlag[WordWrap]);

       if IsFocused and IsDefault then
       begin
         R := ClientRect;
         InflateRect(R, -4, -4);
         FCanvas.Pen.Color := clWindowFrame;
         FCanvas.Brush.Color := clBtnFace;
         DrawFocusRect(FCanvas.Handle, R);
       end;
     end;

     FCanvas.Handle := 0;
    end;



    Вроде ничего не сохраняется и не лочится, однако я не слышал, чтобы были проблемы изменения шрифта у TEdit, если на форме лежит TBitBtn.
  • ЮЮ © (26.03.09 09:05) [4]
    A это обязятельно?


    > FCanvas.Handle := 0;
    > end;


    А то у TBitBtn-а это всегда делается, а у TCustomFlatButton-а только у задизэйбленного
  • Юрий Зотов © (26.03.09 11:29) [5]
    А зачем такие навороты? Разве недостаточно просто выставить BS_FLAT?

    CreateParams(...)
     inherited
       if FFlat then
         ставим BS_FLAT
       else
         убираем BS_FLAT
    end;

    SetFlat(...)
     if FFlat <> Value then
     begin
       FFlat := Value;
       RecreateWnd;
    end;

 
Конференция "Компоненты" » Странности компонента [D5, WinXP]
Есть новые Нет новых   [134466   +3][b:0][p:0.005]