I want to use the Object-oriented concept to divide the UI into four sections(layout) with different sizes. So, each section contains different items(Plot, camera frame, Image, and label)
screen layout example
I have a GUI class:
class GUI(tk.Tk):
def __init__(self):
super().__init__()
self.title("Hello World")
self.resizable(width=False, height=False) # Don't resize UI
self.geometry('{}x{}'.format(500, 500)) # Size of UI
What is the solution to implement that?
I believe I should have a Layout class and the object of this should change its charachteristic.
class layout(tk.Frame):
def __init__(self,parent, *args, **kwargs):
tk.Frame.__init__(self,parent, *args, **kwargs)
Related
I would like to create a contractible panel in a GUI, using the Python package tkinter.
My idea is to create a decorator for the tkinter.Frameclass, adding a nested frame and a "vertical button" which toggles the nested frame.
Sketch: (Edit: The gray box should say Parent of contractible panel)
I got it to toggle just fine, using the nested frame's grid_remove to hide it and then move the button to the left column (otherwise occupied by the frame).
Now I want to be able to use it like any other tkinter.Frame, but let it target the nested frame. Almost acting like a proxy for the nested frame. For example, adding a tkinter.Label (the green Child component in the sketch) to the decorator should add the label to the nested frame component (light yellow tk.Frame in the sketch) not the decorator itself (strong yellow ContractiblePanel in the sketch).
Minimal example: (omitting the toggling stuff and any "formatting"):
(Here's a published (runnable) Repl project)
import tkinter
class ContractiblePanel(tkinter.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self._panel = tkinter.Frame(self)
self._toggle = tkinter.Button(self, text='<', command=self._toggle_panel)
self.grid(row=0, column=0, sticky='nsw')
self._panel.grid(row=0, column=0, sticky='nsw')
self._toggle.grid(row=0, column=1, sticky='nsw')
def _toggle_panel(self):
# ...
if __name__ == '__main__':
root = tkinter.Tk()
root.geometry('128x128')
contractible_panel = ContractiblePanel(root)
Forwarding configuration calls is just overriding the config method I guess?
class ContractiblePanel(tkinter.Frame):
# ...
def config(self, **kwargs):
self._panel.config(**kwargs)
# ...
contractible_panel.config(background='blue')
But I would like to be able to add a child component into the nested panel frame by
label_in_panel = tkinter.Label(contractible_panel, text='yadayada')
How do I get the ContractiblePanel object to act like a proxy to its member _panel, when adding child components?
What other methods/use cases should I consider? I am quite new to tkinter and thus expect the current implementation to break some common practices when developing tkinter GUIs.
This is an interesting question. Unfortunately, tkinter really isn't designed to support what you want. I think it would be less complicated to simply expose the inner frame and add widgets to it.
That being said, I'll present one possible solution. It's not implemented as a python decorator, but rather a custom class.
The difficulty is that you want the instance of the custom class to represent the outer frame in one context (for example, when packing it in your UI) and the inner frame in another context (when adding child widgets to it)
The following solution solves this by making the instance be the inner frame, and then overriding pack,place, and grid so that they operates on the outer frame. This works fine, with an important exception: you cannot use this class directly inside a notebook or embedded in a text widget or canvas.
I've used colors and borders so it's easy to see the individual components, but you can remove the colors in production code, obviously. Also, I used a label instead of a button since I created the screenshot on OSX where the background color of a button can't be changed.
import tkinter as tk
class ContractiblePanel(tk.Frame):
def __init__(self, parent, **kwargs):
self._frame = tk.Frame(parent, **kwargs)
super().__init__(self._frame, bd=2, relief="solid", bg="#EFE4B0")
self._button = tk.Label(
self._frame, text="<", bg="#00A2E8", bd=2,
relief="solid", font=("Helvetica", 20), width=4
)
self._frame.grid_rowconfigure(0, weight=1)
self._frame.grid_columnconfigure(0, weight=1)
self._button.grid(row=0, column=1, sticky="ns", padx=4, pady=4)
super().grid(row=0, column=0, sticky="nsew", padx=4, pady=4)
self._button.bind("<1>", lambda event: self.toggle())
def collapse(self):
super().grid_remove()
self._button.configure(text=">")
def expand(self):
super().grid()
self._button.configure(text="<")
def toggle(self):
self.collapse() if self.winfo_viewable() else self.expand()
def pack(self, **kwargs):
# override to call pack in the private frame
self._frame.pack(**kwargs)
def grid(self, **kwargs):
# override to call grid in the private frame
self._frame.grid(**kwargs)
def place(self, **kwargs):
# override to call place in the private frame
self._frame.place(**kwargs)
root = tk.Tk()
root.geometry("400x300")
cp = ContractiblePanel(root, bg="yellow", bd=2, relief="raised")
cp.pack(side="left", fill="y", padx=10, pady=10)
label = tk.Label(cp, text="Child component", background="#22B14C", height=3, bd=2, relief="solid")
label.pack(side="top", expand=True, padx=20, pady=20)
root.mainloop()
First of all it is kinda gross to use this code and it's very confusing. So I'm really not sure if you really want to take this route. However, it is possible to achieve it.
The basic idea is to have a wrapper and to pretend the wrapper is the actual object you can lie with __str__ and __repr__ about what the class really is. That is not what a proxy means.
class WrapperClass:
def __init__(self, master=None, **kwargs):
self._wrapped_frame = tk.Frame(master, **kwargs)
self._panel = tk.Frame(self._wrapped_frame)
self._toggle = tk.Button(self._wrapped_frame, text='<', command=self._toggle_panel)
self._wrapped_frame.grid(row=0, column=0, sticky='nsw')
self._panel.grid(row=0, column=0, sticky='nsw')
self._toggle.grid(row=0, column=1, sticky='nsw')
return None
def _toggle_panel(self):
print('toggle')
def __str__(self):
return self._panel._w
__repr__ = __str__
You can do even more confusing things by delegate the lookup-chain to the _wrapped_frame inside the WrapperClass this enables you to call on the instance of WrapperFrame() methods like pack or every other method. It kinda works similar for inheritance with the difference that by referring to the object, you will point to different one.
I don't recommend using this code by the way.
import tkinter as tk
NONE = object()
#use an object here that there will no mistake
class WrapperClass:
def __init__(self, master=None, **kwargs):
self._wrapped_frame = tk.Frame(master, **kwargs)
self._panel = tk.Frame(self._wrapped_frame)
self._toggle = tk.Button(self._wrapped_frame, text='<', command=self._toggle_panel)
self._wrapped_frame.grid(row=0, column=0, sticky='nsw')
self._panel.grid(row=0, column=0, sticky='nsw')
self._toggle.grid(row=0, column=1, sticky='nsw')
return None
def _toggle_panel(self):
print('toggle')
def __str__(self):
return self._panel._w
__repr__ = __str__
def __getattr__(self, name):
#when wrapper class has no attr name
#delegate the lookup chain to self.frame
inreturn = getattr(self._wrapped_frame, name, NONE)
if inreturn is NONE:
super().__getattribute__(name)
return inreturn
root = tk.Tk()
wrapped_frame = WrapperClass(root, bg='red', width=200, height=200)
root.mainloop()
I'm using Qt Designer for design GUI to use in python, after designing my desired UI in Qt Designer, convert it to python code and then I changed generated code to do some action in my python code, but if I changed the UI with Qt Designer and convert it to python code again, I lost my previous changes on my code.
how can I solve the problem?
can we Spreading a Class Over Multiple Files in python to write code in other files?
To avoid having these problems it is advisable not to modify this file but to create a new file where we implement a class that uses that design.
For example, suppose you have used the MainWindow template in the design.ui file, then convert it to Ui_Design.py like to the following structure:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
[...]
def retranslateUi(self, MainWindow):
[...]
Then we will create a new file that we will call logic.py where we will create the file that handles the logic and that uses the previous design:
class Logic(QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs):
QMainWindow.__init__(self, *args, **kwargs)
self.setupUi(self)
So even if you modify the design and generate the file again .py you will not have to modify the file of the logic.
To generalize the idea we must have the following rules but for this the logic class must have the following structure:
class Logic(PyQtClass, DesignClass):
def __init__(self, *args, **kwargs):
PyQtClass.__init__(self, *args, **kwargs)
self.setupUi(self)
PyQtClass: This class depends on the design chosen.
Template PyQtClass
─────────────────────────────────────────────
Main Window QMainWindow
Widget QWidget
Dialog with Buttons Bottom QDialog
Dialog with Buttons Right QDialog
Dialog with Without Buttons QDialog
DesignClass: The name of the class that appears in your design.
The advantage of this implementation is that you can implement all the logic since it is a widget, for example we will implement the solution closing pyqt messageBox with closeevent of the parent window :
class Logic(QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs):
QMainWindow.__init__(self, *args, **kwargs)
self.setupUi(self)
def closeEvent(self, event):
answer = QtWidgets.QMessageBox.question(
self,
'Are you sure you want to quit ?',
'Task is in progress !',
QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)
if answer == QtWidgets.QMessageBox.Yes:
event.accept()
else:
event.ignore()
The easiest way is to use the *.ui file directly in the python code, you don't need convert to *.py file every time you change the ui.
you can use this pseudo code in your project.
# imports
from PyQt5 import uic
# load ui file
baseUIClass, baseUIWidget = uic.loadUiType("MainGui.ui")
# use loaded ui file in the logic class
class Logic(baseUIWidget, baseUIClass):
def __init__(self, parent=None):
super(Logic, self).__init__(parent)
self.setupUi(self)
.
.
.
.
def main():
app = QtWidgets.QApplication(sys.argv)
ui = Logic(None)
ui.showMaximized()
sys.exit(app.exec_())
I am trying to build a GUI with the main Frame having nested frame containing header label (created with different class).
In below snippet, I am expecting frame created by FrameHeader class to be inside MainWindow.container Frame (while initialization of MainWindow.container attribute, container attribute is passed as parent).
Still, when the below code is run, FrameHeader frame is at the bottom after container frame, instead of being inside the container frame.
I am new to tkinter, can someone help me out here, what am I missing while going between different classes?
from tkinter import *
class FrameHeader(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, bg='red',relief=RAISED, borderwidth=2)
self.pack(fill=BOTH, side=TOP)
lblTitle = Label(self, text='Welcome to the Program!')
lblTitle.pack(fill=BOTH)
class MainWindow(Tk):
def __init__(self,*args):
Tk.__init__(self,*args)
self.geometry('400x300')
# Main Container
container=Frame(self, bg='black')
container.pack(side=TOP, expand=TRUE, fill=BOTH)
frameHeader=FrameHeader(container, self)
if __name__=='__main__':
mainWindow=MainWindow()
mainWindow.mainloop()
You are neglecting to pass parent to Frame.__init, so the parent of FrameHeader defaults to the root window rather than the container.
The code needs to be this:
Frame.__init__(self, parent, bg='red',relief=RAISED, borderwidth=2)
So I came across a Python3 tkinter GUI code snippet and it doesn't have anything like root = Tk() but IT RUNS! I read this and it is really helpful. But my question is, if the tk window and interpreter is initiated when I create my first widget, how can I add more widgets to the root without specifying it? aka. What should I do when I want to add more widgets to the same program / same window, since I don't have a variable like root to store the root window object?
By the way, there was a controller class like this:
class Controller(tk.Tk):
def __init__ (self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
parentObj = tk.Frame(self)
self.allFrames = {}
...
Does it mean that the parentObj frame is the windows / outmost layer of frame in this app? How do I understand this class definition here? What is tk.Tk.__init__(self, *args, **kwargs) here for?
Controller is a subclass of tk.Tk. Controller is identical to tk.Tk but with enhancements. Thus, doing something=Controller(...) serves the same purpose as something=tk.Tk().
What should I do when I want to add more widgets to the same program / same window,
Use self as the parent if inside the class, use the instance of the class if outside.
class Controller(tk.Tk):
def __init__ (self, *args, **kwargs):
...
self.some_widget = tk.Label(self, ...)
... and ...
root = Controller()
some_other_widget = tk.Label(root, ...)
Does it mean that the parentObj frame is the windows / outmost layer of frame in this app?
No. The outmost "layer" is the instance of Controller. That is the root window. parentObj lives inside that window.
What is tk.Tk.__init__(self, *args, **kwargs) here for?
This is just the standard python way for a subclass to initialize its parent class.
So when ever I initialize my Gui class and then pack() my label and button widget the nice frame stylization I've configured with the frames they exist in breaks. Why is this if you comment out the pack of the button and label widgets it's exactly what I want it to look like.
class Gui(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.parent.wm_title("Conflict found")
self.TV = 'Hello'
# Creation
self.statusbar = statusbar(self, bg="black", height=100, width=300)
self.main = Main(self, bg="grey", height=50, width=300)
# Packing
self.statusbar.pack(side="top", expand=True)
self.statusbar.label.pack()
self.main.pack(side="bottom", expand=True)
self.main.button.pack()
def quit(self):
self.parent.destroy()
class Main(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.button = tk.Button(self, text="Exit", command=self.quit)
class statusbar(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.label = tk.Label(self, text=self.parent.TV, fg='white')
The short answer is that this is how tkinter is designed to work. When you use pack or grid, tkinter will cause the parent window to grow or shrink to fit its children.
This is what you want 99.999% of the time because it lets you focus on the size of the widgets based on character sizes (or pixels when it matters, such as with images) and the GUI will be just the right size.
When you pick a specific size for frames and windows, your program won't look right if the user has different fonts, or a different OS, or a monitor with a different resolution, or the user tries to grow or shrink the window manually.
There are ways around this behavior, but you almost never want to turn this behavior off. The best solution is to focus on your interior widgets and let tkinter worry about the window size.
Instead of thinking "I need a statusbar that is 100 pixels tall and 300 pixels wide", think "I need a statusbar that has room for at least 20 characters, and fills the window horizontally". Tkinter will then do the right thing no matter what font, resolution, or OS you're using.
The height= and width= of a Frame normally only apply if the Frame is empty. As soon as you add any child widgets to it, its size gets recalculated to be the minimum needed to hold all the children. To avoid this, you can call .pack_propagate(0) on the Frame (or .grid_propagate(0) if you're using .grid() on the children).