The _isMaximized() Method
The
_isMaximized() method is a utility function that should have been in the
win32gui API wrappers. The Win32 API itself has a function called
IsZoomed that tells you whether a window is maximized, but it's missing from
win32gui. Luckily, it is pretty easy to reproduce. A window has several generic parts: a title bar, a border, and a client area. The title bar is the strip at the top of the window that contains the title and the minimize/maximize/close buttons. The border is the thin line that surrounds the window and allows you to resize the window by dragging it. The client area is everything in the middle, where all the controls and the content of the window are placed. All these parts are optional, and there are many ways to configure a window, so any particular window may have any combination of these parts, with different styles. It is also possible to override the behavior of these parts with some extra work.
def _isMaximized(self, h):
cr = GetClientRect(h)
clientWidth = cr[2]
return clientWidth == monitor_width
It's important to understand what happens when a window is maximized. The window's size changes so that its client area and the title bar exactly cover the entire screen of the display the window resides in. The border disappears. To determine whether a window is maximized, you can check whether the width of the client area is exactly the width of the monitor. The
GetClientRect() API function provides the boundaries of the client area. Unfortunately, it confusingly returns
left,
top,
width,
height, while
GetWindowRect() returns
left,
top,
right,
bottom.
This function is not 100-percent foolproof. A user may resize a window without maximizing it so that its client area exactly covers the monitor; however that's rare, and for WindowMover it is not a use case worth pursuing further.
The _checkTargets() Method
This method contains WindowMover's main algorithm. The full source code for
_checkTargets() is in
Listing 1, but I'll explain it in smaller chunks:
def _checkTargets(self):
if self.verbose:
print '_checkTargets() here'
windows = _getTopLevelWindows()
for i, w in enumerate(windows):
# Ignore minimized and hidden windows
if not win32gui.IsWindowVisible(w[0]) or \
win32gui.IsIconic(w[0]):
continue
checkTargets_() prints a little greeting (if in verbose mode) and then calls
_getTopLevelWindows() to get the list of top-level windows. Next, it iterates over all the windows using the
enumerate() function. This built-in Python function iterates over any iterable object, such as a list or dictionary, and for each item it returns a value pair containing a sequential number and the item itself. You'll find
enumerate() very convenient when you want to keep track of how many items you have iterated over so far and what the current item index is. Inside the loop it checks whether the current window is minimized or hidden; if so, it simply continues to the next window (there's no need to move hidden or minimized windows because they don't show up on the primary display).
if w[1] == 'Shell_TrayWnd': # Ignore the tray
continue
move = False
is_target = False
for t in self.targets:
# Check both Window class (w[1]) and title (w[2])
if t in w[1] or t in w[2]:
is_target = True
break
if keep and not is_target:
move = True
if not keep and is_target:
move= True
At the top of the preceding code fragment, you'll see another check—this time for the
Shell_TrayWnd. This special window is the system tray and should not be moved. Next, the code initializes the
move and
is_target variables to
False, and then determines whether the current window is a target by iterating the target list, comparing the current window's class and title against each target window. If either matches, the current window is considered a valid target for moving. Note that the comparison checks whether the target is
contained in the window's class or title. For example, if the target is
cmd.exe, any window whose title contains
cmd.exe will match the target. This lets you specify target windows in a flexible way that's less prone to mismatches. After determining that the current window is a target, there is no need to keep checking the other targets, so the code breaks out of the loop.
The final decision about whether to move the current window depends on the two flag variables:
is_target and keep. If the current window is a target (
is_Target is
True) and
keep is
False, it means that the current window should be moved. If the current window is not a target (
is_Target is
False), but
keep is
True, it means that target windows should be kept on the primary display, but the current window should be moved. In all other cases the current window should remain in place.
When a window is a candidate for moving the next step is to find out if the window is maximized. If so, calculate the border width by subtracting the width of the client rectangle (which doesn't include the border) from the window rectangle (which does include the border) and dividing it by two, because the window rectangle includes the width of both the left and right borders:
if move:
# check on which monitor
r = GetWindowRect(w[0])
# move to secondary if necessary
borderWidth = 0
maximized = self._isMaximized(w[0])
if maximized:
cr = GetClientRect(w[0])
borderWidth = (r[2] - r[0] - cr[2]) / 2
The final test is to check if the window is actually in the primary display. In this case the primary display is configured to be the right-hand monitor in the system. This means that the horizontal screen coordinates of the primary display are between
0 and
monitor_width-1 and the horizontal screen coordinates of the secondary display are between
—monitor_width and
-1. The check itself just makes sure the left coordinate of the window is greater or equal to
—borderWidth. For a non-maximized window the
borderWidth is
0, so it's equivalent to check if the
borderWidth is greater or equal to 0. For maximized windows, the border stretches outside the primary display and the program accounts for it. That seems unintuitive, because maximized windows seem to have no border, but when you query their size with
GetWindowRect() their left co-ordinate is
—borderWidth and the client rectangle covers the entire screen—making it only seem as if there is no border. You need to take this little quirk into account when detecting whether the window is in the primary display. For normal windows you don't need to check for
—borderWidth. Therefore, to use a single check that works for both types of windows, WindowMover sets the
borderWidth variable to
0 for normal windows even though they have a border (note that it doesn't actually set the width of the visual border to
0).
Finally, if the window is indeed on the primary display it gets moved using the
_moveWindow() function. If the window is maximized there are some additional gymnastics that change its state (because maximized windows can't be moved) and then maximize the window again after the move.
# Window is in primary display
if r[0] >= -borderWidth:
if maximized:
ShowWindow(w[0], win32con.SW_HIDE)
ShowWindow(w[0], win32con.SW_SHOWNOACTIVATE)
self._moveWindow(w)
ShowWindow(w[0], win32con.SW_MAXIMIZE)
else:
self._moveWindow(w)