Tkinter LabelFrame not appearing when using .grid - python

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.

Related

Can't change styles for classes inheriting from ttk.Frame

So I want to just change the background of my frames so I can layout them properly and I can't seem to change styles for my frames.
style.configure('TFrame', background='red')
style.configure('Blue.TFrame', background='blue')
main_window = MainWindow(root, style='Blue.TFrame')
The above code resuslts in a red background, while I need it to change to blue, and if I don't change TFrame background, there is just no backgound color at all.
My MainWindow class does inherit from ttk.Frame, so I don't know if that is what's causing it...
Minimal Reproducible Example:
import tkinter as tk
from tkinter import ttk
class MainWindow(ttk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent)
self.parent = parent
# Search_company shortcut
self.search_comp = ttk.Entry(self)
self.search_comp.grid(row=0, column=0)
def main():
root = tk.Tk()
root.state('zoomed')
# Configuring styles
style = ttk.Style()
style.configure('TFrame', background='red')
style.configure('Blue.TFrame', background='blue')
main_window = MainWindow(root, style='Blue.TFrame')
main_window.grid(row=0, column=1, sticky='nsew')
root.mainloop()
if __name__ == "__main__":
main()
You aren’t passing the style option to the superclass. It needs to be this:
super().__init__(parent, *args, **kwargs)

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

How to fix the Qtab and create more button and text line in the tab area

Why my Address QLabel not fix in tab1
How to fix the QLabel in the tab1
Why the button is fix width
I wall to create different tab have different button or table or text line
Code:
import os, sys, subprocess
from PyQt4 import QtGui, QtCore
class MainWindow(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.setGeometry(0,0, 900,600)
self.setWindowTitle("GUI")
tab_widget = QtGui.QTabWidget()
tab1 = QtGui.QWidget()
tab2 = QtGui.QWidget()
tab3 = QtGui.QWidget()
p1_vertical = QtGui.QVBoxLayout(tab1)
p2_vertical = QtGui.QVBoxLayout(tab2)
p3_vertical = QtGui.QVBoxLayout(tab3)
tab_widget.addTab(tab1, "tab1")
tab_widget.addTab(tab2, "tab2")
tab_widget.addTab(tab3, "tab3")
button1 = QtGui.QPushButton("button1")
p1_vertical.addWidget(button1)
button2 = QtGui.QPushButton("button2")
p1_vertical.addWidget(button2)
button3 = QtGui.QPushButton("button3")
p1_vertical.addWidget(button3)
button4 = QtGui.QPushButton("button4")
p1_vertical.addWidget(button4)
button3.move(50,100)
button3.resize(50,50)
vbox = QtGui.QVBoxLayout()
#vbox.addWidget(menu_bar)
vbox.addWidget(tab_widget)
self.setLayout(vbox)
label1 = QtGui.QLabel("Address", self)
label1.move(20,70)
edit = QtGui.QLineEdit(self)
edit.setDragEnabled(True)
edit.move(20,95)
edit.resize(150,30)
I think you want to look into QtGui.QGridLayout (http://pyqt.sourceforge.net/Docs/PyQt4/qgridlayout.html). This lets you easily align widgets and layouts on a grid and also specify the size of each column.
In your case this would be something like:
# Create Layout
self.grid = QtGui.QGridLayout()
# Add widgets
self.grid.addWidget(label1,1,1)
self.grid.addWidget(edit,2,1)
If you want a column have a certain width, you can use
self.grid.setColumnMinimumWidth(rowIndex,width)
self.grid.setColumnStretch(rowIndex,stretchFactor)
stretchFactor=1 will cause the row to not be stretched in case of an resizeEvent.
If you only want to set the size of a particular QWidget, you can use
widget.setMaximumSize(width,height)
widget.setMinimumSize(width,height)
I hope this helps.

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