Create a form within a new Desktop using CreateDesktop/SwitchDesktop

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;

Answers


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).


Need Your Help

combobox not updating from viewmodel to view in .net 4.5

c# .net wpf mvvm

I have a ComboBox in wpf that twoway binds between the view and viewmodel.

AngularJS: How to parse JSON string to integer or float to display it in form number field

javascript arrays json angularjs laravel

I am using AngularJS for my front-end and Laravel for the back-end. I am passing a variable to front-end screen called orderItems which contains the following data to my front-end.

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.