Change widget focus by clicking widget [duplicate] - python-3.x

This question already has an answer here:
how to accept/ignore QKeyEvent
(1 answer)
Closed 3 years ago.
I have two PyQt5 widgets and both need keyboard input. Initially, one widget has the setFocusPolicy set to QtCore.Qt.StrongFocus, but when both widgets has this property activated, both of them get the input. I would like to initially set the input in one of them and if the user clicks in the other widget, the focus would be changed to the clicked widget or vice versa.
MRE:
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtWidgets import QOpenGLWidget, QWidget
import sys
class Renderizador(QOpenGLWidget):
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_W:
print("OpenGL")
super().keyPressEvent(event)
class Diedrico(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
def paintEvent(self, event):
qp = QPainter(self)
qp.setPen(QPen(Qt.black))
qp.drawRect(0, 0, 1000, 1000) # Marco
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_W:
print("Widget")
super().keyPressEvent(event)
class UiVentana(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(UiVentana, self).__init__(parent)
self.resize(1500, 1015)
self.setFixedSize(1500, 1015)
self.statusBar().setSizeGripEnabled(False)
self.widget_central = QtWidgets.QWidget(self)
self.Renderizador = Renderizador(self.widget_central)
self.Renderizador.setGeometry(QtCore.QRect(0, 0, 1000, 1000))
self.Renderizador.setFocusPolicy(QtCore.Qt.StrongFocus)
visor = QtWidgets.QWidget(self.widget_central)
visor.setGeometry(1010, 510, 470, 460)
self.scene = QtWidgets.QGraphicsScene(visor)
self.view = QtWidgets.QGraphicsView(self.scene)
self.diedrico = Diedrico(visor)
self.diedrico.setFixedSize(470, 460)
self.view.setFocusPolicy(QtCore.Qt.StrongFocus)
self.scene.addWidget(self.diedrico)
self.setCentralWidget(self.widget_central)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_W:
print("Ui")
super().keyPressEvent(event)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
ui = UiVentana()
ui.show()
sys.exit(app.exec_())

Based on your code, I'm not getting key events from the Diedrico widget, but only from the Renderizador and UiVentana instancies; with your implementation it seems very unlikely that you get key events from both the Renderizador and Diedrico widgets, since their parent hierarchy is completely different and they actually are members of different "physical" windows.
If you really get the Widget and OpenGL outputs from a single key event, it might be a bug, but, frankly, I doubt that, and that's mostly because there are some issues in your implementation, mostly due to confusing parent/child relationship.
If that's not the case (meaning that you're getting the key events from Renderizador and UiVentana), the explanation is simple: a QOpenGLWidget, as any basic QWidget class, doesn't process nor "consume" a key event; as soon as you call the base implementation with super() the event is propagated to its parents (UiVentana, in your case). If you want to stop the event propagation, just return without calling the base implementation of keyPressEvent.
Finally, some notes about your example.
When you want to add a widget to a scene, it must have a parent that is already embedded in the scene or it shouldn't have a parent at all (as in a top level widget). In your code you created the Diedrico widget setting its parent to a child of the "widget_central", which at that point is a widget without no parent (meaning that it would be a top level widget, as in a "window"): no matter what you do afterwards (setting it as the central widget), the topmost parent has not been embedded, and you can't add any of its child to a scene.
Qt itself warns about this when executing your code:
StdErr: QGraphicsProxyWidget::setWidget: cannot embed widget 0x911ae78 which is not a toplevel widget, and is not a child of an embedded widget
Then, you created the view, but you never actually add it to the main window or any of its children. You can see the Diedrico instance only because of the aforementioned problem: the widget is added to the main widget because of the parent set in the class initialization, but it's never added to the scene.
Be very careful when initializing widgets and setting parents, expecially when you're going to embed them into a QGraphicsScene: QWidgets added to a scene are actually QGraphicsProxyWidgets, not "actual" widgets, and some special care is required when dealing with them.

Related

Proper way of managing multiple windows in PySide?

I have this app where I have several settings windows that open when buttons from the main window are clicked. The windows are application modal, so only one is open at a time. I have two ideas as to how to manage them, but I'm not sure which one would be the proper way to do it. I don't particularly care how the values are stored, as long as I can pass them to other windows in the app and do stuff with them.
MainWindow class Option 1:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
central = QWidget()
layout = QVBoxLayout()
button = QPushButton('Show window')
layout.addWidget(button)
window = OtherWindow()
button.clicked.connect(window.show)
# I can pull the settings and pass them on to other windows if needed.
self.setCentralWidget(central)
MainWindow class Option 2:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.other_settings = {}
button = QPushButton('Show window')
button.clicked.connect(self.show_other)
def show_other(self):
other_window = OtherWindow()
if other_window.exec():
self.other_settings.update(other_window.settings)
OtherWindow class:
class OtherWindow(QDialog):
def __init__(self):
super().__init__()
self.settings = {}
# widgets
box = QSpinBox(objectName='spinbox')
box.valueChanged.connect(self.save_settings)
# and so on ...
def save_settings(self):
sender = self.sender()
self.settings[sender.objectName()] = sender.value()
If i've understood your question correctly, look at this link which might be useful.
Small addition from me is, look here.
from PyQt5.QtWidgets import QDialog
from ui_imagedialog import Ui_ImageDialog
class ImageDialog(QDialog):
def __init__(self):
super(ImageDialog, self).__init__()
# Set up the user interface from Designer.
self.ui = Ui_ImageDialog()
self.ui.setupUi(self)
# Make some local modifications.
self.ui.colorDepthCombo.addItem("2 colors (1 bit per pixel)")
# Connect up the buttons.
self.ui.okButton.clicked.connect(self.accept)
self.ui.cancelButton.clicked.connect(self.reject)
Once you've got your .ui files it is a good practice to use (second link, second example - mentoned above) pyside6-uic or pyuic5 - depending of your needs.
Then you're creating class which role is to setup desired QWidget ui converted python module.
So in QtDesigner you're creating a QWidget:
screen_qt_designer
then do all your stuff, that it would like to do - place over QLineEdit, QPushButtons, etc., then save that file in your project's folder. After that all you have to do is use pyuic or pyside6-uic with proper settings (look at --help to set the output filename).
Then all you have is a .ui file (which is used in case you would like to add some widget, or change the widget all all) and your generated python class that inherits QObject, eg:
class Ui_Form(object):
def setupUi(self, Form):
if not Form.objectName():
Form.setObjectName(u"Form")
Form.resize(800, 600)
self.verticalLayout = QVBoxLayout(Form)
self.verticalLayout.setObjectName(u"verticalLayout")
self.label = QLabel(Form)
self.label.setObjectName(u"label")
Then create a another Python file, that inherits this generated class, eg.:
from PySide6.QtWidgets import QWidget
from CreateNewDatabase_ui import Ui_Form
class NewDatabaseWidget(QWidget):
def __init__(self):
super().__init__()
self.ui = Ui_Form()
self.ui.setupUi(self)
That's all. You have your prepared QWidget for adding it into a QStackedWidget. I use it in my own program and it fit me very well.
Edit:
I've forgot about 2 important notes:
Never ever edit your file generated by pyuic or pyside6-uic program. Whenever you wish to change the .ui file, all changes made to autogenerated code will be lost.
It's better to keep all signals/slots, logic mechanism in your QMainWindow class. It's better approach in case of code readability.

How to center new window relative to mainwindow in PyQt?

How do I alter my code to show Loading in the center of MainWindow relative to wherever MainWindow is at on the screen?
What it is doing now is placing the Loading animation on the top left corner, ignoring the position of the MainWindow.
Even if I add geomtery to the MainWindow, the result will be the same.
Here is the code, both of the classes has been imported from a py.file:
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.loading = Loading(parent=self)
self.ui.check.clicked.connect(self.show_animation)
self.show()
def show_animation(self):
self.loading.show()
class Loading(QWidget):
def __init__(self, parent=None):
self.parent = parent
QWidget.__init__(self)
self.ui = Ui_loading()
self.ui.setupUi(self)
self.setWindowModality(QtCore.Qt.ApplicationModal)
self.setWindowFlag(QtCore.Qt.FramelessWindowHint)
self.label_animation = QLabel(self)
self.label_animation.setGeometry(QtCore.QRect(25, 25, 256, 256))
self.movie = QMovie('giphy1.gif')
self.label_animation.setMovie(self.movie)
self.movie.start()
geo = self.geometry()
geo.moveCenter(self.parent.geometry().center())
self.setGeometry(geo)
Your code doesn't work because you're already setting the geometry when you create the Loading instance, and that's too soon: not only when you click the button it's possible that the parent window has been moved or resized, but you're creating the instance when it's not been even shown yet, so it still has a default geometry (usually 640x480 unless it has minimum or maximum size constraints based on its contents).
In order to center the widget on the parent, you have to consider the geometries when it is going to be shown.
class Loading(QWidget):
# ...
def showEvent(self, event):
if not event.spontaneous():
geo = self.geometry()
geo.moveCenter(self.parent.geometry().center())
QtCore.QTimer.singleShot(0, lambda: self.setGeometry(geo))
The check on event.spontaneous() is done in order to only move the window when it's explicitly shown (using show() or setVisible(True)), otherwise it would happen in any case, for example when restoring the window after it's been minimized.
The delayed setGeometry() is required because in certain cases (most commonly, on Linux) some amount of time is required between the request Qt makes to the underlying window system and the moment the window is actually mapped on the screen.

How to get tkinter window to be already transparent when opening?

I want a black window to fade in. When in fullscreen, it perfectly works but I need a specific size and there when its opened, it first appears black before it becomes transparent and the fading starts. Do you have any ideas how to achieve the same smooth effect as for the fullscreen version?
import tkinter as tk
class Fader(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.parent.attributes("-alpha",0.0)
#self.parent.attributes("-fullscreen",True)
self.parent.geometry("600x800")
self.configure(bg='black')
self.fade_in()
def fade_in(self):
alpha = self.parent.attributes("-alpha")
if alpha < 1:
alpha += .01
self.parent.attributes("-alpha", alpha)
self.after(100, self.fade_in)
if __name__ == "__main__":
root = tk.Tk()
root.bind("<Escape>",lambda e: root.destroy())
Fader(root).pack(fill="both", expand=True)
root.mainloop()
You can use withdraw() to hide the window and deiconify() to show to window later on and increase the alpha. But it seems to not work unless you update the tasks or wait for the window to be visible.
Method 1:
Was able to fix this by using update_idletasks(), like:
class Fader(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.parent.attributes('-alpha',0.0)
self.parent.withdraw() #hiding the window
#self.parent.attributes("-fullscreen",True)
self.parent.update_idletasks()
self.parent.geometry("600x800")
self.configure(bg='black')
self.fade_in()
def fade_in(self):
self.parent.deiconify() #bringing it back
..... #same code
Method 2:
Or like said by acw1668, you can use wait_visibility(), like:
class Fader(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.parent.wait_visibility(self.parent)
self.parent.attributes('-alpha',0.0)
self.parent.withdraw()
# self.parent.attributes("-fullscreen",True)
self.parent.geometry("600x800")
self.configure(bg='black')
self.fade_in()
def fade_in(self):
self.parent.deiconify()
...... #same code
A bit more about wait_visibility():
wait_visibility(window=None)
Wait for the given widget to become visible. This is typically used to wait until a new toplevel window appears on the screen. Like wait_variable, this method enters a local event loop, so other parts of the application will still work as usual.
A bit more about update_idletasks():
update_idletasks()
Calls all pending idle tasks, without processing any other events. This can be used to carry out geometry management and redraw widgets if necessary, without calling any callbacks.
Source :- https://effbot.org/tkinterbook/widget.htm

How to fix missing Task Bar icon in second QMainWindow widget

I am making a GUI that had the Welcome page and the main page. The purpose is to let user agree on the welcome page, the welcome page is dismissed and the main page will show up for further step. However, the icon in the taskbar only shows up in the welcome page, when we click into the main window the icon is disappeared and the app appeared to be a minimized window on the bottom left corner in the screen.
The starting page and main window layout is appear like this.
class welcome_window(QtWidgets.QMainWindow):
def __init__(self,parent = None):
super(welcome_window, self).__init__(parent)
self.confirm_button = QtWidgets.QPushButton('Yes')
self.confirm_button.clicked.connect(self.startup)
Main_layout = QtWidgets.QHBoxLayout()
Main_layout.addWidget(self.confirm_button)
self.main.setLayout(Main_layout)
def startup(self):
self.close()
dialog = Main_window(self)
self.dialogs.append(dialog)
dialog.show()
class Main_window(QtWidgets.QMainWindow):
def __init__(self,parent = None):
super(Main_window, self).__init__(parent)
self.setGeometry(50, 50, 1500, 850)
# here is all the step for later operation
def main():
app = QtWidgets.QApplication(sys.argv)
main = welcome_window()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I expected that if the icon located in the taskbar could always stay on, it would be great for my GUI. Thank you all.
First of all, the MRE you gave is not reproducible. When I tried to run it it just didn't work. In this case you had a simple issue so I could just guess what was intended, but when you get more complicated problems people might not be able to help you. So in the future please make sure that we can just copy-paste-execute your code.
The reason that the main window disappears is that it's a member of the Welcome window. When you close the Welcome window, the corresponding python object will deleted and therefore Python will no longer have a reference to the main window. The main window object will be garbage-collected and all kinds of strange things might happen (I would expect it to just disappear).
The solution is to have a reference to the main window that stays valid until the program closes. This can be done by defining it in the main function (and then giving it as a parameter to the Welcome window). Like this...
import sys
from PyQt5 import QtWidgets
# Use a QWidget if you don't need toolbars.
class welcome_window(QtWidgets.QWidget):
def __init__(self, main_window=None, parent = None):
super(welcome_window, self).__init__(parent)
self.main_window = main_window
self.confirm_button = QtWidgets.QPushButton('Yes')
self.confirm_button.clicked.connect(self.startup)
main_layout = QtWidgets.QHBoxLayout() # use lower case for variable names
main_layout.addWidget(self.confirm_button)
self.setLayout(main_layout)
def startup(self):
self.main_window.show()
self.close()
class Main_window(QtWidgets.QMainWindow):
def __init__(self,parent = None):
super(Main_window, self).__init__(parent)
self.setGeometry(50, 50, 1500, 850)
# here is all the step for later operation
# Don't use self.setLayout on a QMainWindow,
# use a central widget and set a layout on that.
self.main_widget = QtWidgets.QWidget()
self.setCentralWidget(self.main_widget)
main_layout = QtWidgets.QHBoxLayout()
self.main_widget.setLayout(main_layout)
main_layout.addWidget(QtWidgets.QLabel("Hello"))
def main():
app = QtWidgets.QApplication(sys.argv)
main = Main_window()
welcome = welcome_window(main_window=main)
welcome.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Some more tips. Don't use setLayout on a QMainWindow. Use a central widget and add your widgets to the layout of the central widget. The layout of the main window is for toolbars and such. See: https://doc.qt.io/qt-5/qmainwindow.html#qt-main-window-framework
Just use a QWidget if you want a simple window without toolbars (like your welcome window),
Best to use lower case for variable names and upper case for class names. E.g. I renamed Main_layout to main_layout. Look at the difference in syntax highlighting by Stack Overflow above.

Python Tkinter GUI File Menu Not Displaying though GUI is operational

I'm relatively new to Python and I'm sure this is an error with the structure of my code, but I cannot seem to get the filemenu to display in my GUI. Can someone tell me what errors I have made with the filemenu inclusion? Also, I am sorry, but the spacing after copying and pasting is a little off. The class indentation level is proper on my side. I am using Python 3.71
Any other comments on better or more Pythonic ways to accomplish what I have here are also welcome and thank you for your help in advance!
from tkinter import *
from tkinter import ttk
import tkinter.scrolledtext as tkst
import os
import tkinter as tk
from functools import partial
from PIL import Image, ImageTk
class UserGui(tk.Tk):
def __init__(self,parent):
self.parent=parent
self.widgets()
def widgets(self):
self.parent.configure(bg='white')
self.frame1_style = ttk.Style()
self.frame1_style.configure('My.TFrame', background='white')
self.frame2_style = ttk.Style()
self.frame2_style.configure('My2.TFrame',background='white')
self.parent.title("TGUI")
self.frame1 = ttk.Frame(self.parent, style='My.TFrame') #Creating Total Window Frame 1
self.frame1.grid(row=0, column=0, sticky=(N, S, E, W))
self.frame2 = ttk.Frame(self.parent, width=100, height=20, style='My2.TFrame')
self.frame2.grid(row=0, column=6, padx=20, pady=5)
#Menu Creation
self.menu1 = tk.Menu(self.parent, tearoff=0)
self.parent.config(menu=self.menu1)
self.fileMenu = tk.Menu(self.menu1, tearoff=0)
self.fileMenu.add_command(label="Open", command=self.donothing)
self.fileMenu.add_command(label="Save", command=self.donothing)
self.fileMenu.add_separator()
self.fileMenu.add_command(label="Exit", command=self.parent.quit)
self.fileMenu.add_cascade(label="File", menu=self.menu1)
self.editMenu = tk.Menu(self.menu1, tearoff=0)
self.editMenu.add_command(label="Cut", command=self.donothing)
self.editMenu.add_command(label="Copy", command=self.donothing)
self.editMenu.add_command(label="Paste", command=self.donothing)
self.editMenu.add_cascade(label="Edit", menu=self.menu1)
def donothing(self):
filewin = Toplevel(self.parent)
button = Button(filewin, text="Do nothing button")
button.pack()
def main():
root=tk.Tk()
ug=UserGui(root)
root.mainloop()
if __name__ == '__main__':
main()
Edit 1,2,3: I have corrected the add_cascade option for menu with menu=self.menu1 and I still do not have a file menu displaying.
EDIT: I'm sorry I didn't notice the Python-3 tag in time, it's all the same except when inherriting you would call super().__init__ instead of the Frame.__init__ directly. That would make it more Py3-like. Even so, this should still work.
Weirdly, pushing the menu.config down to the run function worked for me - even though it looks like it should work the way you did it.
def main():
root=tk.Tk()
ug=UserGui(root)
root.config(menu=ug.fileMenu)
root.mainloop()
if __name__ == '__main__':
main()
Oterwise there are some things you can work on to make it more OOP like and readable. THis is how I usually handle making GUIs. The idea is to split the GUI's into Frames that then do simmilar things. I.e. your app could have left and right Frame where the RightFrame would hold the textbox ad the left Frame would actually have 2 sub frames - one for the names and dropdowns and the other for the buttons. That way each individual functionality is handled by the Frames themselves and it's not all in one giant class, the elements in those Frames are placed relative to the Frame's grid itself, while all the Frames are placed in the MainFrame's grid. This lets you split a lot of code into modules as well and helps with maintainability.
The sub-frames emit "global" events (events bothering other frames) by propagating them through the MainFrame, that's why they all carry a self.parent - their parent frame, and a self.root - the MainFrame. The MainFrame is also the Frame in which I like to put something like self.data which itself is a class on its own (outside Tkinter) that handles all the data input/output and logic so that you don't clutter the GUI code logic with data calculations and logic. Ideally the Data class would handle data errors and GUI would only then have to handle any errors in logic (such as selecting two impossible-to-combine options from the dropdown menus.
from tkinter import *
from tkinter import ttk
class SubFrame(Frame):
def __init__(self, parent, text="Top Right"):
Frame.__init__(self)
self.pack()
self.parent = parent
self.root = parent.root
self.label=Label(self, text=text).pack()
class RightFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, relief=RAISED, borderwidth=1)
self.pack(side=RIGHT, fill=BOTH, expand=1)
self.root = parent
self.label = Label(self, text="Right Frame").pack()
class LeftFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, relief=RAISED, borderwidth=1)
self.pack(side=LEFT, fill=BOTH, expand=1)
self.root = parent
self.label = Label(self, text="Left Frame").pack()
#again Frames which would have a parent class RightFrame and root MainFrame
self.subFrame1 = SubFrame(self)
self.subFrame2 = SubFrame(self, text="Top Right SubFrame 2")
class MainFrame(Tk):
def __init__(self):
Tk.__init__(self)
self.geometry("1100x600")
self.title("Working Example")
self.leftFrame = LeftFrame(self)
self.rightFrame = RightFrame(self)
#self.data = MagicalDataHandlingClass()
def run():
app = MainFrame()
app.mainloop()
EDIT answer to comments that are too long to fit
The call to Frame.__init__(...) is made because the class definition looks like class LeftFrame(Frame). Usually to declare a class what you would write is just class LeftFrame. When you add the bit in the () what is happening is called inheritance. When you inherit from a class (called parent), your class (called child) inherits all of the methods and attributes of parent. But much like you have to initialize your class to get an object, i.e. lf = LeftFrame(...) the parent class has to be initialized too. In Python this initialization is done by calling the special dunder __init__(...) function. So that call to Frame.__init__(...) happens because you need to tell the parent class what are all the values it needs to work properly. In Python 3 however it is recommended that instead of instantiating the parent by name like that you use the super function like super().__init__(....). This happens for a lot of complicated reasons most of which you probably don't have to worry about for a while yet (such as what if you inherit from multiple classes at the same time, what if you inherit from a class that inherited from a different one, etc...). I wouldn't try to feel overwhelmed by understanding the complete power of super() if you're just starting because 99% of the time in Python 3 just doing super().__init__(...) will do exactly what you want even if you don't understand. If you feel like getting in over your head Raymond Hettinger has a good writeup of Super is Super and why exactly it's so much better than old way.
I will post this answer for completeness considering #JasonHarper has not copied it to an answer format and I want others to be able to benefit from the post.
The key was the object that I was calling the add_cascade on the child Menu widget object instead of the main Menu widget object called self.menu1. The key was changing:
self.fileMenu.add_cascade(label="File", menu=self.menu1)
to :
self.menu1.add_cascade(label="File", menu=self.fileMenu)
This was the proper way of adding the fileMenu Menu object to the total Menu widget object of self.menu1.

Resources