Abhishek Mehta
Abhishek Mehta

Reputation: 37

How to get the active/foreground instance of an application in case multiple application instances are running?

I'm trying to interact with Visual Studio's application instance in which the user is working i.e. the one in the foreground. I'm using GetActiveObject() to get the instance of VS. But, in case there are multiple instances of VS running, it always gives the first instance (the one which was opened first).

I tried using AccessibleObjectFromWindow() and using Spy++ I got the Window Class for VS as "HwndWrapper", but the "hr" value is getting negative.

Below is the code:

if (hwnd != null)
{
    EnvDTE80.DTE2 dte = null;
    int hwndChild = 0;
    EnumChildCallback cb = new EnumChildCallback(EnumVisualStudioChildProc);
    EnumChildWindows(hwnd.ToInt32(), cb, ref hwndChild);
    if (hwndChild != 0)
    {
        const uint OBJID_NATIVEOM = 0xFFFFFFF0;
        Guid IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
        int hr = AccessibleObjectFromWindow(hwndChild, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), out IDispatch ptr);
        if (hr >= 0)
        {
            dte = (EnvDTE80.DTE2)ptr.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, ptr, null);
        }
        else
        {
            Console.WriteLine("hr count " + hr + "\n");
        }
    }
    else
    {
        Console.WriteLine("hwndChild count " + hwndChild + "\n");
        dte = (EnvDTE80.DTE2)Marshal.GetActiveObject("VisualStudio.DTE." + VisualStudio.GetInstances());
    }
}

public static bool EnumVisualStudioChildProc(int hWnd, ref int lParam)
{
    StringBuilder buf = new StringBuilder(128);
    GetClassName(hWnd, buf, 128);
    if (buf.ToString().Contains("HwndWrapper"))
    {
        lParam = hWnd;
        return false;
    }
    return true;
}

I tried similar approach for finding the foreground instance of Word (Class name: _Wwg) /Excel also, there its working, Is class name I am using to retrieve the window correct?

Upvotes: 3

Views: 1417

Answers (2)

Simon Mourier
Simon Mourier

Reputation: 138841

One solution is to use UI Automation. So you need to add a reference to UIAutomationClient and UIAutomationTypes, and then use a code like the following sample:

// get the foreground window handle.
// here I used the Windows GetForegroundWindow function but you can use
// any function that defines what is the active/foreground window in your context
var foreground = GetForegroundWindow();

// get all Visual Studio main windows (from the desktop)
foreach (AutomationElement child in AutomationElement.RootElement.FindAll(
    TreeScope.Children, new PropertyCondition(AutomationElement.AutomationIdProperty, "VisualStudioMainWindow")))
{
    // note the unfortunate 32-bit that UI automation uses instead of IntPtr...
    // in practise that shouldn't be a problem
    if (child.Current.NativeWindowHandle == foreground.ToInt32())
    {
        // this is the foreground Visual Studio
        // get its DTE instance
        var obj = GetVisualStudioInstance(child.Current.ProcessId);        
    }
}

// see doc at https://learn.microsoft.com/en-us/previous-versions/ms228755(v=vs.140)
public static object GetVisualStudioInstance(int processId)
{
    CreateBindCtx(0, out var ctx);
    if (ctx == null)
        return null;

    ctx.GetRunningObjectTable(out var table);
    table.EnumRunning(out var enumerator);
    var monikers = new IMoniker[1];
    while (enumerator.Next(1, monikers, IntPtr.Zero) == 0)
    {
        monikers[0].GetDisplayName(ctx, null, out var name);
        if (Regex.Match(name, @"!VisualStudio.DTE\.[0-9]*\.[0-9]*:" + processId).Success)
        {
            table.GetObject(monikers[0], out var obj);
            return obj;
        }
    }
    return null;
}


[DllImport("user32")]
private static extern IntPtr GetForegroundWindow();

[DllImport("ole32")]
private static extern int CreateBindCtx(int reserved, out IBindCtx ppbc); // from System.Runtime.InteropServices.ComTypes

Upvotes: 2

Oleg Bondarenko
Oleg Bondarenko

Reputation: 1820

I guess it should work:

   var processName = "devenv";
   var active = Process.GetProcessesByName(searchName).OrderByDescending(x =>  x.Threads.OfType<ProcessThread>().Count(t => t.ThreadState != ThreadState.Wait)).FirstOrDefault();

the main point is filtering by thread State active status: t.ThreadState != ThreadState.Wait is just simplifying.

for current running application the solution is :

        IntPtr hwnd = GetForegroundWindow();
        uint pid;
        GetWindowThreadProcessId(hwnd, out pid);
        Process p = Process.GetProcessById((int)pid);
        [DllImport("user32.dll")]
        public static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out uint ProcessId);

        [DllImport("user32.dll")]
        private static extern IntPtr GetForegroundWindow();

but it the second example determines only current running application process

Upvotes: -1

Related Questions