【问题标题】:Create a form within a new Desktop using CreateDesktop/SwitchDesktop使用 CreateDesktop/SwitchDesktop 在新桌面中创建表单
【发布时间】:2013-05-02 21:24:20
【问题描述】:

我需要为一个实用程序创建一个系统模式表单,它应该阻止整个窗口,直到输入某些值。所以我正在尝试创建桌面和切换。到目前为止,创建一个桌面切换到它,然后返回对我来说很好。

但是,当我尝试从一个新线程中创建一个表单时,该表单没有显示,但应用程序保留在新创建的空白桌面中,因此在我注销之前永远阻塞屏幕。

我是根据这里找到的代码制作的:

http://developex.com/blog/system-modal-back/

// 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++ 应该可以工作,它会在同一个应用程序中创建一个新表单。

这就是我结束的方式:

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

这有什么缺陷吗?

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不是线程安全的,你不能像你一样使用Application.CreateForm在辅助线程中创建表单。如果要从主 (GUI) 线程以外的线程创建和显示窗口,则需要使用 API 调用来执行此操作。 (请参阅 MSDN 上的 RegisterClassCreateWindow 开始。)
  • 创建一个新进程并使用 IPC 在两者之间进行通信听起来无论如何都是最健壮的设计,无论您使用其他方法有什么问题。我做过类似的事情(虽然在同一个桌面上)并且效果很好 - 我使用命名管道进行通信。我建议你坚持下去!

标签: c++ windows delphi winapi


【解决方案1】:

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

即使不是这样,您的代码也无法做任何有用的事情。那是因为您的线程不包含消息循环。创建表单后,与其关联的线程立即终止。

您可以通过对CreateWindow 等的原始 Win32 调用来完成这项工作。但您至少需要在线程中运行一个消息循环,以在其中创建任何窗口的生命周期。

至于为什么您的代码从不切换回原始桌面,我无法确定。尝试创建表单的代码中可能存在异常,因此恢复原始桌面的代码永远不会运行。该代码应该受到 try/finally 的保护。

一般来说,为了调试调用原始 Win32 API 的代码,您必须包括错误检查。你什么都不做,所以你不知道哪个 API 调用失败了。如果我们还不知道这种方法无论如何都注定要失败,那将是调试此类问题的第一步。


也许我遗漏了一些东西,但我不明白为什么你试图从不同的线程中运行这个表单。是否有任何原因导致它无法用完主 GUI 线程?

为了回答我自己的问题,我遗漏了一些东西。来自SetThreadDesktop的文档:

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

【讨论】:

  • 谢谢,是的,我知道我还缺少循环。但我想试试我拥有的代码。您的主要观点是真正的答案:VCL 表单仅与主 GUI 线程相关联。所以,如果我想显示一个“表单”,我想我需要创建一个由 CreateProcess 调用调用的单个表单应用程序......对吗?
  • 为什么需要这个表单在不同的线程或进程中?为什么不能从主 GUI 线程中运行它。
  • 我在一个线程上创建了它,以遵循主要问题中显示的 C++ 示例。为了“帮助”表单显示在另一个桌面上,这对我来说也很有意义,但是在您解释了 delphi Forms 运行在哪些线程之后,我很清楚无论如何都不需要它。实际上,在我找到 C++ 代码之前,我在主线程中拥有它,但我认为创建专用线程将是导致它无法正常工作的关键。但是在主线程或新线程中,我一直得到相同的结果:只是切换,表单从未出现,也从未切换回来。
  • 好的,看看我的最新更新。您确实需要一个新线程,以便这是与该线程关联的第一个窗口。
  • 谢谢,大卫,让我们回顾一下。 1)我保留我的线程类 2)使用 CreateWindow 手动创建我的表单
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-31
  • 1970-01-01
  • 1970-01-01
  • 2016-10-05
  • 2012-12-16
  • 2011-02-17
相关资源
最近更新 更多