From c9e84c7567076496482c19a8e80925aff680e31f Mon Sep 17 00:00:00 2001 From: markm Date: Tue, 31 Jan 2006 18:44:54 +0000 Subject: [PATCH] Added unit tests, changed from properties to methods added more granularity to many controls --- pywinauto/controls/HwndWrapper.py | 294 +++++---- pywinauto/controls/__init__.py | 3 +- pywinauto/controls/common_controls.py | 882 ++++++++++++++++++++------ pywinauto/controls/win32_controls.py | 215 +++++-- 4 files changed, 1001 insertions(+), 393 deletions(-) diff --git a/pywinauto/controls/HwndWrapper.py b/pywinauto/controls/HwndWrapper.py index 4b0812b..7fd7701 100644 --- a/pywinauto/controls/HwndWrapper.py +++ b/pywinauto/controls/HwndWrapper.py @@ -112,6 +112,7 @@ class HwndWrapper(object): friendlyclassname = '' handle = None + #----------------------------------------------------------- def __init__(self, hwnd): "Initialize the control" # handle if hwnd is actually a HwndWrapper @@ -120,12 +121,6 @@ class HwndWrapper(object): except AttributeError: self.handle = hwnd - if not self.friendlyclassname: - self.FriendlyClassName = handleprops.classname(hwnd) - else: - self.FriendlyClassName = self.friendlyclassname - - # verify that we have been passed in a valid windows handle if not win32functions.IsWindow(hwnd): raise InvalidWindowHandle(hwnd) @@ -137,128 +132,149 @@ class HwndWrapper(object): # when asked for properties self._NeedsImageProp = False - # set the friendly class name to default to - # the class name - self._extra_clientrects = [] - self._extra_props = {} - - self._extra_props['MenuItems'] = self.MenuItems - - # if it is a main window - #if self.IsDialog: - # self.FriendlyClassName = "Dialog" - # default to not having a reference control added self.ref = None - #FriendlyClassName = property( - # handleprops.friendlyclassname, - # doc = "FriendlyClassName of the window ") + #----------------------------------------------------------- + def FriendlyClassName(self): + "Return the friendly class name for the control" + if not self.friendlyclassname: + return handleprops.classname(self) + else: + return self.friendlyclassname #----------------------------------------------------------- - #def _get_classname(self): - # try: - # return self._class_name - # except AttributeError: - # return handleprops.classname(self) + def Class(self): + "Class Name of the window" + return handleprops.classname(self) - Class = property (handleprops.classname, - doc = "Class Name of the window") + #----------------------------------------------------------- + def Text(self): + "Main text of the control" + return handleprops.text(self) - Text = property (handleprops.text, - doc = "Main text of the control") - Style = property (handleprops.style, - doc = "Style of window") - ExStyle = property (handleprops.exstyle, - doc = "Extended Style of window") - ControlID = property (handleprops.controlid, - doc = "The ID of the window") - UserData = property (handleprops.userdata, - doc = "Extra data associted with the window") - ContextHelpID = property (handleprops.contexthelpid, - doc = "The Context Help ID of the window") - IsVisible = property (handleprops.isvisible, - doc = "Whether the window is visible or not") - IsUnicode = property (handleprops.isunicode, - doc = "Whether the window is unicode or not") - IsEnabled = property (handleprops.isenabled, - doc = "Whether the window is enabled or not") + #----------------------------------------------------------- + def Style(self): + "Style of window" + return handleprops.style(self) - Rectangle = property (handleprops.rectangle, - doc = "Rectangle of window") - ClientRect = property (handleprops.clientrect, - doc = "Client rectangle of window") + #----------------------------------------------------------- + def ExStyle(self): + "Extended Style of window" + return handleprops.exstyle(self) - Font = property (handleprops.font, doc = "The font of the window") + #----------------------------------------------------------- + def ControlID(self): + "The ID of the window" + return handleprops.controlid(self) - ProcessID = property (handleprops.processid, - doc = "ID of process that controls this window") + #----------------------------------------------------------- + def UserData(self): + "Extra data associted with the window" + return handleprops.userdata(self) - HasStyle = handleprops.has_style - HasExStyle = handleprops.has_exstyle + #----------------------------------------------------------- + def ContextHelpID(self): + "The Context Help ID of the window" + return handleprops.contexthelpid(self) - IsDialog = property(handleprops.is_toplevel_window, - doc = handleprops.is_toplevel_window.__doc__) + #----------------------------------------------------------- + def IsVisible(self): + "Whether the window is visible or not" + return handleprops.isvisible(self) + + #----------------------------------------------------------- + def IsUnicode(self): + "Whether the window is unicode or not" + return handleprops.isunicode(self) + + #----------------------------------------------------------- + def IsEnabled(self): + "Whether the window is enabled or not" + return handleprops.isenabled(self) + + #----------------------------------------------------------- + def Rectangle(self): + "Rectangle of window" + return handleprops.rectangle(self) + + #----------------------------------------------------------- + def ClientRect(self): + "Client rectangle of window" + return handleprops.clientrect(self) + + #----------------------------------------------------------- + def Font(self): + "The font of the window" + return handleprops.font(self) + + #----------------------------------------------------------- + def ProcessID(self): + "ID of process that controls this window" + return handleprops.processid(self) + + #----------------------------------------------------------- + def HasStyle(self, style): + "Retrun true if the control has the specified sytle" + return handleprops.has_style(self, style) + + #----------------------------------------------------------- + def HasExStyle(self, exstyle): + "Retrun true if the control has the specified extended sytle" + return handleprops.has_exstyle(self, exstyle) + + #----------------------------------------------------------- + def IsDialog(self): + "Retrun true if the control is a top level window" + return handleprops.is_toplevel_window(self) #----------------------------------------------------------- # define the Menu Property - def _get_menuitems(self): + def MenuItems(self): "Return the menu items for the dialog" - if self.IsDialog: - return _GetMenuItems(win32functions.GetMenu(self)) + if self.IsDialog(): + menu_handle = win32functions.GetMenu(self) + self.SendMessage(win32defines.WM_INITMENU, menu_handle) + return _GetMenuItems(menu_handle, self) else: return [] - MenuItems = property (_get_menuitems, - doc = "Return the menu items for the dialog") #----------------------------------------------------------- - def _get_parent(self): + def Parent(self): "Return the parent of this control" parent_hwnd = handleprops.parent(self) - if parent_hwnd: return WrapHandle(parent_hwnd) else: return None - Parent = property (_get_parent, - doc = "Parent window of window") #----------------------------------------------------------- - def _get_texts(self): + def Texts(self): "Return the text for each item of this control" - texts = [self.Text, ] + texts = [self.Text(), ] return texts - Texts = property (_get_texts, doc = "All text items of the control") #----------------------------------------------------------- - # TODO: Make _extra_clientrects a property/method of the class - # rather then a property that is set at initialization - def _get_clientrects(self): + def ClientRects(self): "Return the client rect for each item in this control" - clientrects = [self.ClientRect, ] - clientrects.extend(self._extra_clientrects) - return clientrects - ClientRects = property ( - _get_clientrects, doc = "All client rectanbgles of the control") + return [self.ClientRect(), ] #----------------------------------------------------------- - def _get_fonts(self): + def Fonts(self): "Return the font for each item in this control" - return [self.Font, ] - Fonts = property (_get_fonts, doc = "All fonts of the control") + return [self.Font(), ] #----------------------------------------------------------- - def _get_children(self): + def Children(self): "Return the children of this control" # this will be filled in the callback function child_windows = handleprops.children(self) return [WrapHandle(hwnd) for hwnd in child_windows] - Children = property (_get_children, doc = "The list of children") #----------------------------------------------------------- def IsChild(self, parent): @@ -281,11 +297,12 @@ class HwndWrapper(object): #----------------------------------------------------------- - def SendMessageTimeout(self, message, wparam = 0 , lparam = 0, timeout = .4): - "Send a message to the control and wait for it to return or for timout to finish" + def SendMessageTimeout( + self, message, wparam = 0 , lparam = 0, timeout = .4): + "Send a message to the control and wait for it to return or to timeout" result = ctypes.c_long() - ret = win32functions.SendMessageTimeout(self, message, wparam, lparam, + win32functions.SendMessageTimeout(self, message, wparam, lparam, win32defines.SMTO_NORMAL, int(timeout * 1000), ctypes.byref(result)) return result.value @@ -316,40 +333,40 @@ class HwndWrapper(object): def NotifyParent(self, message): "Send the notification message to parent of this control" - return self.Parent.PostMessage( + return self.Parent().PostMessage( win32defines.WM_COMMAND, - win32functions.MakeLong(self.ControlID, message), + win32functions.MakeLong(self.ControlID(), message), self) #----------------------------------------------------------- def GetProperties(self): "Return the properties of the control as a dictionary" - props = self._extra_props + props = {} # get the className - props['Class'] = self.Class + props['Class'] = self.Class() # set up the friendlyclass defaulting # to the class Name - props['FriendlyClassName'] = self.FriendlyClassName + props['FriendlyClassName'] = self.FriendlyClassName() - props['Texts'] = self.Texts - props['Style'] = self.Style - props['ExStyle'] = self.ExStyle - props['ControlID'] = self.ControlID - props['UserData'] = self.UserData - props['ContextHelpID'] = self.ContextHelpID + props['Texts'] = self.Texts() + props['Style'] = self.Style() + props['ExStyle'] = self.ExStyle() + props['ControlID'] = self.ControlID() + props['UserData'] = self.UserData() + props['ContextHelpID'] = self.ContextHelpID() - props['Fonts'] = self.Fonts - props['ClientRects'] = self.ClientRects + props['Fonts'] = self.Fonts() + props['ClientRects'] = self.ClientRects() - props['Rectangle'] = self.Rectangle + props['Rectangle'] = self.Rectangle() - props['IsVisible'] = self.IsVisible - props['IsUnicode'] = self.IsUnicode - props['IsEnabled'] = self.IsEnabled + props['IsVisible'] = self.IsVisible() + props['IsUnicode'] = self.IsUnicode() + props['IsEnabled'] = self.IsEnabled() - #props['MenuItems'] = [] + props['MenuItems'] = self.MenuItems() #if self.IsVisible and self._NeedsImageProp: # print "\t", self.Class @@ -361,15 +378,15 @@ class HwndWrapper(object): #----------------------------------------------------------- def CaptureAsImage(self): "Return a PIL image of the dialog" - if not (self.Rectangle.width() and self.Rectangle.height()): + if not (self.Rectangle().width() and self.Rectangle().height()): return None # get the control rectangle in a way that PIL likes it box = ( - self.Rectangle.left, - self.Rectangle.top, - self.Rectangle.right, - self.Rectangle.bottom) + self.Rectangle().left, + self.Rectangle().top, + self.Rectangle().right, + self.Rectangle().bottom) # grab the image and get raw data as a string # wrapped in try because PIL is optional @@ -404,11 +421,11 @@ class HwndWrapper(object): # check first if it's parent is enabled # (as long as it is not a dialog!) if not self.friendlyclassname == "Dialog": - if not self.Parent.IsEnabled: + if not self.Parent().IsEnabled(): raise ControlNotEnabled() # then check if the control itself is enabled - if not self.IsEnabled: + if not self.IsEnabled(): raise ControlNotEnabled() #----------------------------------------------------------- @@ -418,11 +435,11 @@ class HwndWrapper(object): # check first if it's parent is visible # (as long as it is not a dialog!) if not self.friendlyclassname == "Dialog": - if not self.Parent.IsVisible: + if not self.Parent().IsVisible(): raise ControlNotVisible() # then check if the control itself is Visible - if not self.IsVisible: + if not self.IsVisible(): raise ControlNotVisible() @@ -444,8 +461,12 @@ class HwndWrapper(object): _perform_click(self, button, pressed, coords, double) waited = 0 - # verify that we have been passed in a valid windows handle - while win32functions.IsWindow(self.handle) and waited < 1.5: + # Keep waiting until both this control and it's parent + # are no longer valid controls + while (win32functions.IsWindow(self) or \ + win32functions.IsWindow(self.Parent())) and \ + waited < 1.5: + time.sleep(.1) waited += .1 @@ -516,7 +537,7 @@ class HwndWrapper(object): self.VerifyActionable() if append: - text = self.Text + text + text = self.Text() + text text = ctypes.c_wchar_p(unicode(text)) self.PostMessage(win32defines.WM_SETTEXT, 0, text) @@ -539,7 +560,7 @@ class HwndWrapper(object): # attach the Python process with the process that self is in win32functions.AttachThreadInput( - win32functions.GetCurrentThreadId(), self.ProcessID, 1) + win32functions.GetCurrentThreadId(), self.ProcessID(), 1) # make sure that the control is in the foreground win32functions.SetForegroundWindow(self) @@ -554,7 +575,7 @@ class HwndWrapper(object): # detach the python process from the window's process win32functions.AttachThreadInput( - win32functions.GetCurrentThreadId(), self.ProcessID, 0) + win32functions.GetCurrentThreadId(), self.ProcessID(), 0) win32functions.WaitGuiThreadIdle(self) return self @@ -600,7 +621,7 @@ class HwndWrapper(object): "Draw an outline around the window" # don't draw if dialog is not visible - if not self.IsVisible: + if not self.IsVisible(): return colours = { @@ -614,7 +635,7 @@ class HwndWrapper(object): colour = colours[colour] if not rect: - rect = self.Rectangle + rect = self.Rectangle() # create the pen(outline) pen_handle = win32functions.CreatePen( @@ -655,7 +676,7 @@ class HwndWrapper(object): # if the menu items haven't been passed in then # get them from the window if not items: - items = self.MenuItems + items = self.MenuItems() # get the text names from the menu items item_texts = [item['Text'] for item in items] @@ -757,10 +778,10 @@ def _perform_click( # send each message for msg in msgs: - ret = ctrl.SendMessageTimeout(msg, flags, click_point) + ctrl.SendMessageTimeout(msg, flags, click_point) # wait until the thread can accept another message - ret = win32functions.WaitGuiThreadIdle(ctrl) + win32functions.WaitGuiThreadIdle(ctrl) # wait a certain(short) time after the click time.sleep(delay_after_click) @@ -792,15 +813,15 @@ def _calc_flags_and_coords(pressed, coords): # should really be in win32defines - I don't know why it isnt! #==================================================================== -def _GetMenuItems(menuHandle): +def _GetMenuItems(menu_handle, ctrl): "Get the menu items as a list of dictionaries" # If it doesn't have a real menu just return - if not win32functions.IsMenu(menuHandle): + if not win32functions.IsMenu(menu_handle): return [] items = [] - item_count = win32functions.GetMenuItemCount(menuHandle) + item_count = win32functions.GetMenuItemCount(menu_handle) # for each menu item for i in range(0, item_count): @@ -822,7 +843,7 @@ def _GetMenuItems(menuHandle): ret = win32functions.GetMenuItemInfo ( - menuHandle, i, True, ctypes.byref(menu_info)) + menu_handle, i, True, ctypes.byref(menu_info)) if not ret: raise ctypes.WinError() @@ -834,21 +855,28 @@ def _GetMenuItems(menuHandle): # if there is text if menu_info.cch: # allocate a buffer - bufferSize = menu_info.cch+1 - text = (ctypes.c_wchar * bufferSize)() + buffer_size = menu_info.cch+1 + text = ctypes.create_unicode_buffer(buffer_size) # update the structure and get the text info menu_info.dwTypeData = ctypes.addressof(text) - menu_info.cch = bufferSize + menu_info.cch = buffer_size win32functions.GetMenuItemInfo ( - menuHandle, i, True, ctypes.byref(menu_info)) + menu_handle, i, True, ctypes.byref(menu_info)) item_prop['Text'] = text.value else: item_prop['Text'] = "" # if it's a sub menu then get it's items if menu_info.hSubMenu: - sub_menu_items = _GetMenuItems(menu_info.hSubMenu)#, indent) + # make sure that the app updates the menu if it need to + ctrl.SendMessage( + win32defines.WM_INITMENUPOPUP, menu_info.hSubMenu, i) + + # get the sub menu items + sub_menu_items = _GetMenuItems(menu_info.hSubMenu, ctrl)#, indent) + + # append them item_prop['MenuItems'] = sub_menu_items items.append(item_prop) @@ -869,12 +897,12 @@ def GetDialogPropsFromHandle(hwnd): # controls on the dialog try: controls = [hwnd, ] - controls.extend(hwnd.Children) + controls.extend(hwnd.Children()) except AttributeError: controls = [WrapHandle(hwnd), ] # add all the children of the dialog - controls.extend(controls[0].Children) + controls.extend(controls[0].Children()) props = [] @@ -888,7 +916,7 @@ def GetDialogPropsFromHandle(hwnd): ctrl_props.handle = ctrl.handle # offset the rectangle from the dialog rectangle - ctrl_props['Rectangle'] -= controls[0].Rectangle + ctrl_props['Rectangle'] -= controls[0].Rectangle() props.append(ctrl_props) diff --git a/pywinauto/controls/__init__.py b/pywinauto/controls/__init__.py index 35bf502..017b108 100644 --- a/pywinauto/controls/__init__.py +++ b/pywinauto/controls/__init__.py @@ -28,7 +28,7 @@ from wraphandle import WrapHandle #==================================================================== def _unittests(): - + "Run some tests on the controls" from pywinauto import win32functions "do some basic testing" @@ -36,7 +36,6 @@ def _unittests(): import sys if len(sys.argv) < 2: - print "blah" handle = win32functions.GetDesktopWindow() else: try: diff --git a/pywinauto/controls/common_controls.py b/pywinauto/controls/common_controls.py index 752dbfb..7a96854 100644 --- a/pywinauto/controls/common_controls.py +++ b/pywinauto/controls/common_controls.py @@ -131,7 +131,7 @@ class ListViewWrapper(HwndWrapper.HwndWrapper): "Class that wraps Windows ListView common control " friendlyclassname = "ListView" - windowclasses = ["SysListView32", r"WindowsForms\d*\.SysListView32\..*",] + windowclasses = ["SysListView32", r"WindowsForms\d*\.SysListView32\..*", ] #---------------------------------------------------------------- def __init__(self, hwnd): @@ -156,10 +156,11 @@ class ListViewWrapper(HwndWrapper.HwndWrapper): remote_mem = _RemoteMemoryBlock(self) # Get each ListView columns text data - nIndex = 0 + index = 0 while True: col = win32structures.LVCOLUMNW() col.mask = win32defines.LVCF_WIDTH | win32defines.LVCF_FMT + #LVCF_TEXT, LVCF_SUBITEM, LVCF_IMAGE, LVCF_ORDER # put the information in the memory that the # other process can read/write @@ -168,7 +169,7 @@ class ListViewWrapper(HwndWrapper.HwndWrapper): # ask the other process to update the information retval = self.SendMessage( win32defines.LVM_GETCOLUMNW, - nIndex, + index, remote_mem.Address()) col = remote_mem.Read(col) @@ -182,7 +183,7 @@ class ListViewWrapper(HwndWrapper.HwndWrapper): # OK - so it didn't work stop trying to get more columns break - nIndex += 1 + index += 1 del (remote_mem) @@ -202,7 +203,7 @@ class ListViewWrapper(HwndWrapper.HwndWrapper): props['ColumnCount'] = self.ColumnCount() if props['ColumnCount'] == 0: props['ColumnCount'] = 1 - props['ColumnWidths'] = [999,] # never trunctated + props['ColumnWidths'] = [999, ] # never trunctated props['ItemData'] = [] for item in self.Items(): @@ -217,6 +218,7 @@ class ListViewWrapper(HwndWrapper.HwndWrapper): #----------------------------------------------------------- def GetItem(self, item_index, subitem_index = 0): + "Return the item of the list view" # set up a memory block in the remote application remote_mem = _RemoteMemoryBlock(self) @@ -242,11 +244,11 @@ class ListViewWrapper(HwndWrapper.HwndWrapper): if retval: # Read the remote text string - charData = ctypes.create_unicode_buffer(2000) - remote_mem.Read(charData, item.pszText) + char_data = ctypes.create_unicode_buffer(2000) + remote_mem.Read(char_data, item.pszText) # and add it to the titles - item.Text = charData.value + item.Text = char_data.value else: item.Text = '' @@ -276,15 +278,14 @@ class ListViewWrapper(HwndWrapper.HwndWrapper): return items #----------------------------------------------------------- - def _get_texts(self): + def Texts(self): "Get the texts for the ListView control" - texts = [self.Text] + texts = [self.Text()] texts.extend([item.Text for item in self.Items()]) return texts - Texts = property(_get_texts, doc = _get_texts.__doc__) - + #----------------------------------------------------------- # TODO: Make the RemoteMemoryBlock stuff more automatic! def UnCheck(self, item): "Uncheck the ListView item" @@ -297,16 +298,16 @@ class ListViewWrapper(HwndWrapper.HwndWrapper): lvitem.state = 0x1000 lvitem.stateMask = win32defines.LVIS_STATEIMAGEMASK - from controls.common_controls import RemoteMemoryBlock + remote_mem = _RemoteMemoryBlock(self) + remote_mem.Write(lvitem) - remoteMem = RemoteMemoryBlock(self) - remoteMem.Write(lvitem) + self.SendMessage( + win32defines.LVM_SETITEMSTATE, item, remote_mem.Address()) - self.SendMessage(win32defines.LVM_SETITEMSTATE, item, remoteMem.Address()) - - del remoteMem + del remote_mem + #----------------------------------------------------------- def Check(self, item): "Check the ListView item" @@ -318,22 +319,83 @@ class ListViewWrapper(HwndWrapper.HwndWrapper): lvitem.state = 0x2000 lvitem.stateMask = win32defines.LVIS_STATEIMAGEMASK - from controls.common_controls import RemoteMemoryBlock + remote_mem = _RemoteMemoryBlock(self) + remote_mem.Write(lvitem) - remoteMem = RemoteMemoryBlock(self) - remoteMem.Write(lvitem) + self.SendMessage( + win32defines.LVM_SETITEMSTATE, item, remote_mem.Address()) - self.SendMessage(win32defines.LVM_SETITEMSTATE, item, remoteMem.Address()) - - del remoteMem + del remote_mem + #----------------------------------------------------------- def IsChecked(self, item): "Return whether the ListView item is checked or not" state = self.SendMessage( - win32defines.LVM_GETITEMSTATE, item, win32defines.LVIS_STATEIMAGEMASK) + win32defines.LVM_GETITEMSTATE, + item, + win32defines.LVIS_STATEIMAGEMASK) - return state & 0x2000 + return state & 0x2000 == 0x2000 + #----------------------------------------------------------- + def IsSelected(self, item): + "Return True if the item is selected" + return win32defines.LVIS_SELECTED == self.SendMessage( + win32defines.LVM_GETITEMSTATE, item, win32defines.LVIS_SELECTED) + + #----------------------------------------------------------- + def IsFocused(self, item): + "Return True if the item has the focus" + return win32defines.LVIS_FOCUSED == self.SendMessage( + win32defines.LVM_GETITEMSTATE, item, win32defines.LVIS_FOCUSED) + + #----------------------------------------------------------- + def Select(self, item): + "Mark the item as selected" + + self.VerifyActionable() + + # first we need to change the state of the item + lvitem = win32structures.LVITEMW() + lvitem.mask = win32defines.LVIF_STATE + lvitem.state = win32defines.LVIS_SELECTED + lvitem.stateMask = win32defines.LVIS_SELECTED + + remote_mem = _RemoteMemoryBlock(self) + remote_mem.Write(lvitem) + + self.SendMessage( + win32defines.LVM_SETITEMSTATE, item, remote_mem.Address()) + + + # now we need to notify the parent that the state has chnaged + nmlv = win32structures.NMLISTVIEW() + nmlv.hdr.hwndFrom = self.handle + nmlv.hdr.idFrom = self.ControlID() + nmlv.hdr.code = win32defines.LVN_ITEMCHANGING + + nmlv.iItem = item + #nmlv.iSubItem = 0 + nmlv.uNewState = win32defines.LVIS_SELECTED + #nmlv.uOldState = 0 + nmlv.uChanged = win32defines.LVIS_SELECTED + nmlv.ptAction = win32structures.POINT() + + remote_mem.Write(nmlv) + + self.Parent().SendMessage( + win32defines.WM_NOTIFY, + self.ControlID(), + remote_mem.Write(lvitem)) + + del remote_mem + + + #----------------------------------------------------------- + def GetSelectedCount(self): + "Return the number of selected items" + + return self.SendMessage(win32defines.LVM_GETSELECTEDCOUNT) @@ -351,24 +413,57 @@ class ListViewWrapper(HwndWrapper.HwndWrapper): class _treeview_element(object): - + "Wrapper around TreeView items" #---------------------------------------------------------------- def __init__(self, elem, tv_handle): + "Initialize the item" self.tree_ctrl = tv_handle self.elem = elem self._as_parameter_ = self.elem #---------------------------------------------------------------- def Text(self): + "Return the text of the item" return self._readitem()[1] #---------------------------------------------------------------- def Item(self): + "Return the item itself" return self._readitem()[0] #---------------------------------------------------------------- - def Children(self): + def State(self): + "Return the state of the item" + return self.Item().state + #---------------------------------------------------------------- + def Rectangle(self): + "Return the rectangle of the item" + remote_mem = _RemoteMemoryBlock(self.tree_ctrl) + + # this is a bit weird + # we have to write the element handle + # but we read the Rectangle afterwards! + remote_mem.Write(ctypes.c_long(self.elem)) + + ret = self.tree_ctrl.SendMessage( + win32defines.TVM_GETITEMRECT, 0, remote_mem.Address()) + + # the item is not visible + if not ret: + rect = None + else: + # OK - it's visible so read it + rect = win32structures.RECT() + remote_mem.Read(rect) + + del remote_mem + return rect + + + #---------------------------------------------------------------- + def Children(self): + "Return the direct children of this control" if self.Item().cChildren not in (0, 1): print "##### not dealing with that TVN_GETDISPINFO stuff yet" pass @@ -406,7 +501,7 @@ class _treeview_element(object): #---------------------------------------------------------------- def Next(self): - + "Return the next item" # get the next element next_elem = self.tree_ctrl.SendMessage( win32defines.TVM_GETNEXTITEM, @@ -425,6 +520,7 @@ class _treeview_element(object): #---------------------------------------------------------------- def SubElements(self): + "Return the list of Children of this control" sub_elems = [] for child in self.Children(): @@ -437,17 +533,20 @@ class _treeview_element(object): #---------------------------------------------------------------- def _readitem(self): + "Read the treeview item" remote_mem = _RemoteMemoryBlock(self.tree_ctrl) item = win32structures.TVITEMW() item.mask = win32defines.TVIF_TEXT | \ win32defines.TVIF_HANDLE | \ - win32defines.TVIF_CHILDREN #| TVIF_STATE | + win32defines.TVIF_CHILDREN | \ + win32defines.TVIF_STATE # set the address for the text item.pszText = remote_mem.Address() + ctypes.sizeof(item) + 1 item.cchTextMax = 2000 item.hItem = self.elem + item.stateMask = -1 # Write the local TVITEM structure to the remote memory block remote_mem.Write(item) @@ -463,7 +562,7 @@ class _treeview_element(object): if retval: remote_mem.Read(item) - self.__item = item + #self.__item = item # Read the remote text string char_data = ctypes.create_unicode_buffer(2000) remote_mem.Read(char_data, item.pszText) @@ -478,27 +577,29 @@ class _treeview_element(object): #==================================================================== class TreeViewWrapper(HwndWrapper.HwndWrapper): - "Class that wraps Windows TreeView common control " + "Class that wraps Windows TreeView common control" friendlyclassname = "TreeView" - windowclasses = ["SysTreeView32",r"WindowsForms\d*\.SysTreeView32\..*"] + windowclasses = ["SysTreeView32", r"WindowsForms\d*\.SysTreeView32\..*"] #---------------------------------------------------------------- def __init__(self, hwnd): "Initialise the instance" super(TreeViewWrapper, self).__init__(hwnd) - self._extra_text = [] + #self._extra_text = [] - remote_mem = _RemoteMemoryBlock(self) + #remote_mem = _RemoteMemoryBlock(self) #---------------------------------------------------------------- def Count(self): + "Return the number of items" return self.SendMessage(win32defines.TVM_GETCOUNT) #---------------------------------------------------------------- - def _get_texts(self): - texts = [self.Text, self.Root().Text()] + def Texts(self): + "Return all the text for the tree view" + texts = [self.Text(), self.Root().Text()] elements = self.Root().SubElements() @@ -506,8 +607,6 @@ class TreeViewWrapper(HwndWrapper.HwndWrapper): return texts - Texts = property(_get_texts, doc = "Return all the text for the tree view") - #---------------------------------------------------------------- def Root(self): "Return the root element of the tree view" @@ -522,14 +621,17 @@ class TreeViewWrapper(HwndWrapper.HwndWrapper): #---------------------------------------------------------------- def GetProperties(self): "Get the properties for the control as a dictionary" - props = HwndWrapper.GetProperties(self) + props = HwndWrapper.HwndWrapper.GetProperties(self) props['Count'] = self.Count() return props + #---------------------------------------------------------------- def GetItem(self, path): + "Read the TreeView item" + # work just based on integers for now current_elem = self.Root() @@ -566,7 +668,7 @@ class TreeViewWrapper(HwndWrapper.HwndWrapper): #---------------------------------------------------------------- def Select(self, path): - + "Select the treeview item" elem = self.GetItem(path) self.SendMessage( win32defines.TVM_SELECTITEM, # message @@ -574,7 +676,15 @@ class TreeViewWrapper(HwndWrapper.HwndWrapper): elem) # item to select + #----------------------------------------------------------- + def IsSelected(self, path): + "Return True if the item is selected" + return win32defines.TVIS_SELECTED == (win32defines.TVIS_SELECTED & \ + self.GetItem(path).State()) + + #---------------------------------------------------------------- def EnsureVisible(self, path): + "Make sure that the TreeView item is visible" elem = self.GetItem(path) self.SendMessage( win32defines.TVM_ENSUREVISIBLE, # message @@ -582,6 +692,70 @@ class TreeViewWrapper(HwndWrapper.HwndWrapper): elem) # item to select +# +# #----------------------------------------------------------- +# # TODO: Make the RemoteMemoryBlock stuff more automatic! +# def UnCheck(self, path): +# "Uncheck the ListView item" +# +# self.VerifyActionable() +# +# elem = self.GetItem(path) +# +## lvitem = win32structures.LVITEMW() +## +## lvitem.mask = win32defines.LVIF_STATE +## lvitem.state = 0x1000 +## lvitem.stateMask = win32defines.LVIS_STATEIMAGEMASK +## +## remote_mem = _RemoteMemoryBlock(self) +## remote_mem.Write(lvitem) +## +## self.SendMessage( +## win32defines.LVM_SETITEMSTATE, item, remote_mem.Address()) +## +## del remote_mem +# +# +# #----------------------------------------------------------- +# def Check(self, path): +# "Check the ListView item" +# +# self.VerifyActionable() +# +# elem = self.GetItem(path) +# +# #lvitem = win32structures.LVITEMW() +# +# lvitem.mask = win32defines.LVIF_STATE +# lvitem.state = 0x2000 +# lvitem.stateMask = win32defines.LVIS_STATEIMAGEMASK +# +# remote_mem = _RemoteMemoryBlock(self) +# remote_mem.Write(lvitem) +# +# self.SendMessage( +# win32defines.LVM_SETITEMSTATE, item, remote_mem.Address()) +# +# del remote_mem +# +# #----------------------------------------------------------- +# def IsChecked(self, path): +# "Return whether the ListView item is checked or not" +# +# elem = self.GetItem(path) +# +# elem.State +# +# state = self.SendMessage( +# win32defines.LVM_GETITEMSTATE, +# item, +# win32defines.LVIS_STATEIMAGEMASK) +# +# return state & 0x2000 +# + + #==================================================================== @@ -596,15 +770,17 @@ class HeaderWrapper(HwndWrapper.HwndWrapper): "Initialise the instance" super(HeaderWrapper, self).__init__(hwnd) - self._fill_header_info() + #self._fill_header_info() #---------------------------------------------------------------- def Count(self): + "Return the number of columns in this header" # get the number of items in the header... return self.SendMessage(win32defines.HDM_GETITEMCOUNT) #---------------------------------------------------------------- def ColumnRectangle(self, column_index): + "Return the rectangle for the column specified by column_index" remote_mem = _RemoteMemoryBlock(self) # get the column rect @@ -625,8 +801,9 @@ class HeaderWrapper(HwndWrapper.HwndWrapper): return rect #---------------------------------------------------------------- - def _get_clientrects(self): - rects = [self.ClientRect,] + def ClientRects(self): + "Return all the client rectangles for the header control" + rects = [self.ClientRect(), ] for col_index in range(0, self.Count()): @@ -634,12 +811,10 @@ class HeaderWrapper(HwndWrapper.HwndWrapper): return rects - ClientRects = property(_get_clientrects, - doc = "Return all the client rectangles for the header control") - #---------------------------------------------------------------- def ColumnText(self, column_index): + "Return the text for the column specified by column_index" remote_mem = _RemoteMemoryBlock(self) @@ -674,54 +849,49 @@ class HeaderWrapper(HwndWrapper.HwndWrapper): return None #---------------------------------------------------------------- - def _get_Texts(self): - texts = [self.Text, ] + def Texts(self): + "Return the texts of the Header control" + texts = [self.Text(), ] for i in range(0, self.Count()): - texts.append(self.ColumnText(self, i)) + texts.append(self.ColumnText(i)) return texts - Texts = property(_get_Texts) +# #---------------------------------------------------------------- +# def _fill_header_info(self): +# "Get the information from the header control" +# remote_mem = _RemoteMemoryBlock(self) +# +# for col_index in range(0, self.Count()): +# +# item = win32structures.HDITEMW() +# item.mask = win32defines.HDI_FORMAT | \ +# win32defines.HDI_WIDTH | \ +# win32defines.HDI_TEXT #| HDI_ORDER +# item.cchTextMax = 2000 +# +# # set up the pointer to the text +# # it should be at the +# item.pszText = remote_mem.Address() + ctypes.sizeof(item) + 1 +# +# # put the information in the memory that the +# # other process can read/write +# remote_mem.Write(item) +# +# # ask the other process to update the information +# retval = self.SendMessage( +# win32defines.HDM_GETITEMW, +# col_index, +# remote_mem.Address()) +# +# if retval: +# item = remote_mem.Read(item) +# +# # Read the remote text string +# charData = ctypes.create_unicode_buffer(2000) +# remote_mem.Read(charData, item.pszText) +# self._extra_texts.append(charData.value) - #---------------------------------------------------------------- - def _fill_header_info(self): - "Get the information from the header control" - remote_mem = _RemoteMemoryBlock(self) - - for col_index in range(0, self.Count()): - - item = win32structures.HDITEMW() - item.mask = win32defines.HDI_FORMAT | \ - win32defines.HDI_WIDTH | \ - win32defines.HDI_TEXT #| HDI_ORDER - item.cchTextMax = 2000 - - # set up the pointer to the text - # it should be at the - item.pszText = remote_mem.Address() + ctypes.sizeof(item) + 1 - - # put the information in the memory that the - # other process can read/write - remote_mem.Write(item) - - # ask the other process to update the information - retval = self.SendMessage( - win32defines.HDM_GETITEMW, - col_index, - remote_mem.Address()) - - if retval: - item = remote_mem.Read(item) - - # Read the remote text string - charData = ctypes.create_unicode_buffer(2000) - remote_mem.Read(charData, item.pszText) - self._extra_texts.append(charData.value) - - #---------------------------------------------------------------- - def _get_texts(self): - return self._extra_texts - Texts = property(_get_texts) #==================================================================== class StatusBarWrapper(HwndWrapper.HwndWrapper): @@ -740,12 +910,19 @@ class StatusBarWrapper(HwndWrapper.HwndWrapper): #---------------------------------------------------------------- def BorderWidths(self): + """Return the border widths of the StatusBar + + A dictionary of the 3 available widths is returned: + Horizontal - the horizontal width + Vertical - The width above and below the status bar parts + Inter - The width between parts of the status bar + """ remote_mem = _RemoteMemoryBlock(self) # get the borders for each of the areas there can be a border. borders = (ctypes.c_int*3)() remote_mem.Write(borders) - numParts = self.SendMessage( + self.SendMessage( win32defines.SB_GETBORDERS, 0, remote_mem.Address() @@ -753,7 +930,7 @@ class StatusBarWrapper(HwndWrapper.HwndWrapper): borders = remote_mem.Read(borders) borders_widths = {} borders_widths['Horizontal'] = borders[0] - borders_widths['Verttical'] = borders[1] + borders_widths['Vertical'] = borders[1] borders_widths['Inter'] = borders[2] del remote_mem @@ -762,6 +939,7 @@ class StatusBarWrapper(HwndWrapper.HwndWrapper): #---------------------------------------------------------------- def NumParts(self): + "Return the number of parts" # get the number of parts for this status bar return self.SendMessage( win32defines.SB_GETPARTS, @@ -770,6 +948,7 @@ class StatusBarWrapper(HwndWrapper.HwndWrapper): #---------------------------------------------------------------- def PartWidths(self): + "Return the widths of the parts" remote_mem = _RemoteMemoryBlock(self) # get the number of parts for this status bar @@ -789,12 +968,13 @@ class StatusBarWrapper(HwndWrapper.HwndWrapper): #---------------------------------------------------------------- def GetPartRect(self, part_index): + "Return the rectangle of the part specified by part_index" remote_mem = _RemoteMemoryBlock(self) # get the rectangle for this item rect = win32structures.RECT() remote_mem.Write(rect) - num_parts = self.SendMessage( + self.SendMessage( win32defines.SB_GETRECT, part_index, remote_mem.Address()) @@ -804,18 +984,18 @@ class StatusBarWrapper(HwndWrapper.HwndWrapper): return rect #---------------------------------------------------------------- - def _get_clientrects(self): - rects = [self.GetClientRect] + def ClientRects(self): + "Return the client rectangles for the control" + rects = [self.ClientRect()] - for i in range(numParts): + for i in range(self.NumParts()): rects.append(self.GetPartRect(i)) return rects - GetClientRects = property(_get_clientrects, - doc = "Return the client rectangles for the control") #---------------------------------------------------------------- def GetPartText(self, part_index): + "Return the text of the part specified by part_index" remote_mem = _RemoteMemoryBlock(self) @@ -831,7 +1011,7 @@ class StatusBarWrapper(HwndWrapper.HwndWrapper): # get the text for this item text = ctypes.create_unicode_buffer(textlen + 1) remote_mem.Write(text) - numParts = self.SendMessage( + self.SendMessage( win32defines.SB_GETTEXTW, part_index, remote_mem.Address() @@ -844,15 +1024,15 @@ class StatusBarWrapper(HwndWrapper.HwndWrapper): #---------------------------------------------------------------- - def _get_texts(self): - texts = [self.Text] + def Texts(self): + "Return the texts for the control" + texts = [self.Text()] - for i in range(numParts): - rects.append(self.GetPartText(i)) + for i in range(self.NumParts()): + texts.append(self.GetPartText(i)) return texts - Texts = property(_get_texts, - doc = "Return the texts for the control") + @@ -868,7 +1048,8 @@ class TabControlWrapper(HwndWrapper.HwndWrapper): "Initialise the instance" super(TabControlWrapper, self).__init__(hwnd) - + self._extra_props = {} + self._extra_clientrects = [] self._extra_texts = [] self._fill_tabcontrol_info() @@ -877,22 +1058,9 @@ class TabControlWrapper(HwndWrapper.HwndWrapper): "Get the information from the Tab control" #tooltipHandle = self.SendMessage(win32defines.TCM_GETTOOLTIPS) - itemCount = self.SendMessage(win32defines.TCM_GETITEMCOUNT) - self._extra_props['TabCount'] = itemCount - remote_mem = _RemoteMemoryBlock(self) - for i in range(0, itemCount): - - rect = win32structures.RECT() - remote_mem.Write(rect) - - self.SendMessage( - win32defines.TCM_GETITEMRECT, i, remote_mem.Address()) - - remote_mem.Read(rect) - - self._extra_clientrects.append(rect) + for i in range(0, self.TabCount()): item = win32structures.TCITEMW() item.mask = win32defines.TCIF_STATE | win32defines.TCIF_TEXT @@ -913,11 +1081,54 @@ class TabControlWrapper(HwndWrapper.HwndWrapper): self._extra_texts.append(text.value) #---------------------------------------------------------------- - def _get_texts(self): - texts = [self.Text] + def GetProperties(self): + "Return the properties of the TabControl as a Dictionary" + props = HwndWrapper.HwndWrapper.GetProperties(self) + + props['TabCount'] = self.TabCount() + + props.update(self._extra_props) + return props + + #---------------------------------------------------------------- + def TabCount(self): + "Return the number of tabs" + return self.SendMessage(win32defines.TCM_GETITEMCOUNT) + + #---------------------------------------------------------------- + def GetTabRect(self, tab_index): + "Return the rectangle to the tab specified by tab_index" + + remote_mem = _RemoteMemoryBlock(self) + + rect = win32structures.RECT() + remote_mem.Write(rect) + + self.SendMessage( + win32defines.TCM_GETITEMRECT, tab_index, remote_mem.Address()) + + remote_mem.Read(rect) + + del remote_mem + + return rect + + #---------------------------------------------------------------- + def ClientRects(self): + "Return the client rectangles for the Tab Control" + + rects = [self.ClientRect()] + for tab_index in range(0, self.TabCount()): + rects.append(self.GetTabRect(tab_index)) + + return rects + + #---------------------------------------------------------------- + def Texts(self): + "Return the texts of the Tab Control" + texts = [self.Text()] texts.extend(self._extra_texts) return texts - Texts = property(_get_texts) #---------------------------------------------------------------- def Select(self, tab): @@ -927,13 +1138,16 @@ class TabControlWrapper(HwndWrapper.HwndWrapper): if isinstance(tab, basestring): # find the string in the tab control - bestText = findbestmatch.find_best_match(tab, self.Texts, self.Texts) - tab = self.Texts.index(bestText) - 1 + best_text = findbestmatch.find_best_match( + tab, self.Texts(), self.Texts()) + tab = self.Texts().index(best_text) - 1 self.SendMessage(win32defines.TCM_SETCURFOCUS, tab) +#==================================================================== class TBButtonWrappper(win32structures.TBBUTTONINFOW): + "Simple wrapper around TBBUTTONINFOW to allow setting new attributes" pass #==================================================================== @@ -952,18 +1166,23 @@ class ToolbarWrapper(HwndWrapper.HwndWrapper): #---------------------------------------------------------------- def ButtonCount(self): + "Return the number of buttons on the ToolBar" return self.SendMessage(win32defines.TB_BUTTONCOUNT) #---------------------------------------------------------------- def GetButton(self, button_index): + "Return information on teh Toolbar button" - #button = win32structures.TBBUTTON() + remote_mem = _RemoteMemoryBlock(self) - #remote_mem.Write(button) + button = win32structures.TBBUTTON() - #self.SendMessage(win32defines.TB_GETBUTTON, i, remote_mem.Address()) + remote_mem.Write(button) - #remote_mem.Read(button) + self.SendMessage( + win32defines.TB_GETBUTTON, button_index, remote_mem.Address()) + + remote_mem.Read(button) button_info = TBButtonWrappper() button_info.cbSize = ctypes.sizeof(button_info) @@ -983,27 +1202,27 @@ class ToolbarWrapper(HwndWrapper.HwndWrapper): # fill the button_info structure remote_mem.Write(button_info) self.SendMessage( - win32defines.TB_GETbutton_infoW, + win32defines.TB_GETBUTTONINFOW, button.idCommand, remote_mem.Address()) remote_mem.Read(button_info) # read the text button_info.text = ctypes.create_unicode_buffer(1999) - remote_mem.Read(button_info.text, remote_mem.Address() + \ + remote_mem.Read(button_info.pszText, remote_mem.Address() + \ ctypes.sizeof(button_info)) del remote_mem return button_info - def _get_texts(self): - texts = [self.Text] + def Texts(self): + "Return the texts of the Toolbar" + texts = [self.Text()] for i in range(0, self.ButtonCount()): texts.append(self.GetButton(i).text) return texts - Texts = property(_get_texts) # #---------------------------------------------------------------- @@ -1020,7 +1239,8 @@ class ToolbarWrapper(HwndWrapper.HwndWrapper): # # remote_mem.Write(button) # -# self.SendMessage(win32defines.TB_GETBUTTON, i, remote_mem.Address()) +# self.SendMessage( +# win32defines.TB_GETBUTTON, i, remote_mem.Address()) # # remote_mem.Read(button) # @@ -1081,6 +1301,7 @@ class ToolbarWrapper(HwndWrapper.HwndWrapper): # RB_GETBANDBORDERS class BandWrapper(win32structures.REBARBANDINFOW): + "Simple wrapper around REBARBANDINFOW to allow setting new attributes" pass #==================================================================== @@ -1097,15 +1318,17 @@ class ReBarWrapper(HwndWrapper.HwndWrapper): #---------------------------------------------------------------- def NumBands(self): + "Return the number of bands in the control" return self.SendMessage(win32defines.RB_GETBANDCOUNT) #---------------------------------------------------------------- def GetBand(self, band_index): + "Get a band of the ReBar control" remote_mem = _RemoteMemoryBlock(self) band_info = BandWrapper() - band_info.cbSize = ctypes.sizeof(bandInfo) + band_info.cbSize = ctypes.sizeof(band_info) band_info.fMask = \ win32defines.RBBIM_CHILD | \ win32defines.RBBIM_CHILDSIZE | \ @@ -1119,11 +1342,11 @@ class ReBarWrapper(HwndWrapper.HwndWrapper): # set the pointer for the text band_info.pszText = remote_mem.Address() + \ - ctypes.sizeof(bandInfo) + ctypes.sizeof(band_info) band_info.cchText = 2000 # write the structure - remote_mem.Write(bandInfo) + remote_mem.Write(band_info) # Fill the structure self.SendMessage( @@ -1136,7 +1359,7 @@ class ReBarWrapper(HwndWrapper.HwndWrapper): # read the text band_info.text = ctypes.create_unicode_buffer(1999) - remote_mem.Read(band_info.text, remote_mem.Address() + \ + remote_mem.Read(band_info.pszText, remote_mem.Address() + \ ctypes.sizeof(band_info)) del remote_mem @@ -1144,18 +1367,19 @@ class ReBarWrapper(HwndWrapper.HwndWrapper): #---------------------------------------------------------------- - def _get_texts(self): - texts = [self.Text] + def Texts(self): + "Return the texts of the Rebar" + texts = [self.Text()] for i in range(0, self.NumBands()): band = self.GetBand(i) texts.append(band.text) return texts - Texts = property(_get_texts) #---------------------------------------------------------------- def GetProperties(self): - props = HwndWrapper.GetProperties(self) + "Return the properties for the ReBar" + props = HwndWrapper.HwndWrapper.GetProperties(self) props['BandCount'] = self.NumBands() @@ -1244,87 +1468,343 @@ class ToolTipsWrapper(HwndWrapper.HwndWrapper): +import unittest + +class ListViewTestCases(unittest.TestCase): + "Unit tests for the ListViewWrapper class" + + def setUp(self): + """Start the application set some data and ensure the application + is in the state we want it.""" + + # start the application + from pywinauto.application import Application + app = Application() + app.start_(r"C:\.projects\py_pywinauto\controlspy0798\List View.exe") + + self.texts = [ + ("Mercury", '57,910,000', '4,880', '3.30e23'), + ("Venus", '108,200,000', '12,103.6', '4.869e24'), + ("Earth", '149,600,000', '12,756.3', '5.9736e24'), + ("Mars", '227,940,000', '6,794', '6.4219e23'), + ("Jupiter", '778,330,000', '142,984', '1.900e27'), + ("Saturn", '1,429,400,000', '120,536', '5.68e26'), + ("Uranus", '2,870,990,000', '51,118', '8.683e25'), + ("Neptune", '4,504,000,000', '49,532', '1.0247e26'), + ("Pluto", '5,913,520,000', '2,274', '1.27e22'), + ] + + self.app = app + self.dlg = app.MicrosoftControlSpy #top_window_() + self.ctrl = app.MicrosoftControlSpy.ListView.ctrl_() + + #self.dlg.MenuSelect("Styles") + + # select show selection always! + #app.ControlStyles.ListBox1.TypeKeys("{UP}" * 26 + "{SPACE}") + + #self.app.ControlStyles.ListBox1.Select("LVS_SHOWSELALWAYS") + #self.app.ControlStyles.ApplyStylesSetWindowLong.Click() + + #self.app.ControlStyles.SendMessage(win32defines.WM_CLOSE) + + def tearDown(self): + "Close the application after tests" + # close the application + self.dlg.SendMessage(win32defines.WM_CLOSE) -def test_listview(ctrl): - print "="*80 - print "ListView" - print "="*80 - lv = ListViewWrapper(ctrl) + def testFriendlyClass(self): + "Make sure the friendly class is set correctly" + self.assertEquals (self.ctrl.FriendlyClassName(), "ListView") - print lv.ColumnCount() - print lv.ItemCount() - assert len(lv.Columns()) == lv.ColumnCount() - assert len(lv.Items()) == lv.ColumnCount() * lv.ItemCount() + def testItemCount(self): + "Test the ItemCount method" + self.assertEquals (self.ctrl.ItemCount(), 9) - for i in lv.Items(): - print "%s '%s'"% (" "* i.iSubItem, i.Text) + def testColumnCount(self): + "Test the ColumnCount method" + self.assertEquals (self.ctrl.ColumnCount(), 4) -def test_treeview(ctrl): - print "="*80 - print "TreeView" - print "="*80 - tv = TreeViewWrapper(ctrl) - print "Number of items:", tv.Count() - root_elem = tv.Root() - print tv.Texts + def testItemText(self): + "Test the item.Text property" + item = self.ctrl.GetItem(1) + + self.assertEquals(item.Text, "Venus") + + def testItems(self): + "Test the Items method" + + flat_texts = [] + for row in self.texts: + flat_texts.extend(row) + + for i, item in enumerate(self.ctrl.Items()): + self.assertEquals(item.Text, flat_texts[i]) -def test_header(ctrl): - print "="*80 - print "Header" - print "="*80 - header = HeaderWrapper(ctrl) + def testTexts(self): + "Test teh Texts method" - for i in range(0, header.Count()): - print header.ColumnRectangle(i) + flat_texts = [] + for row in self.texts: + flat_texts.extend(row) + + self.assertEquals(flat_texts, self.ctrl.Texts()[1:]) -def test_statusbar(ctrl): - print "="*80 - print "StatusBar" - print "="*80 - stat_bar = StatusBarWrapper(ctrl) - print stat_bar.NumParts() + def testGetItem(self): + "Test the GetItem method" - print stat_bar.BorderWidths() + for row in range(self.ctrl.ItemCount()): + for col in range(self.ctrl.ColumnCount()): + self.assertEquals( + self.ctrl.GetItem(row, col).Text, self.texts[row][col]) - print stat_bar.PartWidths() - for i in range(0, stat_bar.NumParts()): - print "\t", `stat_bar.GetPartText(i)` + def testColumn(self): + "Test the Columns method" -def _unittests(): - from pywinauto import findwindows - import os - import time - from pywinauto import handleprops - os.system(r"explorer") - time.sleep(1) + cols = self.ctrl.Columns() + self.assertEqual (len(cols), self.ctrl.ColumnCount()) - explorer = findwindows.find_windows(class_name = "ExploreWClass")[0] + # todo add more checking of column values + #for col in cols: + # print col - for ctrl in handleprops.children(explorer): - if handleprops.classname(ctrl) in ListViewWrapper.windowclasses: - pass - test_listview(ctrl) - if handleprops.classname(ctrl) in TreeViewWrapper.windowclasses: - pass - test_treeview(ctrl) + def testGetSelectionCount(self): + "Test the GetSelectedCount method" - if handleprops.classname(ctrl) in HeaderWrapper.windowclasses: - pass - test_header(ctrl) + self.assertEquals(self.ctrl.GetSelectedCount(), 0) - if handleprops.classname(ctrl) in StatusBarWrapper.windowclasses: - pass - test_statusbar(ctrl) + self.ctrl.Select(1) + self.ctrl.Select(7) + self.ctrl.Select(12) + + self.assertEquals(self.ctrl.GetSelectedCount(), 2) + + + def testIsSelected(self): + "Test IsSelected for some items" + + # ensure that the item is not selected + self.assertEquals(self.ctrl.IsSelected(1), False) + + # select an item + self.ctrl.Select(1) + + # now ensure that the item is selected + self.assertEquals(self.ctrl.IsSelected(1), True) + + + def _testFocused(self): + "Test checking the focus of some items" + + print "Select something quick!!" + import time + time.sleep(3) + #self.ctrl.Select(1) + + print self.ctrl.IsFocused(0) + print self.ctrl.IsFocused(1) + print self.ctrl.IsFocused(2) + print self.ctrl.IsFocused(3) + print self.ctrl.IsFocused(4) + print self.ctrl.IsFocused(5) + #for col in cols: + # print col + + + def testSelect(self): + "Test Selecting some items" + self.ctrl.Select(1) + self.ctrl.Select(3) + self.ctrl.Select(4) + + self.assertEquals(self.ctrl.GetSelectedCount(), 3) + +# +# def testSubItems(self): +# +# for row in range(self.ctrl.ItemCount()) +# +# for i in self.ctrl.Items(): +# +# #self.assertEquals(item.Text, texts[i]) + + + + + +class TreeViewTestCases(unittest.TestCase): + "Unit tests for the TreeViewWrapper class" + + def setUp(self): + """Start the application set some data and ensure the application + is in the state we want it.""" + + # start the application + from pywinauto.application import Application + app = Application() + app.start_(r"C:\.projects\py_pywinauto\controlspy0798\Tree View.exe") + + self.root_text = "The Planets" + self.texts = [ + ("Mercury", '57,910,000', '4,880', '3.30e23'), + ("Venus", '108,200,000', '12,103.6', '4.869e24'), + ("Earth", '149,600,000', '12,756.3', '5.9736e24'), + ("Mars", '227,940,000', '6,794', '6.4219e23'), + ("Jupiter", '778,330,000', '142,984', '1.900e27'), + ("Saturn", '1,429,400,000', '120,536', '5.68e26'), + ("Uranus", '2,870,990,000', '51,118', '8.683e25'), + ("Neptune", '4,504,000,000', '49,532', '1.0247e26'), + ("Pluto", '5,913,520,000', '2,274', '1.27e22'), + ] + + self.app = app + self.dlg = app.MicrosoftControlSpy #top_window_() + self.ctrl = app.MicrosoftControlSpy.TreeView.ctrl_() + + #self.dlg.MenuSelect("Styles") + + # select show selection always, and show checkboxes + #app.ControlStyles.ListBox1.TypeKeys("{HOME}{SPACE}" + "{DOWN}"* 12 + "{SPACE}") + #self.app.ControlStyles.ApplyStylesSetWindowLong.Click() + #self.app.ControlStyles.SendMessage(win32defines.WM_CLOSE) + + def tearDown(self): + "Close the application after tests" + # close the application + self.dlg.SendMessage(win32defines.WM_CLOSE) + + def testFriendlyClass(self): + "Make sure the friendly class is set correctly" + self.assertEquals (self.ctrl.FriendlyClassName(), "TreeView") + + def testItemCount(self): + "Test the ItemCount method" + self.assertEquals (self.ctrl.Count(), 37) + + + def testItemText(self): + "Test the ItemCount method" + + self.assertEquals(self.ctrl.Root().Text(), self.root_text) + + self.assertEquals(self.ctrl.GetItem((0,1,2)).Text(), self.texts[1][3] + " kg") + + + def testSelect(self): + self.ctrl.Select((0, 1, 2)) + + self.ctrl.GetItem((0, 1, 2)).State() + + self.assertEquals(True, self.ctrl.IsSelected((0, 1, 2))) + + + def testEnsureVisible(self): + "make sure that the item is visible" + + # note this is partially a fake test at the moment because + # just by getting an item - we usually make it visible + self.ctrl.EnsureVisible((0,8,2)) + + # make sure that the item is not hidden + self.assertNotEqual(None, self.ctrl.GetItem((0, 8, 2)).Rectangle()) + + + + +# +#def test_listview(ctrl): +# "Not called anymore" +# print "="*80 +# print "ListView" +# print "="*80 +# lv = ListViewWrapper(ctrl) +# +# print lv.ColumnCount() +# print lv.ItemCount() +# assert len(lv.Columns()) == lv.ColumnCount() +# assert len(lv.Items()) == lv.ColumnCount() * lv.ItemCount() +# +# for i in lv.Items(): +# print "%s '%s'"% (" "* i.iSubItem, i.Text) +# +#def test_treeview(ctrl): +# "Not called anymore" +# print "="*80 +# print "TreeView" +# print "="*80 +# tv = TreeViewWrapper(ctrl) +# print "Number of items:", tv.Count() +# root_elem = tv.Root() +# print tv.Texts +# +# +#def test_header(ctrl): +# "Not called anymore" +# print "="*80 +# print "Header" +# print "="*80 +# header = HeaderWrapper(ctrl) +# +# for i in range(0, header.Count()): +# print header.ColumnRectangle(i) +# +# +#def test_statusbar(ctrl): +# "Not called anymore" +# print "="*80 +# print "StatusBar" +# print "="*80 +# stat_bar = StatusBarWrapper(ctrl) +# +# print stat_bar.NumParts() +# +# print stat_bar.BorderWidths() +# +# print stat_bar.PartWidths() +# +# for i in range(0, stat_bar.NumParts()): +# print "\t", `stat_bar.GetPartText(i)` +# +#def _unittests(): +# "Run some Unittests - not called anymore!" +# from pywinauto import findwindows +# import os +# import time +# from pywinauto import handleprops +# os.system(r"explorer") +# time.sleep(1) +# +# explorer = findwindows.find_windows(class_name = "ExploreWClass")[0] +# +# for ctrl in handleprops.children(explorer): +# if handleprops.classname(ctrl) in ListViewWrapper.windowclasses: +# pass +# test_listview(ctrl) +# +# if handleprops.classname(ctrl) in TreeViewWrapper.windowclasses: +# pass +# test_treeview(ctrl) +# +# if handleprops.classname(ctrl) in HeaderWrapper.windowclasses: +# pass +# test_header(ctrl) +# +# if handleprops.classname(ctrl) in StatusBarWrapper.windowclasses: +# pass +# test_statusbar(ctrl) if __name__ == "__main__": - _unittests() + #_unittests() + + unittest.main() + # diff --git a/pywinauto/controls/win32_controls.py b/pywinauto/controls/win32_controls.py index 72c5c63..52800de 100644 --- a/pywinauto/controls/win32_controls.py +++ b/pywinauto/controls/win32_controls.py @@ -48,16 +48,10 @@ class ButtonWrapper(HwndWrapper.HwndWrapper): self._set_if_needs_image() - # default to Button for FriendlyClassName - # might be changed later - #self.FriendlyClassName = "Button" - self.FriendlyClassName = self._friendly_class_name() - - #----------------------------------------------------------- def _set_if_needs_image(self): "Set the _NeedsImageProp attribute if it is an image button" - if self.IsVisible and (\ + if self.IsVisible() and (\ self.HasStyle(win32defines.BS_BITMAP) or \ self.HasStyle(win32defines.BS_ICON) or \ self.HasStyle(win32defines.BS_OWNERDRAW)): @@ -65,27 +59,27 @@ class ButtonWrapper(HwndWrapper.HwndWrapper): self._NeedsImageProp = True #----------------------------------------------------------- - def _friendly_class_name(self): + def FriendlyClassName(self): # get the least significant bit - StyleLSB = self.Style & 0xF + style_lsb = self.Style() & 0xF f_class_name = 'Button' - if StyleLSB == win32defines.BS_3STATE or \ - StyleLSB == win32defines.BS_AUTO3STATE or \ - StyleLSB == win32defines.BS_AUTOCHECKBOX or \ - StyleLSB == win32defines.BS_CHECKBOX: + if style_lsb == win32defines.BS_3STATE or \ + style_lsb == win32defines.BS_AUTO3STATE or \ + style_lsb == win32defines.BS_AUTOCHECKBOX or \ + style_lsb == win32defines.BS_CHECKBOX: f_class_name = "CheckBox" - elif StyleLSB == win32defines.BS_RADIOBUTTON or \ - StyleLSB == win32defines.BS_AUTORADIOBUTTON: + elif style_lsb == win32defines.BS_RADIOBUTTON or \ + style_lsb == win32defines.BS_AUTORADIOBUTTON: f_class_name = "RadioButton" - elif StyleLSB == win32defines.BS_GROUPBOX: + elif style_lsb == win32defines.BS_GROUPBOX: f_class_name = "GroupBox" - if self.Style & win32defines.BS_PUSHLIKE: + if self.Style() & win32defines.BS_PUSHLIKE: f_class_name = "Button" return f_class_name @@ -179,7 +173,7 @@ class ComboBoxWrapper(HwndWrapper.HwndWrapper): return self.SendMessage(win32defines.CB_GETCURSEL) #----------------------------------------------------------- - def _get_droppedrect(self): + def DroppedRect(self): "Get the dropped rectangle of the combobox" droppedRect = win32structures.RECT() @@ -189,11 +183,9 @@ class ComboBoxWrapper(HwndWrapper.HwndWrapper): ctypes.byref(droppedRect)) # we need to offset the dropped rect from the control - droppedRect -= self.Rectangle + droppedRect -= self.Rectangle() return droppedRect - DroppedRect = property(_get_droppedrect, doc = - "The dropped rectangle of the combobox") #----------------------------------------------------------- def ItemCount(self): @@ -215,13 +207,12 @@ class ComboBoxWrapper(HwndWrapper.HwndWrapper): win32defines.CB_GETLBTEXT) #----------------------------------------------------------- - def _get_texts(self): - texts = [self.Text] + def Texts(self): + "Return the text of the items in the listbox" + texts = [self.Text()] texts.extend(self.ItemTexts()) return texts - Texts = property(_get_texts, doc = "get the texts of the listbox") - #----------------------------------------------------------- def GetProperties(self): "Return the properties of the control as a dictionary" @@ -230,7 +221,7 @@ class ComboBoxWrapper(HwndWrapper.HwndWrapper): # get selected item props['SelectedItem'] = self.SelectedIndex() - props['DroppedRect'] = self.DroppedRect + props['DroppedRect'] = self.DroppedRect() props['ItemData'] = [] for i in range(self.ItemCount()): @@ -252,7 +243,7 @@ class ComboBoxWrapper(HwndWrapper.HwndWrapper): if isinstance(item, (int, long)): index = item else: - index = self.Texts.index(item) -1 + index = self.Texts().index(item) -1 # change the selected item self.SendMessageTimeout(win32defines.CB_SETCURSEL, index) @@ -263,7 +254,8 @@ class ComboBoxWrapper(HwndWrapper.HwndWrapper): # return this control so that actions can be chained. return self - + #def IsSelected(self, item): + # pass #==================================================================== class ListBoxWrapper(HwndWrapper.HwndWrapper): @@ -308,7 +300,7 @@ class ListBoxWrapper(HwndWrapper.HwndWrapper): #----------------------------------------------------------- def ItemTexts(self): - "Return the text items of the control" + "Return the text of the items of the listbox" return _get_multiple_text_items( self, win32defines.LB_GETCOUNT, @@ -316,13 +308,12 @@ class ListBoxWrapper(HwndWrapper.HwndWrapper): win32defines.LB_GETTEXT) #----------------------------------------------------------- - def _get_texts(self): - texts = [self.Text] - texts.extend(self.ItemTexts) + def Texts(self): + "Return the texts of the control" + texts = [self.Text()] + texts.extend(self.ItemTexts()) return texts - Texts = property(_get_texts, doc = "get the texts of the listbox") - #----------------------------------------------------------- def GetProperties(self): "Return the properties as a dictionary for the control" @@ -352,10 +343,10 @@ class ListBoxWrapper(HwndWrapper.HwndWrapper): if isinstance(item, (int, long)): index = item else: - index = self.Texts.index(item) + index = self.Texts().index(item) - 1 # change the selected item - self.SendMessageTimeout(win32defines.LB_SETCURSEL, index, 0) + self.SendMessageTimeout(win32defines.LB_SETCURSEL, index) # Notify the parent that we have changed self.NotifyParent(win32defines.LBN_SELCHANGE) @@ -403,43 +394,64 @@ class EditWrapper(HwndWrapper.HwndWrapper): "Initialize the control" super(EditWrapper, self).__init__(hwnd) + + #----------------------------------------------------------- def LineCount(self): "Return how many lines there are in the Edit" return self.SendMessage(win32defines.EM_GETLINECOUNT) - def _get_texts(self): + #----------------------------------------------------------- + def LineLength(self, line_index): + "Return how many characters there are in the line" + + # need to first get a character index of that line + char_index = self.SendMessage(win32defines.EM_LINEINDEX, line_index) + + # now get the length of text on that line + return self.SendMessage ( + win32defines.EM_LINELENGTH, char_index, 0) + + + #----------------------------------------------------------- + def GetLine(self, line_index): + "Return the line specified" + + text_len = self.LineLength(line_index) + # create a buffer and set the length at the start of the buffer + text = ctypes.create_unicode_buffer(text_len+1) + text[0] = unichr(text_len) + + # retrieve the line itself + self.SendMessage (win32defines.EM_GETLINE, line_index, ctypes.byref(text)) + + return text.value + + #----------------------------------------------------------- + def Texts(self): "Get the text of the edit control" - texts = [self.Text,] + texts = [self.Text(),] for i in range(0, self.LineCount()): - textLen = self.SendMessage (win32defines.EM_LINELENGTH, i, 0) - - text = ctypes.create_unicode_buffer(textLen+1) - - # set the length - which is required - text[0] = unichr(textLen) - - self.SendMessage (win32defines.EM_GETLINE, i, ctypes.byref(text)) - - texts.append(text.value) + texts.append(self.GetLine(i)) return texts - Texts = property(_get_texts, _get_texts.__doc__) + #----------------------------------------------------------- + def TextBlock(self): + "Get the text of the edit control" + + return "\n".join(self.Texts()[1:]) #----------------------------------------------------------- - def _get_selectionindices(self): - "Return the indices of the selection" + def SelectionIndices(self): + "The start and end indices of the current selection" start = ctypes.c_int() end = ctypes.c_int() self.SendMessage( win32defines.EM_GETSEL, ctypes.byref(start), ctypes.byref(end)) return (start.value, end.value) - SelectionIndices = property( - _get_selectionindices, - doc = "The start and end indices of the current selection") #----------------------------------------------------------- def GetProperties(self): @@ -490,7 +502,7 @@ class EditWrapper(HwndWrapper.HwndWrapper): if isinstance(start, basestring): string_to_select = start # - start = self.texts[1].index(string_to_select) + start = self.Texts()[1].index(string_to_select) if end is None: end = start + len(string_to_select) @@ -517,7 +529,7 @@ class StaticWrapper(HwndWrapper.HwndWrapper): super(StaticWrapper, self).__init__(hwnd) # if the control is visible - and it shows an image - if self.IsVisible and ( + if self.IsVisible() and ( self.HasStyle(win32defines.SS_ICON) or \ self.HasStyle(win32defines.SS_BITMAP) or \ self.HasStyle(win32defines.SS_CENTERIMAGE) or \ @@ -527,6 +539,7 @@ class StaticWrapper(HwndWrapper.HwndWrapper): +#==================================================================== # the main reason for this is just to make sure that # a Dialog is a known class - and we don't need to take # an image of it (as an unknown control class) @@ -542,8 +555,96 @@ class DialogWrapper(HwndWrapper.HwndWrapper): # get all teh controls controls = [self] - controls.extend(self.Children) + controls.extend(self.Children()) return tests.run_tests(controls, tests_to_run) + #----------------------------------------------------------- + def WriteToXML(self, filename): + "Write the dialog an XML file (requires elementtree)" + controls = [self] + controls.extend(self.Children()) + props = [ctrl.GetProperties() for ctrl in controls] + from pywinauto import XMLHelpers + XMLHelpers.WriteDialogToFile(filename, props) + + + + +import unittest +class EditTestCases(unittest.TestCase): + "Unit tests for the TreeViewWrapper class" + + def setUp(self): + """Start the application set some data and ensure the application + is in the state we want it.""" + + # start the application + from pywinauto.application import Application + app = Application() + + import os.path + path = os.path.split(__file__)[0] + + test_file = os.path.join(path, "test.txt") + + self.test_data = open(test_file, "r").read() + # remove the BOM if it exists + self.test_data = self.test_data.replace("\xef\xbb\xbf", "") + self.test_data = self.test_data.decode('utf-8') + + app.start_("Notepad.exe " + test_file) + + self.app = app + self.dlg = app.UntitledNotepad + self.ctrl = self.dlg.Edit.ctrl_() + + #self.dlg.MenuSelect("Styles") + + # select show selection always, and show checkboxes + #app.ControlStyles.ListBox1.TypeKeys("{HOME}{SPACE}" + "{DOWN}"* 12 + "{SPACE}") + #self.app.ControlStyles.ApplyStylesSetWindowLong.Click() + #self.app.ControlStyles.SendMessage(win32defines.WM_CLOSE) + + def tearDown(self): + "Close the application after tests" + # close the application + self.dlg.MenuSelect("File->Exit") + + + if self.app.Notepad.No.Exists(): + self.app.Notepad.No.Click() + + def testTypeKeys(self): + self.ctrl.SetText("Here is\r\nsome text") + self.assertEquals("\n".join(self.ctrl.Texts()[1:]), "Here is\nsome text") + + def testSetText(self): + # typekeys types at the current caret position (start when opening a new file) + added_text = "Here is some more Text" + self.ctrl.TypeKeys(added_text, with_spaces = True) + expected_text = added_text + self.test_data + + self.assertEquals(self.ctrl.TextBlock(), expected_text) + + def testSelect(self): + self.ctrl.Select(10, 50) + + self.assertEquals((10, 50), self.ctrl.SelectionIndices()) + + def testLineCount(self): + self.assertEquals(self.ctrl.LineCount(), self.test_data.count("\n")+1) + + def testGetLine(self): + for i, line in enumerate(self.test_data.split("\n")): + self.assertEquals(self.ctrl.GetLine(i), line) + + def testTextBlock(self): + self.assertEquals(self.ctrl.TextBlock(), self.test_data) + + +if __name__ == "__main__": + #_unittests() + + unittest.main()