pyWinAuto: c:\.projects\py_pywinauto\pywinauto\controls\common_controls.py

0001# GUI Application automation and testing library
0002# Copyright (C) 2006 Mark Mc Mahon
0003#
0004# This library is free software; you can redistribute it and/or
0005# modify it under the terms of the GNU Lesser General Public License
0006# as published by the Free Software Foundation; either version 2.1
0007# of the License, or (at your option) any later version.
0008#
0009# This library is distributed in the hope that it will be useful,
0010# but WITHOUT ANY WARRANTY; without even the implied warranty of
0011# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
0012# See the GNU Lesser General Public License for more details.
0013#
0014# You should have received a copy of the GNU Lesser General Public
0015# License along with this library; if not, write to the
0016#    Free Software Foundation, Inc.,
0017#    59 Temple Place,
0018#    Suite 330,
0019#    Boston, MA 02111-1307 USA
0020
0021"Classes that wrap the Windows Common controls"
0022
0023__revision__ = "$Revision: 607 $"
0024
0025import time
0026import ctypes
0027
0028from pywinauto import win32functions
0029from pywinauto import win32defines
0030from pywinauto import win32structures
0031from pywinauto import findbestmatch
0032import HwndWrapper
0033
0034from pywinauto.timings import Timings
0035
0036class AccessDenied(RuntimeError):
0037    "Raised when we cannot allocate memory in the control's process"
0038    pass
0039
0040
0041# Todo: I should return iterators from things like Items() and Texts()
0042#       to save building full lists all the time
0043# Todo: ListViews should be based off of GetItem, and then have actions
0044#       Applied to that e.g. ListView.Item(xxx).Select(), rather then
0045#       ListView.Select(xxx)
0046#       Or at least most of the functions should call GetItem to get the 
0047#       Item they want to work with.
0048
0049#====================================================================
0050class _RemoteMemoryBlock(object):
0051    "Class that enables reading and writing memory in a different process"
0052    #----------------------------------------------------------------
0053    def __init__(self, handle, size = 8192):
0054        "Allocatte the memory"
0055        self.memAddress = 0
0056
0057        self._as_parameter_ = self.memAddress
0058
0059        process_id = ctypes.c_long()
0060        win32functions.GetWindowThreadProcessId(
0061            handle, ctypes.byref(process_id))
0062
0063        self.process = win32functions.OpenProcess(
0064                win32defines.PROCESS_VM_OPERATION |
0065                win32defines.PROCESS_VM_READ |
0066                win32defines.PROCESS_VM_WRITE,
0067            0,
0068            process_id)
0069
0070        if not self.process:
0071            raise AccessDenied(
0072                str(ctypes.WinError()) + "process: %d",
0073                process_id.value)
0074
0075        if win32functions.GetVersion() < 2147483648L:
0076            self.memAddress = win32functions.VirtualAllocEx(
0077                self.process,   # remote process
0078                0,                              # let Valloc decide where
0079                size,                   # how much to allocate
0080                    win32defines.MEM_RESERVE |
0081                    win32defines.MEM_COMMIT,    # allocation type
0082                win32defines.PAGE_READWRITE     # protection
0083                )
0084
0085            if not self.memAddress:
0086                raise ctypes.WinError()
0087
0088        else:
0089            raise RuntimeError("Win9x allocation not supported")
0090
0091        self._as_parameter_ = self.memAddress
0092
0093
0094    #----------------------------------------------------------------
0095    def _CloseHandle(self):
0096        "Close the handle to the process."
0097        ret = win32functions.CloseHandle(self.process)
0098
0099        if not ret:
0100            raise ctypes.WinError()
0101
0102    #----------------------------------------------------------------
0103    def CleanUp(self):
0104        "Free Memory and the process handle"
0105        if self.process:
0106            # free up the memory we allocated        
0107            ret = win32functions.VirtualFreeEx(
0108                self.process, self.memAddress, 0, win32defines.MEM_RELEASE)
0109
0110            if not ret:
0111                self._CloseHandle()
0112                raise ctypes.WinError()
0113
0114            self._CloseHandle()
0115
0116
0117    #----------------------------------------------------------------
0118    def __del__(self):
0119        "Ensure that the memory is Freed"
0120        # Free the memory in the remote process's address space
0121        self.CleanUp()
0122
0123    #----------------------------------------------------------------
0124    def Address(self):
0125        "Return the address of the memory block"
0126        return self.memAddress
0127
0128    #----------------------------------------------------------------
0129    def Write(self, data):
0130        "Write data into the memory block"
0131        # write the data from this process into the memory allocated
0132        # from the other process
0133        ret = win32functions.WriteProcessMemory(
0134            self.process,
0135            self.memAddress,
0136            ctypes.pointer(data),
0137            ctypes.sizeof(data),
0138            0);
0139
0140        if not ret:
0141            raise ctypes.WinError()
0142
0143    #----------------------------------------------------------------
0144    def Read(self, data, address = None):
0145        "Read data from the memory block"
0146        if not address:
0147            address = self.memAddress
0148
0149        ret = win32functions.ReadProcessMemory(
0150            self.process, address, ctypes.byref(data), ctypes.sizeof(data), 0)
0151
0152        # disabled as it often returns an error - but
0153        # seems to work fine anyway!!
0154        if not ret:
0155            raise ctypes.WinError()
0156
0157        return data
0158
0159
0160
0161
0162#====================================================================
0163class ListViewWrapper(HwndWrapper.HwndWrapper):
0164    """Class that wraps Windows ListView common control
0165
0166    This class derives from HwndWrapper - so has all the methods o
0167    that class also
0168
0169    **see** HwndWrapper.HwndWrapper_
0170
0171    .. _HwndWrapper.HwndWrapper: class-pywinauto.controls.HwndWrapper.HwndWrapper.html
0172
0173    """
0174
0175    friendlyclassname = "ListView"
0176    windowclasses = ["SysListView32", r"WindowsForms\d*\.SysListView32\..*", ]
0177
0178    #----------------------------------------------------------------
0179    def __init__(self, hwnd):
0180        "Initialise the instance"
0181        super(ListViewWrapper, self).__init__(hwnd)
0182
0183        self.writable_props.extend([
0184            'ColumnCount',
0185            'ItemCount',
0186            'Columns',
0187            'Items'])
0188
0189    #-----------------------------------------------------------
0190    def ColumnCount(self):
0191        """Return the number of columns"""
0192        return self.GetHeaderControl().ItemCount()
0193
0194    #-----------------------------------------------------------
0195    def ItemCount(self):
0196        "The number of items in the ListView"
0197        return self.SendMessage(win32defines.LVM_GETITEMCOUNT)
0198
0199    #-----------------------------------------------------------
0200    def GetHeaderControl(self):
0201        "Returns the Header control associated with the ListView"
0202        #from wraphandle import WrapHandle
0203        #from HwndWrapper import WrapHandle
0204        return HwndWrapper.HwndWrapper(
0205            self.SendMessage(win32defines.LVM_GETHEADER))
0206
0207    #-----------------------------------------------------------
0208    def GetColumn(self, col_index):
0209        "Get the information for a column of the ListView"
0210
0211        col_props = {}
0212
0213        col = win32structures.LVCOLUMNW()
0214        col.mask =               win32defines.LVCF_FMT |               win32defines.LVCF_IMAGE |               win32defines.LVCF_ORDER |               win32defines.LVCF_SUBITEM |               win32defines.LVCF_TEXT |               win32defines.LVCF_WIDTH
0221
0222        remote_mem = _RemoteMemoryBlock(self)
0223
0224        col.cchTextMax = 2000
0225        col.item = remote_mem.Address() + ctypes.sizeof(col) + 1
0226
0227        # put the information in the memory that the
0228        # other process can read/write
0229        remote_mem.Write(col)
0230
0231        # ask the other process to update the information
0232        retval = self.SendMessage(
0233            win32defines.LVM_GETCOLUMNW,
0234            col_index,
0235            remote_mem)
0236
0237        col = remote_mem.Read(col)
0238
0239        # if that succeeded then there was a column
0240        if retval:
0241            col = remote_mem.Read(col)
0242
0243            text = ctypes.create_unicode_buffer(1999)
0244            remote_mem.Read(text, col.pszText)
0245
0246            col_props['order'] = col.iOrder
0247            col_props['text'] = text.value
0248            col_props['format'] = col.fmt
0249            col_props['width'] = col.cx
0250            col_props['image'] = col.iImage
0251            col_props['subitem'] = col.iSubItem
0252
0253        del remote_mem
0254
0255        return col_props
0256
0257    #-----------------------------------------------------------
0258    def Columns(self):
0259        "Get the information on the columns of the ListView"
0260        cols = []
0261
0262        for i in range(0,  self.ColumnCount()):
0263            cols.append(self.GetColumn(i))
0264
0265        return cols
0266
0267    #-----------------------------------------------------------
0268    def ColumnWidths(self):
0269        "Return a list of all the column widths"
0270        return [col['width'] for col in self.Columns()]
0271
0272    #-----------------------------------------------------------
0273    def GetItemRect(self, item_index):
0274        "Return the bounding rectangle of the list view item"
0275        # set up a memory block in the remote application
0276        remote_mem = _RemoteMemoryBlock(self)
0277        rect = win32structures.RECT()
0278
0279        rect.left = win32defines.LVIR_SELECTBOUNDS
0280
0281        # Write the local RECT structure to the remote memory block
0282        remote_mem.Write(rect)
0283
0284        # Fill in the requested item
0285        retval = self.SendMessage(
0286            win32defines.LVM_GETITEMRECT,
0287            item_index,
0288            remote_mem)
0289
0290        # if it succeeded
0291        if not retval:
0292                del remote_mem
0293                raise RuntimeError("Did not succeed in getting rectangle")
0294
0295        rect = remote_mem.Read(rect)
0296
0297        del remote_mem
0298
0299        return rect
0300
0301    #-----------------------------------------------------------
0302    def _as_item_index(self, item):
0303        """Ensure that item is an item index
0304        
0305        If a string is passed in then it will be searched for in the 
0306        list of item titles.
0307        """
0308        index = item
0309        if isinstance(item, basestring):
0310            index = (self.Texts().index(item) - 1) / self.ColumnCount()
0311
0312        return index
0313
0314    #-----------------------------------------------------------
0315    def GetItem(self, item_index, subitem_index = 0):
0316        """Return the item of the list view"
0317         
0318        * **item_index** Can be either the index of the item or a string
0319          with the text of the item you want returned.
0320        * **subitem_index** The 0 based index of the item you want returned.
0321          Defaults to 0.
0322        """
0323
0324        item_data = {}
0325
0326        # ensure the item_index is an integer or 
0327        # convert it to one
0328        item_index = self._as_item_index(item_index)
0329
0330        # set up a memory block in the remote application
0331        remote_mem = _RemoteMemoryBlock(self)
0332
0333        # set up the item structure to get the text
0334        item = win32structures.LVITEMW()
0335        item.mask =               win32defines.LVIF_TEXT |               win32defines.LVIF_IMAGE |               win32defines.LVIF_INDENT |               win32defines.LVIF_STATE
0340
0341        item.iItem = item_index
0342        item.iSubItem = subitem_index
0343        item.stateMask = ctypes.c_uint(-1)
0344
0345        item.cchTextMax = 2000
0346        item.pszText = remote_mem.Address() +               ctypes.sizeof(item) + 1
0348
0349        # Write the local LVITEM structure to the remote memory block
0350        remote_mem.Write(item)
0351
0352        # Fill in the requested item
0353        retval = self.SendMessage(
0354            win32defines.LVM_GETITEMW,
0355            item_index,
0356            remote_mem)
0357
0358        # if it succeeded
0359        if retval:
0360
0361            remote_mem.Read(item)
0362
0363            # Read the remote text string
0364            char_data = ctypes.create_unicode_buffer(2000)
0365            remote_mem.Read(char_data, item.pszText)
0366
0367            # and add it to the titles
0368            item_data['text'] = char_data.value
0369            item_data['state'] = item.state
0370            item_data['image'] = item.iImage
0371            item_data['indent'] = item.iIndent
0372
0373        else:
0374            raise RuntimeError(
0375                "We should never get to this part of ListView.GetItem()")
0376
0377        del remote_mem
0378
0379        return item_data
0380
0381    #-----------------------------------------------------------
0382    def Items(self):
0383        "Get all the items in the list view"
0384        colcount = self.ColumnCount()
0385
0386        if not colcount:
0387            colcount = 1
0388
0389        items = []
0390        # now get the item values...
0391        # for each of the rows
0392        for item_index in range(0, self.ItemCount()):
0393
0394            # and each of the columns for that row
0395            for subitem_index in range(0, colcount):
0396
0397                # get the item
0398                items.append(self.GetItem(item_index, subitem_index))
0399
0400        return items
0401
0402    #-----------------------------------------------------------
0403    def Texts(self):
0404        "Get the texts for the ListView control"
0405        texts = [self.WindowText()]
0406        texts.extend([item['text'] for item in self.Items()])
0407        return texts
0408
0409    #-----------------------------------------------------------
0410    def UnCheck(self, item):
0411        "Uncheck the ListView item"
0412
0413        self.VerifyActionable()
0414
0415        # ensure the item is an integer or 
0416        # convert it to one
0417        item = self._as_item_index(item)
0418
0419        lvitem = win32structures.LVITEMW()
0420
0421        lvitem.mask = win32defines.LVIF_STATE
0422        lvitem.state = 0x1000
0423        lvitem.stateMask = win32defines.LVIS_STATEIMAGEMASK
0424
0425        remote_mem = _RemoteMemoryBlock(self)
0426        remote_mem.Write(lvitem)
0427
0428        self.SendMessageTimeout(
0429            win32defines.LVM_SETITEMSTATE, item, remote_mem)
0430
0431        win32functions.WaitGuiThreadIdle(self)
0432        time.sleep(Timings.after_listviewcheck_wait)
0433
0434        del remote_mem
0435
0436    #-----------------------------------------------------------
0437    def Check(self, item):
0438        "Check the ListView item"
0439
0440        self.VerifyActionable()
0441
0442        # ensure the item is an integer or 
0443        # convert it to one
0444        item = self._as_item_index(item)
0445
0446        lvitem = win32structures.LVITEMW()
0447
0448        lvitem.mask = win32defines.LVIF_STATE
0449        lvitem.state = 0x2000
0450        lvitem.stateMask = win32defines.LVIS_STATEIMAGEMASK
0451
0452        remote_mem = _RemoteMemoryBlock(self)
0453        remote_mem.Write(lvitem)
0454
0455        self.SendMessageTimeout(
0456            win32defines.LVM_SETITEMSTATE, item, remote_mem)
0457
0458        win32functions.WaitGuiThreadIdle(self)
0459        time.sleep(Timings.after_listviewcheck_wait)
0460
0461        del remote_mem
0462
0463    #-----------------------------------------------------------
0464    def IsChecked(self, item):
0465        "Return whether the ListView item is checked or not"
0466
0467        # ensure the item is an integer or 
0468        # convert it to one
0469        item = self._as_item_index(item)
0470
0471        state = self.SendMessage(
0472            win32defines.LVM_GETITEMSTATE,
0473            item,
0474            win32defines.LVIS_STATEIMAGEMASK)
0475
0476        return state & 0x2000 == 0x2000
0477
0478    #-----------------------------------------------------------
0479    def IsSelected(self, item):
0480        "Return True if the item is selected"
0481
0482        # ensure the item is an integer or 
0483        # convert it to one
0484        item = self._as_item_index(item)
0485
0486        return win32defines.LVIS_SELECTED == self.SendMessage(
0487            win32defines.LVM_GETITEMSTATE, item, win32defines.LVIS_SELECTED)
0488
0489    #-----------------------------------------------------------
0490    def IsFocused(self, item):
0491        "Return True if the item has the focus"
0492
0493        # ensure the item is an integer or 
0494        # convert it to one
0495        item = self._as_item_index(item)
0496
0497        return win32defines.LVIS_FOCUSED == self.SendMessage(
0498            win32defines.LVM_GETITEMSTATE, item, win32defines.LVIS_FOCUSED)
0499
0500    #-----------------------------------------------------------
0501    def _modify_selection(self, item, to_select):
0502        """Change the selection of the item
0503
0504        item is the item you want to chagne
0505        to_slect shoudl be tru to select the item and false
0506        to deselect the item
0507        """
0508
0509        self.VerifyActionable()
0510
0511        # ensure the item is an integer or 
0512        # convert it to one
0513        item = self._as_item_index(item)
0514
0515        if item >= self.ItemCount():
0516            raise IndexError("There are only %d items in the list view not %d"%
0517                (self.ItemCount(), item + 1))
0518
0519        # first we need to change the state of the item
0520        lvitem = win32structures.LVITEMW()
0521        lvitem.mask = win32defines.LVIF_STATE
0522
0523        if to_select:
0524            lvitem.state = win32defines.LVIS_SELECTED
0525
0526        lvitem.stateMask = win32defines.LVIS_SELECTED
0527
0528        remote_mem = _RemoteMemoryBlock(self)
0529        remote_mem.Write(lvitem)
0530
0531        self.SendMessageTimeout(
0532            win32defines.LVM_SETITEMSTATE, item, remote_mem)
0533
0534        # now we need to notify the parent that the state has chnaged
0535        nmlv = win32structures.NMLISTVIEW()
0536        nmlv.hdr.hwndFrom = self.handle
0537        nmlv.hdr.idFrom = self.ControlID()
0538        nmlv.hdr.code = win32defines.LVN_ITEMCHANGING
0539
0540        nmlv.iItem = item
0541        #nmlv.iSubItem = 0
0542        nmlv.uNewState = win32defines.LVIS_SELECTED
0543        #nmlv.uOldState = 0
0544        nmlv.uChanged = win32defines.LVIS_SELECTED
0545        nmlv.ptAction = win32structures.POINT()
0546
0547        remote_mem.Write(nmlv)
0548
0549        self.Parent().SendMessageTimeout(
0550            win32defines.WM_NOTIFY,
0551            self.ControlID(),
0552            remote_mem)
0553
0554        del remote_mem
0555
0556        win32functions.WaitGuiThreadIdle(self)
0557        time.sleep(Timings.after_listviewselect_wait)
0558
0559    #-----------------------------------------------------------
0560    def Select(self, item):
0561        """Mark the item as selected
0562
0563        The ListView control must be enabled and visible before an
0564        Item can be selected otherwise an exception is raised"""
0565        self._modify_selection(item, True)
0566
0567    #-----------------------------------------------------------
0568    def Deselect(self, item):
0569        """Mark the item as not selected
0570
0571        The ListView control must be enabled and visible before an
0572        Item can be selected otherwise an exception is raised"""
0573        self._modify_selection(item, False)
0574
0575