7

I am working on a wpf application where instead of exiting the app when user closes the button I am minimizing it to the tray(similar to google talk).

    void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        e.Cancel = true;

        this.Hide();
    }

What I need is if user forgets that there is an instance of the app and tries to open a new instance I have to shut down the second instance and set my application as the foreground app. If the app is in minimized state (not hidden) I am able to do this. I am using the following code

      protected override void OnStartup(StartupEventArgs e)
           {

            Process currentProcess = Process.GetCurrentProcess();


            var runningProcess = (from process in Process.GetProcesses()
                              where
                              process.Id != currentProcess.Id &&
                              process.ProcessName.Equals(
                              currentProcess.ProcessName,
                              StringComparison.Ordinal)
                              select process).FirstOrDefault();
            if (runningProcess != null)
                {
                    Application.Current.Shutdown();

                    ShowWindow(runningProcess.MainWindowHandle, 5);

                    ShowWindow(runningProcess.MainWindowHandle, 3);
                }

           }

      [DllImport("user32.dll")]
      private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);

When the app is minimized it has some unique value for MainWindowHandle. When I hide the app, the MainWindowHandle of runningProcess is showing as 0. I think this is why my application is not opening when it is in hidden state, but don't know how to fix it.

Tell me if I need to post more code or clarify anything. Thank you in advance.

Raj123
  • 971
  • 2
  • 12
  • 24
  • 1
    Rather than manipulating other application, provide a clear communications between them by using `IPC`. Use named `Mutex` to mark *application is started*. When second application started, it can determine that and thenby using IPC say *activate please* and exit. First application will then have a full control of how to activate. – Sinatr Jan 16 '14 at 07:51

3 Answers3

14

When I hide the app, the MainWindowHandle of runningProcess is showing as 0

You're right. If a process doesn't have a graphical interface associated with it (hidden/ minimized) then the MainWindowHandle value is zero.

As workaround, you could try getting the HANDLE for the hidden window by enumerating all open windows using EnumDesktopWindows function and compare its process id with the hidden/ minimized windows's process id.

Update

The WPF's WIN32 window has a bit different behavior than the standard WIN32 window. It has the class name composed of the word HwndWrapper, the name of AppDomain it was created in, and a unique random Guid (which changes on every launch), e.g., HwndWrapper[WpfApp.exe;;4d426cdc-31cf-4e4c-88c7-ede846ab6d44].

Update 2

When WPF's window is hidden by using the Hide() method, it internally calls UpdateVisibilityProperty(Visibility.Hidden), which in turns set the internal visibility flag for UIElement to false. When we call the Show() method of WPF Window, UpdateVisibilityProperty(Visibility.Visible) is called, and the internal visibility flag for UIElement is toggled.

When we show the WPF window using the ShowWindow(), the UpdateVisibilityProperty() method is not trigerred, thus the internal visibility flag does not get reversed (which causes the window to be displayed with black backround).

By looking at the WPF Window internal implementation, the only way to toggle the internal visiblity flag, without calling the Show() or Hide() method, is by sending a WM_SHOWWINDOW message.

const int GWL_EXSTYLE = (-20);
const uint WS_EX_APPWINDOW = 0x40000;

const uint WM_SHOWWINDOW = 0x0018;
const int SW_PARENTOPENING = 3;

[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

[DllImport("user32.dll")]
private static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumWindowsProc ewp, int lParam);

[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);

[DllImport("user32.dll")]
private static extern uint GetWindowTextLength(IntPtr hWnd);

[DllImport("user32.dll")]
private static extern uint GetWindowText(IntPtr hWnd, StringBuilder lpString, uint nMaxCount);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern bool GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);

delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);

static bool IsApplicationWindow(IntPtr hWnd) {
  return (GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_APPWINDOW) != 0;
}

