Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Activate a hidden wpf application when trying to run a second instance

Tags:

c#

wpf

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.

like image 484
Raj123 Avatar asked Jan 16 '14 06:01

Raj123


1 Answers

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));
  }
}
...
like image 77
IronGeek Avatar answered Sep 25 '22 00:09

IronGeek