Python class with tkinter - python

I'm trying to learn the classes in Python.
I wrote a little code with tkinter
from tkinter import *
class window():
def __init__(self, size, title, ):
self.size = size
self.title = title
window = Tk()
window.geometry(self.size)
window.title(self.title)
print('a window has been created')
window.mainloop()
def button(self, window, text, x, y):
self.window = window
self.text = text
self.x = x
self.y = y
button = Button(window, text=text).place(x=str(x), y=str(y))
but I get the error message:
self.tk = master.tk
AttributeError: 'window' object has no attribute 'tk'

you have to say from which file is this function or object is from
window = tkinter.Tk()

I can't see where you write self.tk or where you called the class. Probably, you need to add self. when you declare window.
Like this:
from tkinter import *
class window:
def __init__(self, size, title, ):
self.size = size
self.title = title
self.window = Tk()
self.window.geometry(self.size)
self.window.title(self.title)
print('a window has been created')
self.window.mainloop()
def button(self, window, text, x, y):
self.window = window
self.text = text
self.x = x
self.y = y
button = Button(window, text=text).place(x=str(x), y=str(y))
if __name__ == "__main__":
worker = window("500x500", "nice title") # <-- You need to call the class.
(If you need to declare a button, you can add this line on __init__ function above self.window.mainloop(): self.button(self.window, "Title", 10, 20))

Related

Tkinter widgets not showing on mac (using PyCharm and Terminal)

I am using python 3.9 (and have also tried my code with Python 3.10 in terminal) on a mac (OS High Sierra) and my IDE is PyCharm. No Widgets I have tried with tkinter are showing up. Here I am using Label but I have also used Entry too - i just get a blank window:
Here is my code:
import tkinter
from tkinter import ttk
class View:
def __init__(self, model):
self.root = tkinter.Tk()
self.root.title("Play Suduko")
self.root.grid()
self.form = ttk.Frame(self.root, padding=10)
self.model = model
self.S = self.model.S
for self.i, self.row in enumerate(self.S):
for self.j, self.element in enumerate(self.row):
if self.element != 0:
self.label = ttk.Label(self.form, text=str(self.element))
self.label.grid(row=self.i, column=self.j)
self.root.mainloop()
Not sure what I'm doing wrong. I'm pretty sure it's not the IDE because as I said I tried it in the Terminal with Python 3.10 and got the same thing.
Any help much appreciated.
Edit:
I updated my code to remove form. So here is the new code:
from tkinter import *
from tkinter.ttk import *
class View(Frame):
def __init__(self, model):
self.root = Tk()
self.root.title("Play Suduko")
Frame.__init__(self)
self.model = model
self.S = self.model.S
for self.i, self.row in enumerate(self.S):
for self.j, self.element in enumerate(self.row):
if self.element != 0:
self.label = Label(self.root, textvariable=str(self.element), width=2)
self.label.grid(row=self.i, column=self.j, padx=10, pady=10)
self.root.mainloop()
And this is the window I am seeing now:
Fixed:
from tkinter import *
from tkinter.ttk import *
class View(Frame):
def __init__(self, model):
self.root = Tk()
self.root.title("Play Suduko")
Frame.__init__(self, self.root)
self.model = model
self.S = self.model.S
for self.i, self.row in enumerate(self.S):
for self.j, self.element in enumerate(self.row):
if self.element == 0:
self.element = ""
self.label = Label(self.root, text=str(self.element), width=2, font=("Arial", 40))
self.label.grid(row=self.i, column=self.j+1, padx=5, pady=5)
self.root.mainloop()
:)

.delete is not working inside class in tkinter

