I have an issue with setting focus on one of four child windows from a main window. I tried setFocus to one of the four, but the main window still keeps the focus. I have a combo box that lets you choose which of the four windows to bring into focus. Each of the widows is on a separate monitor.
from PyQt4 import QtGui, QtCore
import numpy as np
from ui_GuiMask import Ui_MainWindow
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self, parent)
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
QtCore.QObject.connect(self.ui.cb_projectorSelector, QtCore.SIGNAL("currentIndexChanged(int)"), self.setProjectorFocus)
self.maskProjector_1 = MaskWindow(screen = 0)
self.maskProjector_1.show()
self.maskProjector_2 = MaskWindow(screen = 0)
self.maskProjector_2.show()
def setProjectorFocus(self):
whichProj = self.ui.cb_projectorSelector.currentIndex()
if whichProj == 0:
self.maskProjector_1.setFocus(True)
self.maskProjector_2.setFocus(False)
elif whichProj == 1:
self.maskProjector_1.setFocus(False)
self.maskProjector_2.setFocus(True)
shouldn't the focus activate one of the windows and move it to front ?
Docs for setFocus() (Emphasis added):
void QWidget::setFocus ( Qt::FocusReason reason )
Gives the keyboard input focus to this widget (or its focus proxy) if
this widget or one of its parents is the active window.
As I understand it, setFocus won't activate a top-level widget (window). It changes focus within the active window.
Use .activateWindow(), probably along with .raise().
Related
I have a question: i am tossing the code from 2ch files, i have already lost ideas. Calling fileA.py opens a window for me with two buttons exit and start. Exit works but when I click start I need to open the second window fileB.pt. (I want both windows to open in one window) Seemingly works only problem I have is it doesn't open "window on window" but "docks underneath" and I have the effect of two windows open :/. Please help, thank you in advance:) Python 3.10
fileA.py
import tkinter as tk
from GUI.module.scale_of_img import get_scale
class FirstPage:
def __init__(self, root):
self.root = root
def get_settings(self):
# Window settings
self.root.title('....')
self.root.resizable(False, False)
self.root.geometry("1038x900")
if __name__ == '__main__':
first = FirstPage(tk.Tk())
first.get_run_first_page()
fileB.py
import tkinter as tk
"importy..."
''' The second side of the application '''
class SecondPage:
def __init__(self, root=None):
self.root = root
self.my_canvas = tk.Canvas(self.root, width=1038, height=678)
self.my_canvas.pack(fill="both", expand=True)
if __name__ == '__main__':
second = SecondPage(tk.Tk())
second.get_run()
in order to put two "windows" in the same "window" you need to put all items inside a Frame, which is basically a container than you can simply pack when you want everything to show and unpack when you want everything in it to disapear.
all items in the first window will be children of a frame and all items in the second window will be children of another frame, and to switch you just need to call pack_forget() on one and pack() on another.
for the first file
class FirstPage:
def __init__(self, root):
self.root = root
self.frame = tk.Frame(root)
self.frame.pack(expand=True)
def get_picture(self):
# all items inside this window must be children of self.frame
self.my_canvas = tk.Canvas(self.frame, width=1038, height=500)
...
def get_second_page(self):
from GUI.module.second_page import SecondPage
self.frame.pack_forget() # to hide first page
# self.frame.destroy() # if you are never brining it back
SecondPage(self.root).get_run()
and for the second file
class SecondPage:
def __init__(self, root=None):
self.root = root
self.frame = tk.Frame(root) # new frame
self.frame.pack(expand=True)
self.my_canvas = tk.Canvas(self.frame, width=1038, height=678)
self.my_canvas.pack(fill="both", expand=True)
def get_button(self):
# Add buttons
# all here should be children of self.frame now
button1 = tk.Button(self.frame, text="...", )
...
you could destroy the first frame when you switch over to save some resources if you don't intend to return to it ever again, but the difference in memory is negligible.
assuming what you want is another Tk window to open, you shouldn't give it the same root, instead use an instance of Toplevel
from tkinter import Toplevel
# class definition here
def get_second_page(self):
from GUI.module.second_page import SecondPage
SecondPage(Toplevel(self.root)).get_run()
passing the Toplevel as a child of self.root is necessary, but note that the two windows have different roots.
Edit: turns out this wasn't what the OP ment by "window on window" -_-, but it am keeping it here for other readers.
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.
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.
This question already has an answer here:
Force keyboard focus to LineEdit QT
(1 answer)
Closed 1 year ago.
So I'm trying to replicate the behavior of windows 10 native search bar, e.g:
user presses some key combo -> search bar appears, already having input focus
user clicks away\focus is lost -> search bar disappears
Ive got most of it to work the way I want it to, my entire app is a single window which inherits from QMainWindow and I use self.hide() on it once I detect focus is lost..
Ive bound some key combo to the method below with the keyboard library:
def bringToTop(self) -> None:
print('got focus')
self.show()
self.raise_()
self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
self.activateWindow()
self.lineEdit.setFocus()
I just want the window to come up and set input focus on the lineEdit, but instead it just flashes in orange in the taskbar.
Ive tried many combinations of the lines in bringToTop, nothing worked.
Simplified version of my code:
import sys
import keyboard
from PyQt5.QtCore import Qt, QRect
from PyQt5.QtGui import QFocusEvent
from PyQt5.QtWidgets import QLineEdit, QMainWindow, QApplication
class SearchWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setEnabled(True)
self.setObjectName("MainWindow")
self.setWindowModality(Qt.NonModal)
self.setContextMenuPolicy(Qt.DefaultContextMenu)
self.setFocusPolicy(Qt.ClickFocus)
self.setAutoFillBackground(False)
self.setFixedSize(300, 50)
self.setWindowFlags(Qt.WindowFlags(Qt.FramelessWindowHint))
self.lineEdit = QLineEdit(self)
self.lineEdit.setGeometry(QRect(30, 10, 230, 20))
self.lineEdit.setObjectName("lineEdit")
self.lineEdit.setFocusPolicy(Qt.ClickFocus)
keyboard.add_hotkey('win+z', self.bringToTop)
self.bringToTop()
def focusOutEvent(self, a0: QFocusEvent) -> None:
self.hide()
def bringToTop(self) -> None:
print('got focus')
self.show()
self.raise_()
self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
self.activateWindow()
self.lineEdit.setFocus()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SearchWindow()
sys.exit(app.exec_())
QWidget.activateWindow() documentation reports that:
On Windows, if you are calling this when the application is not
currently the active one then it will not make it the active window.
It will change the color of the taskbar entry to indicate that the
window has changed in some way. This is because Microsoft does not
allow an application to interrupt what the user is currently doing in
another application.
There's also QWindow.requestActivate(), even if I'm afraid that it'll have the same result.
Unfortunately I cannot test it as I'm on Linux right now, but QWindowsWindowFunctions.setWindowActivationBehavior() might be promising.
EDIT : I've come up with a solution, and it's much more straightforward than I thought. Original code and question at the top. My solution after "The Question" below..
The Example
from PyQt4 import QtGui, QtCore
from example_Ui import Ui_MainWindow
from filler_Ui import Form
class TabFiller(Form):
def __init__(self, parent=None):
Form.__init__(self, parent)
def TabButtonClicked(self):
print("Tab button pressed.")
def LineEditChanged(self):
print("LineEdit contents edited in tab page!")
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
tab_filler = [] # create empty list for tab contents
tab_page = [] # create empty list for tab page
tab_count = 0
def CreateNewTab(self):
tab_title = "New Tab : " + str(self.tab_count)
self.tab_filler.append(TabFiller())
self.tab_filler[self.tab_count].label.setText(tab_title)
self.tab_page.append(self.tab_filler[self.tab_count])
self.tabWidget.addTab(self.tab_page[self.tab_count], tab_title)
self.tab_count += 1
def MainButtonPressed(self):
self.CreateNewTab()
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
MainWindow contains a QTabWidget, which is a Button. clicked() signal has been defined in QtDesigner to be sent to the MainButtonPressed() function inside the MainWindow class.
Form widget also created in QTdesigner. Used to fill additional Tab Pages.
This contains a Button widget, and a LineEdit Widget.
The Question
I can't get my head around how I can tell which widget has been clicked or edited in each tab.
I know that each Tab Page is stored in the list called tab_page.
Within the MainWindow class, how would I receive a clicked() or finishedEditing() signal for a given widget in a currently active tab?
A Solution
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4 import QtGui
from example_Ui import Ui_MainWindow
from filler_Ui import Form
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
tab_index = 1 # 1 because we already made a default tab in QtDesigner
def LineEditChanged(self):
findWidget = self.tabWidget.widget(self.tabWidget.currentIndex()).findChildren(QtGui.QLineEdit, "lineEdit")
if findWidget[0].isModified() == True:
print("LineEdit contents edited in tab page!")
print("Name of page edited :", "'", self.tabWidget.tabText(self.tabWidget.currentIndex()),"'")
def TabButtonPressed(self):
print("YOU DID IT!")
print("Current Tab Index = ", self.tabWidget.currentIndex())
def CreateNewTab(self, tabNum):
tab_title = "New Tab : " + str(self.tab_index)
self.tabWidget.addTab(Form(), tab_title)
def MainButtonPressed(self):
self.CreateNewTab(self.tab_index)
findWidget = self.tabWidget.widget(self.tab_index).findChildren(QtGui.QPushButton, "tabButton")
findWidget[0].clicked.connect(self.TabButtonPressed)
findWidget = self.tabWidget.widget(self.tab_index).findChildren(QtGui.QLineEdit, "lineEdit")
findWidget[0].editingFinished.connect(self.LineEditChanged)
self.tab_index += 1
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Using this there's no need for storing each tab page object in a list. You basically use the QTabWidget to index your pages, and off you go.
If anyone has a more elegant way than this, please inform ;)
As outlined in my edited question, I did find the solution to this, which is to use the QTabWidget to "index" each dynamically created tab page.
In QtDesigner I created a main window with one QTabWidget and one button thusly;
Here's the object tree for that;
NOTE: I added a signal/slot for the "Click Me!" button in QtDesigner, so that when that button is clicked, the MainButtonPressed function is called.
To fill the tab pages, I also created a Form in QtDesigner, with a button and a QLineEdit widget;
And the object tree for that;
I'll reproduce the code here. NOTE: I've now updated this answer to use findChild rather than findChildren above:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4 import QtGui
from example_Ui import Ui_MainWindow
from filler_Ui import Form
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
tab_index = 1 # 1 because we already made a default tab in QtDesigner
def LineEditChanged(self):
findWidget = self.tabWidget.widget(self.tabWidget.currentIndex()).findChild(QtGui.QLineEdit, "lineEdit")
if findWidget.isModified() == True:
print("LineEdit contents edited in tab page!")
print("Name of page edited :", "'", self.tabWidget.tabText(self.tabWidget.currentIndex()),"'")
def TabButtonPressed(self):
print("YOU DID IT!")
print("Current Tab Index = ", self.tabWidget.currentIndex())
def CreateNewTab(self, tabNum):
tab_title = "New Tab : " + str(self.tab_index)
self.tabWidget.addTab(Form(), tab_title)
def MainButtonPressed(self):
self.CreateNewTab(self.tab_index)
findWidget = self.tabWidget.widget(self.tab_index).findChild(QtGui.QPushButton, "tabButton")
findWidget.clicked.connect(self.TabButtonPressed)
findWidget = self.tabWidget.widget(self.tab_index).findChild(QtGui.QLineEdit, "lineEdit")
findWidget.editingFinished.connect(self.LineEditChanged)
self.tab_index += 1
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
When run, pressing the "Click Me!" button on the main tab page creates a new tab, and adds the contents of the "filler" page to it.
The variable tab_index keeps track of how many tabs there are and allows you to reference the contents of each tab.
To find a widget in a tab, you use the findChild function of Qt;
findWidget = self.tabWidget.widget(self.tab_index).findChild(QtGui.QPushButton, "tabButton")
Finding a specific widget is straightforward. You specify the type of widget you're looking for (QtGui.QPushButton) , and the name you assigned it in QtDesigner (tabButton)
In this case the found widget can be referenced by the variable findWidget.
You can then connect signals to function slots as usual;
findWidget.clicked.connect(self.TabButtonPressed)
In this case I used the new-style signal connection method to connect the clicked() signal to a function named TabButtonPressed in my program.
Rinse and repeat for each widget on the Tab Page you wish to do something with.
After that, it really is plain sailing ;)
I hope this information helps others in their GUI endeavours. You can probably use the same technique with the QToolBox widget.