[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky]
[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Make a skinned form in Python, Part 2

[A skinned form built in Python]

The first post in this series described the widgets used to build a skinned form in Python. The form uses various canvas widgets to let the user move and resize the window.

This post explains the events that do the moving and resizing: mouse_down and mouse_drag.

Note that I have only tested this in Windows 11. It should work in Linux- and fruit-based operating systems, but I make no promises.

mouse_down

The mouse_down event handler executes when you press the mouse down over one of the move or resizing widgets.

def mouse_down(self, event): '''The user pressed the mouse down on one of the handles.''' # Save the mouse position. self.start_x = event.x self.start_y = event.y

This ridiculously simple event handler just saves the mouse's current position. The real fun happens in the mouse_drag event handler.

mouse_drag

If you press the mouse down over one of the moving or resizing widgets and then move the mouse, the following mouse_drag event handler executes. This event continues to fire as long as you move the mouse, even if you move it off of the widget, until you release the mouse button.

def mouse_drag(self, event): '''The user is dragging over one of the handles.''' if self.ignore_drag: return # Get the window's current size and position. geometry = self.window.geometry().replace('+', 'x') wid, hgt, x, y = [int(value) for value in geometry.split('x')] # See how much the mouse has moved. dx = event.x - self.start_x dy = event.y - self.start_y # Update the window's size and position. widget = event.widget match widget: case self.drag_canvas: x += dx y += dy case self.nw_canvas: x += dx y += dy wid -= dx hgt -= dy case self.n_canvas: y += dy hgt -= dy case self.ne_canvas: y += dy wid += dx hgt -= dy case self.e_canvas: wid += dx case self.se_canvas: wid += dx hgt += dy case self.s_canvas: hgt += dy case self.w_canvas: x += dx wid -= dx case self.sw_canvas: x += dx wid -= dx hgt += dy # Update the window's size and position. self.window.geometry(f'{wid}x{hgt}+{x}+{y}')

The event handler first checks the ignore_drag flag and returns if it is True. Recall from the previous post that the program sets this flag to true if you press the mouse down over kill_rect. That rectangle is drawn inside the nw_canvas widget, so without this flag, that widget would try to resize the form when you click kill_rect.

If ignore_drag is False, you're trying to resize or move the form so the code uses self.window.geometry to get the window's current position and dimensions. The result is a string of the form 408x300+447+166, which means the window is 408 pixels wide, 300 pixels tall, and has upper left corner at (447, 166). The code splits the string into its pieces and converts its values into integers to get the window's current size and position.

Next, the code subtracts the coordinates where you originally pressed the mouse button from the current mouse coordinates to see how much the mouse has moved and saves the differences in variables dx and dy.

The program then gets the widget that raised the mouse_drag event and uses a match statement to take different actions depending on which canvas you're dragging. You can look through the code to see how each widget modifies the form, but I'll describe three cases.

First, suppose you're dragging the drag_canvas that displays the window's title. Here's the corresponding code.

case self.drag_canvas: x += dx y += dy

In this case, the program adds dx and dy to the window's current position. That moves the window by the same amount that you moved the mouse.

Note that the mouse's current position is measured with respect to the widget's current position so, as this code moves the window and its widgets, the drag_canvas moves, too. That means the mouse's start position and its current position are both measured relative to the widget's current origin, so dx and dy tell you the amount by which the mouse has moved since the last time the program moved the window. That means the dx and dy values tend to be small and the window is moved incrementally each time the mouse moves.

For the second example, suppose you're dragging the se_canvas widget in the window's lower right corner. Here's its code.

case self.se_canvas: wid += dx hgt += dy

This time the code leaves the window's location unchanged and adjusts its width and height.

The third example occurs when you drag the nw_canvas in the window's upper left corner. Here's the corresponding code.

case self.nw_canvas: x += dx y += dy wid -= dx hgt -= dy

Dragging the window's upper left corner adjusts both its size and position. The code first adds dx and dy to the coordinates of the window's upper left corner. It then subtracts those values from the window's width and height. The result is that the code moves the window's upper left corner while keeping its lower right corner unmoved.

You can look through the other case statements to see how the code changes the window's size and position for the other canvases.

After it has adjusted the window's size and position, it calls self.window.geometry to update the window.

Conclusion

This code works fairly well. It works particularly well if you're only changing the window's size or position but not both. There's some slight video tearing when the program needs to adjust both the window's size and position, for example, if you drag the window's left edge or its upper left corner. It's not too bad, though, and it stops when you stop resizing the window.

In my next post, I'll explain how the program can display images instead of the colorful but somewhat boring canvas widgets. Meanwhile, feel free to download the example to experiment with it and to see additional details.

© 2025 Rocky Mountain Computer Consulting, Inc. All rights reserved.