/pywinauto/controls/HwndWrapper.py

https://bitbucket.org/adrianer/pywinauto · Python · 1720 lines · 1383 code · 129 blank · 208 comment · 34 complexity · 6cd38a73460f82ece9166c4d2ef85735 MD5 · raw file

Large files are truncated click here to view the full file

  1. # GUI Application automation and testing library
  2. # Copyright (C) 2006 Mark Mc Mahon
  3. #
  4. # This library is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU Lesser General Public License
  6. # as published by the Free Software Foundation; either version 2.1
  7. # of the License, or (at your option) any later version.
  8. #
  9. # This library is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  12. # See the GNU Lesser General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Lesser General Public
  15. # License along with this library; if not, write to the
  16. # Free Software Foundation, Inc.,
  17. # 59 Temple Place,
  18. # Suite 330,
  19. # Boston, MA 02111-1307 USA
  20. "Basic wrapping of Windows controls"
  21. __revision__ = "$Revision$"
  22. # pylint: disable-msg=W0611
  23. import time
  24. import re
  25. import ctypes
  26. # the wrappers may be used in an environment that does not need
  27. # the actions - as such I don't want to require sendkeys - so
  28. # the following makes the import optional.
  29. from pywinauto import SendKeysCtypes as SendKeys
  30. # I leave this optional because PIL is a large dependency
  31. try:
  32. import ImageGrab
  33. except ImportError:
  34. ImageGrab = None
  35. from pywinauto import win32defines
  36. from pywinauto import win32functions
  37. from pywinauto import win32structures
  38. from pywinauto.timings import Timings
  39. from pywinauto import timings
  40. #from pywinauto import findbestmatch
  41. from pywinauto import handleprops
  42. # also import MenuItemNotEnabled so that it is
  43. # accessible from HwndWrapper module
  44. from menuwrapper import Menu #, MenuItemNotEnabled
  45. #====================================================================
  46. class ControlNotEnabled(RuntimeError):
  47. "Raised when a control is not enabled"
  48. pass
  49. #====================================================================
  50. class ControlNotVisible(RuntimeError):
  51. "Raised when a control is nto visible"
  52. pass
  53. #====================================================================
  54. class InvalidWindowHandle(RuntimeError):
  55. "Raised when an invalid handle is passed to HwndWrapper "
  56. def __init__(self, hwnd):
  57. "Initialise the RuntimError parent with the mesage"
  58. RuntimeError.__init__(self,
  59. "Handle 0x%d is not a vaild window handle"% hwnd)
  60. # metaclass that will know about
  61. class _MetaWrapper(type):
  62. "Metaclass for Wrapper objects"
  63. re_wrappers = {}
  64. str_wrappers = {}
  65. def __init__(cls, name, bases, attrs):
  66. # register the class names, both the regular expression
  67. # or the classes directly
  68. #print "metaclass __init__", cls
  69. type.__init__(cls, name, bases, attrs)
  70. for win_class in cls.windowclasses:
  71. _MetaWrapper.re_wrappers[re.compile(win_class)] = cls
  72. _MetaWrapper.str_wrappers[win_class] = cls
  73. def FindWrapper(handle):
  74. """Find the correct wrapper for this handle"""
  75. class_name = handleprops.classname(handle)
  76. try:
  77. return _MetaWrapper.str_wrappers[class_name]
  78. except KeyError:
  79. wrapper_match = None
  80. for regex, wrapper in _MetaWrapper.re_wrappers.items():
  81. if regex.match(class_name):
  82. wrapper_match = wrapper
  83. _MetaWrapper.str_wrappers[class_name] = wrapper
  84. break
  85. # if it is a dialog then override the wrapper we found
  86. # and make it a DialogWrapper
  87. if handleprops.is_toplevel_window(handle):
  88. import win32_controls
  89. wrapper_match = win32_controls.DialogWrapper
  90. if wrapper_match is None:
  91. wrapper_match = HwndWrapper
  92. return wrapper_match
  93. #if handle in meta.wrappers:
  94. # return meta.wrappers[handle]
  95. FindWrapper = staticmethod(FindWrapper)
  96. #====================================================================
  97. class HwndWrapper(object):
  98. """Default wrapper for controls.
  99. All other wrappers are derived from this.
  100. This class wraps a lot of functionality of underlying windows API
  101. features for working with windows.
  102. Most of the methods apply to every single window type. For example
  103. you can Click() on any window.
  104. Most of the methods of this class are simple wrappers around
  105. API calls and as such they try do the simplest thing possible.
  106. A HwndWrapper object can be passed directly to a ctypes wrapped
  107. C function - and it will get converted to a Long with the value of
  108. it's handle (see ctypes, _as_parameter_)"""
  109. __metaclass__ = _MetaWrapper
  110. friendlyclassname = None
  111. windowclasses = []
  112. handle = None
  113. can_be_label = False
  114. has_title = True
  115. #-----------------------------------------------------------
  116. def __new__(cls, handle):
  117. # only use the meta class to find the wrapper for HwndWrapper
  118. # so allow users to force the wrapper if they want
  119. # thanks to Raghav for finding this.
  120. if cls != HwndWrapper:
  121. obj = object.__new__(cls)
  122. obj.__init__(handle)
  123. return obj
  124. new_class = cls.FindWrapper(handle)
  125. #super(currentclass, cls).__new__(cls[, ...])"
  126. obj = object.__new__(new_class)
  127. obj.__init__(handle)
  128. return obj
  129. #-----------------------------------------------------------
  130. def __init__(self, hwnd):
  131. """Initialize the control
  132. * **hwnd** is either a valid window handle or it can be an
  133. instance or subclass of HwndWrapper.
  134. If the handle is not valid then an InvalidWindowHandle error
  135. is raised.
  136. """
  137. # handle if hwnd is actually a HwndWrapper
  138. try:
  139. self.handle = hwnd.handle
  140. except AttributeError:
  141. self.handle = hwnd
  142. # verify that we have been passed in a valid windows handle
  143. if not win32functions.IsWindow(hwnd):
  144. raise InvalidWindowHandle(hwnd)
  145. # make it so that ctypes conversion happens correctly
  146. self._as_parameter_ = self.handle
  147. #win32functions.WaitGuiThreadIdle(self)
  148. # specify whether we need to grab an image of ourselves
  149. # when asked for properties
  150. self._NeedsImageProp = False
  151. # default to not having a reference control added
  152. self.ref = None
  153. self.appdata = None
  154. self._cache = {}
  155. # build the list of default properties to be written
  156. # Derived classes can either modify this list or override
  157. # GetProperties depending on how much control they need.
  158. self.writable_props = [
  159. 'Class',
  160. 'FriendlyClassName',
  161. 'Texts',
  162. 'Style',
  163. 'ExStyle',
  164. 'ControlID',
  165. 'UserData',
  166. 'ContextHelpID',
  167. 'Fonts',
  168. 'ClientRects',
  169. 'Rectangle',
  170. 'IsVisible',
  171. 'IsUnicode',
  172. 'IsEnabled',
  173. 'MenuItems',
  174. 'ControlCount',
  175. ]
  176. #-----------------------------------------------------------
  177. def FriendlyClassName(self):
  178. """Return the friendly class name for the control
  179. This differs from the class of the control in some cases.
  180. Class() is the actual 'Registered' window class of the control
  181. while FriendlyClassName() is hopefully something that will make
  182. more sense to the user.
  183. For example Checkboxes are implemented as Buttons - so the class
  184. of a CheckBox is "Button" - but the friendly class is "CheckBox"
  185. """
  186. if self.friendlyclassname is None:
  187. self.friendlyclassname = handleprops.classname(self)
  188. return self.friendlyclassname
  189. #-----------------------------------------------------------
  190. def Class(self):
  191. """Return the class name of the window"""
  192. if not self._cache.has_key("class"):
  193. self._cache['class'] = handleprops.classname(self)
  194. return self._cache['class']
  195. #-----------------------------------------------------------
  196. def WindowText(self):
  197. """Window text of the control
  198. Quite a few contorls have other text that is visible, for example
  199. Edit controls usually have an empty string for WindowText but still
  200. have text displayed in the edit window.
  201. """
  202. return handleprops.text(self)
  203. #-----------------------------------------------------------
  204. def Style(self):
  205. """Returns the style of window
  206. Return value is a long.
  207. Combination of WS_* and specific control specific styles.
  208. See HwndWrapper.HasStyle() to easily check if the window has a
  209. particular style.
  210. """
  211. return handleprops.style(self)
  212. #-----------------------------------------------------------
  213. def ExStyle(self):
  214. """Returns the Extended style of window
  215. Return value is a long.
  216. Combination of WS_* and specific control specific styles.
  217. See HwndWrapper.HasStyle() to easily check if the window has a
  218. particular style.
  219. """
  220. return handleprops.exstyle(self)
  221. #-----------------------------------------------------------
  222. def ControlID(self):
  223. """Return the ID of the window
  224. Only controls have a valid ID - dialogs usually have no ID assigned.
  225. The ID usually identified the control in the window - but there can
  226. be duplicate ID's for example lables in a dialog may have duplicate
  227. ID's.
  228. """
  229. return handleprops.controlid(self)
  230. #-----------------------------------------------------------
  231. def UserData(self):
  232. """Extra data associted with the window
  233. This value is a long value that has been associated with the window
  234. and rarely has useful data (or at least data that you know the use
  235. of).
  236. """
  237. return handleprops.userdata(self)
  238. #-----------------------------------------------------------
  239. def ContextHelpID(self):
  240. "Return the Context Help ID of the window"
  241. return handleprops.contexthelpid(self)
  242. #-----------------------------------------------------------
  243. def IsUnicode(self):
  244. """Whether the window is unicode or not
  245. A window is Unicode if it was registered by the Wide char version
  246. of RegisterClass(Ex).
  247. """
  248. return handleprops.isunicode(self)
  249. #-----------------------------------------------------------
  250. def IsVisible(self):
  251. """Whether the window is visible or not
  252. Checks that both the Top Level Parent (probably dialog) that
  253. owns this window and the window itself are both visible.
  254. If you want to wait for a control to become visible (or wait
  255. for it to become hidden) use ``Application.Wait('visible')`` or
  256. ``Application.WaitNot('visible')``.
  257. If you want to raise an exception immediately if a window is
  258. not visible then you can use the HwndWrapper.VerifyVisible().
  259. HwndWrapper.VerifyActionable() raises if the window is not both
  260. visible and enabled.
  261. """
  262. return handleprops.isvisible(self.TopLevelParent()) and \
  263. handleprops.isvisible(self)
  264. #-----------------------------------------------------------
  265. def IsEnabled(self):
  266. """Whether the window is enabled or not
  267. Checks that both the Top Level Parent (probably dialog) that
  268. owns this window and the window itself are both enabled.
  269. If you want to wait for a control to become enabled (or wait
  270. for it to become disabled) use ``Application.Wait('visible')`` or
  271. ``Application.WaitNot('visible')``.
  272. If you want to raise an exception immediately if a window is
  273. not enabled then you can use the HwndWrapper.VerifyEnabled().
  274. HwndWrapper.VerifyReady() raises if the window is not both
  275. visible and enabled.
  276. """
  277. return handleprops.isenabled(self.TopLevelParent()) and \
  278. handleprops.isenabled(self)
  279. #-----------------------------------------------------------
  280. def Rectangle(self):
  281. """Return the rectangle of window
  282. The rectangle is the rectangle of the control on the screen,
  283. coordinates are given from the top left of the screen.
  284. This method returns a RECT structure, Which has attributes - top,
  285. left, right, bottom. and has methods width() and height().
  286. See win32structures.RECT for more information.
  287. """
  288. return handleprops.rectangle(self)
  289. #-----------------------------------------------------------
  290. def ClientRect(self):
  291. """Returns the client rectangle of window
  292. The client rectangle is the window rectangle minus any borders that
  293. are not available to the control for drawing.
  294. Both top and left are always 0 for this method.
  295. This method returns a RECT structure, Which has attributes - top,
  296. left, right, bottom. and has methods width() and height().
  297. See win32structures.RECT for more information.
  298. """
  299. return handleprops.clientrect(self)
  300. #-----------------------------------------------------------
  301. def Font(self):
  302. """Return the font of the window
  303. The font of the window is used to draw the text of that window.
  304. It is a structure which has attributes for Font name, height, width
  305. etc.
  306. See win32structures.LOGFONTW for more information.
  307. """
  308. return handleprops.font(self)
  309. #-----------------------------------------------------------
  310. def ProcessID(self):
  311. """Return the ID of process that owns this window"""
  312. return handleprops.processid(self)
  313. #-----------------------------------------------------------
  314. def HasStyle(self, style):
  315. "Return True if the control has the specified sytle"
  316. return handleprops.has_style(self, style)
  317. #-----------------------------------------------------------
  318. def HasExStyle(self, exstyle):
  319. "Return True if the control has the specified extended sytle"
  320. return handleprops.has_exstyle(self, exstyle)
  321. #-----------------------------------------------------------
  322. def IsDialog(self):
  323. "Return true if the control is a top level window"
  324. if not self._cache.has_key("isdialog"):
  325. self._cache['isdialog'] = handleprops.is_toplevel_window(self)
  326. return self._cache['isdialog']
  327. #-----------------------------------------------------------
  328. def Parent(self):
  329. """Return the parent of this control
  330. Note that the parent of a control is not necesarily a dialog or
  331. other main window. A group box may be the parent of some radio
  332. buttons for example.
  333. To get the main (or top level) window then use
  334. HwndWrapper.TopLevelParent().
  335. """
  336. if not self._cache.has_key("parent"):
  337. parent_hwnd = handleprops.parent(self)
  338. if parent_hwnd:
  339. #return WrapHandle(parent_hwnd)
  340. self._cache["parent"] = HwndWrapper(parent_hwnd)
  341. else:
  342. self._cache["parent"] = None
  343. return self._cache["parent"]
  344. #-----------------------------------------------------------
  345. def TopLevelParent(self):
  346. """Return the top level window of this control
  347. The TopLevel parent is different from the parent in that the Parent
  348. is the window that owns this window - but it may not be a dialog/main
  349. window. For example most Comboboxes have an Edit. The ComboBox is the
  350. parent of the Edit control.
  351. This will always return a valid window handle (if the control has
  352. no top level parent then the control itself is returned - as it is
  353. a top level window already!)
  354. """
  355. if not self._cache.has_key("top_level_parent"):
  356. parent = self.Parent()
  357. if self.IsDialog():
  358. self._cache["top_level_parent"] = self
  359. #return self
  360. elif not parent:
  361. self._cache["top_level_parent"] = self
  362. #return self
  363. elif not parent.IsDialog():
  364. self._cache["top_level_parent"] = parent.TopLevelParent()
  365. #return parent.TopLevelParent()
  366. else:
  367. self._cache["top_level_parent"] = parent
  368. #return parent
  369. return self._cache["top_level_parent"]
  370. #-----------------------------------------------------------
  371. def Texts(self):
  372. """Return the text for each item of this control"
  373. It is a list of strings for the control. It is frequently over-ridden
  374. to extract all strings from a control with multiple items.
  375. It is always a list with one or more strings:
  376. * First elemtent is the window text of the control
  377. * Subsequent elements contain the text of any items of the
  378. control (e.g. items in a listbox/combobox, tabs in a tabcontrol)
  379. """
  380. texts = [self.WindowText(), ]
  381. return texts
  382. #-----------------------------------------------------------
  383. def ClientRects(self):
  384. """Return the client rect for each item in this control
  385. It is a list of rectangles for the control. It is frequently over-ridden
  386. to extract all rectangles from a control with multiple items.
  387. It is always a list with one or more rectangles:
  388. * First elemtent is the client rectangle of the control
  389. * Subsequent elements contain the client rectangle of any items of
  390. the control (e.g. items in a listbox/combobox, tabs in a
  391. tabcontrol)
  392. """
  393. return [self.ClientRect(), ]
  394. #-----------------------------------------------------------
  395. def Fonts(self):
  396. """Return the font for each item in this control
  397. It is a list of fonts for the control. It is frequently over-ridden
  398. to extract all fonts from a control with multiple items.
  399. It is always a list with one or more fonts:
  400. * First elemtent is the control font
  401. * Subsequent elements contain the font of any items of
  402. the control (e.g. items in a listbox/combobox, tabs in a
  403. tabcontrol)
  404. """
  405. return [self.Font(), ]
  406. #-----------------------------------------------------------
  407. def Children(self):
  408. """Return the children of this control as a list
  409. It returns a list of HwndWrapper (or subclass) instances, it
  410. returns an empty list if there are no children.
  411. """
  412. child_windows = handleprops.children(self)
  413. return [HwndWrapper(hwnd) for hwnd in child_windows]
  414. #-----------------------------------------------------------
  415. def ControlCount(self):
  416. "Return the number of children of this control"
  417. return len(handleprops.children(self))
  418. #-----------------------------------------------------------
  419. def IsChild(self, parent):
  420. """Return True if this window is a child of 'parent'.
  421. A window is a child of another window when it is a direct of the
  422. other window. A window is a direct descendant of a given
  423. window if the parent window is the the chain of parent windows
  424. for the child window.
  425. """
  426. # Call the IsChild API funciton and convert the result
  427. # to True/False
  428. return win32functions.IsChild(parent, self.handle) != 0
  429. #-----------------------------------------------------------
  430. def SendMessage(self, message, wparam = 0 , lparam = 0):
  431. "Send a message to the control and wait for it to return"
  432. return win32functions.SendMessage(self, message, wparam, lparam)
  433. #result = ctypes.c_long()
  434. #ret = win32functions.SendMessageTimeout(self, message, wparam, lparam,
  435. # win32defines.SMTO_NORMAL, 400, ctypes.byref(result))
  436. #return result.value
  437. #-----------------------------------------------------------
  438. def SendMessageTimeout(
  439. self,
  440. message,
  441. wparam = 0 ,
  442. lparam = 0,
  443. timeout = None,
  444. timeoutflags = win32defines.SMTO_NORMAL):
  445. """Send a message to the control and wait for it to return or to timeout
  446. If no timeout is given then a default timeout of .4 of a second will
  447. be used.
  448. """
  449. if timeout is None:
  450. timeout = Timings.sendmessagetimeout_timeout
  451. result = ctypes.c_long()
  452. win32functions.SendMessageTimeout(self,
  453. message, wparam, lparam,
  454. timeoutflags, int(timeout * 1000),
  455. ctypes.byref(result))
  456. return result.value
  457. #-----------------------------------------------------------
  458. def PostMessage(self, message, wparam = 0 , lparam = 0):
  459. "Post a message to the control message queue and return"
  460. return win32functions.PostMessage(self, message, wparam, lparam)
  461. #result = ctypes.c_long()
  462. #ret = win32functions.SendMessageTimeout(self, message, wparam, lparam,
  463. # win32defines.SMTO_NORMAL, 400, ctypes.byref(result))
  464. #return result.value
  465. # #-----------------------------------------------------------
  466. # def NotifyMenuSelect(self, menu_id):
  467. # """Notify the dialog that one of it's menu items was selected
  468. #
  469. # **This method is Deprecated**
  470. # """
  471. #
  472. # import warnings
  473. # warning_msg = "HwndWrapper.NotifyMenuSelect() is deprecated - " \
  474. # "equivalent functionality is being moved to the MenuWrapper class."
  475. # warnings.warn(warning_msg, DeprecationWarning)
  476. #
  477. # self.SetFocus()
  478. #
  479. # msg = win32defines.WM_COMMAND
  480. # return self.SendMessageTimeout(
  481. # msg,
  482. # win32functions.MakeLong(0, menu_id), #wparam
  483. # )
  484. #
  485. #-----------------------------------------------------------
  486. def NotifyParent(self, message, controlID = None):
  487. "Send the notification message to parent of this control"
  488. if controlID is None:
  489. controlID = self.ControlID()
  490. return self.Parent().PostMessage(
  491. win32defines.WM_COMMAND,
  492. win32functions.MakeLong(message, controlID),
  493. self)
  494. #-----------------------------------------------------------
  495. def GetProperties(self):
  496. "Return the properties of the control as a dictionary"
  497. props = {}
  498. # for each of the properties that can be written out
  499. for propname in self.writable_props:
  500. # set the item in the props dictionary keyed on the propname
  501. props[propname] = getattr(self, propname)()
  502. if self._NeedsImageProp:
  503. props["Image"] = self.CaptureAsImage()
  504. return props
  505. #-----------------------------------------------------------
  506. def CaptureAsImage(self):
  507. """Return a PIL image of the control
  508. See PIL documentation to know what you can do with the resulting
  509. image"""
  510. if not (self.Rectangle().width() and self.Rectangle().height()):
  511. return None
  512. # PIL is optional so check first
  513. if not ImageGrab:
  514. print("PIL does not seem to be installed. "
  515. "PIL is required for CaptureAsImage")
  516. return None
  517. # get the control rectangle in a way that PIL likes it
  518. box = (
  519. self.Rectangle().left,
  520. self.Rectangle().top,
  521. self.Rectangle().right,
  522. self.Rectangle().bottom)
  523. # grab the image and get raw data as a string
  524. return ImageGrab.grab(box)
  525. #-----------------------------------------------------------
  526. def __hash__(self):
  527. "Returns the hash value of the handle"
  528. return hash(self.handle)
  529. #-----------------------------------------------------------
  530. def __eq__(self, other):
  531. "Returns True if the handles of both controls are the same"
  532. if isinstance(other, HwndWrapper):
  533. return self.handle == other.handle
  534. else:
  535. return self.handle == other
  536. #-----------------------------------------------------------
  537. def VerifyActionable(self):
  538. """Verify that the control is both visible and enabled
  539. Raise either ControlNotEnalbed or ControlNotVisible if not
  540. enabled or visible respectively.
  541. """
  542. win32functions.WaitGuiThreadIdle(self)
  543. self.VerifyVisible()
  544. self.VerifyEnabled()
  545. #-----------------------------------------------------------
  546. def VerifyEnabled(self):
  547. """Verify that the control is enabled
  548. Check first if the control's parent is enabled (skip if no parent),
  549. then check if control itself is enabled.
  550. """
  551. # Check if the control and it's parent are enabled
  552. if not self.IsEnabled():
  553. raise ControlNotEnabled()
  554. #-----------------------------------------------------------
  555. def VerifyVisible(self):
  556. """Verify that the control is visible
  557. Check first if the control's parent is visible. (skip if no parent),
  558. then check if control itself is visible.
  559. """
  560. # check if the control and it's parent are visible
  561. if not self.IsVisible():
  562. raise ControlNotVisible()
  563. #-----------------------------------------------------------
  564. def Click(
  565. self, button = "left", pressed = "", coords = (0, 0), double = False):
  566. """Simulates a mouse click on the control
  567. This method sends WM_* messages to the control, to do a more
  568. 'realistic' mouse click use ClickInput() which uses SendInput() API
  569. to perform the click.
  570. This method does not require that the control be visible on the screen
  571. (i.e. is can be hidden beneath another window and it will still work.)
  572. """
  573. _perform_click(self, button, pressed, coords, double)
  574. return self
  575. #-----------------------------------------------------------
  576. def ClickInput(
  577. self,
  578. button = "left",
  579. coords = (None, None),
  580. double = False,
  581. wheel_dist = 0):
  582. """Click at the specified coordinates
  583. * **button** The mouse button to click. One of 'left', 'right',
  584. 'middle' or 'x' (Default: 'left')
  585. * **coords** The coordinates to click at.(Default: center of control)
  586. * **double** Whether to perform a double click or not (Default: False)
  587. * **wheel_dist** The distance to move the mouse week (default: 0)
  588. NOTES:
  589. This is different from Click in that it requires the control to
  590. be visible on the screen but performs a more realistic 'click'
  591. simulation.
  592. This method is also vulnerable if the mouse if moved by the user
  593. as that could easily move the mouse off the control before the
  594. Click has finished.
  595. """
  596. _perform_click_input(
  597. self, button, coords, double, wheel_dist = wheel_dist)
  598. #-----------------------------------------------------------
  599. def CloseClick(
  600. self, button = "left", pressed = "", coords = (0, 0), double = False):
  601. """Peform a click action that should make the window go away
  602. The only difference from Click is that there are extra delays
  603. before and after the click action.
  604. """
  605. time.sleep(Timings.before_closeclick_wait)
  606. _perform_click(self, button, pressed, coords, double)
  607. def has_closed():
  608. return not (
  609. win32functions.IsWindow(self) or
  610. win32functions.IsWindow(self.Parent()))
  611. # Keep waiting until both this control and it's parent
  612. # are no longer valid controls
  613. timings.WaitUntil(
  614. Timings.closeclick_dialog_close_wait,
  615. Timings.closeclick_retry,
  616. has_closed
  617. )
  618. time.sleep(Timings.after_closeclick_wait)
  619. return self
  620. #-----------------------------------------------------------
  621. def DoubleClick(
  622. self, button = "left", pressed = "", coords = (0, 0)):
  623. "Perform a double click action"
  624. _perform_click(self, button, pressed, coords, double = True)
  625. return self
  626. #-----------------------------------------------------------
  627. def DoubleClickInput(self, button = "left", coords = (None, None)):
  628. "Double click at the specified coordinates"
  629. _perform_click_input(self, button, coords, double = True)
  630. #-----------------------------------------------------------
  631. def RightClick(
  632. self, pressed = "", coords = (0, 0)):
  633. "Perform a right click action"
  634. _perform_click(
  635. self, "right", "right " + pressed, coords, button_up = False)
  636. _perform_click(self, "right", pressed, coords, button_down = False)
  637. return self
  638. #-----------------------------------------------------------
  639. def RightClickInput(self, coords = (None, None)):
  640. "Right click at the specified coords"
  641. _perform_click_input(self, 'right', coords)
  642. #-----------------------------------------------------------
  643. def PressMouse(self, button = "left", pressed = "", coords = (0, 0)):
  644. "Press the mouse button"
  645. #flags, click_point = _calc_flags_and_coords(pressed, coords)
  646. _perform_click(self, button, pressed, coords, button_up = False)
  647. return self
  648. #-----------------------------------------------------------
  649. def PressMouseInput(self, button = "left", coords = (None, None)):
  650. "Press a mouse button using SendInput"
  651. _perform_click_input(self, button, coords, button_up = False)
  652. #-----------------------------------------------------------
  653. def ReleaseMouse(self, button = "left", pressed = "", coords = (0, 0)):
  654. "Release the mouse button"
  655. #flags, click_point = _calc_flags_and_coords(pressed, coords)
  656. _perform_click(self, button, pressed, coords, button_down = False)
  657. return self
  658. #-----------------------------------------------------------
  659. def ReleaseMouseInput(self, button = "left", coords = (None, None)):
  660. "Release the mouse button"
  661. _perform_click_input(self, button, coords, button_down = False)
  662. #-----------------------------------------------------------
  663. def MoveMouse(self, pressed = "left", coords = (0, 0)):
  664. "Move the mouse"
  665. flags, click_point = _calc_flags_and_coords(pressed, coords)
  666. self.SendMessageTimeout(win32defines.WM_MOUSEMOVE, flags, click_point)
  667. win32functions.WaitGuiThreadIdle(self)
  668. return self
  669. #-----------------------------------------------------------
  670. def DragMouse(self,
  671. button = "left",
  672. pressed = "",
  673. press_coords = (0, 0),
  674. release_coords = (0, 0)):
  675. "Drag the mouse"
  676. self.PressMouse(button, pressed, press_coords)
  677. self.MoveMouse(pressed, press_coords)
  678. self.ReleaseMouse(button, pressed, release_coords)
  679. return self
  680. #-----------------------------------------------------------
  681. def SetWindowText(self, text, append = False):
  682. "Set the text of the window"
  683. self.VerifyActionable()
  684. if append:
  685. text = self.WindowText() + text
  686. text = ctypes.c_wchar_p(unicode(text))
  687. self.PostMessage(win32defines.WM_SETTEXT, 0, text)
  688. win32functions.WaitGuiThreadIdle(self)
  689. return self
  690. #-----------------------------------------------------------
  691. def TypeKeys(
  692. self,
  693. keys,
  694. pause = None,
  695. with_spaces = False,
  696. with_tabs = False,
  697. with_newlines = False,
  698. turn_off_numlock = True):
  699. """Type keys to the window using SendKeys
  700. This uses the SendKeys python module from
  701. http://www.rutherfurd.net/python/sendkeys/ .This is the best place
  702. to find documentation on what to use for the ``keys``
  703. """
  704. self.VerifyActionable()
  705. if pause is None:
  706. pause = Timings.after_sendkeys_key_wait
  707. self.SetFocus()
  708. # attach the Python process with the process that self is in
  709. win32functions.AttachThreadInput(
  710. win32functions.GetCurrentThreadId(), self.ProcessID(), 1)
  711. # make sure that the control is in the foreground
  712. win32functions.SetForegroundWindow(self)
  713. #win32functions.SetActiveWindow(self)
  714. # Play the keys to the active window
  715. SendKeys.SendKeys(
  716. keys,
  717. pause, with_spaces,
  718. with_tabs,
  719. with_newlines,
  720. turn_off_numlock)
  721. # detach the python process from the window's process
  722. win32functions.AttachThreadInput(
  723. win32functions.GetCurrentThreadId(), self.ProcessID(), 0)
  724. win32functions.WaitGuiThreadIdle(self)
  725. return self
  726. #-----------------------------------------------------------
  727. def DebugMessage(self, text):
  728. "Write some debug text over the window"
  729. # don't draw if dialog is not visible
  730. dc = win32functions.CreateDC(u"DISPLAY", None, None, None )
  731. if not dc:
  732. raise ctypes.WinError()
  733. rect = self.Rectangle
  734. #ret = win32functions.TextOut(
  735. # dc, rect.left, rect.top, unicode(text), len(text))
  736. ret = win32functions.DrawText(
  737. dc,
  738. unicode(text),
  739. len(text),
  740. ctypes.byref(rect),
  741. win32defines.DT_SINGLELINE)
  742. # delete the Display context that we created
  743. win32functions.DeleteDC(dc)
  744. if not ret:
  745. raise ctypes.WinError()
  746. return self
  747. #-----------------------------------------------------------
  748. def DrawOutline(
  749. self,
  750. colour = 'green',
  751. thickness = 2,
  752. fill = win32defines.BS_NULL,
  753. rect = None):
  754. """Draw an outline around the window
  755. * **colour** can be either an integer or one of 'red', 'green', 'blue'
  756. (default 'green')
  757. * **thickness** thickness of rectangle (default 2)
  758. * **fill** how to fill in the rectangle (default BS_NULL)
  759. * **rect** the coordinates of the rectangle to draw (defaults to
  760. the rectangle of the control.
  761. """
  762. # don't draw if dialog is not visible
  763. if not self.IsVisible():
  764. return
  765. colours = {
  766. "green" : 0x00ff00,
  767. "blue" : 0xff0000,
  768. "red" : 0x0000ff,
  769. }
  770. # if it's a known colour
  771. if colour in colours:
  772. colour = colours[colour]
  773. if not rect:
  774. rect = self.Rectangle()
  775. # create the pen(outline)
  776. pen_handle = win32functions.CreatePen(
  777. win32defines.PS_SOLID, thickness, colour)
  778. # create the brush (inside)
  779. brush = win32structures.LOGBRUSH()
  780. brush.lbStyle = fill
  781. brush.lbHatch = win32defines.HS_DIAGCROSS
  782. brush_handle = win32functions.CreateBrushIndirect(ctypes.byref(brush))
  783. # get the Device Context
  784. dc = win32functions.CreateDC(u"DISPLAY", None, None, None )
  785. # push our objects into it
  786. win32functions.SelectObject(dc, brush_handle)
  787. win32functions.SelectObject(dc, pen_handle)
  788. # draw the rectangle to the DC
  789. win32functions.Rectangle(
  790. dc, rect.left, rect.top, rect.right, rect.bottom)
  791. # Delete the brush and pen we created
  792. win32functions.DeleteObject(brush_handle)
  793. win32functions.DeleteObject(pen_handle)
  794. # delete the Display context that we created
  795. win32functions.DeleteDC(dc)
  796. #-----------------------------------------------------------
  797. def PopupWindow(self):
  798. """Return any owned Popups
  799. Please do not use in production code yet - not tested fully
  800. """
  801. popup = win32functions.GetWindow(self, win32defines.GW_HWNDNEXT)
  802. return popup
  803. #-----------------------------------------------------------
  804. def Owner(self):
  805. """Return the owner window for the window if it exists
  806. Returns None if there is no owner"""
  807. owner = win32functions.GetWindow(self, win32defines.GW_OWNER)
  808. if owner:
  809. return HwndWrapper(owner)
  810. else:
  811. return None
  812. #-----------------------------------------------------------
  813. # def ContextMenuSelect(self, path, x = None, y = None):
  814. # "TODO ContextMenuSelect Not Implemented"
  815. # pass
  816. # #raise NotImplementedError(
  817. # # "HwndWrapper.ContextMenuSelect not implemented yet")
  818. #-----------------------------------------------------------
  819. def _menu_handle(self):
  820. "Simple Overridable method to get the menu handle"
  821. return win32functions.GetMenu(self)
  822. #-----------------------------------------------------------
  823. def Menu(self):
  824. "Return the menu of the control"
  825. menu_hwnd = self._menu_handle()
  826. if menu_hwnd: # and win32functions.IsMenu(menu_hwnd):
  827. return Menu(self, menu_hwnd)
  828. return None
  829. #-----------------------------------------------------------
  830. def MenuItem(self, path):
  831. """Return the menu item specifed by path
  832. Path can be a string in the form "MenuItem->MenuItem->MenuItem..."
  833. where each MenuItem is the text of an item at that level of the menu.
  834. E.g. ::
  835. File->Export->ExportAsPNG
  836. spaces are not important so you could also have written... ::
  837. File -> Export -> Export As PNG
  838. """
  839. if self.appdata is not None:
  840. menu_appdata = self.appdata['MenuItems']
  841. else:
  842. menu_appdata = None
  843. menu = self.Menu()
  844. if menu:
  845. return self.Menu().GetMenuPath(path, appdata = menu_appdata)[-1]
  846. raise RuntimeError("There is no menu.")
  847. #-----------------------------------------------------------
  848. def MenuItems(self):
  849. """Return the menu items for the dialog
  850. If there are no menu items then return an empty list
  851. """
  852. if self.IsDialog() and self.Menu():
  853. #menu_handle = win32functions.GetMenu(self)
  854. #self.SendMessage(win32defines.WM_INITMENU, menu_handle)
  855. return self.Menu().GetProperties()['MenuItems']
  856. #self.SendMessage(win32defines.WM_INITMENU, menu_handle)
  857. #return _GetMenuItems(menu_handle, self)
  858. else:
  859. return []
  860. # #-----------------------------------------------------------
  861. # def MenuClick(self, path):
  862. # "Select the menuitem specifed in path"
  863. #
  864. # self.VerifyActionable()
  865. #
  866. # self.SetFocus()
  867. #
  868. # menu = Menu(self, self._menu_handle())
  869. #
  870. # path_items = menu.GetMenuPath(path)
  871. #
  872. # for menu_item in path_items:
  873. # if not menu_item.IsEnabled():
  874. # raise MenuItemNotEnabled(
  875. # "MenuItem '%s' is disabled"% menu_item.Text())
  876. #
  877. # menu_item.Click()
  878. #
  879. # return self
  880. #-----------------------------------------------------------
  881. def MenuSelect(self, path, ):
  882. "Select the menuitem specifed in path"
  883. self.VerifyActionable()
  884. self.MenuItem(path).Select()
  885. #-----------------------------------------------------------
  886. def MoveWindow(
  887. self,
  888. x = None,
  889. y = None,
  890. width = None,
  891. height = None,
  892. repaint = True):
  893. """Move the window to the new coordinates
  894. * **x** Specifies the new left position of the window.
  895. Defaults to the current left position of the window.
  896. * **y** Specifies the new top position of the window.
  897. Defaults to the current top position of the window.
  898. * **width** Specifies the new width of the window. Defaults to the
  899. current width of the window.
  900. * **height** Specifies the new height of the window. Default to the
  901. current height of the window.
  902. * **repaint** Whether the window should be repainted or not.
  903. Defaults to True
  904. """
  905. cur_rect = self.Rectangle()
  906. # if no X is specified - so use current coordinate
  907. if x is None:
  908. x = cur_rect.left
  909. else:
  910. try:
  911. y = x.top
  912. width = x.width()
  913. height = x.height()
  914. x = x.left
  915. except AttributeError:
  916. pass
  917. # if no Y is specified - so use current coordinate
  918. if y is None:
  919. y = cur_rect.top
  920. # if no width is specified - so use current width
  921. if width is None:
  922. width = cur_rect.width()
  923. # if no height is specified - so use current height
  924. if height is None:
  925. height = cur_rect.height()
  926. # ask for the window to be moved
  927. ret = win32functions.MoveWindow(self, x, y, width, height, repaint)
  928. # check that it worked correctly
  929. if not ret:
  930. raise ctypes.WinError()
  931. win32functions.WaitGuiThreadIdle(self)
  932. time.sleep(Timings.after_movewindow_wait)
  933. #-----------------------------------------------------------
  934. def Close(self):
  935. """Close the window
  936. Code modified from http://msdn.microsoft.com/msdnmag/issues/02/08/CQA/
  937. """
  938. # tell the window it must close
  939. self.PostMessage(win32defines.WM_CLOSE)
  940. start = time.time()
  941. # Keeps trying while
  942. # we have not timed out and
  943. # window is still a valid handle and
  944. # window is still visible
  945. # any one of these conditions evaluates to false means the window is
  946. # closed or we have timed out
  947. def has_closed():
  948. return not (win32functions.IsWindow(self) and self.IsVisible())
  949. # Keep waiting until both this control and it's parent
  950. # are no longer valid controls
  951. timings.WaitUntil(
  952. Timings.closeclick_dialog_close_wait,
  953. Timings.closeclick_retry,
  954. has_closed
  955. )
  956. #-----------------------------------------------------------
  957. def Maximize(self):
  958. """Maximize the window"""
  959. win32functions.ShowWindow(self, win32defines.SW_MAXIMIZE)
  960. #-----------------------------------------------------------
  961. def Minimize(self):
  962. """Minimize the window"""
  963. win32functions.ShowWindow(self, win32defines.SW_MINIMIZE)
  964. #-----------------------------------------------------------
  965. def Restore(self):
  966. """Restore the window"""
  967. # do it twice just in case the window was minimized from being
  968. # maximized - because then the window would come up maximized
  969. # after the first ShowWindow, and Restored after the 2nd
  970. win32functions.ShowWindow(self, win32defines.SW_RESTORE)
  971. win32functions.ShowWindow(self, win32defines.SW_RESTORE)
  972. #-----------------------------------------------------------
  973. def GetShowState(self):
  974. """Get the show state and Maximized/minimzed/restored state
  975. Returns a value that is a union of the following
  976. * SW_HIDE the window is hidden.
  977. * SW_MAXIMIZE the window is maximized
  978. * SW_MINIMIZE the window is minimized
  979. * SW_RESTORE the window is in the 'restored'
  980. state (neither minimized or maximized)
  981. * SW_SHOW The window is not hidden
  982. """
  983. wp = win32structures.WINDOWPLACEMENT()
  984. wp.lenght = ctypes.sizeof(wp)
  985. ret = win32functions.GetWindowPlacement(self, ctypes.byref(wp))
  986. if not ret:
  987. raise ctypes.WinError()
  988. return wp.showCmd
  989. #-----------------------------------------------------------
  990. def GetFocus(self):
  991. """Return the control in the process of this window that has the Focus
  992. """
  993. gui_info = win32structures.GUITHREADINFO()
  994. gui_info.cbSize = ctypes.sizeof(gui_info)
  995. ret = win32functions.GetGUIThreadInfo(
  996. win32functions.GetWindowThreadProcessId(self, 0),
  997. ctypes.byref(gui_info))
  998. if not ret:
  999. return None
  1000. return HwndWrapper(gui_info.hwndFocus)
  1001. #-----------------------------------------------------------
  1002. def SetFocus(self):
  1003. """Set the focus to this control
  1004. Bring the window to the foreground first if necessary."""
  1005. # find the current foreground window
  1006. cur_foreground = win32functions.GetForegroundWindow()
  1007. # if it is already foreground then just return
  1008. if self.handle != cur_foreground:
  1009. # get the thread of the window that is in the foreground
  1010. cur_fore_thread = win32functions.GetWindowThreadProcessId(
  1011. cur_foreground, 0)
  1012. # get the thread of the window that we want to be in the foreground
  1013. control_thread = win32functions.GetWindowThreadProcessId(self, 0)
  1014. # if a different thread owns the active window
  1015. if cur_fore_thread != control_thread:
  1016. # Attach the two threads and set the foreground window
  1017. win32functions.AttachThreadInput(
  1018. cur_fore_thread, control_thread, True)
  1019. win32functions.SetForegroundWindow(self)
  1020. # detach the thread again
  1021. win32functions.AttachThreadInput(
  1022. cur_fore_thread, control_thread, False)
  1023. else: # same threads - just set the foreground window
  1024. win32functions.SetForegroundWindow(self)
  1025. # make sure that we are idle before returning
  1026. win32functions.WaitGuiThreadIdle(self)
  1027. # only sleep if we had to change something!
  1028. time.sleep(Timings.after_setfocus_wait)
  1029. return self
  1030. #-----------------------------------------------------------
  1031. def SetApplicationData(self, appdata):
  1032. """Application data is data from a previous run of the software
  1033. It is essential for running scripts written for one spoke language
  1034. on a different spoken langauge
  1035. """
  1036. self.appdata = appdata
  1037. _scroll_types = {"left": {
  1038. "line" : win32defines.SB_LINELEFT,
  1039. "page" : win32defines.SB_PAGELEFT,
  1040. "end" : win32defines.SB_LEFT,
  1041. },
  1042. "right": {
  1043. "line" : win32defines.SB_LINERIGHT,
  1044. "page" : win32defines.SB_PAGERIGHT,
  1045. "end" : win32defines.SB_RIGHT,
  1046. },
  1047. "up": {
  1048. "line" : win32defines.SB_LINEUP,
  1049. "page" : win32defines.SB_PAGEUP,
  1050. "end" : win32defines.SB_TOP,
  1051. },
  1052. "down": {
  1053. "line" : win32defines.SB_LINEDOWN,
  1054. "page" : win32defines.SB_PAGEDOWN,
  1055. "end" : win32defines.SB_BOTTOM,
  1056. },
  1057. }
  1058. #-----------------------------------------------------------
  1059. def Scroll(self, direction, amount, count = 1):
  1060. """Ask the control to scroll itself
  1061. direction can be any of "up", "down", "left", "right"
  1062. amount can be one of "line", "page", "end"
  1063. count (optional) the number of times to scroll
  1064. """
  1065. # check which message we want to send
  1066. if direction.lower() in ("left", "right"):
  1067. message = win32defines.WM_HSCROLL
  1068. elif direction.lower() in ("up", "down"):
  1069. message = win32defines.WM_VSCROLL
  1070. # the constant that matches direction, and how much
  1071. scroll_type = \
  1072. HwndWrapper._scroll_types[direction.lower()][amount.lower()]
  1073. # Scroll as often as we have been asked to
  1074. while count > 0:
  1075. self.SendMessage(message, scroll_type)
  1076. count -= 1
  1077. return self
  1078. #
  1079. #def MouseLeftClick():
  1080. # pass
  1081. #def MouseRightClick():
  1082. # pass
  1083. #def MouseDoubleClick():
  1084. # pass
  1085. #def MouseDown():
  1086. # pass
  1087. #def MouseUp():
  1088. # pass
  1089. #def MoveMouse():
  1090. # pass
  1091. #def DragMouse():
  1092. # pass
  1093. #
  1094. #def LeftClick(x, y):
  1095. # win32defines.MOUSEEVENTF_LEFTDOWN
  1096. # win32defines.MOUSEEVENTF_LEFTUP
  1097. #
  1098. # # set the cursor position
  1099. # win32functions.SetCursorPos(x, y)
  1100. # time.sleep(Timings.after_setcursorpos_wait)
  1101. #
  1102. # inp_struct = win32structures.INPUT()
  1103. # inp_struct.type = win32defines.INPUT_MOUSE
  1104. # for event in (win32defines.MOUSEEVENTF_LEFTDOWN, win32defines.MOUSEEVENTF_LEFTUP):
  1105. # inp_struct._.mi.dwFlags = event
  1106. # win32functions.SendInput(
  1107. # 1,
  1108. # ctypes.pointer(inp_struct),
  1109. # ctypes.sizeof(inp_struct))
  1110. #
  1111. # time.sleep(Timings.after_clickinput_wait)
  1112. #====================================================================
  1113. def _perform_click_input(
  1114. ctrl = None,
  1115. button = "left",
  1116. coords = (None, None),
  1117. double = False,
  1118. button_down = True,
  1119. button_up = True,
  1120. absolute = False,
  1121. wheel_dist = 0):
  1122. """Peform a click action using SendInput
  1123. All the *ClickInput() and *MouseInput() methods use this function.
  1124. Thanks to a bug report from Tomas Walch (twalch) on sourceforge and code
  1125. seen at http://msdn.microsoft.com/en-us/magazine/cc164126.aspx this
  1126. function now always works the same way whether the mouse buttons are
  1127. swapped or not.
  1128. For example if you send a right click to Notepad.Edit - it will always
  1129. bring up a popup menu rather than 'clicking' it.
  1130. """
  1131. # Handle if the mouse buttons are swapped
  1132. if win32functions.GetSystemMetrics(win32defines.SM_SWAPBUTTON):
  1133. if button.lower() == 'left':
  1134. button = 'right'
  1135. else:
  1136. button = 'left'
  1137. events = []
  1138. if button.lower() == 'left':
  1139. if button_down:
  1140. events.append(win32defines.MOUSE