static IntPtr GetWindowHandle(int pid, string title) {
  var result = IntPtr.Zero;

  EnumWindowsProc enumerateHandle = delegate(IntPtr hWnd, int lParam)
  {
    int id;
    GetWindowThreadProcessId(hWnd, out id);        

    if (pid == id) {
      var clsName = new StringBuilder(256);
      var hasClass = GetClassName(hWnd, clsName, 256);
      if (hasClass) {

        var maxLength = (int)GetWindowTextLength(hWnd);
        var builder = new StringBuilder(maxLength + 1);
        GetWindowText(hWnd, builder, (uint)builder.Capacity);

        var text = builder.ToString(); 
        var className = clsName.ToString();

        // There could be multiple handle associated with our pid, 
        // so we return the first handle that satisfy:
        // 1) the handle title/ caption matches our window title,
        // 2) the window class name starts with HwndWrapper (WPF specific)
        // 3) the window has WS_EX_APPWINDOW style

        if (title == text && className.StartsWith("HwndWrapper") && IsApplicationWindow(hWnd))
        {
          result = hWnd;
          return false;
        }
      }
    }
    return true;
  };

  EnumDesktopWindows(IntPtr.Zero, enumerateHandle, 0);

  return result;
}

Usage Example

...
if (runningProcess.MainWindowHandle == IntPtr.Zero) {
  var handle = GetWindowHandle(runningProcess.Id, runningProcess.MainWindowTitle);
  if (handle != IntPtr.Zero) {
    // show window
    ShowWindow(handle, 5);
    // send WM_SHOWWINDOW message to toggle the visibility flag
    SendMessage(handle, WM_SHOWWINDOW, IntPtr.Zero, new IntPtr(SW_PARENTOPENING));
  }
}
...
IronGeek
  • 4,764
  • 25
  • 27
  • The handle returning from this method is not same as the First instance's handle. However I got a way to know the mainwindow handle of my first instance from the code of my second instance. Can I do something using that? right now when I am trying to use ShowWindow() using this handle its opening but the window is just a black screen (The views and viewmodels are not activated). – Raj123 Jan 16 '14 at 12:35
  • @Raj123 I apologize. I tested the previous code example using a console apps against a simple WinForms application. Turns out WPF window is bit _different_. I've updated the code example to work with WPF window. I've only tested the code with simple WPF apps though. Let me know if it works for you... – IronGeek Jan 16 '14 at 17:51
  • In my case the pointer is returning as 0. And anyway as I said, I found a way to get the main window handle of the first instance. Can't we use this handle to activate the app? I mean when I am trying to do this the window is opening. But the window is just a black screen. – Raj123 Jan 17 '14 at 05:21
  • Yes. If it's the main window handle, then I suppose you can just pass it to `ShowWindow()` function or maybe to `SetForegrounWindow()`. There's some issue though about using `SetForegroundWindow()` to activate an external application, explained here: http://msdn.microsoft.com/en-gb/library/windows/desktop/ms633539%28v=vs.85%29.aspx. About the black screen, does it have the same behavior if you show/ activate the window directly from itself? – IronGeek Jan 17 '14 at 06:31
  • I didn't get what you meant regarding the black screen. (From what I understood) when user closes the app I am minimizing it to notification area. When user clicks the icon I am opening the application and it doesn't have a black screen in this case. When opening a 2nd instance while minimized to notification area I use ShowWindow(first instance main window handle) but now it is showing a black screen. The only option I have now is to exit the app (I implemented this functionality on right clicking the icon). Let me know if you meant something else. – Raj123 Jan 17 '14 at 10:06
  • No, you got it right. I see, So the black screen shown only when you restore the window using `ShowWindow()` but not if you're showing it from the original instance itself (by clicking on the notification icon). – IronGeek Jan 17 '14 at 11:14
  • @Raj123 I've updated the answer. The black background is because the internal visibility flag is still set to false. I've added a workaround by sending `WM_SHOWWINDOW` message using `SendMessage()`. I Hope it helps... – IronGeek Jan 17 '14 at 11:39
  • thanks that did the trick. Though I am not sure why I am not able to get my main window handle using your approach. But once I have the main window handle using ShowWindow and SendMessage methods solved my problem. For those of you who had the same problem , when the first instance is run I am saving the MainWindow handle in a xml file. When trying to run the second instance I am using Application.Current.Shutdown() and just reading the xml file to get the Main Window Handle. I am marking this as answer. – Raj123 Jan 17 '14 at 13:45
  • 2
    The black-window fix using `SendMessage()` did not work for me (no idea why). I was able to fix it however, by listening to the `Activated` event (or overriding `OnActivated`) of the window (which is called when the window is shown with `ShowWindow`), and setting it to visible if it is not there. – amulware Oct 28 '15 at 09:53
  • 1
    In my testing, `runningProcess.MainWindowTitle` always return `empty` when the main window is hide. I have to manually pass the title of my main window to the method `GetWindowHandle` and it works perfectly. – h2nghia Apr 22 '16 at 16:44
  • In my case I've solved the blackscreen changing the calls doing SendMessage first and then ShowWindow. Also I had to remove IsApplicationWindow function from GetWindowHandle because it was returning false on when the title was matching. As h2nghia told, I had to pass the title manually in my case "MainWindow". Thanks for the trick @IronGeek – Jordi Espada Nov 15 '18 at 10:58
  • Same as @JordiEspada *` SendMessage(handle, WM_SHOWWINDOW, IntPtr.Zero, new IntPtr(SW_PARENTOPENING)); ShowWindow(handle, SW_SHOWNORMAL); SetForegroundWindow(handle); `* – Flou Jan 26 '21 at 18:06
