1

I'm trying to implement a button command that launches a new WPF application the first time the user clicks the button and then (when the user clicks the button again) sends it to foreground, if it's already running. The whole thing is running on .Net v4.0

What I've tried to do is working fine, as expected, when the launched process is a normal WPF application, but it doesn't play nice if the launched WPF application has a splash screen. The problem is that SetForegroundWindow fails, because I'm unable to retrieve the correct window handle in that specific case. Can you suggest a fix or a work-around? Assume you can modify the source of both the launcher and the launched WPF.

The relevant code from the View Model of the launcher

   private void ClaimRptLogic()
    {
        if (ClaimRptHandle != IntPtr.Zero)
        {
            ShowWindow(ClaimRptHandle, SW_RESTORE);
            LaunchState = SetForegroundWindow(ClaimRptHandle)? "" : "can't set to foreground";
            return;
        }

        Process rpt = new Process();
        rpt.StartInfo = new ProcessStartInfo()
        {
            WorkingDirectory = ConfigurationManager.AppSettings["ClaimRptPath"],
            FileName = ConfigurationManager.AppSettings["ClaimRptexe"]
        };
        rpt.Start();
        BackgroundWorker bg = new BackgroundWorker();
        bg.DoWork += new DoWorkEventHandler((o, e) => {
            rpt.WaitForExit();
        });
        bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler((o, e) => {
            ClaimRptHandle = IntPtr.Zero;
            LaunchState = "ClaimRpt closed";
        });
        bg.RunWorkerAsync();
        Thread.Sleep(3000);
        ClaimRptHandle = rpt.MainWindowHandle;
    }
  • I didn't try your code, but there is something already out there to look for https://msdn.microsoft.com/en-us/library/cc656886(v=vs.110).aspx – AnjumSKhan Mar 18 '17 at 06:57
  • 1
    @AnjumSKhan "*I didn't try your code*" Really you showed that you didn't read and you didn't understand my question. The **launched** wpf already has a splash screen. –  Mar 18 '17 at 07:35
  • I posted that link as it might tell you something which you might be missing. – AnjumSKhan Mar 18 '17 at 07:51
  • Thanks, but of course I know the link and it is *exactly* how I implemented the splash screen in my process... Btw I've edited the question to further specify that the splash screen is in the launched process, not in the launcher (but I guess it was sort of obvious) –  Mar 18 '17 at 07:57
  • This should help you http://stackoverflow.com/questions/29645968/how-can-i-check-whether-this-window-handle-is-for-the-splash-window-or-for-the-r – AnjumSKhan Mar 18 '17 at 08:19
  • There is no answer there (so *how* does it help?) likely because it is asking for a generic solution/library, while I'm saying that it is possible to change the source of both apps (there is no library, only specific VM) and find whatever workaround to make it work... –  Mar 18 '17 at 08:27
  • did u read those comments, i thought that might help u. – AnjumSKhan Mar 18 '17 at 08:57

2 Answers2

0

Assume you can modify the source of both the launcher and the launched WPF.

Based on this assumption, I could determine the correct handle in the Loaded event of the launched WPF application and send it back to the launcher using a Named Pipe.

private void Window_Loaded(object sender, RoutedEventArgs e)
{   
    var callback = new WindowInteropHelper(this).Handle;
    BackgroundWorker bg = new BackgroundWorker();
    bg.DoWork += (s, a) =>
    {
        WritePipe("at loaded evt: " + callback);
    };
    bg.RunWorkerAsync();
}

private void WritePipe(string line)
{
    using (NamedPipeServerStream server = 
        new NamedPipeServerStream(Environment.UserName, PipeDirection.InOut))
    {
        server.WaitForConnection();
        using (StreamWriter sw = new StreamWriter(server))
        {
            sw.WriteLine(line);
        }
    }
}

and read the correct window handle from the same Named Pipe in another background worker of the launcher

bg.RunWorkerAsync();
Thread.Sleep(3000);
if (rpt.HasExited)
{
    return;
}
LaunchedHandle = rpt.MainWindowHandle;
BackgroundWorker bgPipe = new BackgroundWorker();
bgPipe.DoWork += new DoWorkEventHandler((o, e) => {
    while (!rpt.HasExited)
    {
        string testHandle = ReadPipe();
        if (testHandle.StartsWith("at loaded evt: "))
        {
            Debug.WriteLine(testHandle);
            Debug.WriteLine("CallBack from Launched Process!");
            var handle = testHandle.Replace("at loaded evt: ","");
            LaunchedHandle = new IntPtr(int.Parse(handle));
            return;
        }
        LaunchedHandle = rpt.MainWindowHandle;
        Thread.Sleep(500);
    }
    Debug.WriteLine("Process exited!");
});
bgPipe.RunWorkerAsync();
CanLaunchCmd = true;

with

private string ReadPipe()
{
    string line = "";
    using (NamedPipeClientStream client =
        new NamedPipeClientStream(".", Environment.UserName, PipeDirection.InOut))
    {
        client.Connect();
        using (StreamReader sr = new StreamReader(client))
        {
            line = sr.ReadLine();
        }
        return line;
    }
}

Of course, I'm open to different ideas.

0

Just another option, if you can't modify the launched WPF app, but you know the title caption of its main window, besides the process id, of course.

In that case the background search would be

LaunchedHandle = rpt.MainWindowHandle;
mainWin = rpt.MainWindowHandle;
BackgroundWorker bgTitle = new BackgroundWorker();
bgTitle.DoWork += new DoWorkEventHandler((o, e) => {
while (!rpt.HasExited)
    {

        LaunchedHandle = MainWindowHandle(rpt);
        Thread.Sleep(500);
    }
    Debug.WriteLine("Process exited!");
});
bgTitle.RunWorkerAsync();

using a filter based on the process id

private IntPtr MainWindowHandle(Process rpt)
{
    EnumWindowsProc ewp = new EnumWindowsProc(EvalWindow);
    EnumWindows(ewp, new IntPtr(rpt.Id));
    return mainWin;
}

and a callback testing the title caption (in this example it's Launched)

private bool EvalWindow(IntPtr hWnd, IntPtr lParam)
{
    int procId;
    GetWindowThreadProcessId(hWnd, out procId);
    if (new IntPtr(procId) != lParam)
    {
        return true;
    }
    StringBuilder b = new StringBuilder(50);
    GetWindowText(hWnd, b, 50);
    string test = b.ToString();
    if (test.Equals("Launched"))
    {
        mainWin = hWnd;
    }
    return true;
}