自从我提出这个问题以来已经过去了一段时间,但我在此期间找到了一个解决方案,并将发布它以防它对其他人有用。
错误标题和缺少菜单的问题来自 Delphi XE4 更改了 Firemonkey 应用程序的窗口结构这一事实。在此之前,主窗体的窗口被放置在任务栏上,并具有适当的标题和上下文菜单。在 XE4 中,应用程序创建一个类名为“TFMAppClass”的新窗口,并将其用作放置在任务栏中的主应用程序窗口。主窗体窗口是该窗体的兄弟。
这导致无法设置任务栏按钮标题、没有上下文菜单、无法正确响应单击按钮以及在隐藏主窗体时无法隐藏按钮。
因此,需要从任务栏中隐藏应用程序窗口并显示表单窗口。只做一次是不够的,因为应用程序窗口的样式在每次最小化/恢复时都会重置,并且会重新出现在任务栏上。
要隐藏应用程序窗口,只需调用ShowWindow(AppWindowHandle, SW_HIDE)
.
要在任务栏上显示主窗体窗口,我们必须设置WS_EX_APPWINDOW
扩展窗口样式,SetWindowLong()
并ShowWindow
在每次显示、恢复等应用程序时调用并将其置于前台。
这是通过放置一个挂钩来拦截 WM_CREATE、WM_SHOWWINDOW 和 WM_ACTIVATE 消息并在调用这些消息时应用样式来完成的。为了使使用更容易,所有代码都放在一个单元中,并且在initialization
部件中设置了钩子。
没有可调用的函数。要使用该单位,只需将其放在uses
子句中的某个位置。
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.
顺便说一句,此代码基于来自网络的两个示例。我不知道作者是谁,但我相信他们的想法。
还有一个问题。当应用程序第一次最小化时,应用程序窗口的按钮会临时重新出现,而不是表单按钮。在应用程序再次恢复或最小化后,这种情况就不会再发生了。