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

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()

Related

Tkinter LabelFrame not appearing when using .grid

I am trying to create a UI using Tkinter that uses LabelFrames and I want to lay them out in a grid. However, they are only appearing when I use the .pack method. I'm not sure if this is because they are a container more than a widget but if someone could help me out that would be great.
from tkinter import ttk
from tkinter import *
from tkinter.ttk import *
class MainWindow(tkinter.Tk):
def __init__(self, parent):
tkinter.Tk.__init__(self, parent)
self.parent = parent
self.initialize()
def initialize(self):
self.geometry("788x594")
self.resizable(False, False)
self.title("Testing UI")
Btn = Button(self, text = "Test")
Btn.grid(column = 0, row = 1)
testFrame = LabelFrame(self, text = "Test")
testFrame.grid(column = 0, row = 2, sticky="EW")
if __name__ == "__main__":
app = MainWindow(None)
app.mainloop()
And this is the output I get
Output
The frame is appearing. Because there's nothing in the frame, and because you haven't set a size for the frame, it is only 1 pixel wide and 1 pixel tall.

simpledialog.askstring window from tkinter freezes

I have a problem using simpledialog.askstring from tkinter, it opens two pop up windows, one of them is the regular one to input a value, but the other is a blank window that freezes completely and you have to force the cloosure of the window, enter image description hereas shown in the image in the link. I'm using spyder.
What could be happening?
This is what I have so far:
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtWidgets import QApplication, QMainWindow, QComboBox,
QLabel, QPushButton, QGroupBox
import tkinter as tk
from tkinter import simpledialog
from PIL import ImageGrab
import sys
import cv2
import numpy as np
# import imageToString
import pyperclip
import webbrowser
import pytesseract
pytesseract.pytesseract.tesseract_cmd =
r"C:\Users\amc\AppData\Local\Programs\Tesseract-OCR\tesseract.exe"
class Communicate(QObject):
snip_saved = pyqtSignal()
class MyWindow(QMainWindow):
def __init__(self, parent=None):
super(MyWindow, self).__init__()
self.win_width = 340
self.win_height = 200
self.setGeometry(50, 50, self.win_width, self.win_height)
self.setWindowTitle("PSD Generator")
self.initUI()
def initUI(self):
#Define buttons
self.searchOpen = QPushButton(self)
self.searchOpen.setText("Digitilize Graph")
self.searchOpen.move(10,75)
self.searchOpen.setFixedSize(150,40)
self.searchOpen.clicked.connect(self.snip_graph_clicked)
self.copyPartNum = QPushButton(self)
self.copyPartNum.setText("Snip Table")
self.copyPartNum.move((self.win_width/2)+10, 75)
self.copyPartNum.setFixedSize(150,40)
self.copyPartNum.clicked.connect(self.snip_table_clicked)
self.notificationBox = QGroupBox("Notification Box", self)
self.notificationBox.move(10,135)
self.notificationBox.setFixedSize(self.win_width-20,55)
self.notificationText = QLabel(self)
self.notificationText.move(20, 145)
self.reset_notif_text()
def snip_graph_clicked(self):
self.snipWin = SnipWidget(False, True, self)
self.snipWin.notification_signal.connect(self.reset_notif_text)
self.snipWin.show()
self.notificationText.setText("Snipping... Press ESC to quit snipping")
self.update_notif()
def snip_table_clicked(self):
self.snipWin = SnipWidget(True, False, self)
self.snipWin.notification_signal.connect(self.reset_notif_text)
self.snipWin.show()
self.notificationText.setText("Snipping... Press ESC to quit snipping")
self.update_notif()
def reset_notif_text(self):
self.notificationText.setText("Idle...")
self.update_notif()
def define_notif_text(self, msg):
print('notification was sent')
self.notificationText.setText('notification was sent')
self.update_notif()
def update_notif(self):
self.notificationText.move(20, 155)
self.notificationText.adjustSize()
class SnipWidget(QMainWindow):
notification_signal = pyqtSignal()
def __init__(self, copy_table, scan_graph, parent):
super(SnipWidget, self).__init__()
self.copy_table = copy_table
self.scan_graph = scan_graph
root = tk.Tk()# instantiates window
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
self.setGeometry(0, 0, screen_width, screen_height)
self.setWindowTitle(' ')
self.begin = QtCore.QPoint()
self.end = QtCore.QPoint()
self.setWindowOpacity(0.3)
self.is_snipping = False
QtWidgets.QApplication.setOverrideCursor(
QtGui.QCursor(QtCore.Qt.CrossCursor)
)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.c = Communicate()
self.show()
if self.copy_table == True:
# the input dialog
BH_Ref = simpledialog.askstring(title="New PSD",prompt="Borehole Number Ref:")
Depth = simpledialog.askstring(title="New PSD",prompt="Sample depth (m):")
self.c.snip_saved.connect(self.searchAndOpen)
if self.scan_graph == True:
# the input dialog
BH_Ref = simpledialog.askstring(title="New PSD",prompt="Borehole Number Ref:")
Depth = simpledialog.askstring(title="New PSD",prompt="Sample depth (m):")
self.c.snip_saved.connect(self.IdAndCopy)
def paintEvent(self, event):
if self.is_snipping:
brush_color = (0, 0, 0, 0)
lw = 0
opacity = 0
else:
brush_color = (128, 128, 255, 128)
lw = 3
opacity = 0.3
self.setWindowOpacity(opacity)
qp = QtGui.QPainter(self)
qp.setPen(QtGui.QPen(QtGui.QColor('black'), lw))
qp.setBrush(QtGui.QColor(*brush_color))
qp.drawRect(QtCore.QRect(self.begin, self.end))
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape:
print('Quit')
QtWidgets.QApplication.restoreOverrideCursor();
self.notification_signal.emit()
self.close()
event.accept()
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):
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())
self.is_snipping = True
self.repaint()
QtWidgets.QApplication.processEvents()
img = ImageGrab.grab(bbox=(x1, y1, x2, y2))
self.is_snipping = False
self.repaint()
QtWidgets.QApplication.processEvents()
img = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2GRAY)
self.snipped_image = img
QtWidgets.QApplication.restoreOverrideCursor();
self.c.snip_saved.emit()
self.close()
self.msg = 'snip complete'
self.notification_signal.emit()
def searchAndOpen(self):
img_str = self.imgToStr(self.snipped_image)
msg_str = f'Text in snip is {img_str}'
def IdAndCopy(self):
img_str = self.imgToStr(self.snipped_image)
pyperclip.copy(img_str)
def find_str(self, image_data):
img = image_data
h,w = np.shape(img)
asp_ratio = float(w/h)
img_width = 500
img_height = int(round(img_width/asp_ratio))
desired_image_size = (img_width,img_height)
img_resized = cv2.resize(img, desired_image_size)
imgstr = str(pytesseract.image_to_string(img_resized))
print(imgstr)
return imgstr
def imgToStr(self, image):
# img_str = imageToString.main(image)
img_str = self.find_str(image)
return img_str
def window():
app = QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(app.exec_())
window()

