how to create folder view in pyqt inside main window - python

I'm trying to implement a folder viewer to view the structure of a specific path. and this folder view should look like a the tree widget in PyQT , i know that the file dialog can help , but i need to have it inside my main window.
i tried to implement this using QTreeWidget and i used a recursed function to loop inside folders, but this is too slow. since it needs to recurse around huge number of folders.
is this the right way to do it? or there is a ready qt solution for this problem.
Check the figure below.

for PyQt5 I did this function :
def load_project_structure(startpath, tree):
"""
Load Project structure tree
:param startpath:
:param tree:
:return:
"""
import os
from PyQt5.QtWidgets import QTreeWidgetItem
from PyQt5.QtGui import QIcon
for element in os.listdir(startpath):
path_info = startpath + "/" + element
parent_itm = QTreeWidgetItem(tree, [os.path.basename(element)])
if os.path.isdir(path_info):
load_project_structure(path_info, parent_itm)
parent_itm.setIcon(0, QIcon('assets/folder.ico'))
else:
parent_itm.setIcon(0, QIcon('assets/file.ico'))
then i call it like this :
load_project_structure("/your/path/here",projectTreeWidget)
and i have this result :

Use models and views.
"""An example of how to use models and views in PyQt4.
Model/view documentation can be found at
http://doc.qt.nokia.com/latest/model-view-programming.html.
"""
import sys
from PyQt4.QtGui import (QApplication, QColumnView, QFileSystemModel,
QSplitter, QTreeView)
from PyQt4.QtCore import QDir, Qt
if __name__ == '__main__':
app = QApplication(sys.argv)
# Splitter to show 2 views in same widget easily.
splitter = QSplitter()
# The model.
model = QFileSystemModel()
# You can setRootPath to any path.
model.setRootPath(QDir.rootPath())
# List of views.
views = []
for ViewType in (QColumnView, QTreeView):
# Create the view in the splitter.
view = ViewType(splitter)
# Set the model of the view.
view.setModel(model)
# Set the root index of the view as the user's home directory.
view.setRootIndex(model.index(QDir.homePath()))
# Show the splitter.
splitter.show()
# Maximize the splitter.
splitter.setWindowState(Qt.WindowMaximized)
# Start the main loop.
sys.exit(app.exec_())

Related

PySide6 Exclude Folders from QTreeView

I have an application where i need to display 2 different TreeViews. One for showing the folders (folderView) and the other will display the Files (fileView) inside the selected folder from the folderView. The following Code works fine but i am having a strange issue:
in the screen shot below, if i click on the bin folder for example, then switch back to VBoxGuestAdd.., the fileView will display the bin folder in the fileView.
p.s.: using an ubuntu 22.04 machine
and here my code:
import sys
from PySide6.QtCore import QDir
from PySide6.QtWidgets import QApplication, QWidget, QHBoxLayout, QTreeView, QFileSystemModel
def folderView_selectionchanged():
current_index = folderView.currentIndex()
selected_folder_path = folderModel.fileInfo(current_index).absoluteFilePath()
fileView.setRootIndex(fileModel.setRootPath(selected_folder_path))
app = QApplication(sys.argv)
window = QWidget()
layout = QHBoxLayout()
folderView = QTreeView()
folderModel = QFileSystemModel()
folderModel.setRootPath("/")
folderModel.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs)
folderView.setModel(folderModel)
folderView.selectionModel().selectionChanged.connect(folderView_selectionchanged)
fileView = QTreeView()
fileModel = QFileSystemModel()
fileModel.setRootPath("/")
fileModel.setFilter(QDir.NoDotAndDotDot | QDir.Files)
fileView.setModel(fileModel)
layout.addWidget(folderView)
layout.addWidget(fileView)
window.setLayout(layout)
window.show()
app.exec()
This is a "bug" probably caused by the asynchronous nature of QFileSystemModel, which uses threading to fill the model and delays calls for the model structure updates.
It seems that it's also been already reported as QTBUG-93634, but it has got no attention yet.
A possible workaround is to "reset" the filter and set it again:
def folderView_selectionchanged():
current_index = folderView.currentIndex()
selected_folder_path = folderModel.fileInfo(current_index).absoluteFilePath()
fileView.setRootIndex(fileModel.setRootPath(selected_folder_path))
fileModel.setFilter(QDir.AllDirs)
fileModel.setFilter(QDir.NoDotAndDotDot | QDir.Files)
But the above might not work for big/slow file systems. The only possible solution I can think of is to use a QSortFilterProxyModel and override the filterAcceptsRow() function. It will not be as fast as the basic model, but it will work as expected.
Note that:
filterAcceptsRow() is always checked against the model hierarchy, so always allowing the filter to pass anything outside the filtered directory is mandatory, otherwise it will be filtered out (if the parent directory is filtered out, there is no child to show);
since setRootPath() invalidates the layout and checks the filters again, we must clear the validation until the new root path is set, and then restore it and reset the filters again; this is done by temporarily replacing the actual filtering function with a dummy one that always returns True;
class FileProxy(QSortFilterProxyModel):
validParent = None
def __init__(self):
super().__init__()
self.fsModel = QFileSystemModel()
self.setSourceModel(self.fsModel)
def filterAcceptsRow(self, row, parent):
return (
self.validParent != parent
or not self.fsModel.isDir(
self.fsModel.index(row, 0, parent))
)
def setRootPath(self, path):
func = self.filterAcceptsRow
self.filterAcceptsRow = lambda *args: True
self.validParent = self.fsModel.setRootPath(path)
self.filterAcceptsRow = func
self.invalidateFilter()
return self.mapFromSource(self.validParent)

