3

I am scrapping content from another Windows application. The application has a Listbox and two TRichEdit controls (and other controls without interest).

When using SendKeys.SendWait("{DOWN}") to the Listbox, the content in the two TRichEdit boxes changes. Thats where I want to scrap the content. That works.

RichEdit1 : No problem - I get the content using SendMessageW()

RichEdit2: Big problem. It changes Windows Handle each time I use SendKeys.SendWait on the LIstBox, so I can't access it.

The solution is to find the new Windows Handle for RichEdit2. I think that I can get a list of Handles for RichEdit control and select the one with Handle different from RichEdit1.

Question: How can I get a list of Handles of a specific class (RichEdit) from a different windows forms application?

Or

Does anyone has a better solution? A code snippet in C# will be appreciated.

Thanks in advance.

Anders Finn Jørgensen
  • 1,275
  • 1
  • 17
  • 33

2 Answers2

4

For the question on how to get the RichEdit window handles:

You can PInvoke FindWindowEx setting the child window parameter to NULL to check all child windows and the class name set to the class names of RichEdit control from here:

v1.0 = RICHEDIT
v2.0 & v3.0 = RichEdit20A or RichEdit20W
v4.1 = RICHEDIT50W
v5.0 = RichEdit50W
v6.0 = RichEdit60W

Still, MSDN states that:

The function searches among windows that are child windows of the desktop.

So basically you get a search depth of one. If your controls are nested deeper, then you may need to combine this with EnumChildWindows to perfrom a full depth search.

EDIT

This is a snippet on how to enumerate the windows and find matching windows for a given class using the described method, hope you can fine tune it.

using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace UIAutomation
{
    class Program
    {
        public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);

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

        public static bool EnumChildWindowsCallback(IntPtr hWnd, IntPtr lParam)
        {
            StringBuilder className = new StringBuilder(256);
            GetClassName(hWnd, className, className.Capacity);
            var windowInformation = new WindowInformation(hWnd, lParam, className.ToString());
            _windowLookupMap[hWnd] = windowInformation;
            if (lParam != IntPtr.Zero)
            {
                _windowLookupMap[lParam]._children.Add(windowInformation);
            }
            EnumChildWindows(hWnd, EnumChildWindowsCallback, hWnd);
            return true;
        }

        class WindowInformation
        {
            public IntPtr _parent;

            public IntPtr _hWnd;

            public string _className;

            public List<WindowInformation> _children = new List<WindowInformation>();

            public WindowInformation(IntPtr hWnd, IntPtr parent, string className)
            {
                _hWnd = hWnd;
                _parent = parent;
                _className = className;
            }
        }

        static Dictionary<IntPtr, WindowInformation> _windowLookupMap = new Dictionary<IntPtr, WindowInformation>();

        static void FindWindowsByClass(string className, WindowInformation root, ref  List<WindowInformation> matchingWindows)
        {
            if (root._className == className)
            {
                matchingWindows.Add(root);
            }
            foreach (var child in root._children)
            {
                FindWindowsByClass(className, child, ref matchingWindows);
            }
        }

        static void Main(string[] args)
        {
            var processes = Process.GetProcessesByName("notepad");
            StringBuilder className = new StringBuilder(256);
            GetClassName(processes[0].MainWindowHandle, className, className.Capacity);
            _windowLookupMap[processes[0].MainWindowHandle] = new WindowInformation(processes[0].MainWindowHandle, IntPtr.Zero, className.ToString());
            EnumChildWindows(processes[0].MainWindowHandle, EnumChildWindowsCallback, processes[0].MainWindowHandle);
            List<WindowInformation> matchingWindows = new List<WindowInformation>();
            FindWindowsByClass("Edit", _windowLookupMap.Single(window => window.Value._parent == IntPtr.Zero).Value, ref matchingWindows);
            Console.WriteLine("Found {0} matching window handles", matchingWindows.Count);
        }
    }
}
Rudolfs Bundulis
  • 11,636
  • 6
  • 33
  • 71
  • Are you sure `FindWindow` will work? AFAIK it will work only with top level windows. You may want to look at `FindWindowEx` – Sriram Sakthivel Feb 23 '15 at 11:34
  • 1
    FindWindowEx isn't really going to help. `EnumChildWindows` is your guy. But automation is really the way to go. Why reinvent the wheel? – David Heffernan Feb 23 '15 at 11:40
  • @DavidHeffernan well yeah, I already pointed out in the edit's that you would actually need to combine both to get a full depth search. I had a quick google on the automation stuff, I have never used it before myself. Seems much more elegant. – Rudolfs Bundulis Feb 23 '15 at 11:47
  • @DavidHeffernan again since I'm not that familiar with the UI Automation, dosen't it work with WPF only or I have misunderstood the stuff on MSDN? – Rudolfs Bundulis Feb 23 '15 at 11:57
  • No, any application can expose itself to automation. Not all do. – David Heffernan Feb 23 '15 at 11:59
  • All Windows controls expose the UI Automation interfaces, including those wrapped in .NET class (i.e. Windows Forms applications). Qt implements UI Automation interfaces as well. WPF does, too. I'm not sure about Delphi's VCL, but @David would know. – IInspectable Feb 23 '15 at 13:18
  • @IInspectable The VCL is lame in this regard. Any VCL controls that wrap Win32 controls are visible to automation. But other controls, e.g. non-windowed controls are not. So, the VCL doesn't seem to recognise you, [`IInspectable`](https://msdn.microsoft.com/en-us/library/br205821.aspx). – David Heffernan Feb 23 '15 at 13:21
  • @DavidHeffernan just curious, does the UI Automation also detect message-only windows? – Rudolfs Bundulis Feb 23 '15 at 13:22
  • @RudolfsBundulis I doubt it very much. How could you automate those? – David Heffernan Feb 23 '15 at 13:25
  • @RudolfsBundulis you can get an accessible object/automation element from any window that you have a handle to (and if you don't return one yourself I belive Windows gives you a "standard accessible object"/whatever UI Automation's equivalent is), but accessibility/automation is focused around what's actually visible on screen, so there's not much you can actually DO with that element. I'd be surprised if a message-only window had a reason to provide an element tree. – andlabs Feb 23 '15 at 14:54
  • @andlabs makes sense, my question was for general inquiry, I don't usually deal with C# so was just wondering (since with `FindWindowEx` you can trigger a behaivor to enumerate message only windows), so I can keep that in mind for future if it comes useful. – Rudolfs Bundulis Feb 23 '15 at 15:00
3

Thanks for the answer above. It's very detailed. I ended up using a more simple approach:

 private IntPtr FindHandle()
 {
    while (true)
    {
       IntPtr handle = FindWindowEx(this.ApplicationHandle,IntPtr.Zero,"TRichEdit", null);
       if (handle == null)
       {
           throw new Exception("No handle found");
       }
       if (handle != this.Handle_01)
       {
           return handle;
       }
    }
 }
Anders Finn Jørgensen
  • 1,275
  • 1
  • 17
  • 33