使用CreateDesktop / SwitchDesktop在新桌面中创建表单 [英] Create a form within a new Desktop using CreateDesktop/SwitchDesktop

查看:103
本文介绍了使用CreateDesktop / SwitchDesktop在新桌面中创建表单的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要为一个实用程序创建一个系统模式窗体,该窗体应阻塞整个窗口,直到输入某些值为止。因此,我正在尝试创建桌面和切换。到目前为止,创建一个切换到该桌面的桌面并返回对我来说还可以。



但是,当我尝试创建表单时,线程,该窗体不显示,但应用程序保留在新创建的空白桌面中,因此永远阻塞屏幕,直到我注销为止。



我是根据此处找到的代码实现的:



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

  // ScreenLocker.h 

#pragma一次

使用命名空间System;
使用名称空间System :: Windows :: Forms;

命名空间Developex
{
公共引用类ScreenLocker
{
private:
字符串^ _desktopName;
表格^ _form;
void DialogThread(void);

public:
静态void ShowSystemModalDialog(字符串^ desktopName,Form ^ form);
};
}


// ScreenLocker.cpp

#include stdafx.h
#include ScreenLocker.h

使用名称空间System :: Threading;
使用名称空间System :: Runtime :: InteropServices;

命名空间Developex
{
void ScreenLocker :: DialogThread()
{
//将句柄保存到当前桌面
HDESK hDeskOld = GetThreadDesktop(GetCurrentThreadId());

//创建一个新的桌面
IntPtr ptr = Marshal :: StringToHGlobalUni(_desktopName);
HDESK hDesk = CreateDesktop((LPCWSTR)ptr.ToPointer(),
NULL,NULL,0,GENERIC_ALL,NULL);
元帅:: FreeHGlobal(ptr);

//切换到新桌面
SwitchDesktop(hDesk);

//将新桌面分配给当前线程
SetThreadDesktop(hDesk);

//运行对话框
Application :: Run(_form);

//切换回初始桌面
SwitchDesktop(hDeskOld);
CloseDesktop(hDesk);
}

void ScreenLocker :: ShowSystemModalDialog(String ^ desktopName,Form ^ form)
{
//创建并初始化ScreenLocker实例
ScreenLocker ^ locker = gcnew ScreenLocker();
locker-> _desktopName = desktopName;
locker-> _form = form;

//为对话框
创建一个新线程(gcnew Thread(gcnew ThreadStart(locker,
& Developex :: ScreenLocker :: DialogThread)))->开始();
}
}

好吧,现在我正在尝试翻译到Delphi,到目前为止,这就是我所拥有的:

  unit Utils; 

界面

使用
Windows,消息,SysUtils,变体,类,图形,控件,表单,
对话框,StdCtrls,ADODB,网格, DBGrids,ExtCtrls,ComCtrls,SyncObjs,ShellApi,
AddTimeU;

类型
TFormShowThread = class(TThread)
HDesktopglobal:HDESK;
hDeskOld:HDESK;

UHeapSize:ULong;
tempDword:DWORD;

frm:TfrmBlockScreen;
私有
受保护的
程序执行;覆盖
public
构造函数Create(form:TfrmBlockScreen);
析构函数销毁;覆盖
结尾;

实现

构造函数TFormShowThread.Create(form:TfrmBlockScreen);
开始
FreeOnTerminate:= True;
继承了Create(True);
frm:=表格;
结尾;


析构函数TFormShowThread.Destroy;
开始
继承;
结尾;

过程TFormShowThread.Execute;
开始
hDeskOld:= GetThreadDesktop(GetCurrentThreadId());

HDesktopglobal:= CreateDesktop('Z',nil,nil,0,GENERIC_ALL,nil);

SwitchDesktop(HDesktopglobal);
SetThreadDesktop(HDesktopglobal);

//尝试了此
Application.CreateForm(TfrmBlockScreen,frm);

//也尝试了相同的结果
// frm:= TfrmBlockScreen.Create(nil);
//frm.Show();

SwitchDesktop(hDeskOld);

CloseDesktop(HDesktopglobal);
结尾;

结尾。

我正在使用以下代码运行该代码:

  var 
frmBlockScreen:TfrmBlockScreen;
frmShowThread:TFormShowThread;
开始

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