-1

Thanks IronGeek, This is great. I'm just learning c# and struggled for a while trying to get this to work. Also I cant 'add comment' as insufficient reputation here so hence this post. I'm using WPF .Net 5.0. I searched around to implement this, so for other newbies they will also need something like the following in their program to receive the message (sorry, not sure what page I copied this from, so many of them (individuals will need to make their own Mainwindow_Loaded event handler).

    private void Mainwindow_Loaded_Event(object sender, RoutedEventArgs e)
    {
        hwndSource = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
        hwndSource.AddHook(new HwndSourceHook(WndProc));
    }

    private  IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == WM_SHOWWINDOW)
        {
            MessageBox.Show("I recieved WM_SHOWWINDOW");
            handled = true;
        }
        return IntPtr.Zero;
    }

The 'bring to front' tip you mentioned was also needed in my case, here is what is needed: (from Bring Word to Front ) put this in the declarations section:

[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);

and put the following just after the 'SendMessage(handle, WM_SHOWWINDOW, IntPtr.Zero, new IntPtr(SW_PARENTOPENING));' statement:

SetForegroundWindow(handle);

Without this, the activated window just remains hidden behind other windows and it has to be found by manually fishing around in the taskbar. So now I've finally got this going for a non-hidden window but now need to look at what is needed for a hidden one as that is my real goal.

β.εηοιτ.βε
  • 33,893
  • 13
  • 69
  • 83
unstuck
  • 27
  • 3
-1

Following on from my early post, and to quote an earlier comment from IronGeek the issue is ' If a process doesn't have a graphical interface associated with it (hidden/ minimized) then the MainWindowHandle value is zero'. Therefore any attempt to pass a hidden Window's handle is doomed as it doesn't exist.

So I have found a work-around, although it requires the target process to regularly check for the presence of a new message. Therefore this is still not ideal, but it works for me in 2021 (WPF, .Net 5.0) and doesn't need to import the user32.dll. Rather it carries out a makeshift type of Inter Process Communication (IPC) using the MainWindowTitle as a container to send a message passively. The MainWindowTitle is settable at runtime and is viewable from other processes, therefore it can be used like an inter-process variable. This is my entire solution below, note it needs to be published to a local folder to see how it runs as the point is to run multiple instances.

<Window x:Class="TitleComsTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TitleComsTest"
    mc:Ignorable="d"
    Name="TitleComsTest" Title="TitleComsTest" Height="400" Width="600" 
    WindowStartupLocation = "CenterScreen" Closing="TitleComsTest_Closing" Visibility="Hidden">
<Grid>
    <TextBox Name ="TextBox1" HorizontalAlignment="Center" VerticalAlignment="Center" Height="230" Width="460"/>
    <Button Name="QuitButton" Content=" Really Quit " HorizontalAlignment="Center" Margin="0,0,0,30" VerticalAlignment="Bottom" Click="QuitButton_Click"/>
</Grid>

The code behind:

using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Threading;