how to find parent TabPage of widget in pyqt5?

I found code for finding child widget <Top-widget.findChildren(widget-type)>, but I couldn't able to find command for getting parent widget.
I created QTabWidget with multiple tabs . in each tabs I have some widgets.
I want to highlight tab page of given widget element.
I created one code to achieve this. and it is working. but I feels this is not straight forward. can you suggest better solution to achieve the same?
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
def color_parent_tab(widget,Tab):
tabs={Tab.widget(i):i for i in range(Tab.count())}
while widget!=None:
try:
if widget in tabs:
Tab.tabBar().setTabTextColor(tabs[widget], QColor('red'))
widget=widget.parent()
except Exception as e:print(e)
if __name__ == '__main__':
app = QApplication([])
window=QTabWidget()
w=QWidget()
VL=QVBoxLayout(w)
window.addTab(w,'tab1')
L=QLabel('Label1')
VL.addWidget(L)
L2=QLabel('Label2')
VL.addWidget(L2)
w=QWidget()
VL=QVBoxLayout(w)
window.addTab(w,'tab2')
L3=QLabel('Label1')
VL.addWidget(L3)
L4=QLineEdit()
VL.addWidget(L4)
color_parent_tab(L3,window)
window.showMaximized()
app.exec()
You can use isAncestorOf, which returns True if the widget in the argument is a child (or grandchild, at any level):
def color_parent_tab(widget, Tab):
for i in range(Tab.count()):
if Tab.widget(i).isAncestorOf(widget):
Tab.tabBar().setTabTextColor(i, QColor('red'))
return

How to reference & change a PyQt5 window object from another window class or from an extra .py file such as init_events_ui.py

