2

After removing tkinter default window settings I want to add functionality to move and resize window. Below are the key pieces of code I've taken from a few places and adjusted. I'm trying to make it so when the mouse is below 199 pixels from the top of the window the event response is to resize the window (this is the best I could come up with for two events attached to the mouse). Anyway, I can move the window but after resizing once I can't move again and i get an error:

TypeError: unsupported operand type(s) for +: 'int' and 'str'

on this line:

self.geometry(self.winfo_width() + 'x' + self.winfo_height() + '+{0}+{1}'.format(event.x_root +xwin,event.y_root +ywin))

I've tried to put in numbers to fix the size after its resized. That allows me to move again but the window size is fixed to the figures i replace self.winfo_width() + 'x' + self.winfo_height() +. so there's got to be something in this line above that I'm overlooking.

I'm ok with a big restructuring/scripting endeavor but if someone would point to my syntax error that would get my make shift script to work that would be great. I'm still trying to build intuition for how classes, events and bindings work. I'm using python 3.7.

from tkinter import *
import tkinter as tk
from tkinter import ttk

# resize class code take from https://stackoverflow.com/questions/22421888/tkinter-windows-without-title-bar-but-resizable
class Example(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.floater = FloatingWindow(self)
        self.withdraw()


class FloatingWindow(tk.Toplevel):
    def __init__(self, *args, **kwargs):
        tk.Toplevel.__init__(self, *args, **kwargs)

        # fucntion i brought in to close window
        def end_app_2():
            self.destroy()
            quit()

        self.overrideredirect(True)
        self.wm_geometry("400x400")
        self.label = tk.Label(self, text="Grab the lower-right corner to resize")
        self.label.pack(side="bottom", fill="both", expand=True)
        self.canvas_2=Canvas(self,bg='steelblue1')
        self.canvas_2.pack(anchor='s', side='bottom')
        self.title_bar_2 = tk.Frame(self, height=25, bg='SteelBlue1', relief='raised', bd=1)
        self.title_bar_2.pack(anchor='n', fill='x', side="bottom" )
        self.close_button = Button(self.title_bar_2, text='X', command=end_app_2)
        self.close_button.pack( fill='x', side="right" )
        self.grip = ttk.Sizegrip(self)
        self.grip.place(relx=1.0, rely=1.0, anchor="se")
        self.grip.lift(self.label)
        self.grip.bind("<B1-Motion>", self.OnMotion)

        # move window
        def get_pos(event):
            xwin = self.winfo_x()
            ywin = self.winfo_y()
            startx = event.x_root
            starty = event.y_root
            ywin = ywin - starty
            xwin = xwin - startx

            def move_window(event):
                global size_change
                if size_change==True:
                    self.geometry(self.winfo_width() + 'x' + self.winfo_height() + '+{0}+{1}'.format(event.x_root + xwin, event.y_root + ywin))
                else:
                    self.geometry('400x400' + '+{0}+{1}'.format(event.x_root + xwin, event.y_root + ywin))

            if ywin>=-199:
                self.bind('<B1-Motion>', move_window)

        self.bind('<Button-1>', get_pos)


    def OnMotion(self, event):
        x1 = self.winfo_pointerx()
        y1 = self.winfo_pointery()
        x0 = self.winfo_rootx()
        y0 = self.winfo_rooty()
        self.geometry("%sx%s" % ((x1-x0),(y1-y0)))
        global size_change
        size_change=True
        return

app=Example()
size_change=BooleanVar()
app.mainloop()
Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
costa rica
  • 85
  • 1
  • 12

1 Answers1

3

Your error should tell you all you need here.

unsupported operand type(s) for +: 'int' and 'str'

This is telling you that you cannot add integers and string together. One easy fix is to change this line:

self.geometry(self.winfo_width() + 'x' + self.winfo_height() + '+{0}+{1}'.format(event.x_root + xwin, event.y_root + ywin))

To this:

self.geometry(str(self.winfo_width()) + 'x' + str(self.winfo_height()) + '+{0}+{1}'.format(event.x_root + xwin, event.y_root + ywin))

By converting the integer that winfo is returning to a string you can concatenate without any issue.

That said I would use format() for all of the variables here instead.

This is how I would write that line of code:

self.geometry('{}x{}+{}+{}'.format(self.winfo_width(), self.winfo_height(), event.x_root + xwin, event.y_root + ywin))

All that said I see a few other issue that will need to be address for your code to work properly.

  1. You do not need to use both from tkinter import * and import tkinter as tk. Just use import tkinter as tk. It is the preferred method as it helps prevent overwriting of methods.

  2. You do not need to define everything as a class attribute so reduce the use of self. to only where it is needed.

  3. Follow a standard naming convention. Take some time to read up on PEP8.

  4. Your OnMotion function has a return for no reason. That line can be deleted.

  5. You are using functions inside of a class as well as using global. One of the major benefits to a class is the ability to avoid global by using a class attribute. So I moved your variable size_change into the __init__ and then changed your function to a method for get_pos.

  6. In your OnMotion function you were doing size_change = True but you already defined that variable as a BooleanVar() so what you needed to do is set the value instead. Like this: size_change.set(True).

See cleaned up code below:

import tkinter as tk
from tkinter import ttk


class Example(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.floater = FloatingWindow()
        self.withdraw()


class FloatingWindow(tk.Toplevel):
    def __init__(self):
        super().__init__()
        self.overrideredirect(True)
        self.wm_geometry("400x400")
        self.size_change = tk.BooleanVar()
        self.label = tk.Label(self, text="Grab the lower-right corner to resize")
        self.label.pack(side="bottom", fill="both", expand=True)
        canvas_2 = tk.Canvas(self, bg='steelblue1')
        canvas_2.pack(anchor='s', side='bottom')
        title_bar_2 = tk.Frame(self, height=25, bg='SteelBlue1', relief='raised', bd=1)
        title_bar_2.pack(anchor='n', fill='x', side="bottom")
        tk.Button(title_bar_2, text='X', command=self.end_app_2).pack(fill='x', side="right")
        grip = ttk.Sizegrip(self)
        grip.place(relx=1.0, rely=1.0, anchor="se")
        grip.lift(self.label)
        grip.bind("<B1-Motion>", self.on_motion)
        self.bind('<Button-1>', self.get_pos)

    def end_app_2(self):
        self.destroy()

    def get_pos(self, event):
        xwin = self.winfo_x()
        ywin = self.winfo_y()
        startx = event.x_root
        starty = event.y_root
        ywin = ywin - starty
        xwin = xwin - startx

        def move_window(event):
            if self.size_change:
                self.geometry('{}x{}+{}+{}'.format(self.winfo_width(), self.winfo_height(),
                                                   event.x_root + xwin, event.y_root + ywin))
            else:
                self.geometry('400x400' + '+{0}+{1}'.format(event.x_root + xwin, event.y_root + ywin))

        if ywin >= -199:
            self.bind('<B1-Motion>', move_window)

    def on_motion(self, _=None):
        x1 = self.winfo_pointerx()
        y1 = self.winfo_pointery()
        x0 = self.winfo_rootx()
        y0 = self.winfo_rooty()
        self.geometry("%sx%s" % ((x1-x0), (y1-y0)))
        self.size_change.set(True)


if __name__ == '__main__':
    app = Example().mainloop()

All that said I think this is more complicated then it needs to be. After trying to address the issue in the on_motion method I decided to just rewrite the entire thing as a good section of code was not even needed to get the functionality.

I believe that:

if ywin >= -199:
    self.bind('<B1-Motion>', move_window)

Was the cause of the problem after resizing.

Try this code instead and let me know if you have any questions:

import tkinter as tk
import tkinter.ttk as ttk


class Win(tk.Tk):
    def __init__(self):
        super().__init__()
        self.columnconfigure(0, weight=1)
        self.rowconfigure(2, weight=1)
        self.overrideredirect(True)
        self.wm_geometry("400x400")
        self.minsize(400, 400)
        self.x = 0
        self.y = 0

        title_bar_2 = tk.Frame(self, height=25, bg='SteelBlue1', relief='raised', bd=1)
        title_bar_2.grid(row=0, column=0, sticky='ew')
        tk.Button(title_bar_2, text='X', command=self.destroy).pack(fill='x', side="right")
        self.canvas_2 = tk.Canvas(self, bg='steelblue1')
        self.canvas_2.grid(row=1, column=0)
        tk.Label(self, text="Grab the lower-right corner to resize").grid(row=2, column=0)
        grip = ttk.Sizegrip(self)
        grip.place(relx=1.0, rely=1.0, anchor="se")

        title_bar_2.bind('<ButtonPress-1>', self.button_press)
        title_bar_2.bind('<B1-Motion>', self.move_window)

    def move_window(self, event):
        x = self.winfo_pointerx() - self.x
        y = self.winfo_pointery() - self.y
        self.geometry('+{}+{}'.format(x, y))

    def button_press(self, event):
        self.x = event.x
        self.y = event.y


win = Win()
win.mainloop()
Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
  • This is super helpful. I will spend time a lot of time on this page but i still have a lingering issue: I can now resize the window then move it. However, after i move the window the resize function doesn't work. Also after the first move the cursor gets positioned to the top middle of the window. I know i ran into this problem earlier but i've been going through my notes and don't see understand how i solved. I think there is something with my on_motion function. the way its currently calculating i always get a negative number for the y1-y0 coordinate – costa rica Jan 29 '20 at 12:47
  • @costarica I added a 2nd example that is a bit more straight forward. I think some of your code was not needed so I reduced it to the minimum to reproduce the behavior you were looking for. – Mike - SMT Jan 29 '20 at 15:05
  • Very grateful for this help.My most important quest is somewhat complicated – I think. I have another class (that I may have made with your help) that links two textboxes with one scrollbar. I want to keep the same style in another window so I’m mimicking what you helped me put together and nest it within the class for the window- is that what I should do? Below is part of the code - maybe you can spot something I’m doing immediately wrong with it? 'class Win_2(tk.Tk): def __init__(self)... class scroll_link: def __init__(self, Win_2):' – costa rica Jan 30 '20 at 15:48
  • "class scroll_link: def __init__(self, Win_2): scroll_1= Scrollbar(self.Win_2, orient=VERTICAL, command=self.yview) scroll_2= self.text_1= Text(self.Win_2, width=30,height=25,yscrollcommand=scroll_1.set) scroll_1.grid(row=3, column=3, sticky='ns') self.text_1.configure(state='disabled', width=width_temp) self.text_2.configure(state='disabled', width=width_temp_2, wrap='none') def xview(self, *args): self.text_2.xview(*args) def yview(self, *args): self.text_1.yview(*args) self.text_2.yview(*args)" – costa rica Jan 30 '20 at 16:00
  • @costarica I think that should be asked in a new question. So it can be better answered. – Mike - SMT Jan 30 '20 at 18:19