namespace TitleComsTest
{
    public partial class MainWindow : Window
    {
        //string for MainWindowTitle when first instance and visible:
        const string my_Visible_exe_Name = "TitleComstest";
        //string for MainWindowTitle when first instance and Hidden:
        const string my_Hidden_exe_Name = "TitleComstest...";
        //string for MainWindowTitle when 2nd instance :
        const string my_exe_Name_Flag = "TitleComstest, Please-Wait";   

        bool reallyCloseThisProgram = false;
        private DispatcherTimer timer1, timer2;
        
        public MainWindow()
        {
            InitializeComponent();

            //Get an array of processes with the chosen name
            Process[] TitleComstests = Process.GetProcessesByName(my_Visible_exe_Name);
            if (TitleComstests.Length > 1)
            {   //Then this is not the first instance
                for (int i = 0; i < TitleComstests.Length; i++)
                {
                    if (TitleComstests[i].MainWindowTitle == my_Visible_exe_Name)
                    {   //The first instance is visible as the MainWindowTitle has been set to the visible name
                        Close(); //Quit - nothing to do but close the new instance
                    }
                }
                //The first instance is hidden, so set MainWindowTitle so the first instance can see it and react
                this.Title = my_exe_Name_Flag;
                this.WindowState = WindowState.Minimized; //Minimize the window to avoid having two windows shown at once
                this.Visibility = Visibility.Visible;     //The second instance needs to be visible (minimized is enough) to be seen
                StartTimerQuit(4000); //arbitrary time, needs to be longer than 2000ms which is the checking period - see StartTimerLook(2000);
            }
            else
            {
                TextBox1.Text = "This is Multi-instance demo using the 'MainWindowTitle' to send messages\r\nto the first (hidden) instance to wake it up.";
                TextBox1.Text += "\r\n\r\nThis demo requires the program be published to a local folder and \r\nnot run in the debugger.";
                TextBox1.Text += "\r\n\r\nYou can type here to mark this instance: _____________ \r\n\r\nand then hide me by clicking top right close window 'X'";
                TextBox1.Text += "\r\n\r\nOnce closed then start the program again to see the 1st instance pop up.";
                TextBox1.Text += "\r\n\r\nFinally use the 'Really Quit' button to end this demo.";
                this.Visibility = Visibility.Visible;
            }
        }

        private void StartTimerQuit(Int32 interval) //Timer to Quit setup and start
        {
            timer1 = new DispatcherTimer();   timer1.Tick += timerQuit_Tick;
            timer1.Interval = new TimeSpan(0, 0, 0, 0, interval);   timer1.Start();
        }
        private void timerQuit_Tick(object sender, EventArgs e)
        {
            reallyCloseThisProgram = true;   Close(); 
        }

        private void TitleComsTest_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (!reallyCloseThisProgram)
            {
                e.Cancel = true;
                this.Title = my_Hidden_exe_Name; //Set the Title text to flag a hidden state
                this.Visibility = Visibility.Hidden;
                //Start checking every 2 secs at the process names - could be faster but this is a constant background process
                StartTimerLook(2000);
            }
        }

        private void StartTimerLook(Int32 interval) //Timer to look for new instances setup and start
        {
            timer2 = new DispatcherTimer();  timer2.Tick += timerLook_Tick;
            timer2.Interval = new TimeSpan(0, 0, 0, 0, interval);   timer2.Start();
        }

        private void timerLook_Tick(object sender, EventArgs e)
        {   //Every timer interval check to see if a process is present with the Ttile name flag 
            Process[] myNameFlagProcesses = Process.GetProcessesByName(my_Visible_exe_Name);

            for (int i = 0; i < myNameFlagProcesses.Length; i++)
            {
                if (myNameFlagProcesses[i].MainWindowTitle == my_exe_Name_Flag) //If name flag is seen ...
                {   //... then wake up
                    TextBox1.Text += "\r\n Saw the other window";
                    this.Visibility = Visibility.Visible;
                    this.Title = my_Visible_exe_Name; //Set the Title text to flag a visible state
                    this.Show();
                    this.Activate();
                    timer2.Stop();
                }
            }

        }

        private void QuitButton_Click(object sender, RoutedEventArgs e)
        {
            reallyCloseThisProgram = true;   Close();
        }
    }

}
unstuck
  • 27
  • 3