How can I make the menubar in PyQt5 have initial focus? - python

I have created a GUI with a menubar. However, when I run the app and the window opens I want to be able to iterate through the buttons using only the keyboard. This works for the buttons and lineedit fields, but does not work for the MenuBar.
How can I be able to get focus on the MenuBar such that I can iterate through the actions in the menubar using only the keyboard?
from PyQt5 import QtCore, QtWidgets, QtGui
class ConfigNumbers(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.setupUi ( self)
self.setWindowTitle ( 'Nr configs' )
def setupUi(self, ConfigNumbers):
ConfigNumbers.setEnabled ( True )
ConfigNumbers.setFocusPolicy ( QtCore.Qt.TabFocus )
layout = QtWidgets.QGridLayout()
#ELEMENTS
#ACTIONS TO MENUBAR
self.menuBar = QtWidgets.QMenuBar()
delete = self.menuBar.addMenu("Delete")
add = self.menuBar.addMenu("Add")
deleteNr = []
addNr = QtWidgets.QAction("Add New Master", self)
addNr.setShortcut("Ctrl+A")
# addNr.triggered.connect(lambda: self.addNewNr())
add.addAction(addNr)
for nr in range (1,5):
deleteNr.append(QtWidgets.QAction("Nr %d" % nr, self))
delete.addAction(deleteNr[nr-1])
QtCore.QTimer.singleShot(0, self.menuBar.setFocus)
layout.addWidget(self.menuBar)
layout.addWidget(QtWidgets.QPushButton("push"))
self.setLayout( layout )
def main():
app = QtWidgets.QApplication(sys.argv)
window = ConfigNumbers()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

You don't need to focus the menu to be able to select it with the keyboard, you can select it by using the key Alt which is the standard key to achieve this, furthermore, you can add the ampersand character before a letter to your menu action titles and then you can use the combination of Alt+letter to access a menu, for example:
import sys
from PyQt5 import QtCore, QtWidgets, QtGui
class ConfigNumbers(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.setupUi ( self)
self.setWindowTitle ( 'Nr configs' )
def setupUi(self, ConfigNumbers):
ConfigNumbers.setEnabled ( True )
ConfigNumbers.setFocusPolicy ( QtCore.Qt.TabFocus )
layout = QtWidgets.QGridLayout()
#ELEMENTS
#ACTIONS TO MENUBAR
self.menuBar = QtWidgets.QMenuBar()
delete = self.menuBar.addMenu("Dele&te")
add = self.menuBar.addMenu("&Add")
deleteNr = []
addNr = QtWidgets.QAction("Add New &Master", self)
addNr.setShortcut("Ctrl+A")
# addNr.triggered.connect(lambda: self.addNewNr())
add.addAction(addNr)
for nr in range (1,5):
deleteNr.append(QtWidgets.QAction("Nr %d" % nr, self))
delete.addAction(deleteNr[nr-1])
layout.addWidget(self.menuBar)
layout.addWidget(QtWidgets.QPushButton("push"))
self.setLayout( layout )
def main():
app = QtWidgets.QApplication(sys.argv)
window = ConfigNumbers()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Now when I hit and release the key Alt I can see the following:
Now you can use the arrow keys to move through the menus; If I hit Alt+A it opens the "Add" menu and so on...
You should also avoid focusing on items using a timer as it may interact with user actions in the interface.

Related

Return clicked button id instead of (accept, reject flags) on QDialog- PyQt5

Summary:
I've been using QMessageBox in my application (for this purpose asking for saving project before closing, Error messages), And now I want to have a custom style (Custom title bar, custom buttons and so on), I found it is hard to do that with QMessageBox, And since I've been using a QDialog in my application as well (For this purpose: Taking input from user), I decided to use a custom QDialog (Make my own style on it) instead of QMessageBox for these all previous purpose, since it is easier to customize and creating my style.
The problem:
The problem that I have now is: QDialog return only a flag value (0,1) depending on the button role And that's fine if I have only 2 buttons BUT here I have 3 (Save, Cancel, Close), Save will return 1 and both Close and Cancel return 0. And QMessageBox as I've seen it return the id of the Button that was clicked.
An example (commented well hopefully :D):
from PyQt5.QtWidgets import *
class CustomDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Data will be lost")
label = QLabel("Are you sure you want close the app before saving?")
buttonBox = QDialogButtonBox()
buttonBox.addButton(QDialogButtonBox.Save)
buttonBox.addButton(QDialogButtonBox.Cancel)
buttonBox.addButton(QDialogButtonBox.Close)
layout = QVBoxLayout()
layout.addWidget(label)
layout.addWidget(buttonBox)
self.resize(300, 100)
self.setLayout(layout)
# These two lines, return 0 or 1.
buttonBox.rejected.connect(self.reject)
buttonBox.accepted.connect(self.accept)
class Window(QWidget):
def __init__(self):
super().__init__()
label = QLabel('Hello Dialog', self)
open_dialog_button = QPushButton('Open Dialog', self)
open_dialog_button.clicked.connect(self.showDialog)
open_message_box_button = QPushButton('Open Message Box', self)
open_message_box_button.clicked.connect(self.show_message_box)
layout = QVBoxLayout()
layout.addWidget(label)
layout.addWidget(open_dialog_button)
layout.addWidget(open_message_box_button)
self.setLayout(layout)
def showDialog(self):
self.dialog = CustomDialog(self)
btn_clicked = self.dialog.exec_() # dialog exec returns 0 or 1 (Save = 1, Close and Cancel returns 0)
# I want something like this.
if btn_clicked == QDialogButtonBox.Save:
print("Close.. After Saving...")
elif btn_clicked == QDialogButtonBox.Close:
print("Close.. Without saving")
else:
print("Cancel.. Don't exit the program")
def show_message_box(self):
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setText("Are you sure you want close the app before saving?")
msg.setStandardButtons(QMessageBox.Close | QMessageBox.Save | QMessageBox.Cancel)
msg.setWindowTitle("Data will be lost")
btn_clicked = msg.exec_()
# Here i can do this.
if btn_clicked == QMessageBox.Save:
print("Close.. After Saving...")
elif btn_clicked == QMessageBox.Close:
print("Close.. Without saving")
else:
print("Cancel.. Don't exit the program")
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
win = Window()
win.resize(200, 200)
win.show()
sys.exit(app.exec_())
I've found the solution and it worked with me by using a custom signal and slot, but still not sure if it is good or not. waiting for approval and then I can mark this solution as an accepted one.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class CustomDialog(QDialog):
# Create a signal
signal = pyqtSignal(int)
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Data will be lost")
label = QLabel("Are you sure you want close the app before saving?")
self.buttonBox = QDialogButtonBox()
self.buttonBox.addButton(QDialogButtonBox.Save)
self.buttonBox.addButton(QDialogButtonBox.Cancel)
self.buttonBox.addButton(QDialogButtonBox.Close)
layout = QVBoxLayout()
layout.addWidget(label)
layout.addWidget(self.buttonBox)
self.resize(300, 100)
self.setLayout(layout)
# connect each button with custom slot
self.buttonBox.button(QDialogButtonBox.Save).clicked.connect(lambda: self.customSlot(QDialogButtonBox.Save))
self.buttonBox.button(QDialogButtonBox.Close).clicked.connect(lambda: self.customSlot(QDialogButtonBox.Close))
self.buttonBox.button(QDialogButtonBox.Cancel).clicked.connect(lambda: self.customSlot(QDialogButtonBox.Cancel))
# connect signal with buil-in function from QDialog (done())
# This done function will return <int> value and close the dialog.
self.signal.connect(self.done)
def customSlot(self, button_id):
# emit button's id
self.signal.emit(button_id)
class Window(QWidget):
def __init__(self):
super().__init__()
label = QLabel('Hello Dialog', self)
open_dialog_button = QPushButton('Open Dialog', self)
open_dialog_button.clicked.connect(self.showDialog)
layout = QVBoxLayout()
layout.addWidget(label)
layout.addWidget(open_dialog_button)
self.setLayout(layout)
def showDialog(self):
dialog = CustomDialog()
btn_clicked = dialog.exec_() # dialog exec returns button's id
# Now you can use the following lines of code
if btn_clicked == QDialogButtonBox.Save:
print("Close.. After Saving...")
elif btn_clicked == QDialogButtonBox.Close:
print("Close.. Without saving")
else:
print("Cancel.. Don't exit the program")
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
win = Window()
win.resize(200, 200)
win.show()
sys.exit(app.exec_())

QFileDialog.getOpenFileName change button text from 'Open' to 'Remove'

I am using QFileDialog.getOpenFileName(self,'Remove File', "path", '*.pdf') to select a file and get the path in order to remove it from my application. The issue is the QFileDialog.getOpenFileName window button says 'Open' when selecting a file which will be confusing to the user.
Is there any way to change the button text from 'Open' to 'Remove'/'Delete'
When using the static method QFileDialog::getOpenFileName() the first thing is to obtain the QFileDialog object and for that we use a QTimer and the findChild() method:
# ...
QtCore.QTimer.singleShot(0, self.on_timeout)
filename, _ = QtWidgets.QFileDialog.getOpenFileName(...,
options=QtWidgets.QFileDialog.DontUseNativeDialog)
def on_timeout(self):
dialog = self.findChild(QtWidgets.QFileDialog)
# ...
Then you can get the text iterating over the buttons until you get the button with the searched text and change it:
for btn in dialog.findChildren(QtWidgets.QPushButton):
if btn.text() == "&Open":
btn.setText("Remove")
That will work at the beginning but every time you interact with the QTreeView they show, update the text to the default value, so the same logic will have to be applied using the currentChanged signal from the selectionModel() of the QTreeView but for synchronization reasons it is necessary Update the text later using another QTimer.singleShot(), the following code is a workable example:
import sys
from PyQt5 import QtCore, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
button = QtWidgets.QPushButton("Press me")
button.clicked.connect(self.on_clicked)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(button)
#QtCore.pyqtSlot()
def on_clicked(self):
QtCore.QTimer.singleShot(0, self.on_timeout)
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self,
"Remove File",
"path",
"*.pdf",
options=QtWidgets.QFileDialog.DontUseNativeDialog,
)
def on_timeout(self):
dialog = self.findChild(QtWidgets.QFileDialog)
dialog.findChild(QtWidgets.QTreeView).selectionModel().currentChanged.connect(
lambda: self.change_button_name(dialog)
)
self.change_button_name(dialog)
def change_button_name(self, dialog):
for btn in dialog.findChildren(QtWidgets.QPushButton):
if btn.text() == self.tr("&Open"):
QtCore.QTimer.singleShot(0, lambda btn=btn: btn.setText("Remove"))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
The first step can be avoided if the static method is not used and create the dialog using an instance of QFileDialog:
import sys
from PyQt5 import QtCore, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
button = QtWidgets.QPushButton("Press me")
button.clicked.connect(self.on_clicked)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(button)
#QtCore.pyqtSlot()
def on_clicked(self):
dialog = QtWidgets.QFileDialog(
self,
"Remove File",
"path",
"*.pdf",
supportedSchemes=["file"],
options=QtWidgets.QFileDialog.DontUseNativeDialog,
)
self.change_button_name(dialog)
dialog.findChild(QtWidgets.QTreeView).selectionModel().currentChanged.connect(
lambda: self.change_button_name(dialog)
)
if dialog.exec_() == QtWidgets.QDialog.Accepted:
filename = dialog.selectedUrls()[0]
print(filename)
def change_button_name(self, dialog):
for btn in dialog.findChildren(QtWidgets.QPushButton):
if btn.text() == self.tr("&Open"):
QtCore.QTimer.singleShot(0, lambda btn=btn: btn.setText("Remove"))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
While I appreciate the solution provided by #eyllanesc, I'd like to propose a variation.
Under certain circumstances, the code for that answer might fail, specifically:
the delay that X11 suffers from mapping windows;
the checking of localized button strings;
the selection using the file name edit box;
Considering the above, I propose an alternate solution, based on the points above.
For obvious reasons, the main point remains: the dialog must be non-native.
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class FileDialogTest(QWidget):
def __init__(self):
super().__init__()
layout = QHBoxLayout(self)
self.fileEdit = QLineEdit()
layout.addWidget(self.fileEdit)
self.selectBtn = QToolButton(icon=QIcon.fromTheme('folder'), text='…')
layout.addWidget(self.selectBtn)
self.selectBtn.clicked.connect(self.showSelectDialog)
def checkSelectDialog(self):
dialog = self.findChild(QFileDialog)
if not dialog.isVisible():
# wait for dialog finalization, as testOption might fail
return
# dialog found, stop the timer and delete it
self.sender().stop()
self.sender().deleteLater()
if not dialog.testOption(dialog.DontUseNativeDialog):
# native dialog, we cannot do anything!
return
def updateOpenButton():
selection = tree.selectionModel().selectedIndexes()
if selection and not tree.model().isDir(selection[0]):
# it's a file, change the button text
button.setText('Select my precious file')
tree = dialog.findChild(QTreeView)
button = dialog.findChild(QDialogButtonBox).button(
QDialogButtonBox.Open)
# check for selected files on open
updateOpenButton()
# connect the selection update signal
tree.selectionModel().selectionChanged.connect(
lambda: QTimer.singleShot(0, updateOpenButton))
def showSelectDialog(self):
QTimer(self, interval=10, timeout=self.checkSelectDialog).start()
path, filter = QFileDialog.getOpenFileName(self,
'Select file', '<path_to_file>',
"All Files (*);;Python Files (*.py);; PNG Files (*.png)",
options=QFileDialog.DontUseNativeDialog)
if path:
self.fileEdit.setText(path)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
ex = FileDialogTest()
ex.show()
sys.exit(app.exec())
Obviously, for PyQt6 you'll need to use the proper Enum namespaces (i.e. QFileDialog.Option.DontUseNativeDialog, etc.).

Move widget pyqt in layout

Hi i have buttons in my code i would like when the user press insert new button it will move all the other button one row below and create a new button just under the one it was pressed this is my code
bascily i am trying to move all the button in layout one row below and after i add the new button :
def Insert_Stage(self) :
button = self.sender()
idx = self.Layout.indexOf(button)
location = self.Layout.getItemPosition(idx)
x=location[0]
z=self.Layout.rowCount()
print(x,z)
while(z >x+1):
items= self.Layout.itemAt(z)
# setting the item as widget
widget=items.widget()
index= self.Layout.indexOf(widget)
loc=self.Layout.getItemPosition(index)
d=loc[0]
y=loc[1]
if y!=0:
#widget.move(d+100,d)
self.Layout.addWidget(widget,(d+1),1)
else:
self.Layout.addWidget(widget,d+1,0)
z-=1
stage=QtGui.QPushButton(self)
stage.setObjectName(button.objectName())
k=(int(button.objectName()[5:])+1)
stage.setText('stage%d'%k)
self.Layout.addWidget(stage,(location[0]+1),0)
Assuming you are using a QVBoxLayout you have to use the insertWidget() method:
from PyQt4 import QtCore, QtGui
class Widget(QtGui.QLineEdit):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
lay = QtGui.QVBoxLayout(self)
for i in range(5):
btn = QtGui.QPushButton(
'button {}'.format(i),
clicked=self.on_clicked
)
lay.addWidget(btn)
#QtCore.pyqtSlot()
def on_clicked(self):
btn = self.sender()
ix = self.layout().indexOf(btn)
new_btn = QtGui.QPushButton(
"button {}".format(self.layout().count()),
clicked=self.on_clicked
)
self.layout().insertWidget(ix+1, new_btn)
if __name__ == '__main__':
import sys
app = QtGui.QApplication.instance()
if app is None:
app = QtGui.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())

