Skip to content

Commit

Permalink
Merge pull request #330 from airelil/master
Browse files Browse the repository at this point in the history
Fix issue #322: implement BaseWrapper.__repr__() and ElementInfo.__repr__()
  • Loading branch information
vasily-v-ryabov committed Mar 5, 2017
2 parents a1a220d + 5cee784 commit f420ba8
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 52 deletions.
46 changes: 45 additions & 1 deletion pywinauto/base_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,51 @@ def __init__(self, element_info, active_backend):
self._cache = {}
self.actions = ActionLogger()
else:
raise RuntimeError('NULL pointer used to initialize BaseWrapper')
raise RuntimeError('NULL pointer was used to initialize BaseWrapper')

def __repr__(self):
"""Representation of the wrapper object
The method prints the following info:
* type name as a module name and a class name of the object
* title of the control or empty string
* friendly class name of the control
* unique ID of the control calculated as a hash value from a backend specific ID.
Notice that the reported title and class name can be used as hints to prepare
a windows specification to access the control, while the unique ID is more for
debugging purposes helping to distinguish between the runtime objects.
"""
return '<{0}, {1}>'.format(self.__str__(), self.__hash__())

def __str__(self):
"""Pretty print representation of the wrapper object
The method prints the following info:
* type name as a module name and class name of the object
* title of the wrapped control or empty string
* friendly class name of the wrapped control
Notice that the reported title and class name can be used as hints
to prepare a windows specification to access the control
"""
module = self.__class__.__module__
module = module[module.rfind('.') + 1:]
type_name = module + "." + self.__class__.__name__

try:
title = self.texts()[0]
except IndexError:
title = ""

class_name = self.friendly_class_name()

return "{0} - '{1}', {2}".format(type_name, title, class_name)

def __hash__(self):
"""Returns the hash value of the handle"""
# Must be implemented in a sub-class
raise NotImplementedError()

#------------------------------------------------------------
@property
Expand Down
26 changes: 26 additions & 0 deletions pywinauto/element_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,36 @@

"""Interface for classes which should deal with different backend elements"""


class ElementInfo(object):

"""Abstract wrapper for an element"""

def __repr__(self):
"""Representation of the element info object
The method prints the following info:
* type name as a module name and a class name of the object
* title of the control or empty string
* class name of the control
* unique ID of the control, usually a handle
"""
return '<{0}, {1}>'.format(self.__str__(), self.handle)

def __str__(self):
"""Pretty print representation of the element info object
The method prints the following info:
* type name as a module name and class name of the object
* title of the control or empty string
* class name of the control
"""
module = self.__class__.__module__
module = module[module.rfind('.') + 1:]
type_name = module + "." + self.__class__.__name__

return "{0} - '{1}', {2}".format(type_name, self.name, self.class_name)

def set_cache_strategy(self, cached):
"""Set a cache strategy for frequently used attributes of the element"""
raise NotImplementedError()
Expand Down
95 changes: 59 additions & 36 deletions pywinauto/handleprops.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,14 @@ def text(handle):
# WM_GETTEXTLENGTH may hang even for notepad.exe main window!
c_length = win32structures.DWORD(0)
result = win32functions.SendMessageTimeout(
handle,
win32defines.WM_GETTEXTLENGTH,
0, 0,
win32defines.SMTO_ABORTIFHUNG,
500,
ctypes.byref(c_length))
handle,
win32defines.WM_GETTEXTLENGTH,
0,
0,
win32defines.SMTO_ABORTIFHUNG,
500,
ctypes.byref(c_length)
)
if result == 0:
ActionLogger().log('WARNING! Cannot retrieve text length for handle = ' + str(handle))
return None
Expand All @@ -88,63 +90,75 @@ def text(handle):

return textval


#=========================================================================
def classname(handle):
"""Return the class name of the window"""
class_name = (ctypes.c_wchar * 257)()
win32functions.GetClassName (handle, ctypes.byref(class_name), 256)
win32functions.GetClassName(handle, ctypes.byref(class_name), 256)
return class_name.value


#=========================================================================
def parent(handle):
"""Return the handle of the parent of the window"""
return win32functions.GetParent(handle)


#=========================================================================
def style(handle):
"""Return the style of the window"""
return win32functions.GetWindowLong (handle, win32defines.GWL_STYLE)
return win32functions.GetWindowLong(handle, win32defines.GWL_STYLE)


#=========================================================================
def exstyle(handle):
"""Return the extended style of the window"""
return win32functions.GetWindowLong (handle, win32defines.GWL_EXSTYLE)
return win32functions.GetWindowLong(handle, win32defines.GWL_EXSTYLE)


#=========================================================================
def controlid(handle):
"""Return the ID of the control"""
return win32functions.GetWindowLong (handle, win32defines.GWL_ID)
return win32functions.GetWindowLong(handle, win32defines.GWL_ID)


#=========================================================================
def userdata(handle):
"""Return the value of any user data associated with the window"""
return win32functions.GetWindowLong (handle, win32defines.GWL_USERDATA)
return win32functions.GetWindowLong(handle, win32defines.GWL_USERDATA)


#=========================================================================
def contexthelpid(handle):
"""Return the context help id of the window"""
return win32functions.GetWindowContextHelpId (handle)
return win32functions.GetWindowContextHelpId(handle)


#=========================================================================
def iswindow(handle):
"""Return True if the handle is a window"""
return bool(win32functions.IsWindow(handle))


#=========================================================================
def isvisible(handle):
"""Return True if the window is visible"""
return bool(win32functions.IsWindowVisible(handle))


#=========================================================================
def isunicode(handle):
"""Return True if the window is a Unicode window"""
return bool(win32functions.IsWindowUnicode(handle))


