I have coded out a small application using PyQt5. The application gives a tree view of all folders in the computer plus a lineEditd where the user can type things:
import sys
from PyQt5.QtWidgets import (
QMainWindow, QApplication,
QHBoxLayout,QWidget,
QVBoxLayout,QFileSystemModel, QTreeView, QLineEdit
)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
layout = QHBoxLayout()
application = App()
layout.addWidget(application)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
class App(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.model = QFileSystemModel()
self.model.setNameFilters([''])
self.model.setNameFilterDisables(0)
self.model.setRootPath('')
self.tree = QTreeView()
self.tree.setModel(self.model)
self.tree.setAnimated(False)
self.tree.setSortingEnabled(True)
layout = QVBoxLayout()
layout.addWidget(self.tree)
self.input = QLineEdit()
layout.addWidget(self.input)
self.setLayout(layout)
self.show()
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
My next goal is to make my application able to detect mouse click on the tree diagram. More precisely, in an event of a mouse click on any folder in the tree, I want to know the directory of the folder which the user clicks (so that I can, for instance, update the LineEdit by filling it with the directory)
To achieve such a goal, the first thing I need to do is to make mouse click in the tree an event. If I add a method like:
def mouseMoveEvent(self, e):
#some code
It will not give me an event reflecting the fact that the mouse click happens in the tree. I am therefore stuck on even making mouse click in tree an event, nor to mention reading the directory .
Edit:
By writing
self.tree.clicked
I am able to detect any click in the tree. However, I still do not know how to get the directory.
If you want to get the directory using the view's clicked signal then you should use the model:
self.tree.clicked.connect(self.handle_clicked)
def handle_clicked(self, index):
filename = self.model.fileName(index)
path = self.model.filePath(index)
fileinfo = self.model.fileInfo(index)
print(filename, path, fileinfo)
Related
I am learning Python by creating an MDI application in PyQt5. This application contains a class derived from QMdiSubWindow. These sub-windows need their own menu to be added to the main menu-bar. Where is the 'correct' place to create and show/hide that part of the menu which is only relevant to the sub-window when it's in focus? And where should the menu be destroyed (if it doesn't happen automatically because ownership is taken by the parent)? My attempt at detecting when the sub-window gains/loses focus causes infinite recursion, presumably because the newly visible menu steals the focus back from the sub-window.
This is probably such a common requirement that it's not mentioned in the tutorials, but the only reference in the docs to sub-window menus seems to just refer to the system menu, and not the main menu-bar. Most other Q&A's just refer to activating other sub-windows from the main menu. Several hours of searching haven't quite got what I need, so thank you for your help in either pointing me to the right place in the docs, or by improving my code ... or even both!
A minimal app to illustrate the problem:
#!/usr/bin/python3
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class TestSubWin(QMdiSubWindow):
def __init__(self, parent=None):
super().__init__()
self.setWidget(QLabel("Hello world"))
self.own_menu = QMenu("Sub win menu")
parent.menuBar().addMenu(self.own_menu)
# Add sub-window actions to the menu here
## Causes infinite recursion
# def focusOutEvent(self, event):
# self.own_menu.setVisible(False)
#
# def focusInEvent(self, event):
# self.own_menu.setVisible(True)
class MainWindow(QMainWindow):
def __init__(self, parent = None):
super(MainWindow, self).__init__(parent)
self.mdi = QMdiArea()
self.setCentralWidget(self.mdi)
bar = self.menuBar()
file = bar.addMenu("File")
file.addAction("New")
file.triggered[QAction].connect(self.windowaction)
self.setWindowTitle("MDI demo")
def windowaction(self, q):
if q.text() == "New":
sub = TestSubWin(self)
self.mdi.addSubWindow(sub)
sub.show()
def main():
app = QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
QMenu and QMenuBar don't take ownership of QActions (and QMenus), unless when created with the functions that accepts icon/title arguments.
This also means that you shall not need to destroy the menus, but only remove them from the menu bar.
The solution is to connect to the subWindowActivated signal, remove the previously added menu, retrieve the menu for the newly active sub window (if any) and add it.
Note that in order to remove a menu from QMenuBar you have to use removeAction() along with the menuAction(), which is the action associated with the menu and shown as menubar title for the menu (or item in a menu for sub menus).
In the following example I'm creating a base subclass for any mdi subwindows that will support menubar menus, and further subclasses for different window types.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MenuSubWin(QMdiSubWindow):
own_menu = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setAttribute(Qt.WA_DeleteOnClose)
def menu(self):
return self.own_menu
class TestSubWin1(MenuSubWin):
def __init__(self):
super().__init__()
self.setWidget(QLabel("Hello world"))
self.own_menu = QMenu("Sub win menu 1")
self.own_menu.addAction('Test 1')
class TestSubWin2(MenuSubWin):
def __init__(self):
super().__init__()
self.setWidget(QLabel("How are you?"))
self.own_menu = QMenu("Sub win menu 2")
self.own_menu.addAction('Test 2')
class MainWindow(QMainWindow):
def __init__(self, parent = None):
super(MainWindow, self).__init__(parent)
self.mdi = QMdiArea()
self.setCentralWidget(self.mdi)
bar = self.menuBar()
fileMenu = bar.addMenu("File")
new1Action = fileMenu.addAction("New 1")
new1Action.setData(TestSubWin1)
new2Action = fileMenu.addAction("New 2")
new2Action.setData(TestSubWin2)
fileMenu.triggered.connect(self.newWindow)
self.setWindowTitle("MDI demo")
self.subWinMenu = None
self.mdi.subWindowActivated.connect(self.subWindowActivated)
def subWindowActivated(self, subWindow):
if self.subWinMenu:
self.menuBar().removeAction(self.subWinMenu.menuAction())
self.subWinMenu = None
if subWindow is None or not hasattr(subWindow, 'menu'):
return
self.subWinMenu = subWindow.menu()
if self.subWinMenu:
self.menuBar().addMenu(self.subWinMenu)
def newWindow(self, action):
cls = action.data()
if not cls:
return
sub = cls()
self.mdi.addSubWindow(sub)
sub.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
Notes:
you shall always set the WA_DeleteOnClose attribute when directly adding a QMdiSubWindow (as opposed to adding a QWidget), otherwise the window will still exist for the MDI area and listed in the subWindowList(), thus preventing proper focus switching (and menu removal) upon closure;
for simplicity, I used the setData() feature of QAction with the class of the window that has to be created;
specifying the signature of signals is only required when signals do have overrides, which is unnecessary for triggered() since it has no overrides; note that Qt is gradually removing signal overrides, preferring explicit and unique signals instead;
I need to execute a block of code when the user clicks on the tab of a tabbified QDockWidget. So far I've been doing this via a hack using the "visibilityChanged" event but this is now causing issues (for example, if I have several tabbified dock widgets and I drag one out so that it is floating, the tabbified one underneath will fire its "visibilityChanged" event which I will mistakenly interpret as the user clicking the tab). How can I receive proper notification when a user clicks on a QDockWidgets' tab? I've experimented with the "focusInEvent" of QDockWidget but it doesn't seem to fire when the tab is clicked.
When you use tabifyDockWidget() method QMainWindow creates a QTabBar, this is not directly accessible but using findChild() you can get it, and then use the tabBarClicked signal
from PyQt4 import QtCore, QtGui
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
first_dock = None
for i in range(10):
dock = QtGui.QDockWidget("title {}".format(i), self)
dock.setWidget(QtGui.QTextEdit()) # testing
self.addDockWidget(QtCore.Qt.TopDockWidgetArea, dock)
if first_dock:
self.tabifyDockWidget(first_dock, dock)
else:
first_dock = dock
dock.raise_()
tabbar = self.findChild(QtGui.QTabBar, "")
tabbar.tabBarClicked.connect(self.onTabBarClicked)
def onTabBarClicked(self, index):
tabbar = self.sender()
text = tabbar.tabText(index)
print("index={}, text={}".format(index, text))
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
~EDIT (original question still below)~ when I remove the self.setGeometry() call in the new window it works as it should. Why is that? I'm still building out the UI for it, but once I do I hope I don't continue to have this problem...
~EDIT 2~ Just realized it should be self.resize() not self.setGeometry()...
self.solved()
:(
I'm just learning PyQt5 and just doing a little messing around. For some reason when I try to open a new window from the main application window, the whole thing closes. Putting in some print statements to track progress shows that it's not actually creating the new window either.
Main Window code:
import sys
from PyQt5.QtWidgets import QMainWindow, QAction, QApplication
from newLeague import *
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
newLeagueAction = QAction('Create New League', self)
newLeagueAction.setShortcut('Ctrl+N')
newLeagueAction.setStatusTip('Create a new league from scratch')
newLeagueAction.triggered.connect(self.createNewLeague)
openLeagueAction = QAction('Open Existing League', self)
openLeagueAction.setShortcut('Ctrl+E')
openLeagueAction.setStatusTip('Continue with a previously started league')
openLeagueAction.triggered.connect(self.openExistingLeague)
exitAction = QAction('Quit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Quit the application...')
exitAction.triggered.connect(self.close)
self.statusBar()
mainMenu = self.menuBar()
fileMenu = mainMenu.addMenu('&File')
fileMenu.addAction(newLeagueAction)
fileMenu.addAction(openLeagueAction)
fileMenu.addAction(exitAction)
self.resize(1920, 1080)
self.setWindowTitle("Brackets")
def createNewLeague(self):
'''shows dialog to create a new league'''
self.newLeague = CreateLeague()
self.newLeague.show()
print('New League being created...')
def openExistingLeague(self):
print('Existing League opening...')
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Here is the second window:
from PyQt5.QtWidgets import QMainWindow
class CreateLeague(QMainWindow):
def __init__(self):
super(CreateLeague, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(600, 500)
self.setWindowTitle('Create A New League')
I've looked at other examples such as this, and this, and I'm not seeing what it is I'm doing different. I've experimented with using parent as an argument in the constructors and the result is no different.
Your Main Window code is ok, but you should remove CreateLeague, self arguments from the super parameters in your second window, then, your code should work fine.
See below:
from PyQt5.QtWidgets import QMainWindow
class CreateLeague(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.resize(600, 500)
self.setWindowTitle('Create A New League')
I have to write a program with an option to open an image from a file. I have to use QFileDialog and display image in QLabel, using QPixmap. I am able to use them individually but I didn't manage to combine them.
I think I need to take my image name from dlg.selectedFiles but I don't know how to choose the moment when there is useful data in it. Do I need to make a loop in my main program, and constantly check if there is image to open? Can I send a signal to my label using openAction.triggered.connect(...)?
from PyQt4 import QtGui
import sys
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent = None):
super(MainWindow, self).__init__(parent)
menubar = self.menuBar()
fileMenu = menubar.addMenu('File')
dlg = QtGui.QFileDialog(self)
openAction = QtGui.QAction('Open', self)
openAction.triggered.connect(dlg.open)
fileMenu.addAction(openAction)
#label = QtGui.QLabel(self)
#pixmap = QtGui.QPixmap('')
#label.setPixmap(pixmap)
def main():
app = QtGui.QApplication(sys.argv)
win = MainWindow()
win.show()
app.exec_()
if __name__ == '__main__':
sys.exit(main())
You need to make your own slot and connect it to the openAction signal.
In your __init__ function do:
openAction.triggered.connect(self.openSlot)
In your class MainWindow define the following function:
def openSlot(self):
# This function is called when the user clicks File->Open.
filename = QtGui.QFileDialog.getOpenFileName()
print(filename)
# Do your pixmap stuff here.
Hello I am working on data archive program with python 2.7. I have one mainWindow and there is some elements (Buttons, text lines etc.) Clicking a button open a dialog form page. User select their answer on that dialog page. Dialog page has a button named 'save'. When clicking save button dialog class saving user's selections to database. I want to do, when user clicked to save button on dialog, It will enable some elements on the mainWindow which is not enabled. I am doing this with these codes entering to dialog class and save button function:
self.ui.onceBut.setEnabled(True) etc.
But I am taking an error:
AttributeError: 'onceDlg' object has no attribute 'onceBut'
onceDlg is dialog pages class name.
How can I solve this and I can do what I want? Thanks in advance.
self in self.ui.onceBut.setEnabled(True) refers to dialog, so you get error because your onceBut is in your mainWindow not in onceDlg dialog.
Solution: as #Radio say - Communication between components in Qt are often done using signals and slots, but it's not the only way.
Easier way is to simply pass main window to dialog window, so you can manipulate with it's buttons, or whatever you want, inside dialog window. In next example I've done that in line dialog = Dialog(self), where self refers to MainWindow and it is used as mainWin inside Dialog. Run it, click on first button, dialog will show, click save button in dialog and second button in main will be changed and disabled.
I hope I've helped you.
import sys
from PyQt4 import QtCore, QtGui
class Dialog(QtGui.QDialog):
def __init__(self, mainWin):
QtGui.QDialog.__init__(self,mainWin)
self.setWindowTitle(self.tr("Dialog window"))
self.main = mainWin
button = QtGui.QPushButton()
button.setText( "Save (disable dummy button)" )
layout = QtGui.QVBoxLayout()
layout.addWidget(button)
self.setLayout(layout)
self.connect(button, QtCore.SIGNAL("clicked()"), self.save)
self.resize(200, 100)
def save(self):
self.main.button2.setEnabled(False)
self.main.button2.setText( "changed from dialog" )
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle(self.tr("Main window"))
self.button1 = QtGui.QPushButton()
self.button2 = QtGui.QPushButton()
self.button1.setText( "Open dialog" )
self.button2.setText( "Dummy" )
layout = QtGui.QVBoxLayout()
layout.addWidget(self.button1)
layout.addWidget(self.button2)
self.window = QtGui.QWidget()
self.window.setLayout(layout)
self.setCentralWidget(self.window);
self.connect(self.button1, QtCore.SIGNAL("clicked()"), self.showDialog)
self.resize(360, 145)
def showDialog(self):
dialog = Dialog(self)
dialog.show()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
main = MainWindow()
main.show();
sys.exit(app.exec_())