pyqt5 Is there a limit to loading widgets using clicked.connect?

I'm using the QPushButton to load the UI. First -> Jumin -> Department -> next -> next I want to create the UI in order. The problem is that I can not load the third Department into the QMainwindow window. I do not know why
When you create a widget in QVBoxLayout, it changes the size of the widget according to the wallpaper like wxpython layout (wx.all). Can not change the position (move) and size (resize) by automatic centering?
import sys
from PyQt5.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.center_widget = QWidget()
self.setCentralWidget(self.center_widget)
self.FirstUI()
def FirstUI(self):
self.btn1 = QPushButton('test1', self)
self.btn1.move(50, 50)
self.btn1.clicked.connect(self.btn1_click)
def JuminUI(self):
self.ju1 = QLineEdit('13')
self.btn2 = QPushButton('^^^^^^^^^^')
self.ju_text = QLabel('asd')
self.jumim_layout = QVBoxLayout()
self.jumim_layout.addWidget(self.ju_text)
self.jumim_layout.addWidget(self.ju1)
self.jumim_layout.addWidget(self.btn2)
self.centralWidget().setLayout(self.jumim_layout)
self.btn2.clicked.connect(self.btn2_click)
def DepartmentUI(self):
self.depart_layout = QVBoxLayout()
self.depart_layout.addWidget(QPushButton('sdfsdf'))
self.centralWidget().setLayout(self.depart_layout)
def btn1_click(self):
self.btn1.deleteLater()
self.JuminUI()
def btn2_click(self):
self.ju1.deleteLater()
self.btn2.deleteLater()
self.ju_text.deleteLater()
self.DepartmentUI()
if __name__ == "__main__":
app = QApplication(sys.argv)
fream = MainWindow()
fream.show()
app.exec_()
creating and removing widgets is almost always a bad idea, and your code falls into those bad ideas, it's always best to hide the widgets and for that you should use the QStackedWidget, what QStackedWidget does is just make a widget visible on all widgets that you have been assigned by changing the currentIndex.
import sys
from functools import partial
from PyQt5 import QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.center_widget = QtWidgets.QStackedWidget()
self.setCentralWidget(self.center_widget)
self.FirstUI()
self.JuminUI()
self.DepartmentUI()
def FirstUI(self):
widget = QtWidgets.QWidget()
self.btn1 = QtWidgets.QPushButton('test1', widget)
self.btn1.move(50, 50)
self.center_widget.addWidget(widget)
self.btn1.clicked.connect(partial(self.center_widget.setCurrentIndex, 1))
def JuminUI(self):
widget = QtWidgets.QWidget()
lay = QtWidgets.QVBoxLayout(widget)
self.ju1 = QtWidgets.QLineEdit('13')
self.btn2 = QtWidgets.QPushButton('^^^^^^^^^^')
self.ju_text = QtWidgets.QLabel('asd')
lay.addWidget(self.ju_text)
lay.addWidget(self.ju1)
lay.addWidget(self.btn2)
self.center_widget.addWidget(widget)
self.btn2.clicked.connect(partial(self.center_widget.setCurrentIndex, 2))
def DepartmentUI(self):
widget = QtWidgets.QWidget()
lay = QtWidgets.QVBoxLayout(widget)
lay.addWidget(QtWidgets.QPushButton('sdfsdf'))
self.center_widget.addWidget(widget)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
fream = MainWindow()
fream.show()
sys.exit(app.exec_())