Is it possible to add another .py file (via import) and use it as an initialisation and event handler for a PyQt5 Multiwindow project other window & main .py files?
I have a project that consists of the following files:
main.py - the main app which imports the Ui...py files below & the ui_init_events.py file also
main.py - the main program
Ui_main_window_ui.py - a complied Qt UI to display the main window
Ui_frmConsoleLog_ui.py - a complied Qt UI to display a window with a textEdit & comboBox objects
ui_init_events.py - I want this file to have functions for setting each windows Ui object fields as well as contain the Ui windows object events such as btn.clicked.connect(self.SomeotherFunc)
I have found both these posts helpful but am stuck as I do not know how to reference objects other than the self object and can't find where this is explained. See: PyQt: Modifying Widget Object from another Function
Multiple Windows in PyQt4
In the 2nd post they are using multi-windows (via the QDialog) object also, however they are only using a single .py file. I am using Visual Studio Code & have built the Ui files then complied them. They are works in progress so I expect to make more changes meaning they wil be overwritten so I do not want to edit these files.
I cannot work out how to reference and change the properties in another window for initialisation purposes. The thread is:
Here is the main bits. Currently I call a function to return some data using self.combobox.additems (see #1) but I think calling this over and over again from main.py somewhat decreases code readability.
(#2) Therefore I would like advice how to move all the initialisation parts of an existing PyQt window (being an imported .py file used to generate the window + controls) into a separate .py file (eg named ui_init_events.py).
However in trying & researching this I do not know nor can find an example how to reference the objects using their full hierarchical naming convention. I have tried Application. QWindow., etc. I only know how to use self.... and it isn't working (of course as that would be the referring to the function itself I understand that its being called in, not the PyQt window I'm wanting to reference). See (#3) for what is not working.
Any ideas please on resources available to assist with understanding how PyQt labels its objects from the root of the object tree is appreciated as well as an example of how to set the comboBox_Selector in the Ui_frmConsoleLog window from app.py or another .py file other than from the Ui_frmConsoleLog_ui.py or within the class definition under --init--(self, parent=None): ?
The desired file structure is like this:
+-- main.py (or app.py)
|
+--- Ui_main_window_ui.py (has setupui & adds objects for main window)
|
+--- Ui_main_window_init.py (has init code for Ui_main_window_ui.py)
|
+--- Ui_main_window_events.py (has event code for Ui_main_window_ui.py)
|
+--- Ui_frmConsoleLog_ui.py (has setupui & adds objects for 2nd window)
|
+--- Ui_frmConsoleLog_init.py (has init code for Ui_frmConsoleLog_ui.py)
|
+--- Ui_frmConsoleLog_events.py (has event code for Ui_frmConsoleLog_ui.py)
main.py file contents
# Default imports
import sys, os, io, sched, time
import ui_resources_rc
from PyQt5.QtCore import QObject, QThread, pyqtSignal
from configparser import ConfigParser
...a portion of code here uses a ini config file for retrieving/saving some settings of the UI & I want to set the properties for each object accordingly...
from PyQt5.QtWidgets import (QApplication, QDialog, QMainWindow, QMessageBox, QWidget)
from PyQt5.uic import loadUi
from Ui_main_window_ui import Ui_MainWindow
from Ui_frmConsoleLog_ui import Ui_frmConsoleLog
from ui_init_events import (funcUpdateFormCombos)
...todo... open a network connection to the system (its an inverter)
# This window will be used for filtering network poll event types
class ConsoleLogWindow(QWidget, Ui_frmConsoleLog):
def __init__(self):
super().__init__()
self.setWindowTitle('Console Log')
class AppWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.win = None # No Console Log window visible yet.
self.setupUi(self)
#(1) Here is where I will have to call this a lot of times to
# populate the vaious window objects which I dont want to do
self.comboBox_Selector.addItems(funcUpdateFormCombos())
self.action_Console.triggered.connect(self.OpenConsoleLogWindow)
self.action_About.triggered.connect(self.about)
# Populate the controls as per the config file
#initialise() <-- (2) Here is where I want to call a
# function in ui_init_events.py to setup
# the form's objects as created in QtDesigner
# & exported as a .py file. I want to continue
# to edit each PyQt window later so I dont want
# to add to the Ui_... files themselves but in
# main.py call a function that has the code to
# set the object fields like checked, combobox
# list contents, etc.
def OpenConsoleLogWindow(self, checked):
if self.win is None:
self.win = ConsoleLogWindow()
self.win.show()
else:
self.win.close() # Close window.
self.win = None # Discard reference.
if __name__ == "__main__":
app = QApplication(sys.argv)
win = AppWindow()
win.show()
print('HERE')
sys.exit(app.exec())
ui_init_events.py file contents (this is the file I want to house all window object setup without editing the PyQt UI converted .ui to .py files such as Ui_main_window_u.py & Ui_frmConsoleLog_ui.py which I don't want to edit at all.
# Contains the initialisation of widgets fields
def funcUpdateFormCombos():
LIST = ['Amps', 'Volts', 'Watts']
LIST.sort()
return tuple(LIST)
# Would prefer to have a single file per Ui_...py file that houses
# all the events that get triggered for each Ui windows.
# How is this done? eg: <not working> (3)
def initialise()
frmConsoleLog.self.comboBox_Selector.addItems('Watts','Amps','Volts')
Contents of Ui_frmConsoleLog_ui.py (has the combobox I want to populate)
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_frmConsoleLog(object):
def setupUi(self, frmConsoleLog):
frmConsoleLog.setObjectName("frmConsoleLog")
frmConsoleLog.resize(640, 468)
frmConsoleLog.setToolTip("")
frmConsoleLog.setStatusTip("")
frmConsoleLog.setAccessibleName("")
self.horizontalLayout = QtWidgets.QHBoxLayout(frmConsoleLog)
self.horizontalLayout.setContentsMargins(0, 1, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.comboBox_Selector = QtWidgets.QComboBox(frmConsoleLog)
self.comboBox_Selector.setObjectName("comboBox_Selector")
self.horizontalLayout.addWidget(self.comboBox_Selector)
self.textEdit_ConsoleLog = QtWidgets.QTextEdit(frmConsoleLog)
self.textEdit_ConsoleLog.setEnabled(True)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.textEdit_ConsoleLog.sizePolicy().hasHeightForWidth())
self.textEdit_ConsoleLog.setSizePolicy(sizePolicy)
self.textEdit_ConsoleLog.setObjectName("textEdit_ConsoleLog")
self.horizontalLayout.addWidget(self.textEdit_ConsoleLog)
self.retranslateUi(frmConsoleLog)
QtCore.QMetaObject.connectSlotsByName(frmConsoleLog)
def retranslateUi(self, frmConsoleLog):
_translate = QtCore.QCoreApplication.translate
frmConsoleLog.setWindowTitle(_translate("frmConsoleLog", "Console Log"))
Worked it out, thanks to an answer in my previous post (that the poster removed?), here's the snippet that works and allows your functions in another .py file to set MainWindow pyQt object control properties in the parent main.py file for a much tidier project. I honestly don't care that professional devs think that is not good practice (if they do they are very closed minded).
In app.py (or main.py - I had two files and was playing with names)
class AppWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.win = None # No Console Log window visible yet.
self.setupUi(self)
from init_Ui_main_window import (initMainWindow)
initMainWindow(self)
...
In init_Ui_main_window.py:
def initMainWindow(window):
funcLoadConfig()
# Populate the controls in the main window
window.line_Settings_Simple_Default_Selection.setText(str(SETTINGS_SIMPLE_DEFAULT_SELECTOR))
Note: SETTINGS_SIMPLE_DEFAULT_SELECTOR is a globally defined static variable that wont ever be changed as its read from a config ini file. The user will be able to change the line value if they want to override the default, but it's nice to have a default value populated.
Was that easy as when initMainWindow(self) is called self from the previous object context is passed across to the function and set in function variable window, so to reference the main windows objects its just a case of referencing window.[object name as one would in main.py]

Adding items to QComboBox

I'm trying to add items to two combo boxes.
The code below runs with no errors, I see the list I'm trying to add and "fin" is printed to the terminal, but the combo boxes are showing up empty.
from PyQt5.QtWidgets import QMainWindow
from PyQt5 import QtWidgets
# import GUI from designer file
from main_menu import Ui_main_menu
# import other functions
from add_functions import ChangeLists
class Main(QMainWindow, Ui_main_menu):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.setupUi(self)
self.init_lists()
def init_lists(self):
# Team List
team_list_file = open(r'C:\NHLdb_pyqt\files\NHLteams.txt', 'r')
team_list = team_list_file.read().splitlines()
team_list_file.close()
print("team list: ", team_list)
# Initial Player List
player_list_init = "Please Select a Team"
# Populate combo box lists
self.team_select_combobox.addItems(team_list)
self.player_select_combobox.addItem(player_list_init)
# connect combo box to function that will change player list based on team list selection
# self.team_select_combobox.currentTextChanged.connect(ChangeLists.team_changed)
print("fin")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
main_menu = QtWidgets.QDialog()
ui = Main()
ui.setupUi(main_menu)
# main_menu = Main()
main_menu.show()
sys.exit(app.exec_())
You're using two methods of loading the ui at the same time, the multiple inheritance approach and the "direct approach", and you're actually showing the main_menu instance of QDialog (which doesn't have any init_lists function).
The result is that even if the init_lists is called, it's "shown" (actually, not) in the wrong window, since you're showing the main_menu instance.
Clearly, you should not use both of them, as the first is enough (and usually the most used/suggested), and then show the correct instance object:
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
main_menu = Main()
main_menu.show()
sys.exit(app.exec_())
Do note that there's something else that is wrong with your implementation: you're inheriting from QMainWindow in the Main class, but later you're trying to set up the ui using a QDialog.
Only the base class created in Designer should be used (it doesn't matter what approach you use to load the ui). I can assume that the ui was created using a QDialog (otherwise an exception would have occurred, as a QMainWindow ui would try to use setCentralWidget() which is a function that doesn't exist for QDialog).
So, you either create a new main window in Designer and copy your existing layout in that (if you need the features of a QMainWindow, such as a menu bar, a status bar, dock widgets or toolbars), or you correctly use the QDialog class in the constructor:
class Main(QDialog, Ui_main_menu):
# ...

How to limit QTreeView directory in PyQt5? [duplicate]

This is the code i have to display a tree view of a directory called "C:\Myfolder".
import sys
from PyQt4 import QtGui,QtCore
class Myview(QtGui.QMainWindow):
def __init__(self,parent=None):
QtGui.QMainWindow.__init__(self)
model = QtGui.QFileSystemModel()
model.setRootPath('C:\Myfolder')
view = QtGui.QTreeView()
view.setModel(model)
self.setCentralWidget(view)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
myview = Myview()
myview.show()
sys.exit(app.exec_())
Even though i set the RootPath to "C:\Myfolder" , the tree view display all the drives and folder.
How can i limit the QFileSystemModel so that TreeView only display items inside "C:\Myfolder" directory?
You would need to add view.setRootIndex(model.index("C:\Myfolder")) according to the QFileSystemModel documentation.
It is extremely important that you write "C:/Myfolder" and NOT "C:\Myfolder". Otherwise, it thinks the directory does not exist and will always show you all drives and all folders.

Categories

Resources