#=========================================================================
def isenabled(handle):
"""Return True if the window is enabled"""
return bool(win32functions.IsWindowEnabled(handle))


#=========================================================================
def is64bitprocess(process_id):
"""Return True if the specified process is a 64-bit process on x64
Expand All @@ -162,27 +176,31 @@ def is64bitprocess(process_id):

return (not is32)


#=========================================================================
def is64bitbinary(filename):
"""Check if the file is 64-bit binary"""
import win32file
binary_type = win32file.GetBinaryType(filename)
return binary_type != win32file.SCS_32BIT_BINARY


#=========================================================================
def clientrect(handle):
"""Return the client rectangle of the control"""
client_rect = win32structures.RECT()
win32functions.GetClientRect(handle, ctypes.byref(client_rect))
return client_rect


#=========================================================================
def rectangle(handle):
"""Return the rectangle of the window"""
rect = win32structures.RECT()
win32functions.GetWindowRect(handle, ctypes.byref(rect))
return rect


#=========================================================================
def font(handle):
"""Return the font as a LOGFONTW of the window"""
Expand Down Expand Up @@ -232,7 +250,7 @@ def font(handle):
if is_toplevel_window(handle):

if "MS Shell Dlg" in fontval.lfFaceName or \
fontval.lfFaceName == "System":
fontval.lfFaceName == "System":
# these are not usually the fonts actaully used in for
# title bars so we need to get the default title bar font

Expand All @@ -257,12 +275,14 @@ def font(handle):

return fontval


#=========================================================================
def processid(handle):
"""Return the ID of process that controls this window"""
_, process_id = win32process.GetWindowThreadProcessId(int(handle))
return process_id


#=========================================================================
def children(handle):
"""Return a list of handles to the children of this window"""
Expand All @@ -280,9 +300,9 @@ def enum_child_proc(hwnd, lparam):

# define the child proc type
enum_child_proc_t = ctypes.WINFUNCTYPE(
ctypes.c_int, # return type
win32structures.HWND, # the window handle
win32structures.LPARAM) # extra information
ctypes.c_int, # return type
win32structures.HWND, # the window handle
win32structures.LPARAM) # extra information

# update the proc to the correct type
proc = enum_child_proc_t(enum_child_proc)
Expand All @@ -292,55 +312,58 @@ def enum_child_proc(hwnd, lparam):

return child_windows


#=========================================================================
def has_style(handle, tocheck):
"""Return True if the control has style tocheck"""
hwnd_style = style(handle)
return tocheck & hwnd_style == tocheck


#=========================================================================
def has_exstyle(handle, tocheck):
"""Return True if the control has extended style tocheck"""
hwnd_exstyle = exstyle(handle)
return tocheck & hwnd_exstyle == tocheck


#=========================================================================
def is_toplevel_window(handle):
"""Return whether the window is a top level window or not"""
# only request the style once - this is an optimization over calling
# (handle, style) for each style I wan to check!
style_ = style(handle)

if (style_ & win32defines.WS_OVERLAPPED == win32defines.WS_OVERLAPPED or \
if (style_ & win32defines.WS_OVERLAPPED == win32defines.WS_OVERLAPPED or
style_ & win32defines.WS_CAPTION == win32defines.WS_CAPTION) and \
not (style_ & win32defines.WS_CHILD == win32defines.WS_CHILD):
not (style_ & win32defines.WS_CHILD == win32defines.WS_CHILD):
return True
else:
return False


#=========================================================================
def dumpwindow(handle):
"""Dump a window to a set of properties"""
props = {}

for func in (
text,
classname,
rectangle,
clientrect,
style,
exstyle,
contexthelpid,
controlid,
userdata,
font,
parent,
processid,
isenabled,
isunicode,
isvisible,
children,
):
for func in (text,
classname,
rectangle,
clientrect,
style,
exstyle,
contexthelpid,
controlid,
userdata,
font,
parent,
processid,
isenabled,
isunicode,
isvisible,
children,
):

props[func.__name__] = func(handle)

Expand Down
20 changes: 20 additions & 0 deletions pywinauto/unittests/test_hwndwrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from __future__ import print_function
from __future__ import unicode_literals

import six
import time
#import pprint
#import pdb
Expand Down Expand Up @@ -504,6 +505,25 @@ def testSetKeyboardFocus(self):
self.dlg.set.set_keyboard_focus()
self.assertEqual(self.dlg.get_focus(), self.dlg.set.handle)

def test_pretty_print(self):
"""Test __str__ method for HwndWrapper based controls"""
if six.PY3:
assert_regex = self.assertRegex
else:
assert_regex = self.assertRegexpMatches

wrp = self.dlg.wrapper_object()
assert_regex(wrp.__str__(), "^hwndwrapper.DialogWrapper - 'Common Controls Sample', Dialog$")
assert_regex(wrp.__repr__(), "^<hwndwrapper.DialogWrapper - 'Common Controls Sample', Dialog, [0-9-]+>$")

wrp = self.ctrl
assert_regex(wrp.__str__(), "^win32_controls.ButtonWrapper - 'Command button here', Button$")
assert_regex(wrp.__repr__(), "^<win32_controls.ButtonWrapper - 'Command button here', Button, [0-9-]+>$")

wrp = self.dlg.TabControl.wrapper_object()
assert_regex(wrp.__str__(), "^common_controls.TabControlWrapper - '', TabControl$")
assert_regex(wrp.__repr__(), "^<common_controls.TabControlWrapper - '', TabControl, [0-9-]+>$")


class HwndWrapperMenuTests(unittest.TestCase):

Expand Down

0 comments on commit f420ba8

Please sign in to comment.