The ThreadMonitor Class
The real work takes place in the ThreadMonitor class, which is a subclass of threading.Thread from Python's standard threading library module. It exposes the mandatory
run() method that runs in a separate thread. Here's the code:
class MonitorThread(threading.Thread):
def __init__(self, verbose=False, targets=None):
threading.Thread.__init__(self)
self.targets = targets
if self.targets == None:
self.targets = default_targets
self.verbose = verbose
self.done = threading.Event()
The
__init__ method takes two arguments (the 'self' argument is an implicit one like the 'this' pointer in C++, and is not considered an argument):
verbose and
targets, set to appropriate defaults, and keeps them for later reference. The
verbose argument determines how verbose the monitor's output should be. The
targets argument is the list of windows to monitor; if it's the default
None, the method uses the
default_targets module variable). The most important job of the
__init__ method is to fire the
done event. This synchronization object allows the main thread to stop the monitor thread (used only when running for a specific number of seconds).
The run() and stop() methods
These methods control the lifetime of the worker thread. The
run() method calls the
_checkTargets() method repeatedly as long as the
done event is not set, and sleeps between each iteration so it doesn't monopolize machine resources. This method launches on a separate thread when the main thread calls
start().
def run(self):
while not self.done.isSet():
self._checkTargets()
self.done.wait(0.1)
time.sleep(check_delay)
print 'Done'
The
stop() method sets the
done event that the
run() method checks. The main thread calls it to stop the thread's operation cleanly.
def stop(self):
self.done.set()
The _moveWindow() Method
Finally, with all the framework code in place, here's how the program actually moves a window:
def _moveWindow(self, hwnd, dx=-monitor_width, dy=height_difference) :
r = GetWindowRect(hwnd)
# calculate new position
x = r[0] + dx
y = r[1] + dy
# calculate width/height
width = r[2]-r[0]
height = r[3]-r[1]
MoveWindow(hwnd, x, y, width, height, True)
As you saw earlier, the
_checkTargets() method calls
_moveWindow() when it needs to move a window. The method accepts the handle (
hwnd) of the window to be moved and two values that determine how much to offset the window horizontally (
dx) and vertically (
dy). The default for
dx is
-monitor_width, which means move it from the right display to the equivalent location in the left display. The default for
dy is
height_difference, which moves the window vertically as needed in the secondary display if a height difference exists between the displays.
The method starts by calling the
GetWindowRect() API function, which returns a tuple of the form (left, top, right, bottom). It calculates the new position by adding
dx and
dy to the left and top coordinates and then calculates the width and height, which remain the same. Finally, it actually moves the window by calling the
MoveWindow() API function. Note the different coordinate systems (
top,
left,
right,
bottom) and (
top,
left,
width,
height). It's easy to become confused (which produces interesting bugs) by passing
right and
bottom instead of
width and
height.