delphitaskbardelphi-xe4firemonkey-fm3

Missing taskbar button context menu in Windows XP with FM3


I upgraded to XE4 recently and am now in the progress of finding the changes and so-called fixes from XE3.

One that surprised me a lot is that the context menu for the button on the taskbar doesn't appear any more.

It is very easy to duplicate: Just create a new Firemonkey project in XE4 and run it on Windows. Right click on the taskbar application button and see if the context menu comes up. I mean the menu with "Close", "Restore", "Minimize", etc. This is only on Windows XP and Server 2003. On Win7 it works and shows the "Close" menu item.

Also the title of the button is now different. It should be "Form 1" as the caption of the main form, but instead it is Project1 as the executable name. This is on all Windows versions.

Can someone help me with this one? People still use XP and this behavior is quite unexpected for the user.

Thanks


Solution

  • Some time has passed since I asked the question, but I found a solution in the meantime and will post it in case it is useful for someone else.

    The problem with the wrong caption and missing menu comes from the fact that Delphi XE4 changed the window structure of the Firemonkey apps. Before that the window of the main form was placed on the taskbar and had the appropriate caption and context menu. In XE4 the app creates a new window with a class name "TFMAppClass" and uses it as the main application window that is placed in the taskbar. The main form window is a sibling of that one.
    This leads to not being able to set the taskbar button caption, not having a context menu, not responding properly to clicks on the button and not being to hide the button when the main form is hidden.

    So, what was needed was to hide the app window from the taskbar and show the form window instead. To do it once is not enough though, because the app window's style is reset on each minimize/restore and it reappears on the taskbar.

    To hide the app window simply call ShowWindow(AppWindowHandle, SW_HIDE).
    To show the main form window on the taskbar we have to set the WS_EX_APPWINDOW extended window style with SetWindowLong() and call ShowWindow each time the app is shown, restored, etc and bring it to the foreground.

    This is done by placing a hook to intercept the WM_CREATE, WM_SHOWWINDOW and WM_ACTIVATE messages and apply the styles when those messages are called. To make the usage easier all of the code is placed in a single unit and the hook is set in the initialization part.
    There are no functions to call. To use the unit simply place it somewhere in the uses clause.

    unit FM3TaskbarFix;
    
    interface
    
    implementation
    
    {$IFDEF MSWINDOWS}
    uses
      Winapi.Messages, Winapi.Windows, System.Sysutils, Fmx.Forms, Fmx.Platform.Win;
    
    var
      GHookHandle: HHOOK;      // Handle for the hook we set
      GAppWnd    : HWND = 0;   // Handle of the main application window
    
    function CallWndProc(nCode: Integer; iWParam: WPARAM; iLParam: LPARAM): LRESULT; stdcall;
    var
      ActiveThreadID, WindowThreadID: DWORD;
      ProcMsg: TCWPStruct;
    begin
      Result := CallNextHookEx(GHookHandle, nCode, iWParam, iLParam);
    
      if (nCode < 0) then
        Exit;
    
      ProcMsg := PCWPStruct(iLParam)^;
    
      case ProcMsg.message of
        WM_CREATE:
          // Save the "main" app window handle for later usage. There is only one window with the TFMAppClass class per app
          if (GAppWnd = 0) and (PCREATESTRUCT(ProcMsg.lParam)^.lpszClass = 'TFMAppClass') then
            GAppWnd := ProcMsg.hwnd;
    
        WM_ACTIVATE, WM_SHOWWINDOW:
        begin
          // Hide the app window. This has to be called on each minimize, restore, etc.
          if IsWindowVisible(GAppWnd) then
            ShowWindow(GAppWnd, SW_HIDE);
    
          // Only handle Show/Activate. wParam of 1 means the app is shown or activated, NOT hidden or deactivated
          // Also apply the style settings only to the Application.MainForm
          // We don't want to show other forms on the taskbar
          if (ProcMsg.wParam = 1) and
             (GetWindow(ProcMsg.hwnd, GW_OWNER) = GAppWnd) and Assigned(Application.MainForm) and
             (WindowHandleToPlatform(Application.MainForm.Handle).Wnd = ProcMsg.hwnd) then
          begin
            // Show the main form on the taskbar
            SetWindowLong(ProcMsg.hwnd, GWL_EXSTYLE, GetWindowLong(ProcMsg.hwnd, GWL_EXSTYLE) or WS_EX_APPWINDOW);
            ShowWindow(ProcMsg.hwnd, SW_SHOW);
    
            ActiveThreadID := GetWindowThreadProcessId(GetForegroundWindow, nil);
            WindowThreadID := GetWindowThreadProcessId(ProcMsg.hwnd, nil);
            AttachThreadInput(WindowThreadID, ActiveThreadID, True);
            try
              SetForegroundWindow(ProcMsg.hwnd);
              SetActiveWindow(ProcMsg.hwnd);
            finally
              AttachThreadInput(WindowThreadID, ActiveThreadID, False);
            end;
          end;
        end; { WM_ACTIVATE, WM_SHOWWINDOW }
    
      end; { case }
    end;
    
    initialization
      GHookHandle := SetWindowsHookEx(WH_CALLWNDPROC, CallWndProc, 0, GetCurrentThreadID);
    
    finalization
      UnhookWIndowsHookEx(GHookHandle);
    
    {$ENDIF}
    
    end.
    

    Btw, this code is based on two samples from the net. I don't know who the authors are, but I give them credit for the idea.
    There is one issue that remains. When the app is first minimized the button of the app window reappears temporarily, instead of the form button. After the app is restored or minimized again this doesn't happen anymore.