2

I am working on a console executable that may run in multi monitor environment. It may be started by double clicking on the Exe file name inside Windows Explorer. I want to move the console to the same monitor as the Windows Explorer window that started it. Is there any way to do it? I was able to get parent process and get MainWindowHandle. But it will not give me the correct window. It will give me the first Explorer window. See code below. SetupConsoleWindow is the main function.

internal static class NativeMethods
{
    internal const int SWP_NOSIZE = 0x0001;

    [DllImport("kernel32.dll")]
    internal static extern bool AllocConsole();

    [DllImport("user32.dll", EntryPoint = "SetWindowPos")]
    internal static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);

    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern IntPtr GetConsoleWindow();
}

    public static Process GetParentProcess()
    {
        var currentProcessName = Process.GetProcessById(Process.GetCurrentProcess().Id).ProcessName;
        var processesByName = Process.GetProcessesByName(currentProcessName);
        for (var index = 0; index < processesByName.Length; index++)
        {
            string processIndexdName = index == 0 ? currentProcessName : currentProcessName + "#" + index;
            var processId = new PerformanceCounter("Process", "ID Process", processIndexdName);
            if ((int)processId.NextValue() == Process.GetCurrentProcess().Id)
                break;
        }
        var parentPerformanceCounter = new PerformanceCounter("Process", "Creating Process ID", currentProcessName);
        Process parentProcess = Process.GetProcessById((int)parentPerformanceCounter.NextValue());
        return parentProcess;
    }

    public static void SetupConsoleWindow()
    {
        NativeMethods.AllocConsole();
        var parentProcess = GetParentProcess();
        if (parentProcess != null)
        {
            IntPtr consoleWindowHandle = NativeMethods.GetConsoleWindow();
            Screen parentScreen = Screen.FromHandle(parentProcess.MainWindowHandle);
            Rectangle monitor = parentScreen.WorkingArea;
            NativeMethods.SetWindowPos(consoleWindowHandle, 0, monitor.Left, monitor.Top, 0, 0, NativeMethods.SWP_NOSIZE);
        }
    }
SparcU
  • 742
  • 1
  • 7
  • 27
  • 2
    Explorer.exe is a single-instance app, it typically displays many windows. No, you cannot find out which specific one was the one that got your process started. Looking at the Z-order of those windows might pay off, it would tend to be the one that is just below your console window since that is the one the user accessed last. GetWindow() function with GW_HWNDPREV. – Hans Passant Jul 08 '18 at 16:38
  • 2
    Also, it might not have been launched by an explorer instance. – Neil Jul 08 '18 at 17:21
  • In my case it is explorer for sure. I can verify just in case – SparcU Jul 08 '18 at 18:19
  • @SparcU Yes, but your case is not every case. – ImmortaleVBR Jul 08 '18 at 19:07
  • Explorer is not necessarily a single-instance app. There's a setting to force file browser windows to open in a new instance of explorer.exe. – Eryk Sun Jul 09 '18 at 05:57

1 Answers1

1

So what you want to do is launch your program on the same monitor as the Windows Explorer window that started it. Doing this the way you currently intend to may be technically possible, but it would be cumbersome and flaky. It would involve enumerating all the open windows, figuring out which ones are Explorer windows, then for each of those which folder is being viewed and then which file is currently selected. You would then compare the name of that file with your program's name and, if multiple such windows are found, choose the most top-level one. You would have to hope that the file did not somehow get deselected between the double click and your main().

This process is well documented here. For reference, here is what the code to get the currently selected item looks like in C++:

#include <shlobj.h>
#include <exdisp.h>

TCHAR g_szPath[MAX_PATH];
TCHAR g_szItem[MAX_PATH];

