Ticket T1135725
Visible to All Users

Change WM_MOUSEACTIVATE handling

created 2 years ago

The current handling of the WM_MOUSEACTIVATE message in the dxBars unit does not take the MouseActivate event into account. As a consequence of this it is impossible to control what happens when the user activates the application with a mouse click.

For example, we have an application with a ribbon and a number of forms docked to the main form. The docked forms are called "views".
The buttons on the ribbon are enabled and disabled depending of which view is active. We also have contextual tabs on the ribbon which depends on the active view.

This works fine as long as all views remain docked to the main form but if the user undocks a view, e.g. to move it to another monitor, the current behavior in dxBars breaks our enable/disable logic: As soon as the user clicks a button on the ribbon, the button activates the main form.
This becomes a problem if we for example have two views: One docked and one undocked. With the undocked view active, the user clicks a button. This causes the button to activate the main form which in turn deactivates the undocked view and activate the docked view thus changing the context. In effect it becomes impossible to click any button that depends on the undocked view being active.

Luckily Windows provides a mechanism to solve this problem: The WM_MOUSEACTIVATE message. WM_MOUSEACTIVATE is surfaced neatly by the VCL as the MouseActivate event handler. By handling the MouseActivate event on the main form we should be able to specify that a mouse click should not activate the window by setting the MouseActivate to maNoActivate. In this example we block activation if the form is already active:

Delphi
procedure TMyForm.FormMouseActivate(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y, HitTest: Integer; var MouseActivate: TMouseActivate); begin if (not IsFormActive(Self)) then MouseActivate := maNoActivate; end;

Unfortunately this is where dxBars gets in the way. The way WM_MOUSEACTIVATE is handled in dxBars it is not possible to override the handling with the MouseActivate event handler because dxBars unconditionally activate the window regardless of any prior handling of WM_MOUSEACTIVATE.

I request that this behavior be changed so dxBars take WM_MOUSEACTIVATE into account.


The following changes implement the change in the dxBars unit:

Delphi
// Do not explicitly activate window in `WM_MOUSEACTIVATE` handler. // Optional: Take prior handling of `WM_MOUSEACTIVATE` into account. procedure TdxDockControl.WMMouseActivate(var Message: TWMMouseActivate); begin inherited; if (FBarManager <> nil) and not IsDesigning and (dxBarGetParentPopupWindow(Self, True) = nil) then begin {$ifdef PATCHED} // Only modify result if no one else did (e.g. parent MouseActivate event) if (Message.Result = 0) then {$endif PATCHED} Message.Result := MA_NOACTIVATE; dxSetZOrder(MainForm.Handle, HWND_TOP {$ifndef PATCHED}, True{$endif PATCHED}); end; end;
Delphi
// Optional: Take prior handling of `WM_MOUSEACTIVATE` into account. procedure TCustomdxBarControl.WMMouseActivate(var Message: TWMMouseActivate); begin inherited; {$ifdef PATCHED} // Only modify result if no one else (e.g. parent MouseActivate event) did if (Message.Result = 0) then {$endif PATCHED} Message.Result := MA_NOACTIVATE; end;
Delphi
// Do not activate the window if application is already active. procedure TdxBarControl.DoBarGetFocus(ASelectedItem: TdxBarItemControl); ... begin ... if not IsCustomizing and not (IsPopup and ParentBar.Focused) and (dxBarGetParentPopupWindow(Self, True) = nil) then // Only activate if application isn't already active. Required in order for custom WM_MOUSEACTIVATE handling to work. dxSetZOrder(MasterForm.Handle, HWND_TOP, {$ifdef PATCHED}not Application.Active{$else PATCHED}True{$endif PATCHED}); ... end;

Only the modified calls to dxSetZOrder are required. The rest are just to make custom WM_MOUSEACTIVATE handling (e.g. MouseActivate event) work as it should.

Show previous comments (7)
AM AM
Anders Melander 2 years ago

    Yes that would work for the test case - but unfortunately the test case is just a very simplified example used to demonstrate the problem. The logic of our actual applications are much more complex. For example not all views are docked forms and activating some other form or control will deactivate the view thus causing the contextual tab to be hidden.
    The actual logic is more like this:

    Delphi
    procedure TFormMain.DockPanelViewBActivate(Sender: TdxCustomDockControl; Active: Boolean); begin if (Active) then begin ActiveView := ViewB; ActiveControl := SomeDummyControl; // This was your workaround... end else ActiveView := ViewNone; // ...but this will cause the view to hide its context regardless end;

    I understand that you're trying to find workarounds to avoid changing the base library but, as far as I've been able to determine, no workarounds are possible in this case because the problem is really caused by dxBars not taking WM_MOUSEACTIVATE into account. Fixing that would enable me to selectively not activate the main form on mouse clicks - for example if the user clicks on the ribbon.

    AP AP
    Alex Pa (DevExpress Support) 2 years ago

      To my regret, we cannot suggest a solution for this task out of the box. Thank you for sharing details of your research and information about this usage scenario.

      AM AM
      Anders Melander 2 years ago

        This was not a request for help. This is a report of a problem in your library which breaks a mechanism provided by Windows (WM_MOUSEACTIVATE) and the VCL (TForm.MouseActivate event).

        Your answer appears to indicate that you disagree and are not going to do anything about it. If that is the case, say so directly.

        Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

        Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.