Question

Would someone mind helping me out with a problem I've been stuck on for a bit? I'm using C# and trying to start a couple processes, and later move those windows to separate monitors.

So far this was the main idea:

Process p1 = Process.Start(@"1.pptx");
Process p2 = Process.Start(@"2.pptx");

SetWindowPos(p1.MainWindowHandle, -1,
                        0,
                        0,
                       100,
                        100,
                        SWP_SHOWWINDOW);
SetWindowPos(p2.MainWindowHandle, -1,
                        200,
                        200,
                       100,
                        100,
                        SWP_SHOWWINDOW);

But after trying a bunch of different things, I haven't been able to get it to work. Could anyone give me some pointers?

As a side note which is confusing me, if I print those to process IDs (p1, p2), and then run this code:

Process[] processlist = Process.GetProcesses();

foreach (Process process in processlist)
{
       if (!String.IsNullOrEmpty(process.MainWindowTitle))
       {
            Console.WriteLine("Process: {0} ID: {1} Window title: {2}", process.ProcessName, process.Id, process.MainWindowTitle);
       }
}

those process IDs don't exist. I know there must be something simple I'm missing...?

UPDATE: The reason for the problem above is that for some reason the MainWindowTitle didn't have a value, so it wasn't printing the pid.

Was it helpful?

Solution 2

For those who are still looking for an answer, this is what I did to get it working.

First, use EnumWindows to get a handle to each open window before starting any new process. Then start your process, then check all windows again making sure they're visible and have window text. If you have only 1 new process, chances are that's your new window. If not, I've tried it 3 times before failing. So far, the code has worked great.

Here is the code for helper functions/Win32 API calls.

        public delegate bool EnumedWindow(IntPtr handleWindow, ArrayList handles);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool EnumWindows(EnumedWindow lpEnumFunc, ArrayList lParam);

        public static ArrayList GetWindows()
        {
            ArrayList windowHandles = new ArrayList();
            EnumedWindow callBackPtr = GetWindowHandle;
            EnumWindows(callBackPtr, windowHandles);

            return windowHandles;
        }

        private static bool GetWindowHandle(IntPtr windowHandle, ArrayList windowHandles)
        {
            windowHandles.Add(windowHandle);
            return true;
        }

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool IsWindowVisible(IntPtr hWnd);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

    const int SWP_SHOWWINDOW = 0x0040;
    [DllImport("user32.dll", EntryPoint = "SetWindowPos", SetLastError = true)]
    public static extern Boolean SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x,     int Y, int cx, int cy, int wFlags);

My main logic then went something like this (adjust as necessary):

List<IntPtr> alreadyOpenWindows = new List<IntPtr>();
foreach (IntPtr ip in GetWindows())
{
     alreadyOpenWindows.Add(ip);
}

Process.Start("your command here");
System.Threading.Thread.Sleep(1000);

foreach (IntPtr ip in GetWindows())
{
    // To consider it a new window, it must be visible, it must not have been open previously, and it must have window text length > 0
    if (IsWindowVisible(ip) && alreadyOpenWindows.Contains(ip) == false)
    {
        StringBuilder windowText = new StringBuilder();
        windowText.Length = 256;
        GetWindowText(ip, windowText, windowText.Length);

        if (windowText.Length > 0)
        {
            numNewWindows++;
            handle = ip;
            // break if your confident there will only be one new window opened
         }
    }
}


// Check numNewWindows if you'd like

if (handle != IntPtr.Zero)
{
    SetWindowPos(handle, -1,
         this.GetScreen().WorkingArea.X,
         this.GetScreen().WorkingArea.Y,
         this.GetScreen().WorkingArea.Width,
         this.GetScreen().WorkingArea.Height,
         SWP_SHOWWINDOW);
}

OTHER TIPS

When you use Process.Start to open a document this is handled by the shell. The shell looks in the file association registry and takes whatever steps are needed to open the document.

This may involve creating a new process but equally may not. Office applications will typically reuse already open processes to open new documents. That's what is happening here.

And when this does happen, when no new process is started, the shell returns 0 for the new process handle. That's reflected back to the .net Process object. It explains why you have no main window handle.

So fundamentally your basic approach is flawed. Using Process.Start will not yield window handles for these documents. You'll have to find another way to locate these windows. For instance EnumWindows or a CBT hook. Or perhaps COM automation is the right solution.

As an aside it seems that you did not check for errors when you called SetWindowPos. That would have helped you work this out more quickly. Always check return values when calling Win32 functions.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top