How to Auto-scroll QT Text Field Widget

A code below creates a window with QTextBrowser widget.
For five seconds the new lines of text added to QTextBrowser using
its .insertPlainText() method.
As soon as text fills an entire field QTextBrowser automatically adds a scroll bar. But it does nothing to scroll down so the last line of text would be always visible (or readable) by the user.
I would like to know how to make QTestBrowser scroll down automatically so the last line of the text is always within its text field or visible. How to achieve it?
Screenshot:
Code:
import sys, time
from PyQt4 import QtCore, QtGui
class MyDialog(QtGui.QDialog):
def __init__( self, parent = None ):
super(MyDialog, self).__init__(parent)
self._console = QtGui.QTextBrowser(self)
layout = QtGui.QVBoxLayout()
layout.addWidget(self._console)
self.setLayout(layout)
def updateField(self):
for m in range(5):
for n in range(100):
self._console.insertPlainText('%s : '%n)
QtGui.qApp.processEvents()
time.sleep(1)
if ( __name__ == '__main__' ):
app = None
if ( not QtGui.QApplication.instance() ):
app = QtGui.QApplication([])
dlg = MyDialog()
dlg.show()
dlg.updateField()
if ( app ): app.exec_()
Just use .append instead of .insertPlainText to ensure auto scrolling:
self._console.append('%s : '%n)
Since QTextBrowser is sub-classed from QTextEdit widget be we can use its .ensureCursorVisible() method.
To make QTextBrowser scroll down automatically as it is being filled with text all we need to do is to add an extra line to enable its property responsible for this feature:
self._console.ensureCursorVisible()
Here is the working code solution:
import sys, time
from PyQt4 import QtCore, QtGui
class MyDialog(QtGui.QDialog):
def __init__( self, parent = None ):
super(MyDialog, self).__init__(parent)
self._console = QtGui.QTextBrowser(self)
self._console.ensureCursorVisible()
layout = QtGui.QVBoxLayout()
layout.addWidget(self._console)
self.setLayout(layout)
def updateField(self):
for m in range(5):
for n in range(100):
self._console.insertPlainText('%s : '%n)
QtGui.qApp.processEvents()
time.sleep(1)
if ( __name__ == '__main__' ):
app = None
if ( not QtGui.QApplication.instance() ):
app = QtGui.QApplication([])
dlg = MyDialog()
dlg.show()
dlg.updateField()
if ( app ): app.exec_()

Categories

Resources