How to set width QVboxLayout

import sys
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import QApplication,QDialog,QPushButton,QVBoxLayout,QWidget
class Main(QDialog):
def __init__(self):
super(Main, self).__init__()
self.ui()
# Group Of Drage Event
def mousePressEvent(self,event):
self.offset = event.pos()
def mouseMoveEvent(self, e):
x = e.globalX()
y = e.globalY()
x_w = self.offset.x()
y_w = self.offset.y()
self.move(x - x_w, y - y_w)
def ui(self):
# TitleBar
self.setWindowFlags(Qt.FramelessWindowHint)
# Window Size
self.setGeometry(600,300,400,500)
# Window Background Color
self.BackGroundColor = QPalette()
self.BackGroundColor.setColor(QPalette.Background, QColor(255,255,255))
self.setPalette(self.BackGroundColor)
# NavBar Button
self.btn = QPushButton('Test',self)
self.btn1 = QPushButton("Test1",self)
# NavBar Layout
self.layout = QVBoxLayout(self)
self.layout.addWidget(self.btn)
self.layout.addWidget(self.btn1)
self.layout.set
self.setLayout(self.layout)
# Close img
self.closeBtn = QPushButton(self)
self.closeBtn.setGeometry(368,0,32,32)
self.closeBtn.setFlat(True)
self.closeBtn.setStyleSheet('QPushButton{background-color: rgba(0,0,0,0.0)}')
self.closeBtn.setIcon(QIcon('img/close.png'))
self.closeBtn.setIconSize(QSize(10,10))
self.closeBtn.clicked.connect(QCoreApplication.instance().quit)
# Maximize icon
self.maxBtn = QPushButton(self)
self.maxBtn.setGeometry(self,336,0,32,32)
self.maxBtn.setFlat(True)
self.maxBtn.setStyleSheet('QPushButton{background-color: rgba(0,0,0,0.0)}')
self.maxBtn.setIcon(QIcon('img/max.png'))
self.maxBtn.setIconSize(QSize(14,14))
# Minimize Incon
self.minBtn = QPushButton(self)
self.minBtn.setGeometry(304,0,32,32)
self.minBtn.setFlat(True)
self.minBtn.setStyleSheet('QPushButton{background-color: rgba(0,0,0,0.0)}')
self.minBtn.setIcon(QIcon('img/min.png'))
self.minBtn.setIconSize(QSize(10,10))
def main():
app = QApplication()
win = Main()
win.show()
app.exec_()
if __name__ == "__main__":
main()
I want to fixed navbar on the left. So, I create instance of QVBoxLayout and add widget to my Layout. and I had searched google, stackoverflow. i Don't get any information about my problem
but I don't know how to set layout widget. please teach me. Thank you.
if you don't understand my text please tell me i will describe that
Version:
PySide 5.14.2.1
Python 3.7.7
The layouts are not visual elements, so they do not have any geometric element associated with them, such as the width size, the layout is a handle of size and positions.
In this case the solution is to establish a container with a fixed size and in that container place the buttons with the help of a layout:
class Main(QDialog):
def __init__(self):
super(Main, self).__init__()
self.ui()
# Group Of Drage Event
def mousePressEvent(self, event):
self.offset = event.pos()
def mouseMoveEvent(self, e):
x = e.globalX()
y = e.globalY()
x_w = self.offset.x()
y_w = self.offset.y()
self.move(x - x_w, y - y_w)
def ui(self):
# TitleBar
self.setWindowFlags(Qt.FramelessWindowHint)
# Window Size
self.setGeometry(600, 300, 400, 500)
# Window Background Color
self.BackGroundColor = QPalette()
self.BackGroundColor.setColor(QPalette.Background, QColor(255, 255, 255))
self.setPalette(self.BackGroundColor)
# NavBar Button
self.btn = QPushButton("Test")
self.btn1 = QPushButton("Test1")
left_container = QWidget(self)
left_container.setFixedWidth(100)
# NavBar layout
self.layout = QVBoxLayout(left_container)
self.layout.addWidget(self.btn)
self.layout.addWidget(self.btn1)
hlay = QHBoxLayout(self)
hlay.addWidget(left_container)
hlay.addStretch()
# Close img
self.closeBtn = QPushButton(self)
self.closeBtn.setGeometry(368, 0, 32, 32)
self.closeBtn.setFlat(True)
self.closeBtn.setStyleSheet("QPushButton{background-color: rgba(0,0,0,0.0)}")
self.closeBtn.setIcon(QIcon("img/close.png"))
self.closeBtn.setIconSize(QSize(10, 10))
self.closeBtn.clicked.connect(QCoreApplication.instance().quit)
# Maximize icon
self.maxBtn = QPushButton(self)
self.maxBtn.setGeometry(336, 0, 32, 32)
self.maxBtn.setFlat(True)
self.maxBtn.setStyleSheet("QPushButton{background-color: rgba(0,0,0,0.0)}")
self.maxBtn.setIcon(QIcon("img/max.png"))
self.maxBtn.setIconSize(QSize(14, 14))
# Minimize Incon
self.minBtn = QPushButton(self)
self.minBtn.setGeometry(304, 0, 32, 32)
self.minBtn.setFlat(True)
self.minBtn.setStyleSheet("QPushButton{background-color: rgba(0,0,0,0.0)}")
self.minBtn.setIcon(QIcon("img/min.png"))
self.minBtn.setIconSize(QSize(10, 10))

