使用CreateDesktop / SwitchDesktop在新桌面内创build表单

我需要为应用程序创build一个系统模式表单,该表单应该阻止整个窗口,直到input特定值。 所以我正在尝试创build桌面和切换。 到目前为止,创build一个桌面切换到它,并返回正常工作对我来说。

但是, 当我尝试创build表单时,从新线程中,表单不显示,但应用程序保留在新创build的空白桌面,因此永远屏蔽屏幕,直到我注销。

我在这里find的代码中创build它:

System Modal Dialog

// ScreenLocker.h #pragma once using namespace System; using namespace System::Windows::Forms; namespace Developex { public ref class ScreenLocker { private: String ^_desktopName; Form ^_form; void DialogThread(void); public: static void ShowSystemModalDialog (String ^desktopName, Form ^form); }; } // ScreenLocker.cpp #include "stdafx.h" #include "ScreenLocker.h" using namespace System::Threading; using namespace System::Runtime::InteropServices; namespace Developex { void ScreenLocker::DialogThread() { // Save the handle to the current desktop HDESK hDeskOld = GetThreadDesktop(GetCurrentThreadId()); // Create a new desktop IntPtr ptr = Marshal::StringToHGlobalUni(_desktopName); HDESK hDesk = CreateDesktop((LPCWSTR)ptr.ToPointer(), NULL, NULL, 0, GENERIC_ALL, NULL); Marshal::FreeHGlobal(ptr); // Switch to the new deskop SwitchDesktop(hDesk); // Assign new desktop to the current thread SetThreadDesktop(hDesk); // Run the dialog Application::Run(_form); // Switch back to the initial desktop SwitchDesktop(hDeskOld); CloseDesktop(hDesk); } void ScreenLocker::ShowSystemModalDialog(String ^desktopName, Form ^form) { // Create and init ScreenLocker instance ScreenLocker ^locker = gcnew ScreenLocker(); locker->_desktopName = desktopName; locker->_form = form; // Create a new thread for the dialog (gcnew Thread(gcnew ThreadStart(locker, &Developex::ScreenLocker::DialogThread)))->Start(); } } 

那么,现在我试图“翻译”到Delphi,到目前为止,这是我的:

 unit Utils; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ADODB, Grids, DBGrids, ExtCtrls, ComCtrls, SyncObjs, ShellApi, AddTimeU; type TFormShowThread = class(TThread) HDesktopglobal: HDESK; hDeskOld: HDESK; UHeapSize: ULong; tempDword: DWORD; frm : TfrmBlockScreen; private protected procedure Execute; override; public constructor Create(form : TfrmBlockScreen); destructor Destroy; override; end; implementation constructor TFormShowThread.Create(form : TfrmBlockScreen); begin FreeOnTerminate := True; inherited Create(True); frm := form; end; destructor TFormShowThread.Destroy; begin inherited; end; procedure TFormShowThread.Execute; begin hDeskOld := GetThreadDesktop(GetCurrentThreadId()); HDesktopglobal := CreateDesktop('Z', nil, nil, 0, GENERIC_ALL, nil); SwitchDesktop(HDesktopglobal); SetThreadDesktop(HDesktopglobal); // tried this Application.CreateForm(TfrmBlockScreen, frm); // also tried this with same result //frm := TfrmBlockScreen.Create(nil); //frm.Show(); SwitchDesktop(hDeskOld); CloseDesktop(HDesktopglobal); end; end. 

我用这个代码运行:

 var frmBlockScreen : TfrmBlockScreen; frmShowThread : TFormShowThread; begin frmShowThread := TFormShowThread.Create(frmBlockScreen); frmShowThread.Priority := tpNormal; frmShowThread.OnTerminate := ThreadDone; frmShowThread.Start(); 

我不明白为什么这不起作用,C ++,据说应该工作,它在同一个应用程序中创build一个新的forms。

这就是我如何结束的:

我把我想要显示的表单移动到一个新的项目中,并将其编译为timeup.exe。 我使用下面显示的过程创build了一个进程,发送桌面作为参数,以便我可以将进程分配给该桌面。 这样,我甚至不需要创build一个新的线程…它的工作到目前为止。

这有什么缺点吗?

 var HDesktopglobal: HDESK; hDeskOld: HDESK; sDesktopName : String; begin Application.Initialize; Application.MainFormOnTaskbar := True; try hDeskOld := GetThreadDesktop(GetCurrentThreadId()); sDesktopName := 'TimeUpDesktop'; HDesktopglobal := CreateDesktop(PWideChar(sDesktopName), nil, nil, 0, GENERIC_ALL, nil); SwitchDesktop(HDesktopglobal); SetThreadDesktop(HDesktopglobal); ExecNewProcess('TimeUp.exe', sDesktopName); SwitchDesktop(hDeskOld); CloseDesktop(HDesktopglobal); finally SwitchDesktop(hDeskOld); CloseDesktop(HDesktopglobal); end; Application.Run; end. procedure ExecNewProcess(ProgramName : String; Desktop : String); var StartInfo : TStartupInfo; ProcInfo : TProcessInformation; CreateOK : Boolean; begin { fill with known state } FillChar(StartInfo,SizeOf(TStartupInfo),#0); FillChar(ProcInfo,SizeOf(TProcessInformation),#0); StartInfo.cb := SizeOf(TStartupInfo); StartInfo.lpDesktop := PChar(Desktop); CreateOK := CreateProcess(PChar(ProgramName),nil, nil, nil,False, CREATE_NEW_PROCESS_GROUP+NORMAL_PRIORITY_CLASS, nil, nil, StartInfo, ProcInfo); { check to see if successful } if CreateOK then //may or may not be needed. Usually wait for child processes WaitForSingleObject(ProcInfo.hProcess, INFINITE); end; 

在进一步研究之前,您需要认识到VCL设计强制所有的VCL表单都与主GUI线程相关联。 你不能在不同的线程上创建它们。 所以你的设计从根本上是有缺陷的。 除了主GUI线程之外,您永远无法在任何线程中创建VCL表单。

即使情况不是这样,你的代码也不能做任何有用的事情。 这是因为你的线程不包含消息循环。 一旦表单被创建,它所关联的线程就终止。

你可以使用原始的Win32调用CreateWindow等工作。但是,你至少需要在你的线程中运行一个消息循环,以便在那里创建的任何窗口的生命周期。

至于为什么你的代码永远不会切换回原来的桌面,我不能确定。 也许在尝试创建表单的代码中存在一个例外,因此恢复原始桌面的代码永远不会运行。 该代码应该由try / finally保护。

一般来说,为了调试调用原始Win32 API的代码,必须包含错误检查。 你不做任何事情,所以你不知道哪个API调用失败。 这将是调试这样的问题的第一步,如果我们不知道这种方法注定要失败,不管怎样。


也许我错过了一些东西,但是我不明白为什么你要从不同的线程运行这个表单。 有什么理由不能用完主GUI线程吗?

回答我自己的问题,我错过了一些东西。 从SetThreadDesktop的文档:

如果调用线程在其当前桌面上有任何窗口或挂钩,则SetThreadDesktop函数将失败(除非hDesktop参数是当前桌面的句柄)。