void CALLBACK RecalcText(HWND hwnd, UINT, UINT_PTR, DWORD)
{
 HWND hwndFind = GetForegroundWindow();
 g_szPath[0] = TEXT('\0');
 g_szItem[0] = TEXT('\0');

 IShellWindows *psw;
 if (SUCCEEDED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL,
                                IID_IShellWindows, (void**)&psw))) {
  VARIANT v;
  V_VT(&v) = VT_I4;
  IDispatch  *pdisp;
  BOOL fFound = FALSE;
  for (V_I4(&v) = 0; !fFound && psw->Item(v, &pdisp) == S_OK;
       V_I4(&v)++) {
   IWebBrowserApp *pwba;
   if (SUCCEEDED(pdisp->QueryInterface(IID_IWebBrowserApp, (void**)&pwba))) {
     HWND hwndWBA;
     if (SUCCEEDED(pwba->get_HWND((LONG_PTR*)&hwndWBA)) &&
       hwndWBA == hwndFind) {
       fFound = TRUE;
       IServiceProvider *psp;
       if (SUCCEEDED(pwba->QueryInterface(IID_IServiceProvider, (void**)&psp))) {
         IShellBrowser *psb;
         if (SUCCEEDED(psp->QueryService(SID_STopLevelBrowser,
                              IID_IShellBrowser, (void**)&psb))) {
           IShellView *psv;
           if (SUCCEEDED(psb->QueryActiveShellView(&psv))) {
             IFolderView *pfv;
             if (SUCCEEDED(psv->QueryInterface(IID_IFolderView,
                                               (void**)&pfv))) {
               IPersistFolder2 *ppf2;
               if (SUCCEEDED(pfv->GetFolder(IID_IPersistFolder2,
                                            (void**)&ppf2))) {
                 LPITEMIDLIST pidlFolder;
                 if (SUCCEEDED(ppf2->GetCurFolder(&pidlFolder))) {
                   if (!SHGetPathFromIDList(pidlFolder, g_szPath)) {
                     lstrcpyn(g_szPath, TEXT("<not a directory>"), MAX_PATH);
                   }
                   int iFocus;
                   if (SUCCEEDED(pfv->GetFocusedItem(&iFocus))) {
                     LPITEMIDLIST pidlItem;
                     if (SUCCEEDED(pfv->Item(iFocus, &pidlItem))) {
                       IShellFolder *psf;
                       if (SUCCEEDED(ppf2->QueryInterface(IID_IShellFolder,
                                                          (void**)&psf))) {
                         STRRET str;
                         if (SUCCEEDED(psf->GetDisplayNameOf(pidlItem,
                                                   SHGDN_INFOLDER,
                                                   &str))) {
                           StrRetToBuf(&str, pidlItem, g_szItem, MAX_PATH);
                         }
                         psf->Release();
                       }
                       CoTaskMemFree(pidlItem);
                     }
                   }
                   CoTaskMemFree(pidlFolder);
                 }
                 ppf2->Release();
               }
               pfv->Release();
             }
             psv->Release();
           }
           psb->Release();
         }
         psp->Release();
       }
     }
     pwba->Release();
   }
    pdisp->Release();
  }
  psw->Release();
 }
 InvalidateRect(hwnd, NULL, TRUE);
}

However, you may be making this more complicated than it needs to be. Consider this instead:

mnistic
  • 10,866
  • 2
  • 19
  • 33
  • I think that `HWND hwndFind = GetForegroundWindow();` where `hwndFind` is then used in the loop for comparison, ruins it all, in this case (well, it just requires some editing). But detecting the screen from the mouse position and the Explorer instance window contained in that screen bounds should work (enough). I also recall that an application executed from an Explorer windows in different screen is visible in that same screen. – Jimi Jul 09 '18 at 00:06
  • 1
    Perfectly possible for the cursor to be on some other monitor, for instance if the program was opened using the keyboard. – David Heffernan Jul 09 '18 at 06:25
  • 1
    This is great idea. Just to use cursor location. Never thought of that. Thank you ! – SparcU Jul 09 '18 at 08:56