pyWinAuto: c:\.projects\py_pywinauto\pywinauto\controls\HwndWrapper.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"Basic wrapping of Windows controls"
0022
0023__revision__ = "$Revision: 607 $"
0024
0025# pylint:  disable-msg=W0611
0026
0027import time
0028import re
0029import ctypes
0030
0031# the wrappers may be used in an environment that does not need
0032# the actions - as such I don't want to require sendkeys - so
0033# the following makes the import optional.
0034try:
0035    import SendKeys
0036except ImportError:
0037    pass
0038
0039# I leave this optional because PIL is a large dependency
0040try:
0041    import PIL.ImageGrab
0042except ImportError:
0043    pass
0044
0045from pywinauto import win32defines
0046from pywinauto import win32functions
0047from pywinauto import win32structures
0048from pywinauto.timings import Timings
0049
0050#from pywinauto import findbestmatch
0051from pywinauto import handleprops
0052
0053# also import MenuItemNotEnabled so that it is
0054# accessible from HwndWrapper module
0055from menuwrapper import Menu #, MenuItemNotEnabled
0056
0057#====================================================================
0058class ControlNotEnabled(RuntimeError):
0059    "Raised when a control is not enabled"
0060    pass
0061
0062#====================================================================
0063class ControlNotVisible(RuntimeError):
0064    "Raised when a control is nto visible"
0065    pass
0066
0067#====================================================================
0068class InvalidWindowHandle(RuntimeError):
0069    "Raised when an invalid handle is passed to HwndWrapper "
0070    def __init__(self, hwnd):
0071        "Initialise the RuntimError parent with the mesage"
0072        RuntimeError.__init__(self,
0073            "Handle 0x%d is not a vaild window handle"% hwnd)
0074
0075
0076
0077
0078# metaclass that will know about
0079class _MetaWrapper(type):
0080    "Metaclass for Wrapper objects"
0081    re_wrappers = {}
0082    str_wrappers = {}
0083
0084    def __init__(cls, name, bases, attrs):
0085        # register the class names, both the regular expression
0086        # or the classes directly
0087
0088        #print "metaclass __init__", cls
0089        type.__init__(cls, name, bases, attrs)
0090
0091        for win_class in cls.windowclasses:
0092            _MetaWrapper.re_wrappers[re.compile(win_class)] = cls
0093            _MetaWrapper.str_wrappers[win_class] = cls
0094
0095    def FindWrapper(handle):
0096        """Find the correct wrapper for this handle"""
0097        class_name = handleprops.classname(handle)
0098
0099
0100        try:
0101            return _MetaWrapper.str_wrappers[class_name]
0102        except KeyError:
0103            wrapper_match = None
0104
0105            for regex, wrapper in _MetaWrapper.re_wrappers.items():
0106                if regex.match(class_name):
0107                    wrapper_match = wrapper
0108                    _MetaWrapper.str_wrappers[class_name] = wrapper
0109
0110                    break
0111
0112        # if it is a dialog then override the wrapper we found
0113        # and make it a DialogWrapper
0114        if handleprops.is_toplevel_window(handle):
0115            import win32_controls
0116            wrapper_match = win32_controls.DialogWrapper
0117
0118        if wrapper_match is None:
0119            wrapper_match = HwndWrapper
0120        return wrapper_match
0121
0122
0123        #if handle in meta.wrappers:
0124        #    return meta.wrappers[handle]
0125    FindWrapper = staticmethod(FindWrapper)
0126
0127
0128#====================================================================
0129class HwndWrapper(object):
0130    """Default wrapper for controls.
0131
0132    All other wrappers are derived from this.
0133
0134    This class wraps a lot of functionality of underlying windows API
0135    features for working with windows.
0136
0137    Most of the methods apply to every single window type. For example
0138    you can Click() on any window.
0139
0140    Most of the methods of this class are simple wrappers around
0141    API calls and as such they try do the simplest thing possible.
0142
0143    A HwndWrapper object can be passed directly to a ctypes wrapped
0144    C function - and it will get converted to a Long with the value of
0145    it's handle (see ctypes, _as_parameter_)"""
0146
0147    __metaclass__ = _MetaWrapper
0148
0149    friendlyclassname = None
0150    windowclasses = []
0151    handle = None
0152
0153    #-----------------------------------------------------------
0154    def __new__(cls, handle):
0155        new_class = cls.FindWrapper(handle)
0156        #super(currentclass, cls).__new__(cls[, ...])"
0157
0158        obj = object.__new__(new_class)
0159        obj.__init__(handle)
0160        return obj
0161
0162
0163    #-----------------------------------------------------------
0164    def __init__(self, hwnd):
0165        """Initialize the control
0166
0167        * **hwnd** is either a valid window handle or it can be an
0168          instance or subclass of HwndWrapper.
0169
0170        If the handle is not valid then an InvalidWindowHandle error
0171        is raised.
0172        """
0173
0174        # handle if hwnd is actually a HwndWrapper
0175        try:
0176            self.handle = hwnd.handle
0177        except AttributeError:
0178            self.handle = hwnd
0179
0180        # verify that we have been passed in a valid windows handle
0181        if not win32functions.IsWindow(hwnd):
0182            raise InvalidWindowHandle(hwnd)
0183
0184        # make it so that ctypes conversion happens correctly
0185        self._as_parameter_ = self.handle
0186
0187        #win32functions.WaitGuiThreadIdle(self)
0188
0189        # specify whether we need to grab an image of ourselves
0190        # when asked for properties
0191        self._NeedsImageProp = False
0192
0193        # default to not having a reference control added
0194        self.ref = None
0195
0196        self.appdata = None
0197
0198        self._cache = {}
0199
0200        # build the list of default properties to be written
0201        # Derived classes can either modify this list or override
0202        # GetProperties depending on how much control they need.
0203        self.writable_props = [
0204            'Class',
0205            'FriendlyClassName',
0206            'Texts',
0207            'Style',
0208            'ExStyle',
0209            'ControlID',
0210            'UserData',
0211            'ContextHelpID',
0212            'Fonts',
0213            'ClientRects',
0214            'Rectangle',
0215            'IsVisible',
0216            'IsUnicode',
0217            'IsEnabled',
0218            'MenuItems',
0219            'ControlCount',
0220            ]
0221
0222
0223    #-----------------------------------------------------------
0224    def FriendlyClassName(self):
0225        """Return the friendly class name for the control
0226
0227        This differs from the class of the control in some cases.
0228        Class() is the actual 'Registered' window class of the control
0229        while FriendlyClassName() is hopefully something that will make
0230        more sense to the user.
0231
0232        For example Checkboxes are implemented as Buttons - so the class
0233        of a CheckBox is "Button" - but the friendly class is "CheckBox"
0234        """
0235        if self.friendlyclassname is None:
0236            self.friendlyclassname = handleprops.classname(self)
0237        return self.friendlyclassname
0238
0239    #-----------------------------------------------------------
0240    def Class(self):
0241        """Return the class name of the window"""
0242        if not self._cache.has_key("class"):
0243            self._cache['class'] = handleprops.classname(self)
0244        return self._cache['class']
0245
0246    #-----------------------------------------------------------
0247    def WindowText(self):
0248        """Window text of the control
0249
0250        Quite  a few contorls have other text that is visible, for example
0251        Edit controls usually have an empty string for WindowText but still
0252        have text displayed in the edit window.
0253        """
0254        return handleprops.text(self)
0255
0256    #-----------------------------------------------------------
0257    def Style(self):
0258        """Returns the style of window
0259
0260        Return value is a long.
0261
0262        Combination of WS_* and specific control specific styles.
0263        See HwndWrapper.HasStyle() to easily check if the window has a
0264        particular style.
0265        """
0266        return handleprops.style(self)
0267
0268    #-----------------------------------------------------------
0269    def ExStyle(self):
0270        """Returns the Extended style of window
0271
0272        Return value is a long.
0273
0274        Combination of WS_* and specific control specific styles.
0275        See HwndWrapper.HasStyle() to easily check if the window has a
0276        particular style.
0277        """
0278        return handleprops.exstyle(self)
0279
0280    #-----------------------------------------------------------
0281    def ControlID(self):
0282        """Return the ID of the window
0283
0284        Only controls have a valid ID - dialogs usually have no ID assigned.
0285
0286        The ID usually identified the control in the window - but there can
0287        be duplicate ID's for example lables in a dialog may have duplicate
0288        ID's.
0289        """
0290        return handleprops.controlid(self)
0291
0292    #-----------------------------------------------------------
0293    def UserData(self):
0294        """Extra data associted with the window
0295
0296        This value is a long value that has been associated with the window
0297        and rarely has useful data (or at least data that you know the use
0298        of).
0299        """
0300        return handleprops.userdata(self)
0301
0302    #-----------------------------------------------------------
0303    def ContextHelpID(self):
0304        "Return the Context Help ID of the window"
0305        return handleprops.contexthelpid(self)
0306
0307    #-----------------------------------------------------------
0308    def IsUnicode(self):
0309        """Whether the window is unicode or not
0310
0311        A window is Unicode if it was registered by the Wide char version
0312        of RegisterClass(Ex).
0313        """
0314        return handleprops.isunicode(self)
0315
0316    #-----------------------------------------------------------
0317    def IsVisible(self):
0318        """Whether the window is visible or not
0319
0320        Checks that both the Top Level Parent (probably dialog) that
0321        owns this window and the window itself are both visible.
0322
0323        If you want to wait for a control to become visible (or wait
0324        for it to become hidden) use ``Application.Wait('visible')`` or
0325        ``Application.WaitNot('visible')``.
0326
0327        If you want to raise an exception immediately if a window is
0328        not visible then you can use the HwndWrapper.VerifyVisible().
0329        HwndWrapper.VerifyActionable() raises if the window is not both
0330        visible and enabled.
0331        """
0332
0333        return handleprops.isvisible(self.TopLevelParent()) and               handleprops.isvisible(self)
0335
0336    #-----------------------------------------------------------
0337    def IsEnabled(self):
0338        """Whether the window is enabled or not
0339
0340        Checks that both the Top Level Parent (probably dialog) that
0341        owns this window and the window itself are both enabled.
0342
0343        If you want to wait for a control to become enabled (or wait
0344        for it to become disabled) use ``Application.Wait('visible')`` or
0345        ``Application.WaitNot('visible')``.
0346
0347        If you want to raise an exception immediately if a window is
0348        not enabled then you can use the HwndWrapper.VerifyEnabled().
0349        HwndWrapper.VerifyReady() raises if the window is not both
0350        visible and enabled.
0351        """
0352        return handleprops.isenabled(self.TopLevelParent()) and               handleprops.isenabled(self)
0354
0355    #-----------------------------------------------------------
0356    def Rectangle(self):
0357        """Return the rectangle of window
0358
0359        The rectangle is the rectangle of the control on the screen,
0360        coordinates are given from the top left of the screen.
0361
0362        This method returns a RECT structure, Which has attributes - top,
0363        left, right, bottom. and has methods width() and height().
0364        See win32structures.RECT for more information.
0365        """
0366        return handleprops.rectangle(self)
0367
0368    #-----------------------------------------------------------
0369    def ClientRect(self):
0370        """Returns the client rectangle of window
0371
0372        The client rectangle is the window rectangle minus any borders that
0373        are not available to the control for drawing.
0374
0375        Both top and left are always 0 for this method.
0376
0377        This method returns a RECT structure, Which has attributes - top,
0378        left, right, bottom. and has methods width() and height().
0379        See win32structures.RECT for more information.
0380        """
0381        return handleprops.clientrect(self)
0382
0383    #-----------------------------------------------------------
0384    def Font(self):
0385        """Return the font of the window
0386
0387        The font of the window is used to draw the text of that window.
0388        It is a structure which has attributes for Font name, height, width
0389        etc.
0390
0391        See win32structures.LOGFONTW for more information.
0392        """
0393        return handleprops.font(self)
0394
0395    #-----------------------------------------------------------
0396    def ProcessID(self):
0397        """Return the ID of process that owns this window"""
0398        return handleprops.processid(self)
0399
0400    #-----------------------------------------------------------
0401    def HasStyle(self, style):
0402        "Return True if the control has the specified sytle"
0403        return handleprops.has_style(self, style)
0404
0405    #-----------------------------------------------------------
0406    def HasExStyle(self, exstyle):
0407        "Return True if the control has the specified extended sytle"
0408        return handleprops.has_exstyle(self, exstyle)
0409
0410    #-----------------------------------------------------------
0411    def IsDialog(self):
0412        "Return true if the control is a top level window"
0413
0414        if not self._cache.has_key("isdialog"):
0415            self._cache['isdialog'] = handleprops.is_toplevel_window(self)
0416
0417        return self._cache['isdialog']
0418
0419    #-----------------------------------------------------------
0420    def Parent(self):
0421        """Return the parent of this control
0422
0423        Note that the parent of a control is not necesarily a dialog or
0424        other main window. A group box may be the parent of some radio
0425        buttons for example.
0426
0427        To get the main (or top level) window then use
0428        HwndWrapper.TopLevelParent().
0429        """
0430
0431        if not self._cache.has_key("parent"):
0432
0433            parent_hwnd = handleprops.parent(self)
0434
0435            if parent_hwnd:
0436                #return WrapHandle(parent_hwnd)
0437
0438                self._cache["parent"] = HwndWrapper(parent_hwnd)
0439            else:
0440                self._cache["parent"] = None
0441
0442        return self._cache["parent"]
0443
0444    #-----------------------------------------------------------
0445    def TopLevelParent(self):
0446        """Return the top level window of this control
0447
0448        The TopLevel parent is different from the parent in that the Parent
0449        is the window that owns this window - but it may not be a dialog/main
0450        window. For example most Comboboxes have an Edit. The ComboBox is the
0451        parent of the Edit control.
0452
0453        This will always return a valid window handle (if the control has
0454        no top level parent then the control itself is returned - as it is
0455        a top level window already!)
0456        """
0457
0458        if not self._cache.has_key("top_level_parent"):
0459
0460            parent = self.Parent()
0461
0462            if self.IsDialog():
0463                self._cache["top_level_parent"] = self
0464                #return self
0465
0466            elif not parent:
0467                self._cache["top_level_parent"] = self
0468                #return self
0469
0470            elif not parent.IsDialog():
0471                self._cache["top_level_parent"] = parent.TopLevelParent()
0472                #return parent.TopLevelParent()
0473            else:
0474                self._cache["top_level_parent"] = parent
0475                #return parent
0476
0477        return self._cache["top_level_parent"]
0478
0479    #-----------------------------------------------------------
0480    def Texts(self):
0481        """Return the text for each item of this control"
0482
0483        It is a list of strings for the control. It is frequently over-ridden
0484        to extract all strings from a control with multiple items.
0485
0486        It is always a list with one or more strings:
0487
0488          * First elemtent is the window text of the control
0489          * Subsequent elements contain the text of any items of the
0490            control (e.g. items in a listbox/combobox, tabs in a tabcontrol)
0491        """
0492        texts = [self.WindowText(), ]
0493        return texts
0494
0495    #-----------------------------------------------------------
0496    def ClientRects(self):
0497        """Return the client rect for each item in this control
0498
0499        It is a list of rectangles for the control. It is frequently over-ridden
0500        to extract all rectangles from a control with multiple items.
0501
0502        It is always a list with one or more rectangles:
0503
0504          * First elemtent is the client rectangle of the control
0505          * Subsequent elements contain the client rectangle of any items of
0506            the control (e.g. items in a listbox/combobox, tabs in a
0507            tabcontrol)
0508        """
0509
0510        return [self.ClientRect(), ]
0511
0512    #-----------------------------------------------------------
0513    def Fonts(self):
0514        """Return the font for each item in this control
0515
0516        It is a list of fonts for the control. It is frequently over-ridden
0517        to extract all fonts from a control with multiple items.
0518
0519        It is always a list with one or more fonts:
0520
0521          * First elemtent is the control font
0522          * Subsequent elements contain the font of any items of
0523            the control (e.g. items in a listbox/combobox, tabs in a
0524            tabcontrol)
0525        """
0526        return [self.Font(), ]
0527
0528    #-----------------------------------------------------------
0529    def Children(self):
0530        """Return the children of this control as a list
0531
0532        It returns a list of HwndWrapper (or subclass) instances, it
0533        returns an empty list if there are no children.
0534        """
0535
0536        child_windows = handleprops.children(self)
0537        return [HwndWrapper(hwnd) for hwnd in child_windows]
0538
0539    #-----------------------------------------------------------
0540    def ControlCount(self):
0541        "Return the number of children of this control"
0542
0543        return len(handleprops.children(self))
0544
0545    #-----------------------------------------------------------
0546    def IsChild(self, parent):
0547        """Return True if this window is a child of 'parent'.
0548
0549        A window is a child of another window when it is a direct of the
0550        other window. A window is a direct descendant of a given
0551        window if the parent window is the the chain of parent windows
0552        for the child window.
0553        """
0554
0555        # Call the IsChild API funciton and convert the result
0556        # to True/False
0557        return win32functions.IsChild(parent, self.handle) != 0
0558
0559    #-----------------------------------------------------------
0560    def SendMessage(self, message, wparam = 0 , lparam = 0):
0561        "Send a message to the control and wait for it to return"
0562        return win32functions.SendMessage(self, message, wparam, lparam)
0563
0564        #result = ctypes.c_long()
0565        #ret = win32functions.SendMessageTimeout(self, message, wparam, lparam,
0566        #    win32defines.SMTO_NORMAL, 400, ctypes.byref(result))
0567
0568        #return result.value
0569
0570
0571    #-----------------------------------------------------------
0572    def SendMessageTimeout(
0573        self,
0574        message,
0575        wparam = 0 ,
0576        lparam = 0,
0577        timeout = None,
0578        timeoutflags = win32defines.SMTO_NORMAL):
0579        """Send a message to the control and wait for it to return or to timeout
0580
0581        If no timeout is given then a default timeout of .4 of a second will
0582        be used.
0583        """
0584
0585        if timeout is None:
0586            timeout = Timings.sendmessagetimeout_timeout
0587
0588        result = ctypes.c_long()
0589        win32functions.SendMessageTimeout(self,
0590            message, wparam, lparam,
0591            timeoutflags, int(timeout * 1000),
0592            ctypes.byref(result))
0593
0594        return result.value
0595
0596
0597    #-----------------------------------------------------------
0598    def PostMessage(self, message, wparam = 0 , lparam = 0):
0599        "Post a message to the control message queue and return"
0600        return win32functions.PostMessage(self, message, wparam, lparam)
0601
0602        #result = ctypes.c_long()
0603        #ret = win32functions.SendMessageTimeout(self, message, wparam, lparam,
0604        #    win32defines.SMTO_NORMAL, 400, ctypes.byref(result))
0605
0606        #return result.value
0607
0608
0609#    #-----------------------------------------------------------
0610#    def NotifyMenuSelect(self, menu_id):
0611#        """Notify the dialog that one of it's menu items was selected
0612#
0613#        **This method is Deprecated**
0614#        """
0615#
0616#        import warnings
0617#        warning_msg = "HwndWrapper.NotifyMenuSelect() is deprecated - " \
0618#            "equivalent functionality is being moved to the MenuWrapper class."
0619#        warnings.warn(warning_msg, DeprecationWarning)
0620#
0621#        self.SetFocus()
0622#
0623#        msg = win32defines.WM_COMMAND
0624#        return self.SendMessageTimeout(
0625#            msg