canvas.move not moving in python

I have a circle that I want to move around a map, I am in the early stages, but I've looked in many places and can't figure out why the circle isn't moving. my code is
import Tkinter as Tkinter
class gameScreen:
def moveup(self, event):
self.canvas.move(self.char, -100, 0)
self.canvas.focus(self.char)
self.canvas.update()
def __init__(self, master):
self.master = master
master.title("Game")
master.resizable(width=False, height=False)
self.img = tkinter.PhotoImage(file = "platformer.gif")
self.canvas = tkinter.Canvas(master, width=self.img.width(),
height=self.img.height())
self.canvas.pack(expand="YES",fill="both")
self.canvas.create_image(0, 0,anchor="nw", image = self.img)
self.char = tkinter.PhotoImage(file = "hero.gif")
self.canvas.create_oval(0, 0, 50, 50, fill="red")
self.x = 0
self.y = 0
master.bind("<Up>", self.moveup)
root = tkinter.Tk()
my_gui = gameScreen(root)
root.mainloop()`
You have to give the move method an id or tag for an object on the canvas. The id is returned when you create the canvas item.
class gameScreen:
def moveup(self, event):
...
self.canvas.move(self.canvas_item, -100, 0)
...
def __init__(self, master):
...
self.canvas_item = self.canvas.create_oval(...)
...

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