Skip to content

Commit

Permalink
Merge pull request pywinauto#526 from vasily-v-ryabov/fix_set_focus
Browse files Browse the repository at this point in the history
Fix set_focus() for UIA backend
  • Loading branch information
vasily-v-ryabov authored Jul 30, 2018
2 parents 0c4e63d + 4cf4192 commit 960682d
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 11 deletions.
2 changes: 1 addition & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pywin32
six
Pillow==4.0.0
Pillow==5.2.0
coverage
nose
codecov
Expand Down
28 changes: 28 additions & 0 deletions docs/HISTORY.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,34 @@
Change Log
==========

0.6.5 Handling Privileges, AutomationID for Win32 etc.
--------------------------------------------------------------------
30-July-2018

Enhancements:
* Check admin privileges for both target app and Python process. This
allows detecting cases when window messages won't work.
* Add ``automation_id`` and ``control_type`` properties
for "win32" backend (the most useful for WinForms). Correct
child_window() keywords are ``auto_id`` and ``control_type``.
* Switch pypiwin32 dependency to pywin32 which became official again.
* New generators ``iter_children()`` and ``iter_descendants()``.
* Add method ``is_checked()`` to "win32" check box.

Bug Fixes:
* Method ``Application().connect(...)`` works better with ``timeout``
argument.
* Fix ``.set_focus()`` for "uia" backend including minimized window case
(issue #443).
* ``maximize()/minimize()`` methods can be chained now.
* Fix passing keyword arguments to a function for decorators
``@always_wait_until_passes`` and ``@always_wait_until``.
* Use correct types conversion for WaitGuiThreadIdle (issue #497).
* Fix reporting code coverage on Linux.
* Use .format() for logging BaseWrapper actions (issue #471).
* Print warning in case binary type is not determined (issue #387).


0.6.4 NULL pointer access fix and enhancements
--------------------------------------------------------------------
21-January-2018
Expand Down
11 changes: 11 additions & 0 deletions pywinauto/base_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
import re
import time
import win32process
import win32gui
import win32con
import six

try:
Expand Down Expand Up @@ -311,6 +313,15 @@ def is_enabled(self):
"""
return self.element_info.enabled #and self.top_level_parent().element_info.enabled

# -----------------------------------------------------------
def was_maximized(self):
"""Indicate whether the window was maximized before minimizing or not"""
if self.handle:
(flags, _, _, _, _) = win32gui.GetWindowPlacement(self.handle)
return (flags & win32con.WPF_RESTORETOMAXIMIZED == win32con.WPF_RESTORETOMAXIMIZED)
else:
return None

#------------------------------------------------------------
def rectangle(self):
"""
Expand Down
13 changes: 9 additions & 4 deletions pywinauto/controls/hwndwrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -1302,9 +1302,7 @@ def set_focus(self):
"""
# "steal the focus" if there is another active window
# otherwise it is already into the foreground and no action required
cur_foreground = win32gui.GetForegroundWindow()

if self.handle != cur_foreground:
if not self.has_focus():
# Notice that we need to move the mouse out of the screen
# but we don't use the built-in methods of the class:
# self.mouse_move doesn't do the job well even with absolute=True
Expand All @@ -1313,7 +1311,10 @@ def set_focus(self):

# change active window
if self.is_minimized():
win32gui.ShowWindow(self.handle, win32con.SW_RESTORE)
if self.was_maximized():
self.maximize()
else:
self.restore()
else:
win32gui.ShowWindow(self.handle, win32con.SW_SHOW)
win32gui.SetForegroundWindow(self.handle)
Expand All @@ -1328,6 +1329,10 @@ def set_focus(self):
# Non PEP-8 alias
SetFocus = deprecated(set_focus)

def has_focus(self):
"""Check the window is in focus (foreground)"""
return self.handle == win32gui.GetForegroundWindow()

def has_keyboard_focus(self):
"""Check the keyboard focus on this control."""
control_thread = win32process.GetWindowThreadProcessId(self.handle)[0]
Expand Down
23 changes: 17 additions & 6 deletions pywinauto/controls/uiawrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@
from __future__ import print_function

import six
import comtypes
import time
import warnings
import comtypes

from .. import backend
from ..timings import Timings
Expand Down Expand Up @@ -400,14 +401,24 @@ def has_keyboard_focus(self):
# -----------------------------------------------------------
def set_focus(self):
"""Set the focus to this element"""
if self.is_keyboard_focusable() and not self.has_keyboard_focus():
try:
self.element_info.element.SetFocus()
except comtypes.COMError:
pass # TODO: add RuntimeWarning here
try:
if self.is_minimized():
if self.was_maximized():
self.maximize()
else:
self.restore()
except uia_defs.NoPatternInterfaceError:
pass
try:
self.element_info.element.SetFocus()
except comtypes.COMError as exc:
warnings.warn('The window has not been focused due to ' \
'COMError: {}'.format(exc), RuntimeWarning)

return self

# TODO: figure out how to implement .has_focus() method (if no handle available)

# -----------------------------------------------------------
def close(self):
"""
Expand Down
48 changes: 48 additions & 0 deletions pywinauto/unittests/test_uiawrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -1584,6 +1584,54 @@ def test_tv_drag_n_drop(self):
self.assertEqual(itm.window_text(), 'Months')


class WindowWrapperTests(unittest.TestCase):

"""Unit tests for the UIAWrapper class for Window elements"""

def setUp(self):
"""Set some data and ensure the application is in the state we want"""
_set_timings()

test_folder = os.path.join(os.path.dirname(os.path.dirname(
os.path.dirname(os.path.abspath(__file__)))), r"apps/MouseTester")
self.qt5_app = os.path.join(test_folder, "mousebuttons.exe")

# start the application
self.app = Application(backend='uia')
self.app = self.app.start(self.qt5_app)

self.dlg = self.app.MouseButtonTester.wrapper_object()
self.another_app = None

def tearDown(self):
"""Close the application after tests"""
self.app.kill()
if self.another_app:
self.another_app.kill()
self.another_app = None

def test_issue_443(self):
"""Test .set_focus() for window that is not keyboard focusable"""
self.dlg.minimize()
self.assertEqual(self.dlg.is_minimized(), True)
self.dlg.set_focus()
self.assertEqual(self.dlg.is_minimized(), False)
self.assertEqual(self.dlg.is_normal(), True)

# run another app instance (in focus now)
self.another_app = Application(backend="win32").start(self.qt5_app)
# eliminate clickable point at original app by maximizing second window
self.another_app.MouseButtonTester.maximize()
self.another_app.MouseButtonTester.set_focus()
self.assertEqual(self.another_app.MouseButtonTester.has_focus(), True)

self.dlg.set_focus()
# another app instance has lost focus
self.assertEqual(self.another_app.MouseButtonTester.has_focus(), False)
# our window has been brought to the focus (clickable point exists)
self.assertEqual(self.dlg.element_info.element.GetClickablePoint()[-1], 1)


if __name__ == "__main__":
if UIA_support:
unittest.main()

0 comments on commit 960682d

Please sign in to comment.