Конференция "WinAPI" » Сообщения в неклиентской области окна
 
  • ProgRAMmer Dimonych © (05.02.10 00:37) [0]
    Окно WS_POPUP прорисовываю самостоятельно, в т.ч. свой заголовок с кнопками сворачивания/разворачивания/закрытия (дальше - СРЗ). Обработкой сообщения WM_NCHITTEST заставил систему поверить, что мой заголовок - это и есть заголовок окна. Теперь окно перетаскивается за "заголовок" без дополнительных телодвижений в программе.

    Стоит задача: обработать случай, когда щелчок попадает по одной из нарисованных мной кнопок СРЗ.

    Мои действия:
    1. Пишу обработчик WM_NCLBUTTONUP. Обнаруживаю, что сообщение не приходит. MSDN+Google: нужно обработать WM_NCLBUTTONDOWN, чтобы система не перехватывала ввод от мыши и не думала, что пользователь начинает перетаскивать окно.
    2. Добавляю обработчик WM_LBUTTONDOWN, возвращаю 0 в соответствии с MSDN, чтобы дать понять, что обрабатываю сам. Получаю закономерную реакцию: кнопки нажимаются, а окно становится неподвижным.
    3. Изменяю обработчик WM_LBUTTONDOWN так, чтобы 0 возвращался только при условии, что мышь на одной из кнопок СРЗ, в противном случае передаю сообщение на растерзание DefWindowProc(). Кнопки нажимаются, окно перетаскивается. Но после того, как перетаскивание случилось, кнопки уже не реагируют.
    4. MSDN+Google подсказывают красивое на первый взгляд решение: не дать системе опомниться и в обработчике WM_NCHITTEST для областей, соответствующих кнопкам, возвращать HTBORDER, а не HTCAPTION. Результат: в зависимости от того, что возвращается вместо HTCAPTION, поведение меняется, но желаемого добиться не удаётся. Как частный случай: если возвращать HTCLOSE - система при щелчке нагло прорисовывает стандартную кнопку закрытия окна поверх нарисованной мною.

    Есть ли вообще шансы обойтись без ручной обработки перетаскивания окна? Каким способом вообще лучше всего реализовать такую функциональность?

    P.S. По традиции, системы: все Win32.
  • Игорь Шевченко © (05.02.10 01:14) [1]
    Возвращай HTCAPTION только если ты находишься в области своего заголовка, но не своих кнопок, в случае кнопок возвращай HTCLIENT и обрабатывай нажатия самостоятельно (только SetCapture не надо делать)
  • Игорь Шевченко © (05.02.10 01:28) [2]
    Виноват, SetCapture надо делать
  • ProgRAMmer Dimonych © (05.02.10 01:36) [3]
    Упс, это ж тогда обрабатывать обычное WM_LBUTTONUP? Работает, спс.
  • ProgRAMmer Dimonych © (05.02.10 01:36) [4]
    Без SetCapture() почему-то %)
  • Игорь Шевченко © (05.02.10 02:02) [5]
    ProgRAMmer Dimonych ©   (05.02.10 01:36) [4]


    > Без SetCapture() почему-то %)


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

    Зачем нужно вызывать SetCapture - если ты нажмешь левой клавишей мыши в области кнопки управления окном и начнешь перемещать мышь, не отпуская нажатой клавиши, без SetCapture каждое мышиное событие будет посылать сообщения WM_NCHITTEST, WM_SETCURSOR, WM_(NC)MOUSEMOVE, пока курсор находится в области твоего окна. При уходе мыши из твоего окна, мышиные сообщения твоему окну не посылаются и у тебя останется состояние "зависшего WM_LBUTTONDOWN".

    Если же при обработке WM_LBUTTONDOWN вызвать SetCapture, то во-первых, при движении мыши не будет посылаться WM_NCHITTEST, во-вторых, сообщения о перемещениях и состоянии клавиш мыши будут посылаться твоему окну, вне зависимости от того, где находится курсор мыши.

    До тех пор, пока ты не отпустишь кнопку.
  • Игорь Шевченко © (05.02.10 02:04) [6]
    Я когда-то писал пример для самостоятельного рисования заголовка с кнопками:

    unit Popup2;

    interface
    uses
     Windows, Messages, SysUtils,
     Classes, Graphics, Controls, Forms,
     Dialogs, StdCtrls, ExtCtrls;

    type
     TCapturedButton = (cbtNone, cbtClose, cbtMinimize);

     TfPopup2 = class(TForm)
       ClientPanel: TPanel;
       CloseButton: TButton;
       procedure FormPaint(Sender: TObject);
       procedure FormClose(Sender: TObject; var Action: TCloseAction);
       procedure CloseButtonClick(Sender: TObject);
       procedure FormCreate(Sender: TObject);
       procedure FormActivate(Sender: TObject);
       procedure FormDeactivate(Sender: TObject);
       procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
         Shift: TShiftState; X, Y: Integer);
       procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
         Shift: TShiftState; X, Y: Integer);
       procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X,
         Y: Integer);
     private
       FCaptionRect : TRect;
       FCapturedButton : TCapturedButton;
       FMouseInCloseButton : Boolean;
       FMouseInMinimizeButton : Boolean;
       FCloseButtonRect : TRect;
       FMinimizeButtonRect : TRect;
       FWindowActive : Boolean;
       FWindowMinimized : Boolean;
       FSavedHeight : Integer;
       procedure WMHCHitTest (var Message : TWMNCHitTest); message WM_NCHITTEST;
       procedure MinimizeButtonClick;
     end;

    var
     fPopup2: TfPopup2;

    implementation

    {$R *.dfm}

    procedure TfPopup2.FormPaint(Sender: TObject);
    var
     ARect : TRect;
     CaptionFlags : UINT;
     MinButtonKind : UINT;
    begin
     CaptionFlags := DC_GRADIENT OR DC_SMALLCAP OR DC_TEXT;
     if FWindowActive then
       CaptionFlags := CaptionFlags OR DC_ACTIVE;
     ARect := ClientRect;
    { DrawEdge рисует линию толщиной в 2 пикселя (Tnx, [MBo]) }
     DrawEdge(Canvas.Handle, ARect, BDR_RAISED, BF_RECT);
     DrawCaption(Handle, Canvas.Handle, FCaptionRect, CaptionFlags);
     if FWindowMinimized then
       MinButtonKind := DFCS_CAPTIONMAX
     else
       MinButtonKind := DFCS_CAPTIONMIN;
     if (FCapturedButton = cbtClose) AND (FMouseInCloseButton) then
       DrawFrameControl(Canvas.Handle, FCloseButtonRect, DFC_CAPTION, DFCS_CAPTIONCLOSE OR DFCS_PUSHED)
     else
       DrawFrameControl(Canvas.Handle, FCloseButtonRect, DFC_CAPTION, DFCS_CAPTIONCLOSE);
     if (FCapturedButton = cbtMinimize) AND (FMouseInMinimizeButton) then
       DrawFrameControl(Canvas.Handle, FMinimizeButtonRect, DFC_CAPTION, MinButtonKind OR DFCS_PUSHED)
     else
       DrawFrameControl(Canvas.Handle, FMinimizeButtonRect, DFC_CAPTION, MinButtonKind);
    end;

    procedure TfPopup2.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
     Action := caFree;
    end;

    procedure TfPopup2.CloseButtonClick(Sender: TObject);
    begin
     Close();
    end;

    procedure TfPopup2.FormCreate(Sender: TObject);
    begin
     SetRect(FCaptionRect, GetSystemMetrics(SM_CXSIZEFRAME), GetSystemMetrics(SM_CYSIZEFRAME),
             Width - GetSystemMetrics(SM_CXSIZEFRAME),
             GetSystemMetrics(SM_CYSMCAPTION)+GetSystemMetrics(SM_CYSIZEFRAME)-1);
     FCloseButtonRect := FCaptionRect;
     InflateRect(FCloseButtonRect, -1, -1);
     FCloseButtonRect.Left := FCloseButtonRect.Right -
                                (FCloseButtonRect.Bottom - FCloseButtonRect.Top);
     FMinimizeButtonRect := FCloseButtonRect;
     OffsetRect(FMinimizeButtonRect, - (FCloseButtonRect.Right - FCloseButtonRect.Left) - 2, 0);
    end;

    procedure TfPopup2.FormActivate(Sender: TObject);
    begin
     FWindowActive := true;
     InvalidateRect(Handle, @FCaptionRect, false);
    end;

    procedure TfPopup2.FormDeactivate(Sender: TObject);
    begin
     FWindowActive := false;
     InvalidateRect(Handle, @FCaptionRect, false);
    end;

    procedure TfPopup2.WMHCHitTest (var Message : TWMNCHitTest);
    begin
     if PtInRect (FCaptionRect,
            ScreenToClient(SmallPointToPoint(Message.Pos))) and NOT
        PtInRect (FCloseButtonRect,
            ScreenToClient(SmallPointToPoint(Message.Pos))) and not
        PtInRect (FMinimizeButtonRect,
            ScreenToClient(SmallPointToPoint(Message.Pos))) then
       Message.Result := HTCAPTION
     else
       inherited;
    end;

    procedure TfPopup2.FormMouseDown(Sender: TObject; Button: TMouseButton;
     Shift: TShiftState; X, Y: Integer);
    begin
     if Button <> mbLeft then
       Exit;
     if NOT PtInRect(FCaptionRect, Point(X,Y)) then
       Exit;
     if PtInRect(FCloseButtonRect, Point(X,Y)) then begin
       FCapturedButton := cbtClose;
       SetCapture(Handle);
       FMouseInCloseButton := true;
     end else if PtInRect(FMinimizeButtonRect, Point(X,Y)) then begin
       FCapturedButton := cbtMinimize;
       FMouseInMinimizeButton := true;
       SetCapture(Handle);
     end;
     if FCapturedButton <> cbtNone then
       InvalidateRect(Handle, @FCaptionRect, false);
    end;

    procedure TfPopup2.FormMouseUp(Sender: TObject; Button: TMouseButton;
     Shift: TShiftState; X, Y: Integer);
    var
     OldButton : TCapturedButton;
    begin
     if Button <> mbLeft then
       Exit;
     if FCapturedButton = cbtNone then
       Exit;
     OldButton := cbtNone;
     if PtInRect(FCloseButtonRect, Point(X,Y)) then
       OldButton := cbtClose
     else if PtInRect(FMinimizeButtonRect, Point(X,Y)) then
       OldButton := cbtMinimize;
     ReleaseCapture();
     FCapturedButton := cbtNone;
     if OldButton <> cbtNone then begin
       InvalidateRect(Handle, @FCaptionRect, false);
       case OldButton of
       cbtClose:
         Close();
       cbtMinimize:
         MinimizeButtonClick;
       end;
     end;
    end;

    procedure TfPopup2.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
     Y: Integer);
    begin
     if FCapturedButton = cbtNone then
       Exit;
     FMouseInCloseButton := PtInRect(FCloseButtonRect, Point(X,Y));
     FMouseInMinimizeButton := PtInRect(FMinimizeButtonRect, Point(X,Y));
     InvalidateRect(Handle, @FCloseButtonRect, false);
     InvalidateRect(Handle, @FMinimizeButtonRect, false);
    end;

    procedure TfPopup2.MinimizeButtonClick;
    begin
     if NOT FWindowMinimized then begin
       FSavedHeight := Height;
       Height := GetSystemMetrics(SM_CYSIZEFRAME)*2 +
             FCaptionRect.Bottom - FCaptionRect.Top;
     end else
       Height := FSavedHeight;
     FWindowMinimized := NOT FWindowMinimized;
     Invalidate;
    end;

    end.

  • ProgRAMmer Dimonych © (05.02.10 02:27) [7]
    Спасибо за пример, сейчас попробую вчитаться. Мне, наверное, показалось, что всё работает нормально, потому что пока что никакой визуально наблюдаемой реакции на нажатие (без отпускания) и наведение для этих кнопок не реализовано... Так что надо будет действительно покопаться как следует.
 
Конференция "WinAPI" » Сообщения в неклиентской области окна
Есть новые Нет новых   [134432   +18][b:0][p:0.003]