Generic-user-small Chris Jansen 7 posts

This is code that I wrote to enumerate through the children of a parent window. I wrote it because of issues described in the FindWindowEx thread. I also created a class that actually recurses into each child to see if it has any children, but this wasn’t beneficial for my uses.

The class uses the EnumChildWindows Win32 API method, which takes a callback method as an argument. I’m using the win32-api from the Win32 Utils project (http://rubyforge.org/projects/win32utils), which supports callback methods for API method calls.

The ChildEnumerator class has two methods. enum_children just populates a hash with all of the children of a parent window. find_child looks for a specific child. Both methods have static versions. Note that my code has log4r statements in it, which you could easily replace with puts.


# Enumerate through the children of a window
class ChildEnumerator
  include WindowsGui

  attr_reader :children

  # Define the EnumChildWindows API call.
  @@EnumChildWindows = Win32::API.new('EnumChildWindows', 'LKP', 
    'L', 'user32')

  def initialize
    @children = Hash.new
  end

  # Static method to enumerate through children of the window_handle.
  def self.enum_children( window_handle )
    enumerator = ChildEnumerator.new
    enumerator.enum_children( window_handle )
    return enumerator.children
  end

  # Enumerate through children of the window_handle (static).
  def enum_children( window_handle )
    @children.clear

    @log.debug("Finding children of handle #{window_handle}")

    enum_proc = Win32::API::Callback.new('L', 'I'){ |winHandle|
      @log.debug("Child #{winHandle}")
      @children[winHandle] = Window.new(winHandle) if (@children[winHandle] == nil)
      true
    }

    @@EnumChildWindows.call(window_handle, enum_proc, nil)
  end

  # Static method to look for the specified window text or class in the
  # children of the window_handle.
  def self.find_child( window_handle, id, search_class = true )
    enumerator = ChildEnumerator.new
    enumerator.find_child( window_handle, id, search_class )
  end

  # Look for the specified window text or class in the children 
  # of the window_handle.
  def find_child( window_handle, id, search_class = true )

    matching_child = 0

    @log.debug("Searching for child #{id} in children of handle #{window_handle}")

    enum_proc = Win32::API::Callback.new('L', 'I'){ |winHandle|
      @log.debug("Child #{winHandle}")

      class_name = ''
      buffer = "\0" * 1024

      # Look for a match on the text or class of the child control.

      # length = get_window_text(winHandle, buffer, buffer.length)
      length = send_with_buffer(winHandle, WM_GETTEXT, buffer.length, buffer)
      text = (length == 0) ? '' : buffer[0..length -1]

      if (id.gsub('_', '&').downcase == text.downcase)
        # The control text matches
        @log.debug("   MATCHED on text: #{text}")
        matching_child = winHandle
      elsif (search_class)
        buffer = "\0" * 1024
        length = get_class_name winHandle, buffer, buffer.length
        class_name = (length == 0) ? '' : buffer[0..length -1]

        if (id == class_name)
          @log.debug("   MATCHED on class: #{class_name}")
          # The control class matches
          matching_child = winHandle
        end
      end

      @log.debug("   No match, text: #{text}, class: #{class_name}") if (matching_child == 0)

      # If a match is found, return false, which stops the enumeration
      # of child windows.
      (matching_child == 0)
    }

    @@EnumChildWindows.call(window_handle, enum_proc, nil)
    @log.debug("Enumeration complete, matching_child=#{matching_child}")

    matching_child    # Return the matching child handle.
  end
end

I integrated it into the child method of the Window class thusly:


when String
  # Allow the user to use an underscore to specify an 
  # ampersand for the control name.
  by_title = find_window_ex @handle, 0, nil, id.gsub('_', '&')
  by_class = find_window_ex @handle, 0, id, nil
  if (by_title == 0 && by_class == 0)
    @log.debug("Did not find by title or class, recursing children")
    result = ChildEnumerator.find_child(handle, id)
    @log.debug("Found child #{result} for id #{id}") if (result > 0)
  else
    result = (by_title > 0) ? by_title : by_class
  end
 
2201682121_40eda4245f_o_small Ian Dees 35 posts

Nice! Thanks for posting this.

2 posts, 2 voices