I am trying to make a tkinter desktop application (Notepad) using classes but I found an Attribute Error in my code. I made three files "menu_option.py", "textarea.py" and "window_frame.py". Run the "menu_option.py" file so that you found the error. I am using python (3.9). Also is there any another way to connect "new_file" function to menu item.
Here is my code below:
menu_option.py
from tkinter import *
from textarea import TextArea
from window_frame import Window
class Menu_Option(TextArea):
file = None
def __init__(self, master, newfile=None):
super().__init__(root)
self.Menubar = 'MenuBar'
self.Filemenu = 'FileMenu'
self.root = self.root
self.master = self.master
self.newfile = newfile
def launch(self):
self.Menubar = Menu(self.root)
self.Filemenu = Menu(master=self.Menubar, tearoff=0)
self.Filemenu.add_command(label='New File...', command=self.newfile)
self.Menubar.add_cascade(label='File', menu=self.Filemenu)
self.root.config(menu=self.Menubar)
class Features(Menu_Option):
def __init__(self, master):
super().__init__(master, newfile=self.new_file)
def new_file(self):
global file
self.root.title(self.title)
file = None
self.textarea.delete(1.0, END)
if __name__ == '__main__':
root = Tk()
Window(root).launch()
TextArea(root).launch()
Menu_Option(root).launch()
Features(root).launch()
root.mainloop()
textarea.py
from tkinter import *
from window_frame import Window
from tkinter.scrolledtext import ScrolledText
class TextArea(Window):
def __init__(self, name):
super().__init__(name)
self.name = self.root
self.master = 'root'
self.textarea = 'text_area'
self.font = 'courier 14 normal'
def launch(self):
self.textarea = ScrolledText(self.root, font=self.font)
self.textarea.pack(expand=True, fill=BOTH)
if __name__ == '__main__':
root = Tk()
Window(root).launch()
TextArea(root).launch()
root.mainloop()
window_frame.py
from tkinter import *
class Window:
def __init__(self, root):
self.root = root
self.geometry = '1000x550+100+100'
self.title = 'Untitled - ProBook'
def launch(self):
self.root.geometry(self.geometry)
self.root.title(self.title)
if __name__ == '__main__':
root = Tk()
Window(root).launch()
root.mainloop()
Error:
Exception in Tkinter callback
Traceback (most recent call last):
File "D:\Installed Programs\Python\lib\tkinter\__init__.py", line 1892, in __call__
return self.func(*args)
File "C:\Users\vaish\OneDrive\ProBook\menu_option.py", line 35, in new_file
self.textarea.delete(1.0, END)
AttributeError: 'str' object has no attribute 'delete'
Since Menu_Option() has override launch() function, therefore TextArea.launch() will not be executed and so instance variable textarea is still a string.
If child class wants to inherit parent class launch() functionality, you need to call the parent class launch() in its launch() function:
textarea.py
class TextArea(Window):
...
def launch(self):
super().launch() # call parent class launch()
self.textarea = ScrolledText(self.root, font=self.font)
self.textarea.pack(expand=True, fill=BOTH)
menu_option.py
class Menu_Option(TextArea):
...
def launch(self):
super().launch() # execute parent class launch()
...
...
if __name__ == "__main__":
root = Tk()
#Window(root).launch() # <- no need to execute
#TextArea(root).launch() # <- no need to execute
#Menu_Option(root).launch() # <- no need to execute
Features(root).launch()
root.mainloop()
Note that Window(root).launch(), TextArea(root).launch() and Menu_Option(root).launch() are not required.
You have initialized self.textarea="text_area" in textarea.py . But when you import it in menu_option.py, you are overwriting the function launch, which is supposed to set the value of self.textarea to a ScrolledText and pack it. To solve this you have to include the code in launch function of textarea.py in the function launch of Menu_Option class.
from tkinter import *
from tkinter.scrolledtext import ScrolledText
from textarea import TextArea
from window_frame import Window
class Menu_Option(TextArea):
file = None
def __init__(self, master, newfile=None):
super().__init__(root)
self.Menubar = 'MenuBar'
self.Filemenu = 'FileMenu'
self.root = self.root
self.master = self.master
self.newfile = newfile
def launch(self):
self.Menubar = Menu(self.root)
#You have to include these 2 lines of code which were overwritten
self.textarea = ScrolledText(self.root, font=self.font)
self.textarea.pack(expand=True, fill=BOTH)
self.Filemenu = Menu(master=self.Menubar, tearoff=0)
self.Filemenu.add_command(label='New File...', command=self.newfile)
self.Menubar.add_cascade(label='File', menu=self.Filemenu)
self.root.config(menu=self.Menubar)

