winformsclicktoolstriptoolstripbutton

Simulate click on Windows Froms ToolStrip button in another process


I've found the handle of a Windows Forms ToolStrip in another application. (Window name is toolStrip1, class name is WindowsForms10.Window.8.app.0.378734a.)

Is there any way to enumerate the child buttons, find a button by caption and simulate a button click? The buttons are not child windows, so EnumChildWindows doesn't work.

Simulating a mouse click with constant coordinates on the ToolStrip itself is not a very good option as the available buttons and button captions may change.


Solution

  • As @Jimi suggested, the solution is to use the UI Automation Windows API. I've managed to find the button by going down in the UI automation tree of UI elements from the desktop window to the given element, matching the names of elements. Then I called Invoke on it to simulate a click. Here my a highly unstructured "C-style" C++11 test code for demonstration purposes, in case anyone else finds it useful. You will need to link to ole32.lib and oleaut32.lib.

    #include <iostream>
    #include <UIAutomation.h>
    
    bool GetChildElementByName(IUIAutomationElement * * Child,
                               IUIAutomationElement * Parent,
                               const wchar_t * Name, IUIAutomation * UIAut);
    
    int main(int argc, char * argv[])
    {
        // Initialize COM
        switch ( CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED) )
        {
        case S_OK:
            break;
        case S_FALSE:
            CoUninitialize();
            // fall through
        default:
            std::cout << "CoInitializeEx error.\n";
            return 1;
        }
        // Create a CUIAutomation object and query IUIAutomation interface
        IUIAutomation * uiautomation;
        if ( CoCreateInstance(CLSID_CUIAutomation, nullptr, CLSCTX_INPROC_SERVER,
                              IID_IUIAutomation, reinterpret_cast<LPVOID *>(&uiautomation)) != S_OK )
        {
            std::cout << "CoCreateInstance error.\n";
            CoUninitialize();
            return 1;
        }
    
        // Get the desktop UI element
        IUIAutomationElement * desktop;
        if ( uiautomation->GetRootElement(&desktop) != S_OK )
        {
            std::cout << "GetRootElement error.\n";
            uiautomation->Release();
            CoUninitialize();
            return 1;
        }
    
        // Find a button element in a window inside a toolstrip.
        IUIAutomationElement * elem;
        // Fix memory leak...
        if
        (
            !GetChildElementByName(&elem, desktop, L"Thermo Scientific LabWriter  V4.6", uiautomation) ||
            !GetChildElementByName(&elem, elem, L"toolStrip1", uiautomation) ||
            !GetChildElementByName(&elem, elem, L"Print", uiautomation)
        )
        {
            desktop->Release();
            uiautomation->Release();
            CoUninitialize();
            return 1;
        }
        desktop->Release();
    
        // The invoke control pattern contains the Invoke method that can be used to click the button
        IUIAutomationInvokePattern * invokepattern;
        if ( elem->GetCurrentPattern(UIA_InvokePatternId,
                                     reinterpret_cast<IUnknown * *>(&invokepattern)) != S_OK )
        {
            std::cout << "GetCurrentPattern error.\n";
            elem->Release();
            uiautomation->Release();
            CoUninitialize();
            return 1;
        }
        if ( invokepattern == nullptr )
        {
            // Possibly element is not even a button, as it should have the invoke control pattern.
            std::cout << "Invoke pattern not present.\n";
            elem->Release();
            uiautomation->Release();
            CoUninitialize();
            return 1;
        }
    
        // Click the button
        if ( invokepattern->Invoke() != S_OK )
        {
            std::cout << "Button click failed.\n";
            invokepattern->Release();
            elem->Release();
            uiautomation->Release();
            CoUninitialize();
            return 1;
        }
    
        elem->Release();
        invokepattern->Release();
        uiautomation->Release();
        CoUninitialize();
    
        std::cout << "Done.\n";
        return 0;
    }
    
    bool GetChildElementByName(IUIAutomationElement * * Child,
                               IUIAutomationElement * Parent,
                               const wchar_t * Name, IUIAutomation * UIAut)
    {
        // Create a condition that matches elements with name *Name
        IUIAutomationCondition * cond;
        // Parameter for the condition is a string-typed VARIANT containing the name.
        VARIANT name;
        VariantInit(&name);
        name.vt = VT_BSTR;
        name.bstrVal = SysAllocString(Name);
        if ( name.bstrVal == nullptr )
        {
            std::cout << "SysAllocString error.\n";
            return false;
        }
        if ( UIAut->CreatePropertyCondition(UIA_NamePropertyId, name, &cond) != S_OK )
        {
            std::cout << "CreatePropertyCondition error.\n";
            VariantClear(&name);
            return false;
        }
        VariantClear(&name);
    
        // Find the first child element satisfying the condition.
        if ( Parent->FindFirst(TreeScope_Children, cond, Child)  != S_OK )
        {
            std::cout << "FindFirst error.\n";
            cond->Release();
            return false;
        }
        cond->Release();
        if ( *Child == nullptr )
        {
            std::cout << "Child element \"";
            std::wcout << Name;
            std::cout << "\" not found.\n";
            return false;
        }
        // Child element found
        return true;
    }