I am trying to create a gallery view in PyQT5. I am currently trying to group the photos (grey boxes) in months as seen below. I am reading the file location and the date taken of the images through a SQL database.
So far I have managed to lazy load all of the images into the QtWidgets.QListView using a basic model, seen below. I have never worked with PyQt before and I am getting a little lost. Can anyone help me?
import sys
import CustomWidgets as cw
import platform
from PyQt5 import QtCore, QtGui, QtWidgets
import os
import threading
import math
import pyodbc
# GUI FILE
from PhotosPage import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# connect tab Pages
self.ui.PageButton_Photos.clicked.connect(lambda: self.ui.stackedWidget.setCurrentWidget(self.ui.PhotosPage))
self.ui.PageButton_Albums.clicked.connect(lambda: self.ui.stackedWidget.setCurrentWidget(self.ui.AlbumsPage))
self.ui.PageButton_People.clicked.connect(lambda: self.ui.stackedWidget.setCurrentWidget(self.ui.PeoplePage))
self.ui.PageButton_Groups.clicked.connect(lambda: self.ui.stackedWidget.setCurrentWidget(self.ui.GroupsPage))
# hide Album info
self.ui.AlbumInfoFrame.hide()
# set model for photos
model = TestListModel(self.ui.PhotosFrame)
self.ui.PhotosFrame.setModel(model)
self.ui.PhotosFrame.setSpacing(5)
self.show()
class TestListModel(QtCore.QAbstractListModel):
def __init__(self, parent=None):
QtCore.QAbstractListModel.__init__(self, parent)
self.list = parent
self.fileList = os.listdir('P:/PhotoSorterInfo/MiniPhotos')
# self.setFileList()
def setFileList(self):
print(self.readDataBase()[0])
def rowCount(self, index):
return len(self.fileList)
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
if not self.list.indexWidget(index):
button = QtWidgets.QPushButton()
button.setIcon(QtGui.QIcon("P:/PhotoSorterInfo/MiniPhotos/" + self.fileList[index.row()]))
button.setIconSize(QtCore.QSize(300, 175))
button.setFlat(True)
self.list.setIndexWidget(index, button)
return QtCore.QVariant()
if role == QtCore.Qt.SizeHintRole:
return QtCore.QSize(300, 175)
def columnCount(self, index):
pass
def readDataBase(self, id=10):
returnInfo = DataBase.execute(
"""SELECT Files_tbl.ID, Files_tbl.Name, Files_tbl.FileType, Files_tbl.Width, Files_tbl.Height, Files_tbl.Size, Files_tbl.DateTaken, FileDirectories_tbl.FileLocation
FROM Files_tbl, FileDirectories_tbl
WHERE FileDirectories_tbl.ID = Files_tbl.FileLocation
ORDER BY DateTaken DESC;""")
# place info into dictionary
Photos = []
results = []
columns = [column[0] for column in DataBase.description]
for row in returnInfo.fetchall():
Photos.append(dict(zip(columns, row)))
return Photos
if __name__ == "__main__":
filePath = os.getcwd()
DataBase = pyodbc.connect(r'Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ=' + filePath + r'\Database\PhotosDatabase.accdb;')
DataBase = DataBase.cursor()
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
Related
I have Enabled downloads in pyqtwebengineview and it works well. However,I have some problems with the site:"https://www.remove.bg"
So I downloaded The html content of that site and the problem is an anchor tag.
Download
If I simply remove this target atrribute,It works well.
This works
Download
difference is just target atrribute.What to do. How to make this site work correctly.
The redirection link has an attribute of "_blank" that according to the protocol must open a new tab so in a previous answer I explained what the implementation should be, I will omit that part and will only show the code where I remove the new tab when the download is finished.
import sys
from functools import cached_property
from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets
class WebView(QtWebEngineWidgets.QWebEngineView):
def createWindow(self, type_):
if not isinstance(self.window(), Browser):
return
if type_ == QtWebEngineWidgets.QWebEnginePage.WebBrowserTab:
return self.window().tab_widget.create_tab()
class TabWidget(QtWidgets.QTabWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTabsClosable(True)
def create_tab(self):
view = WebView()
index = self.addTab(view, "(Untitled)")
self.setTabIcon(index, view.icon())
view.titleChanged.connect(self.update_title)
view.iconChanged.connect(self.update_icon)
self.setCurrentWidget(view)
return view
def update_title(self, title):
view = self.sender()
index = self.indexOf(view)
self.setTabText(index, title)
def update_icon(self, icon):
view = self.sender()
index = self.indexOf(view)
self.setTabIcon(index, icon)
class Browser(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
QtWebEngineWidgets.QWebEngineProfile.defaultProfile().downloadRequested.connect(
self.on_downloadRequested, QtCore.Qt.UniqueConnection
)
self.setCentralWidget(self.tab_widget)
view = self.tab_widget.create_tab()
view.load(QtCore.QUrl("https://www.remove.bg"))
#cached_property
def tab_widget(self):
return TabWidget()
def on_downloadRequested(self, download):
download.finished.connect(self.download_finished)
default_path = download.downloadFileName()
suffix = QtCore.QFileInfo(default_path).suffix()
path, _ = QtWidgets.QFileDialog.getSaveFileName(
self, "Save File", default_path, "*." + suffix
)
if path:
download.setPath(path)
download.accept()
def download_finished(self):
self.tab_widget.removeTab(self.tab_widget.currentIndex())
def main():
app = QtWidgets.QApplication(sys.argv)
w = Browser()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I want to let users create files by dragging items from PyQt to the system file explorer. Since some of the files will be very large, I also need to delay setting the data to when the user finishes the drop instead of immediately on start of drag.
This example seems to be what I need: https://doc.qt.io/archives/4.6/draganddrop-delayedencoding.html
I tried converting that to a simple PyQt5 version where dragging a QPushButton into a folder will create a plain text file, but it's not working for me... when I run it dropping does nothing and my cursor looks like this:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import typing
import time
class MimeData(QtCore.QMimeData):
dataRequested = QtCore.pyqtSignal(str)
def formats(self) -> typing.List[str]:
return QtCore.QMimeData.formats(self) + ["text/plain"]
def retrieveData(self, mime_type: str, preferred_type: QtCore.QVariant.Type):
self.dataRequested.emit(mime_type)
return QtCore.QMimeData.retrieveData(self, mime_type, preferred_type)
class SourceWidget(QtWidgets.QWidget):
mimeData: MimeData = None
def __init__(self, parent=None):
super().__init__(parent)
layout = QtWidgets.QVBoxLayout()
button = QtWidgets.QPushButton("Drag Me")
button.pressed.connect(self.start_drag)
layout.addWidget(button)
self.setLayout(layout)
#QtCore.pyqtSlot()
def create_data(self, mime_type):
if mime_type == "text/plain":
time.sleep(0.25) # Simulate large file
self.mimeData.setData("text/plain", b"my text file contents")
#QtCore.pyqtSlot()
def start_drag(self):
self.mimeData = MimeData()
self.mimeData.dataRequested.connect(self.create_data)
drag = QtGui.QDrag(self)
drag.setMimeData(self.mimeData)
drag.exec(QtCore.Qt.CopyAction)
if __name__ == "__main__":
app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv)
w = SourceWidget()
w.show()
app.exec_()
Here's what I ended up with for dragging and dropping files from PyQt5 into file explorer, and only having the file write once the mouse is released to finalize the drop.
import time
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
import tempfile
import os
# Use win32api on Windows because the pynput and mouse packages cause lag
# https://github.com/moses-palmer/pynput/issues/390
if os.name == 'nt':
import win32api
def mouse_pressed():
return win32api.GetKeyState(0x01) not in [0, 1]
else:
import mouse
def mouse_pressed():
return mouse.is_pressed()
class DelayedMimeData(QtCore.QMimeData):
def __init__(self):
super().__init__()
self.callbacks = []
def add_callback(self, callback):
self.callbacks.append(callback)
def retrieveData(self, mime_type: str, preferred_type: QtCore.QVariant.Type):
mp = mouse_pressed()
if not mp:
for callback in self.callbacks.copy():
self.callbacks.remove(callback)
callback()
return QtCore.QMimeData.retrieveData(self, mime_type, preferred_type)
class Navigator(QtWidgets.QTreeWidget):
def __init__(self):
super().__init__()
self.setHeaderLabels(["Name"])
QtWidgets.QTreeWidgetItem(self, ['Test1'])
QtWidgets.QTreeWidgetItem(self, ['Test2'])
QtWidgets.QTreeWidgetItem(self, ['Test3'])
self.setAcceptDrops(True)
self.setDragEnabled(True)
self.setDragDropMode(self.DragDrop)
self.setDefaultDropAction(Qt.MoveAction)
self.setSelectionMode(self.ExtendedSelection)
self.setSelectionBehavior(self.SelectRows)
self.setContextMenuPolicy(Qt.CustomContextMenu)
def startDrag(self, actions):
drag = QtGui.QDrag(self)
names = [item.text(0) for item in self.selectedItems()]
mime = DelayedMimeData()
path_list = []
for name in names:
path = os.path.join(tempfile.gettempdir(), 'DragTest', name + '.txt')
os.makedirs(os.path.dirname(path), exist_ok=True)
print(path)
def write_to_file(path=path, name=name, widget=self):
with open(path, 'w+') as f:
print("Writing large file(s)...")
time.sleep(2) # Sleep to simulate long file write
f.write(f"Contents of {name}")
mime.add_callback(write_to_file)
path_list.append(QtCore.QUrl.fromLocalFile(path))
mime.setUrls(path_list)
mime.setData('application/x-qabstractitemmodeldatalist',
self.mimeData(self.selectedItems()).data('application/x-qabstractitemmodeldatalist'))
drag.setMimeData(mime)
drag.exec_(Qt.MoveAction)
super().startDrag(actions)
app = QtWidgets.QApplication([])
nav = Navigator()
nav.show()
app.exec_()
In the default behavior of editing a cell in a QtableView, when the user clicks away either to another widget or closes the form, the edits are lost. After a lot of googling, I have found a way to save the edits if the user selects another widget in the form, but if the form is closed, the edits are still lost. The blog post is here.
I attempted to call the closeEditor method from the forms closeEvent, but it requires two parameters: the editor and hint. I can provide QAbstractItemDelegate.NoHint but the editor is expecting the QlineEdit object where the editing is taking place. I am lost on how to provide this for the cell currently being edited.
Here is a gif of the current behaviour:
My question is how do I provide the QlineEdit of the cell being edited?
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtSql import *
from PyQt5.QtWidgets import *
from phones import *
class Main(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.resize(490, 998)
self.layoutWidget = QWidget(self)
self.layoutWidget.setObjectName("layoutWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget)
self.horizontalLayout_7 = QtWidgets.QHBoxLayout()
self.new_phone = QtWidgets.QPushButton(self.layoutWidget)
self.new_phone.setObjectName("new_phone")
self.new_phone.setText("New Phone")
self.horizontalLayout_7.addWidget(self.new_phone)
self.delete_phone = QtWidgets.QPushButton(self.layoutWidget)
self.delete_phone.setObjectName("delete_phone")
self.delete_phone.setText("Delete phone")
self.horizontalLayout_7.addWidget(self.delete_phone)
self.verticalLayout.addLayout(self.horizontalLayout_7)
self.phone_view = Syn_tableview()
self.verticalLayout.addWidget(self.phone_view)
self.cont_id = '9'
self.setCentralWidget(self.layoutWidget)
self.new_phone.clicked.connect(self.add_phone)
self.populate_phones()
def populate_phones(self):
self.phone_model = QSqlTableModel(self)
self.phone_model.setTable("contact_phones")
self.phone_model.setFilter("contact_id='{0}'".format(self.cont_id))
self.phone_model.select()
self.phone_view.setModel(self.phone_model)
self.phone_view.resizeColumnsToContents()
def add_phone(self):
self.phone_model.submitAll()
self.phone_model.setEditStrategy(QSqlTableModel.OnManualSubmit)
row = self.phone_model.rowCount()
record = self.phone_model.record()
record.setGenerated('id', False) #primary key
record.setValue('contact_id', self.cont_id) #foreign key
self.phone_model.insertRecord(row, record)
phone_index_edit = self.phone_model.index(row, self.phone_model.fieldIndex('phone_number'))
self.phone_view.edit(phone_index_edit)
def closeEvent(self, event):
submit = self.phone_model.submitAll()
#This is the problem
self.phone_view.closeEditor("QLineEdit", QAbstractItemDelegate.NoHint)
class Syn_tableview(QTableView):
def __init__(self, *args, **kwargs):
QTableView.__init__(self, *args, **kwargs)
def closeEditor(self, editor, hint):
if hint == QAbstractItemDelegate.NoHint:
QTableView.closeEditor(self, editor,
QAbstractItemDelegate.SubmitModelCache)
if __name__=="__main__":
app=QApplication(sys.argv)
db = QSqlDatabase.addDatabase("QPSQL");
db.setHostName(server)
db.setDatabaseName(database)
db.setUserName(user)
db.setPassword(pword)
myapp = Main()
myapp.show()
sys.exit(app.exec_())
The delegate editors are children of the QTableView so you can use findChildren to get them, to make sure they are not other children you can set an objectName that allows you to filter them:
import sys
from PyQt5 import QtCore, QtSql, QtWidgets
def create_connection():
db = QtSql.QSqlDatabase.addDatabase("QPSQL")
# FIXME
db.setHostName("server")
db.setDatabaseName("database")
db.setUserName("user")
db.setPassword("pword")
if not db.open():
print(db.lastError().text())
return False
return True
class Syn_Delegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = super(Syn_Delegate, self).createEditor(parent, option, index)
if isinstance(editor, QtWidgets.QWidget):
editor.setObjectName("syn_editor")
return editor
class Syn_Tableview(QtWidgets.QTableView):
def closeEditor(self, editor, hint):
if hint == QtWidgets.QAbstractItemDelegate.NoHint:
hint = QtWidgets.QAbstractItemDelegate.SubmitModelCache
super(Syn_Tableview, self).closeEditor(editor, hint)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.new_phone = QtWidgets.QPushButton(self.tr("New Phone"))
self.delete_phone = QtWidgets.QPushButton(self.tr("Delete phone"))
self.phone_view = Syn_Tableview()
self.phone_model = QtSql.QSqlTableModel()
self.phone_model.setEditStrategy(QtSql.QSqlTableModel.OnManualSubmit)
self.phone_view.setModel(self.phone_model)
self.phone_view.resizeColumnsToContents()
delegate = Syn_Delegate(self)
self.phone_view.setItemDelegate(delegate)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QGridLayout(central_widget)
lay.addWidget(self.new_phone, 0, 0)
lay.addWidget(self.delete_phone, 0, 1)
lay.addWidget(self.phone_view, 1, 0, 1, 2)
self._contact_id = "9"
self.populate_phones()
self.new_phone.clicked.connect(self.add_phone)
#property
def contact_id(self):
return self._contact_id
def populate_phones(self):
self.phone_model.setTable("contact_phones")
self.phone_model.setFilter("contact_id='{0}'".format(self.contact_id))
self.phone_model.select()
#QtCore.pyqtSlot()
def add_phone(self):
self.phone_model.submitAll()
row = self.phone_model.rowCount()
record = self.phone_model.record()
record.setGenerated("id", False) # primary key
record.setValue("contact_id", self.contact_id) # foreign key
self.phone_model.insertRecord(row, record)
phone_index_edit = self.phone_model.index(
row, self.phone_model.fieldIndex("phone_number")
)
if phone_index_edit.isValid():
self.phone_view.edit(phone_index_edit)
def closeEvent(self, event):
for editor in self.phone_view.findChildren(QtWidgets.QWidget, "syn_editor"):
self.phone_view.commitData(editor)
submit = self.phone_model.submitAll()
super().closeEvent(event)
def main():
app = QtWidgets.QApplication(sys.argv)
if not create_connection():
sys.exit(-1)
w = MainWindow()
w.show()
w.resize(640, 480)
ret = sys.exit(app.exec_())
sys.exit(ret)
if __name__ == "__main__":
main()
How does one use a QFileSystemModel to populate several QComboBox with subdirectories?
I have built a project management tool that allows me to create and manage my projects. I am currently using a combination of os.listdir and json to populate and validate my QComboboxes. But I am trying to learn a more modelview approach with QFileSystemModel.
So this is what I have:
class FileSystemModel(QW.QFileSystemModel):
def __init__(self, root, parent=None):
QW.QFileSystemModel.__init__(self, parent)
self.root = root
self.rootIndex = self.setRootPath(root)
class Window(QW.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__()
self.init()
def init(self):
layout = QW.QVBoxLayout()
self.cbox = QW.QComboBox()
self.cbox2 = QW.QComboBox()
self.model = FileSystemModel("C:\\projects\\")
self.cbox.setModel(self.model)
self.cbox2.setModel(self.model)
self.cbox.setRootModelIndex(self.model.rootIndex)
self.cbox.currentIndexChanged.connect(self._indexChanged)
layout.addWidget(self.cbox)
layout.addWidget(self.cbox2)
self.setLayout(layout)
def _indexChanged(self):
row = self.sender().currentIndex()
index = self.sender().rootModelIndex().child(row, 0)
self.cbox2.setRootModelIndex(index)
def main():
app = QW.QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
I was attempting to repopulate the cbox2 using the index from cbox, but with my code it doesn't seem to work - it just stays empty.
Okay here is modified version of what you had:
from sys import exit as sysExit
from PyQt5.QtCore import QDir, pyqtSlot
from PyQt5.QtWidgets import QApplication, QWidget, QFileSystemModel, QHBoxLayout, QComboBox
class SysDirModel(QFileSystemModel):
def __init__(self, DirPath):
QFileSystemModel.__init__(self)
self.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs)
self.setReadOnly(True)
# Property
self.setRootPath(DirPath)
# Property
self.RootIndex = self.index(DirPath)
class SysFileModel(QFileSystemModel):
def __init__(self, DirPath):
QFileSystemModel.__init__(self)
self.setFilter(QDir.NoDotAndDotDot | QDir.Files)
self.setReadOnly(True)
# Property
self.setRootPath(DirPath)
# Property
self.RootIndex = self.index(DirPath)
def ResetPath(self, DirPath):
self.setRootPath(DirPath)
self.RootIndex = self.index(DirPath)
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self.setGeometry(150, 150, 450, 100)
# If you use forward slash this works in Windows as well and it is cleaner
self.SysDirs = SysDirModel('C:/projects/')
self.SysFils = SysFileModel('C:/projects/')
# Setup first ComboBox
self.cbxDirs = QComboBox()
self.cbxDirs.setMinimumWidth(200)
self.cbxDirs.setModel(self.SysDirs)
self.cbxDirs.setRootModelIndex(self.SysDirs.RootIndex)
# This sends a Signal to a predefined Slot
self.cbxDirs.currentIndexChanged.connect(self.IndexChanged)
self.cbxFiles = QComboBox()
self.cbxFiles.setMinimumWidth(200)
self.cbxFiles.setModel(self.SysFils)
self.cbxFiles.setRootModelIndex(self.SysFils.RootIndex)
HBox = QHBoxLayout()
HBox.addWidget(self.cbxDirs)
HBox.addStretch(1)
HBox.addWidget(self.cbxFiles)
self.setLayout(HBox)
# This is the receiver of a Signal (aka Slot) so it ought to be used as such
#pyqtSlot(int)
def IndexChanged(self, RowIdx):
# Get your Current DirPath based on the Selected Value
index = self.cbxDirs.rootModelIndex().child(RowIdx, 0)
DirPath = self.cbxDirs.model().filePath(index)
# Reset what ComboBox 2's Model and what it is looking at
self.cbxFiles.clear()
self.SysFils.ResetPath(DirPath)
self.cbxFiles.setModel(self.SysFils)
if __name__ == '__main__':
MainThred = QApplication([])
MainGui = MainWindow()
MainGui.show()
sysExit(MainThred.exec_())
Recently, I am working on a code completion demo. I want to create a tooltip association with items in the popup(). The tooltip shows some information queried by the item that it is associated with when users select one item in the popup listview. I tried currentCompletion() to get the item content, it only returned the first completion for one time. How to fix this?
Here is my application
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
def get_data(model):
# Searching through the Doc
# Result shows in a dictionary structure
result = {"alpha": 0, "apple": 1, "agree": 2}
icon_address = ['..\..\Src\Img\A.png', '..\..\Src\Img\E.png','..\..\Src\Img\O.png']
for cmd, value in result.items():
item = QStandardItem(cmd)
item.setIcon(QIcon(icon_address[value]))
model.insertRow(0, item)
class CodeAC:
def __init__(self, input_line):
self.completer = QCompleter()
input_line.setCompleter(self.completer)
self.model = QStandardItemModel()
def active_script(self):
get_data(self.model)
self.completer.setModel(self.model)
def tip_balloon(self):
key = self.completer.currentRow()
print(key)
Here is my Main:
from Src.Extention.src.code_ac import *
import sys
import ctypes
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.extention = None
self.entry = QLineEdit(self)
def init_main(self):
self.setGeometry(600,600,800,600)
self.setWindowTitle('VISA Communication')
self.setWindowIcon(QIcon('..\..\Src\Img\icon.png'))
self.show()
def active_extention(self):
self.extention = CodeAC(self.entry)
self.extention.active_script()
if __name__ == '__main__':
app = QApplication(sys.argv)
root = MainWindow()
root.init_main()
root.active_extention()
sys.exit(app.exec_())
Print only give 0, even I select the second completion. Here is the screen shoot
You must use activated signal of QCompleter and modify the tip_balloon:
[...]
self.completer.setModel(self.model)
self.completer.activated.connect(self.tip_balloon)
def tip_balloon(self, text):
print(text)