Scott Chamberlain
Scott Chamberlain

Reputation: 127603

Allow a child program to call Close on a Form in the parent app

I have a small launcher program, it loads a Splash screen on it's own thread and displays it. If a set of conditions are met it needs to launch another application and keep the splash screen visible till the other application says it is ok to close the splash screen.

enter image description here

The Launcher will always have a lifetime that starts before Child App and ends after Child App closes.

Here is some snippets of relevant code

The common DLL:

namespace Example.Common
{
    public partial class SplashScreen : Form
    {
        public SplashScreen()
        {
            InitializeComponent();
        }

        static SplashScreen splashScreen = null;
        static Thread thread = null;

        static public void ShowSplashScreen()
        {
            // Make sure it is only launched once.
            if (splashScreen != null)
                return;

            thread = new Thread(new ThreadStart(SplashScreen.ShowForm));
            thread.IsBackground = true;
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
        }

        // A static entry point to launch SplashScreen.
        static private void ShowForm()
        {
            splashScreen = new SplashScreen();
            Application.Run(splashScreen);
        }

        // A static method to close the SplashScreen
        static public void CloseForm()
        {
            splashScreen.Close();
        }
    }
}

The Inital Launcher:

/// <summary>
/// This application is a small launcher to launch the real graphical launcher. It is small and lightweight and should be rarely be updated.
/// It will call the ProgramLauncher, the program launcher will return in it's status code the PID of the instance it launched or -1
/// if no subsequent program was started.
/// </summary>
[STAThread]
static void Main()
{
    //Show the Splash screen;
    Example.Common.SplashScreen.ShowSplashScreen();

    //(Snip)

    if (rights == UserRights.None)
    {
        SplashScreen.CloseForm();
        MessageBox.Show("Your user does not have permission to connect to the server.", "Unable to logon", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }
    //If the user has full desktop access, give it to them and launch a new instance of the launcher.
    else if (rights.HasFlag(UserRights.FullDesktopAccess))
    {
        Process explorer = new Process();
        explorer.StartInfo.FileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe");
        if (explorer.Start() == false)
        {
            MessageBox.Show("Explorer failed to start.");
        }
        else
        {
            //Close the splash screen.
            SplashScreen.CloseForm();

            //If the user can shadow start a new instance of the launcher inside explorer.
            if (rights.HasFlag(UserRights.ShadowNormalUser) || rights.HasFlag(UserRights.ShadowDemoUser))
            {
                //Start a new copy of the program so people can use it to shadow easily.
                var shadowProc = new Process();
                shadowProc.StartInfo.FileName = "ProgramLauncher.exe";
                shadowProc.StartInfo.UseShellExecute = false;
                shadowProc.Start();
            }
            explorer.WaitForExit();
        }
    }
    else
    {
        Process programLauncher = new Process();
        programLauncher.StartInfo.FileName = "ProgramLauncher.exe";
        programLauncher.StartInfo.UseShellExecute = false;

        //Launch the graphical launcher.
        programLauncher.Start();
        programLauncher.WaitForExit();

        //Check to see if the graphical launcher launched some other process.
        if (programLauncher.ExitCode >= 0)
        {
            //If there was a pid, don't close the micro launcher till after it closes.
            Process runningProcess = Process.GetProcessById(programLauncher.ExitCode);
            runningProcess.WaitForExit();
        }

    }
}

What is the easiest way to let ProgramLauncher close the SplashScreen instance MicroLauncher created?

Upvotes: 1

Views: 356

Answers (3)

Marcel N.
Marcel N.

Reputation: 13986

You need to have SplashScreen pass it's window handle (HWND) to ProgramLauncher. Then, ProgramLauncher can use the SendMessage winapi function to send a WM_SYSCOMMAND message to the target window:

public const int WM_SYSCOMMAND = 0x0112;
public const int SC_CLOSE = 0xF060;
SendMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0);

In WinForms, you can get a form's native handle with Handle. The platform invoke code for SendMessage is here.

At least I don't see an easier way now, but I think it's easier than any IPC mechanism out there.

Upvotes: 1

Tim Copenhaver
Tim Copenhaver

Reputation: 3302

There are lots of ways of doing this, with pros and cons to each. Possibly the easiest way is to redirect standard output from your ProgramLauncher process and wire it up to an event in the MicroLauncher application (see here for an example). From your ProgramLauncher program, you write a certain message to standard output. When that message is received by MicroLauncher, you close the window.

Another option is to pass the HWND of your splash screen to ProgramLauncher as a command-line parameter, then ProgramLauncher can use SendMessage(WM_SYSCOMMAND, SC_CLOSE) to close the window (see here for an example).

You can also look into methods of IPC, sending custom Windows messages, or probably a thousand other possibilities, but those two ideas may get you started.

Upvotes: 1

Roman Starkov
Roman Starkov

Reputation: 61512

Easiest way I can think of: have the child app create a named mutex, and have the parent app wait until someone's created it, checking every now and then.

Not very elegant and open to abuse (where another app intentionally creates a mutex with the same name), but in practice, I doubt that'll be a problem.

Upvotes: 0

Related Questions