我不明白为什么这不起作用,C ++应该起作用,它创建了一个



这就是我结束操作的方式:



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



这是否有任何缺陷?

  var 
HDesktopglobal:HDESK;
hDeskOld:HDESK;

sDesktopName:字符串;
开始
应用程序。
Application.MainFormOnTaskbar:=真;

试试
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);

最后
SwitchDesktop(hDeskOld);
CloseDesktop(HDesktopglobal);
结尾;

Application.Run;
结尾。



过程ExecNewProcess(ProgramName:String; Desktop:String);
var
StartInfo:TStartupInfo;
ProcInfo:TProcessInformation;
CreateOK:布尔值;
开始

{用已知状态填充}
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);

{检查是否成功}
如果CreateOK,则
// //是否需要。通常等待子进程
WaitForSingleObject(ProcInfo.hProcess,INFINITE);
结尾;


解决方案

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



即使不是这种情况,您的代码也无法执行任何操作有用。那是因为您的线程不包含消息循环。窗体一经创建,与其关联的线程就终止。



您可以通过对 CreateWindow 等的原始Win32调用来实现此目的。但是至少您需要在其中创建的所有窗口的生命周期内在线程中运行消息循环。



关于为什么您的代码从不切换回原始桌面的原因,我不确定。可能在尝试创建表单的代码中存在异常,因此用于还原原始桌面的代码永远不会运行。



一般来说,要调试调用原始Win32 API的代码,必须包含错误检查。您无需执行任何操作,因此也不知道哪个API调用失败。如果我们不知道这种方法注定要失败,那将是调试此类问题的第一步。






也许我遗漏了一些东西,但是对我来说,为什么你试图从另一个线程运行这种形式并不明显。



要回答我自己的问题,我遗漏了一些东西。从 SetThreadDesktop


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



I need to create a system modal form for an utility that should block the entire windows until certain values are entered. So I'm experimenting with creating desktops and switching. So far, creating a desktop switching to it, and going back works fine for me.

But, when I try to create a form, from within a new thread, the form does not show up but the application keeps in the newly created blank desktop, therefore blocking the screen forever until I logoff.

I made it based in the code found here:

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();
   }
}

Well, now I'm trying to "translate" that to Delphi and so far this is what I have:

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.

I'm running that with this code:

var
  frmBlockScreen : TfrmBlockScreen;
  frmShowThread : TFormShowThread;
begin

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

I don't understand why this does not work and the C++, supposedly should work, it creates a new form within the same application.

This is how I ended doing it:

I moved the form that I wanted to show, to a new project and compiled it as timeup.exe. I created a process with the procedure shown below, sending the Desktop as parameter so I can assign the process to that desktop. This way I didn't even needed to create a new thread... it's working so far.

Is there any flaw in this?

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;

解决方案

Before you go any further you need to recognise that the VCL design forces all VCL forms to be associated with the main GUI thread. You cannot create them on a different thread. So your design is fundamentally flawed in that way. You are never going to be able to create VCL forms in any thread other than the main GUI thread.

Even if that was not the case, your code could not do anything useful. That's because your thread does not contain a message loop. No sooner has the form been created, the thread which it is associated with terminates.

You could make this work with raw Win32 calls to CreateWindow etc. But you'd need at the very least to run a message loop in your thread for the lifetime of any windows created there.

As to why your code never switches back to the original desktop, I cannot be sure. Perhaps there is an exception in the code that attempts to create the form and so the code that restores the original desktop never runs. That code should be protected by a try/finally.

As a general point, in order to debug code which calls raw Win32 APIs, you must include error checking. You don't do any, and so you don't know which API call is failing. That would be the first step to debugging a problem like this, if we didn't already know that the approach is doomed to failure no matter what.


Perhaps I'm missing something, but it's not obvious to me why you are trying to run this form out of a different thread. Is there any reason why it cannot run out of the main GUI thread?

And to answer my own question, I am missing something. From the documentation of SetThreadDesktop:

The SetThreadDesktop function will fail if the calling thread has any windows or hooks on its current desktop (unless the hDesktop parameter is a handle to the current desktop).

这篇关于使用CreateDesktop / SwitchDesktop在新桌面中创建表单的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