Using widget in a tab of ttk.Notebook?

I'm trying to display a simple Notebook widget with two tabs. Here's the code of what I tried without all the unnecessary code in between:
import tkinter as tk
from tkinter import ttk
color_bg = "gray20"
font = "Lucida Sans Typewriter"
DEFAULT_FONT_SIZE = 16
class Tab(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.pack()
self.textfield = tk.Text(
self.parent,
font = (font, DEFAULT_FONT_SIZE),
background = color_bg,
bd = 10,
relief = tk.FLAT)
self.textfield.pack(fill = tk.BOTH, expand = True)
class TabDisplay(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.pack(fill = tk.BOTH, expand = True)
self.tabholder = ttk.Notebook(parent)
self.tabholder.pack(fill = tk.BOTH, expand = True, side = tk.TOP)
self.viewtab = Tab(self.tabholder)
self.edittab = Tab(self.tabholder)
self.tabholder.add(self.viewtab, text = "View")
self.tabholder.add(self.edittab, text = "Edit")
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
# Set size and position
self.x_pos = 0
self.y_pos = 0
self.width = self.parent.winfo_screenwidth()
self.height = self.parent.winfo_screenheight()
self.parent.geometry(f"{self.width}x{self.height}+{self.x_pos}+{self.y_pos}")
self.parent.resizable = True
# Add Widgets
self.tabdisplay = TabDisplay(self)
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(fill = tk.BOTH, expand = True)
root.mainloop()
When I run this, it does not display an actual notebook. It just displays the two tabs below eachother. However, when I replace Tab(self.tabholder) with tk.Frame(self.tabholder) it functions perfectly (apart from not using the contents of the Tab() class).
Why does it not display correctly with my Tab() class? I have never had issues with classes that inherit from tk.Frame until I started using ttk.Notebook(). Is it an issue with ttk?
EDIT: I have since found out that the actual culprit is the Text widget within the Tab() class. Now my question is why does adding a widget break the notebook?
Think of a frame like a box. You create a box, and then you put widgets inside the box. Only, in your case you're creating widgets in the box but placing them outside the box when you add them to self.parent rather than self.
When you create a class that inherits from tk.Frame, every widget inside that class should be a direct child or descendant of self. In doing so, an instance of the class becomes a self-contained object that can be the child of any other widget.
For example:
class Tab(tk.Frame):
def __init__(self, parent):
...
self.textfield = tk.Text(
self,
...
... and:
class TabDisplay(tk.Frame):
def __init__(self, parent):
...
self.tabholder = ttk.Notebook(self)
...

Should I re-write my Tkinter in PyQt, or vice versa?

I wrote a Tkinter app, and I wanted to add screen snipping, so I found a separate program from GitHub (screen-snip) written in PyQt, which I was importing and using in my Tkinter app. Then I decided to combine the programs in order to ask an SO question about why they aren't totally working together. I've learned not to combine Tk and Qt.
So now my question is, should I rewrite my program in Qt, or Tk?
Which is better for this situation?
My currently mixed-Tk/Qt program works when you select an image file, but now the screen-snip portion with Qt class MyWidget(QtWidgets.QWidget): causes it to freeze and then crash.
I think the problem might be a result of mixing Qt with Tk, but I'm not sure.
I originally had two instances of tkinter running, which allowed me to get the screen ship with a new window, but caused trouble with the button window, so I replaced this by trying to use tk.Toplevel
class MyWidget(QtWidgets.QWidget):
def __init__(self, master):
super().__init__()
self.master = master
self.window = tk.Toplevel(self.master)
and that's when I ran into trouble. The widget no longer works at all, and the program crashes without any clues or errors. Any idea why?
Simplified Code:
import tkinter as tk
from tkinter import filedialog
from PIL import ImageTk, Image, ImageGrab
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
import numpy as np
import cv2
class ButtonImg:
def __init__(self, master):
self.newWindow = None
self.master = master
self.fontA = ("arial", 20, "bold")
self.canvas = tk.Canvas(height = 5)
self.canvas.pack()
self.button = tk.Button(bg="#61B5DA", height = 5, text = "Select Image",
font = self.fontA, command = self.changeImage)
self.button.pack(fill="both")
def changeImage(self):
print('open second window')
self.newWindow = tk.Toplevel(self.master)
img = AcquireImage(self.newWindow)
self.master.wait_window(self.newWindow)
print('close second window')
if img.image_selected: # check if image was selected
self.image = img.image_selected
self.button.configure(image=self.image, height=self.image.height())
class AcquireImage:
def __init__(self, master):
self.master = master
self.fontA = ("arial", 20, "bold")
self.frame = tk.Frame(master, bg="#96beed")
self.frame.pack(fill="both", expand=True)
self.button1 = tk.Button(self.frame, text="Select Image File", padx=5, pady=5, bg="#6179DA",
font = self.fontA, command =lambda: self.show_dialogs(1))
self.button1.grid(row=0, column=0, sticky="nsew")
self.button2 = tk.Button(self.frame, text="Get Screen Snip", padx=5, pady=5, bg="#6179DA",
font = self.fontA, command=lambda: self.show_dialogs(2))
self.button2.grid(row=0, column=1, sticky="nsew")
self.image_selected = None
def show_dialogs(self, method):
if method == 1:
ret = filedialog.askopenfilename() #filedialog.askopenfilename(initialdir='/home/user/images/')
if ret:
self.image_selected = ImageTk.PhotoImage(file = ret)
self.master.destroy()
elif method == 2:
newWin = MyWidget(self.master)
newWin.show()
ret = newWin.img
if ret:
self.image_selected = ImageTk.PhotoImage(file = ret)
class MyWidget(QtWidgets.QWidget):
def __init__(self, master):
super().__init__()
self.master = master
self.window = tk.Toplevel(self.master)
screen_width = self.thirdWin.winfo_screenwidth()
screen_height = self.thirdWin.winfo_screenheight()
self.setGeometry(0, 0, screen_width, screen_height)
self.setWindowTitle(' ')
self.begin = QtCore.QPoint()
self.end = QtCore.QPoint()
self.img = None
self.setWindowOpacity(0.3)
QtWidgets.QApplication.setOverrideCursor(
QtGui.QCursor(QtCore.Qt.CrossCursor)
)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
print('Capture the screen...')
self.show()
def getRect(self):
# a commodity function that always return a correctly sized
# rectangle, with normalized coordinates
width = self.end.x() - self.begin.x()
height = abs(width * 2 / 3)
if self.end.y() < self.begin.y():
height *= -1
return QtCore.QRect(self.begin.x(), self.begin.y(),
width, height).normalized()
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setPen(QtGui.QPen(QtGui.QColor('black'), 3))
qp.setBrush(QtGui.QColor(128, 128, 255, 128))
qp.drawRect(self.getRect())
def mousePressEvent(self, event):
self.begin = event.pos()
self.end = self.begin
self.update()
def mouseMoveEvent(self, event):
self.end = event.pos()
self.update()
def mouseReleaseEvent(self, event):
self.close()
rect = self.getRect()
self.img = ImageGrab.grab(bbox=(
rect.topLeft().x(),
rect.topLeft().y(),
rect.bottomRight().x(),
rect.bottomRight().y()
))
#self.img.save('capture.png')
self.img = cv2.cvtColor(np.array(self.img), cv2.COLOR_BGR2RGB)
cv2.imshow('Captured Image', self.img)
cv2.waitKey(0)
#cv2.destroyAllWindows()
if __name__ == '__main__':
root = tk.Tk()
app = ButtonImg(root)
root.mainloop()
As said in the comments, the best is to use a single GUI toolkit so you need either to rewrite your code for Qt or rewrite the snipping code using tkinter. I don't know Qt much so I cannot help you with the first option. However, the screenshot is actually taken using PIL, not some Qt specific method, so the snipping code can be rewritten in tkinter.
All you need is a fullscreen toplevel containing a canvas with a draggable rectangle, like in Drawing rectangle using mouse events in Tkinter. To make the toplevel fullscreen: toplevel.attributes('-fullscreen', True)
The toplevel needs to be partially transparent, which can be achieved with toplevel.attributes('-alpha', <value>). I am using Linux (with XFCE desktop environment) and I need to add toplevel.attributes('-type', 'dock') to make the transparency work.
All put together in a class, this give:
import sys
import tkinter as tk
from PIL import ImageGrab
import cv2
import numpy as np
class MyWidget(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.configure(cursor='cross')
if sys.platform == 'linux':
self.attributes('-type', 'dock') # to make transparency work in Linux
self.attributes('-fullscreen', True)
self.attributes('-alpha', 0.3)
self.canvas = tk.Canvas(self, bg='white')
self.canvas.pack(fill='both', expand=True)
self.begin_x = 0
self.begin_y = 0
self.end_x = 0
self.end_y = 0
self.canvas.create_rectangle(0, 0, 0, 0, outline='gray', width=3, fill='blue', tags='snip_rect')
self.canvas.bind('<ButtonPress-1>', self.mousePressEvent)
self.canvas.bind('<B1-Motion>', self.mouseMoveEvent)
self.canvas.bind('<ButtonRelease-1>', self.mouseReleaseEvent)
print('Capture the screen...')
def mousePressEvent(self, event):
self.begin_x = event.x
self.begin_y = event.y
self.end_x = self.begin_x
self.end_y = self.begin_y
self.canvas.coords('snip_rect', self.begin_x, self.begin_y, self.end_x, self.end_y)
def mouseMoveEvent(self, event):
self.end_x = event.x
self.end_y = event.y
self.canvas.coords('snip_rect', self.begin_x, self.begin_y, self.end_x, self.end_y)
def mouseReleaseEvent(self, event):
self.destroy()
self.master.update_idletasks()
self.master.after(100) # give time for screen to be refreshed so as not to see the blue box on the screenshot
x1 = min(self.begin_x, self.end_x)
y1 = min(self.begin_y, self.end_y)
x2 = max(self.begin_x, self.end_x)
y2 = max(self.begin_y, self.end_y)
img = ImageGrab.grab(bbox=(x1, y1, x2, y2))
self.img = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB)
cv2.imshow('Captured Image', self.img)
cv2.waitKey(0)
if __name__ == '__main__':
root = tk.Tk()
tk.Button(root, text='Snip', command=lambda: MyWidget(root)).pack()
root.mainloop()

Tkinter: Making 'classes' for buttons and labels

So, pretty much I have many different buttons and labels in a tkinter frame, and I all want them to have similar properties. Lets say I want all of them to have a foreground color of red, and have a transparent background (can I even do that? This transparent background is just for buttons.)
Can I have a class for buttons (I think this is in ttk, but it would be preferable if it wasn't) similar to css that would make all my buttons and labels have red text?
You could extend Button class and define its properties as you wish. For example:
from tkinter import *
class MyButton(Button):
def __init__(self, *args, **kwargs):
Button.__init__(self, *args, **kwargs)
self['bg'] = 'red'
root = Tk()
root.geometry('200x200')
my_button = MyButton(root, text='red button')
my_button.pack()
root.mainloop()
from tkinter import *
class My_Button(Button):
def __init__(self, text, row, col, command, color=None, **kwargs):
self.text = text
self.row = row
self.column = col
self.command = command
self.color = color
super().__init__()
self['bg'] = self.color
self['text'] = self.text
self['command'] = self.command
self.grid(row=self.row, column=self.column)
def dothings():
print('Button class worked')
window = Tk()
window.title("Test Button Class")
window.geometry('400x200')
btn1 = My_Button("Click Me", 0, 0, dothings, 'green')
window.mainloop()

Categories

Resources