4

I need to embed a WinForms form (with BorderStyle = None) into the Inno Setup Wizard and have an issue.

Here is an Inno Setup script:

procedure EmbedConfiguratorForm(parentWnd: HWND);
  external 'EmbedConfiguratorForm@files:configurator.dll stdcall';

procedure InitializeWizard();
var
  cfgPageHandle: HWND;
begin
  cfgPageHandle := CreateCustomPage(wpSelectDir, 
    'Configuration', 
    ExpandConstant(description)).Surface.Handle;
  EmbedConfiguratorForm(cfgPageHandle);
end;

Here is a C# code:

class WizardWindow : IWin32Window
{
    public WizardWindow(IntPtr handle)
    {
        Handle = handle;
    }

    public WizardWindow(int handle) : this(new IntPtr(handle))
    {
    }

    public IntPtr Handle { get; private set; }
}

public static class MainClass
{
    [DllExport("EmbedConfiguratorForm", CallingConvention.StdCall)]
    public static void EmbedConfiguratorForm(int parentWnd)
    {
        // System.Diagnostics.Debugger.Launch();
        ConfiguratorForm form = new ConfiguratorForm();
        form.Show(new WizardWindow(parentWnd));
    }
}

It works but not as expected. After setup loads, it automatically call EmbedConfiguratorForm from configurator.dll and the form shows but not into setup wizard page. It shows behind (see screenshot). So what am I doing wrong?

enter image description here

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
Alexey Kulikov
  • 1,097
  • 1
  • 14
  • 38
  • Possible duplicate of [How to embed a form from a DLL into an Inno Setup wizard page?](http://stackoverflow.com/questions/21795300/how-to-embed-a-form-from-a-dll-into-an-inno-setup-wizard-page) – Martin Prikryl Jan 04 '16 at 06:27
  • Your solution is the same as in the duplicate question, except that they pass a parent handle to the DLL, while you pass a child handle from the DLL. – Martin Prikryl Jan 04 '16 at 06:28
  • Sure. Remark is winforms form has no `Parent` as handle (`pointer`) but only `Form` or `Control`. So the easiest way is to use `SetParent` winapi function. I think this will be then same where You will call `SetParent` from InnoSetup or from DLL – Alexey Kulikov Jan 04 '16 at 08:51
  • You can use the `NativeWindow` to wrap handle, see http://stackoverflow.com/a/213751/850848 – Martin Prikryl Jan 04 '16 at 08:55
  • Have you try to use this (`NativeWindow`) by youself? Sorry but It not works in my issue – Alexey Kulikov Jan 04 '16 at 09:08

1 Answers1

5

Solved.

The solution is to return a handle of new window (form) from DLL and use user32.SetParent WinAPI function to force embed the form into the wizard. Here a piece of code.

C#:

namespace configurator
{
    class WizardWindow : IWin32Window
    {
        public WizardWindow(IntPtr handle)
        {
            Handle = handle;
        }

        public WizardWindow(int handle) : this(new IntPtr(handle))
        {
        }

        public IntPtr Handle { get; private set; }
    }

    public static class MainClass
    {
        private static ConfiguratorForm _configuratorForm;

        [DllExport("EmbedConfiguratorForm", CallingConvention.StdCall)]
        public static IntPtr EmbedConfiguratorForm(int parentWnd)
        {
            _configuratorForm = new ConfiguratorForm();
            _configuratorForm.Show(new WizardWindow(parentWnd));
            return _configuratorForm.Handle;

        }

        [DllExport("CloseConfiguratorForm", CallingConvention.StdCall)]
        public static void CloseConfiguratorForm()
        {
            if (_configuratorForm != null)
            {
                _configuratorForm.Close();
                _configuratorForm.Dispose();
                _configuratorForm = null;
            }
        }
    }
}

Inno Setup script:

[Code]
const
  description = 'my page description';

var
  configFile: string;
  configuratorPage: TWizardPage;

function EmbedConfiguratorForm(parentWnd: HWND): HWND;
external 'EmbedConfiguratorForm@files:configurator.dll stdcall';

procedure CloseConfiguratorForm();
external 'CloseConfiguratorForm@files:configurator.dll stdcall';

function SetParent(hWndChild, hWndNewParent: HWND): HWND;
external 'SetParent@user32.dll stdcall';

procedure InitializeWizard();
begin
  configuratorPage := CreateCustomPage(wpSelectDir, 
    'Title', 'Description');
end;

procedure ShowConfigurationStep();
var
  cfgPageHandle: HWND;
  cfgWinHandle: HWND;
begin
  cfgPageHandle := configuratorPage.Surface.Handle;
  cfgWinHandle := EmbedConfiguratorForm(cfgPageHandle);
  SetParent(cfgWinHandle, cfgPageHandle);
end;

procedure CurPageChanged(CurPageId: Integer);
begin
  if (CurPageId = configuratorPage.ID) then
  begin
    ShowConfigurationStep();
  end else
  begin
    CloseConfiguratorForm(); // here we can make some optimization like checking previos page
  end;
end;

procedure DeinitializeSetup();
begin
  CloseConfiguratorForm();
end;

Note about C# DLL:
It uses UnmanagedExports NuGet packet (contains DLLExportAttribute).

Note about Inno Setup script:
In InitializeWizard function we need just create new page, but DLL call we need to implement into CurPageChanged to ensure our page is opened now.


After some research works I have created a little sample project explain Two-Way integration of .Net and InnoSetup

https://github.com/sharpcoder7/innoGlue.net

Alexey Kulikov
  • 1,097
  • 1
  • 14
  • 38