AttributeError: 'QTextEdit' object has no attribute 'text' - qt

Sometimes I need to make multiple copies of code with incrementing numbers.
In a form I'm coding, I need to make upwards of 12 checkboxes, each of which requires the following code:
self.checkBox1 = QtGui.QCheckBox()
self.checkBox1.setGeometry(QtCore.QRect(20, 20, 70, 17))
self.checkBox1.setObjectName(_fromUtf8("checkBox1"))
The script below enables me to avoid the boring task of manually changing the numbers for each checkbox.
I just copy the 3 lines above into the windows clipboard, and then...
I insert "checkBox" into the first field in the form, "1" into the second field, and 12 into the third field.
When I hit the "ok" button, the 12 sequentially numbered copies of the 3 lines appear in the 4th field in the form.
I hope this provides some help to other people.
Marc
Here's my code:
# -*- coding: latin-1 -*-
"""
duplicate_text_with_incrementing_nos_for_programming_and_paste_to_clipboard.py
Harvest text from clipboard and run functions below, and then paste back to clipboard
"""
import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtCore import (Qt, SIGNAL)
from PyQt4.QtGui import (QApplication, QDialog, QHBoxLayout, QLabel,
QPushButton)
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.initUI()
def initUI(self):
okButton01 = QtGui.QPushButton("OK")
cancelButton01 = QtGui.QPushButton("Cancel")
okButton01.clicked.connect(self.fn_okButton01_clicked)
cancelButton01.clicked.connect(QtCore.QCoreApplication.instance().quit)
self.cancelButton01 = cancelButton01
prefix_label = QtGui.QLabel('Prefix')
digit_label = QtGui.QLabel('Digit')
iterations_label = QtGui.QLabel('Iterations')
clip_label = QtGui.QLabel('Clip')
prefixEdit = QtGui.QLineEdit()
digitEdit = QtGui.QLineEdit()
iterationsEdit = QtGui.QLineEdit()
reviewEdit = QtGui.QTextEdit()
self.prefix_label = prefix_label
self.digit_label = digit_label
self.iterations_label = iterations_label
self.clip_label = clip_label
self.prefixEdit = prefixEdit
self.digitEdit = digitEdit
self.iterationsEdit = iterationsEdit
self.reviewEdit = reviewEdit
hbox01 = QtGui.QHBoxLayout()
hbox01.addWidget(prefix_label)
hbox01.addWidget(prefixEdit)
hbox01.addWidget(digit_label)
hbox01.addWidget(digitEdit)
hbox01.addWidget(iterations_label)
hbox01.addWidget(iterationsEdit)
hbox03 = QtGui.QHBoxLayout()
hbox03.addWidget(clip_label)
hbox03.addWidget(reviewEdit)
self.reviewEdit.setText(fn_getText())
hbox00 = QtGui.QHBoxLayout()
hbox00.addStretch(1)
hbox00.addWidget(okButton01)
hbox00.addWidget(cancelButton01)
vbox0 = QtGui.QVBoxLayout()
vbox0.addLayout(hbox01)
vbox0.addStretch(1)
vbox0.addLayout(hbox03)
vbox0.addStretch(1)
vbox0.addLayout(hbox00)
self.setLayout(vbox0)
self.setGeometry(300, 300, 600, 300) #class PySide.QtCore.QRectF(left, top, width, height) http://srinikom.github.com/pyside-docs/PySide/QtCore/QRectF.html#PySide.QtCore.QRectF
self.setWindowTitle('Duplicate Code Strings W/Increasing Numbers')
self.show()
def fn_okButton01_clicked(self):
prefixEditText = str(self.prefixEdit.text())
digitEditText = str(self.digitEdit.text())
iterationsEditText = str(self.iterationsEdit.text())
nutext = prefixEditText + ' ' + digitEditText + ' ' + iterationsEditText
print 'Line 89: nutext = ' + str(nutext)
original_clip = self.reviewEdit.toPlainText() # PySide.QtGui.QLineEdit.text(), http://srinikom.github.com/pyside-docs/PySide/QtGui/QLineEdit.html
txt2paste2clipbd = fn_duplicate_code_with_increments(texte=str(original_clip), no_of_copies=str(iterationsEditText), string_b4_digits=str(prefixEditText), digits=str(digitEditText))
self.reviewEdit.setPlainText(txt2paste2clipbd)
setWinClipText(txt2paste2clipbd)
#self.deleteLater()
#event.accept() #http://www.qtcentre.org/threads/20895-PyQt4-Want-to-connect-a-window-s-close-button
#self.destroy()
def formm():
app = QApplication(sys.argv)
form = Form()
form.show()
app.exec_()
def fn_getText():
# get text from clipboard
win32clipboard.OpenClipboard()
text = win32clipboard.GetClipboardData(win32clipboard.CF_TEXT)
win32clipboard.CloseClipboard()
return text
def setWinClipText(aString):
# Send text to clipboard
import win32clipboard
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText(aString)
win32clipboard.CloseClipboard()
def fn_duplicate_code_with_increments(texte, no_of_copies, string_b4_digits, digits):
"""
to do: combine args 2 and 3, and use re module to determine with chars are the digits to increment
"""
import re
import string
i = 0
tempclipDup = texte[:]
temp_instance = ''
accumulator = ''
while i <= int(no_of_copies) - 1:
i +=1
orig_str = string_b4_digits + str(digits)
replact_st = string_b4_digits + str(i)
temp_instance = tempclipDup.replace(orig_str, replact_st)
if len(accumulator) > 2:
accumulator = accumulator + '\n' + temp_instance
else:
accumulator = temp_instance
return accumulator
if 1 == 1:
import os
import sys
import subprocess
import win32clipboard
import win32con
fn_operation_log = ''
arg_sent_2this_script = ''
alternative = 1
if alternative == 1:
formm()
elif alternative == 2:
txt2paste2clipbd = fn_duplicate_code_with_increments(texte=fn_getText(), no_of_copies=3, string_b4_digits='hbox', digits=1)
setWinClipText(txt2paste2clipbd)

The property is called plainText for QTextEdit.
(Contrary to the single line QLineEdit, which has a text property)

Related

How to close window on Button Click and Update Table in the Main Window in PyQT?

I have a table in my MainWindow in which the data is coming from the MySQL Database.
There is a push button to add new item in that table. When you click on the Push Button, a new Window opens which contains the form. Once you have filled the form, and you press a "Add Section" button on the second window, it is supposed to perform these three things,
Insert the Given data back into the Database (this is working)
Close the Second Window (not working)
Update the Table in the Main Window with the new record.
Now, this is the basic function, which is getting the record from the database and displaying it in the Table on MainWindow,
def get_section_data(self):
mycursor = self.DB.cursor()
Subquery = "Select * from tbl_section"
mycursor.execute(Subquery)
numcols = len(mycursor.fetchall()[0])
mycursor.execute(Subquery)
numrows = len(mycursor.fetchall())
self.ui.section_table.setRowCount(numrows)
self.ui.section_table.setColumnCount(numcols)
mycursor.execute(Subquery)
tablerow = 0
for row in mycursor.fetchall():
tablecol = 0
while tablecol < numcols:
self.ui.section_table.setItem(tablerow, tablecol, PySide2.QtWidgets.QTableWidgetItem(str(row[tablecol])))
tablecol += 1
tablerow += 1
this is the function which gets called when you click on "Add Section" on the Main Window (this opens the New Form)
def executeAddSectionPage(self):
self.window = QtWidgets.QMainWindow()
self.add_section_form = Ui_Add_Section_Window()
self.add_section_form.setupUi(self.window)
self.window.show()
self.add_section_form.section_add_form_button.clicked.connect(self.insert_section_info)
Following is the function which is called when you click on the "Add Section" in the 2nd form,
def insert_section_info(self):
id = self.add_section_form.section_id_add.text()
name = self.add_section_form.section_name_add.text()
mycursor = self.DB.cursor()
sql = "Insert into tbl_section (section_id, section_name) VALUES (%s, %s)"
val = (int(id), name)
mycursor.execute(sql, val)
self.DB.commit()
# self.add_section_form.hide() -> I tried to add this but it didn't work
When I click on the "Add Section" button on my second window, it doesn't do anything (except storing the record in the database at backend)
When I again click on it, it crashes both the windows.
How can I perform the functionalities that I require?
Following is the complete code of my main.py
## IMPORTS
import sys
import os
import PySide2
import mysql.connector as mc
from qt_material import *
from PyQt5 import QtWidgets, uic
from add_section_page import Ui_Add_Section_Window
#####################################
# Import GUI File
from ui_interface import *
######################################
# class Add_Section(QMainWindow):
# def __init__(self):
# super(Add_Section, self).__init__()
# self.uic.loadUi('add_section_page.ui', self)
## Main Window Class
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
try:
self.DB = mc.connect(host="localhost", user="root", password="", database="timetable_manager")
except mc.Error as e:
print("Error")
# Load Style Sheet
apply_stylesheet(app, theme="dark_cyan.xml")
# Remove Window Title Bar
self.setWindowFlags(PySide2.QtCore.Qt.FramelessWindowHint)
# Set main background to transparent
self.setAttribute(PySide2.QtCore.Qt.WA_TranslucentBackground)
# Shadow Style Effect
self.shadow = QGraphicsDropShadowEffect(self)
self.shadow.setBlurRadius(50)
self.shadow.setXOffset(0)
self.shadow.setYOffset(0)
self.shadow.setColor(QColor(0, 92, 157, 550))
# Apply shadow to central widget
self.ui.centralwidget.setGraphicsEffect(self.shadow)
self.setWindowIcon(PySide2.QtGui.QIcon(":/icons/airplay.svg"))
# Set Window Title
self.setWindowTitle("Timetable Manager")
# Window Size grip to resize window
QSizeGrip(self.ui.size_grip)
# Minimize Window
self.ui.minimize_window_button.clicked.connect(lambda: self.showMinimized())
# Close Window
self.ui.close_window_button.clicked.connect(lambda: self.close())
# Maximize Window
self.ui.restore_window_button.clicked.connect(lambda: self.restore_or_maximize_window())
# Navigate to Section Page
self.ui.section_menu_button.clicked.connect(lambda: self.ui.stackedWidget.setCurrentWidget(self.ui.section_page))
self.ui.course_menu_button.clicked.connect(lambda: self.ui.stackedWidget.setCurrentWidget(self.ui.course_page))
self.ui.add_section_button.clicked.connect(self.executeAddSectionPage)
self.show()
self.get_section_data()
def executeAddSectionPage(self):
self.window = QtWidgets.QMainWindow()
self.add_section_form = Ui_Add_Section_Window()
self.add_section_form.setupUi(self.window)
self.window.show()
self.add_section_form.section_add_form_button.clicked.connect(self.insert_section_info)
def insert_section_info(self):
id = self.add_section_form.section_id_add.text()
name = self.add_section_form.section_name_add.text()
mycursor = self.DB.cursor()
sql = "Insert into tbl_section (section_id, section_name) VALUES (%s, %s)"
val = (int(id), name)
mycursor.execute(sql, val)
self.DB.commit()
# self.add_section_form.hide() -> I tried to add this but it didn't work
def get_section_data(self):
mycursor = self.DB.cursor()
Subquery = "Select * from tbl_section"
mycursor.execute(Subquery)
numcols = len(mycursor.fetchall()[0])
mycursor.execute(Subquery)
numrows = len(mycursor.fetchall())
self.ui.section_table.setRowCount(numrows)
self.ui.section_table.setColumnCount(numcols)
mycursor.execute(Subquery)
tablerow = 0
for row in mycursor.fetchall():
tablecol = 0
while tablecol < numcols:
self.ui.section_table.setItem(tablerow, tablecol, PySide2.QtWidgets.QTableWidgetItem(str(row[tablecol])))
tablecol += 1
tablerow += 1
def restore_or_maximize_window(self):
if self.isMaximized():
self.showNormal()
#self.ui.restore_window_button.setIcon()
else:
self.showMaximized()
#self.ui.restore_window_button.setIcon()
#def get_section_data(self):
# sqlquery = "SELECT * FROM tbl_section"
# cur = self.db.cursor()
# for row in cur.execute(sqlquery):
# print(row)
## EXECUTE APP
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())

How do I connect WindowContextHelpButtonHint to a widget click?

I can add self.setWindowFlags(Qt.Window | Qt.WindowContextHelpButtonHint | Qt.WindowCloseButtonHint) to the constructor of my QMainWindow which adds the '?' button. Now I want to add contextual help information to virtually all UI components. How do I connect a click on the '?' button and then a click on some ui element (say a qlistview) such that a help message pops up?
Below is part of my program stripped to make it easy. How do I make it such that when the user clicks the '?' and then either a QDoubleSpinBox or the QListView or any of the QPushButtons that a help message displays?
I have searched for this but it seems the vast amount of questions and answers are about how to remove the '?' button or how to add it. None actually deal with how to alter the button's behaviour.
I've also tried using setInputMethodHints() to assign hints to ui components but this method doesn't accept custom strings as I assumed it would.
Btw: I also would like the '?' button to not replace the minimization button. I have yet to figure out how to achieve this either.
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
APPNAME = 'Mesoscale Brain Explorer'
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setWindowTitle(APPNAME)
self.sidebar = Sidebar()
self.setWindowFlags(Qt.Window | Qt.WindowContextHelpButtonHint | Qt.WindowCloseButtonHint)
self.setup_ui()
def setup_ui(self):
self.pl_frame = QFrame()
splitter = QSplitter(self)
self.enable = lambda yes: splitter.setEnabled(yes)
splitter.setHandleWidth(3)
splitter.setStyleSheet('QSplitter::handle {background: #cccccc;}')
splitter.addWidget(self.sidebar)
splitter.addWidget(self.pl_frame)
self.setCentralWidget(splitter)
self.menu = self.menuBar()
m = self.menu.addMenu('&File')
a = QAction('&New project', self)
a.setShortcut('Ctrl+N')
a.setStatusTip('Create new project')
m.addAction(a)
a = QAction('&Open project', self)
a.setShortcut('Ctrl+O')
a.setStatusTip('Open project')
m.addAction(a)
a = QAction("&Quit", self)
a.setShortcut("Ctrl+Q")
a.setStatusTip('Leave The App')
a.setIcon(QIcon('pics/quit.png'))
m.addAction(a)
about_action = QAction('&About ' + APPNAME, self)
about_action.setStatusTip('About ' + APPNAME)
about_action.setShortcut('F1')
m = self.menu.addMenu('&Project')
m.setEnabled(False)
a = QAction("&Close", self)
a.setStatusTip('Close project')
m.addAction(a)
self.project_menu = m
help_menu = self.menu.addMenu('&Help')
help_menu.addAction(about_action)
class Sidebar(QWidget):
open_pipeconf_requested = pyqtSignal()
open_datadialog_requested = pyqtSignal()
automate_pipeline_requested = pyqtSignal()
x_origin_changed = pyqtSignal(float)
y_origin_changed = pyqtSignal(float)
units_per_pixel_changed = pyqtSignal(float)
def __init__(self, parent=None):
super(Sidebar, self).__init__(parent)
self.x_origin = QDoubleSpinBox()
self.y_origin = QDoubleSpinBox()
self.units_per_pixel = QDoubleSpinBox()
self.setup_ui()
def setup_ui(self):
self.setContentsMargins(4, 6, 5, 0)
vbox = QVBoxLayout()
hbox = QHBoxLayout()
hbox.addWidget(QLabel('Origin:'))
hbox.addWidget(QLabel('Units per pixel:'))
vbox.addLayout(hbox)
hbox2 = QHBoxLayout()
hhbox = QHBoxLayout()
hhbox2 = QHBoxLayout()
hhbox.addWidget(QLabel("X:"))
hhbox.addWidget(self.x_origin)
hhbox.addWidget(QLabel("Y:"))
hhbox.addWidget(self.y_origin)
hhbox2.addWidget(self.units_per_pixel)
hbox2.addLayout(hhbox)
hbox2.addLayout(hhbox2)
vbox.addLayout(hbox2)
self.units_per_pixel.setDecimals(5)
self.units_per_pixel.setMaximum(100000)
self.x_origin.setMaximum(100000)
self.y_origin.setMaximum(100000)
self.pl_list = QListView()
self.pl_list.setIconSize(QSize(18, 18))
self.pl_list.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.pl_list.setEditTriggers(QAbstractItemView.NoEditTriggers)
vbox.addWidget(QLabel('Pipeline:'))
vbox.addWidget(self.pl_list)
pb = QPushButton('&Automation')
pb.clicked.connect(self.automate_pipeline_requested)
vbox.addWidget(pb)
vbox.addSpacerItem(QSpacerItem(0, 1, QSizePolicy.Minimum, QSizePolicy.Expanding))
pb = QPushButton('&Configure Pipeline')
pb.clicked.connect(self.open_pipeconf_requested)
vbox.addWidget(pb)
pb = QPushButton('&Manage Data')
pb.clicked.connect(self.open_datadialog_requested)
vbox.addWidget(pb)
vbox.setStretch(0, 0)
vbox.setStretch(1, 0)
vbox.setStretch(2, 0)
self.setLayout(vbox)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setApplicationName(APPNAME)
app.setOrganizationName('University of British Columbia')
w = MainWindow()
w.resize(1060, 660)
w.show()
app.exec_()
app.deleteLater()
del w
sys.exit()

QLabel is not clickable in my program

The program is working correctly but QLabel is not clickable.
from PyQt5.QtWidgets import *
import sys
import bs4
import urllib.request
from PyQt5.QtWidgets import *
import sys
from PyQt5.QtWidgets import QApplication
webpage=urllib.request.urlopen('http://www.boluolay.com/')
soup = bs4.BeautifulSoup(webpage, 'html.parser')
class ButonluPencere(QWidget):
def __init__(self):
super().__init__()
self.resize(640, 480)
buton = QPushButton(self)
buton.setText("Bolu Olay Gazetesi")
buton.setGeometry(400,10,100,50)
buton.clicked.connect(self.getir)
kayan=QScrollBar(self)
kayan.setGeometry(370, 60, 16, 160)
buton.clicked.connect(self.boluolay_koseyazilari)
buton.clicked.connect(self.boluolay_manset)
def getir(self):
boluolay_manset=QLabel(self)
boluolay_manset.setText("MANŞET")
boluolay_manset.setGeometry(100,-15,500,70)
boluolay_manset.show()
satirBoyu=20
i=1
for yazarlar in soup.find_all("a",class_="main_cuff"):
for yazar_adi in yazarlar.find_all("div",class_="title"):
etiket = QLabel(self)
etiket.setOpenExternalLinks(True)
etiket.setGeometry(100,satirBoyu,500,satirBoyu+20)
etiket.setText(''+str(i) + ". " + yazar_adi.text.strip() + '')
etiket.show()
i+=1
satirBoyu += 10
def boluolay_manset(self):
boluolay_manset=QLabel(self)
boluolay_manset.setText("DİĞER HABERLER")
boluolay_manset.setGeometry(100,180,500,70)
boluolay_manset.show()
satirBoyu=140
i=1
for manset in soup.find_all("ul",class_="news marginTen"):
for diger_haber in manset("a",class_="title"):
etiket = QLabel(self)
etiket.setOpenExternalLinks(True)
etiket.setGeometry(100,satirBoyu,500,satirBoyu+50)
etiket.setText(''+str(i) + ". " + diger_haber.text.strip() + '')
etiket.show()
i+=1
satirBoyu += 10
def boluolay_koseyazilari(self):
boluolay_ky=QLabel(self)
boluolay_ky.setText("KÖŞE YAZARLARI")
boluolay_ky.setGeometry(100,450,500,70)
boluolay_ky.show()
webpage=urllib.request.urlopen('http://www.boluolay.com/yazarlar.html')
soup = bs4.BeautifulSoup(webpage, 'html.parser')
satirBoyu=320
i=1
for manset in soup.find_all("div",id="yazark"):
for mansetb in manset.find_all("b"):
for yazar_konu in manset("span"):
ab=yazar_konu
etiket = QLabel(self)
etiket.setOpenExternalLinks(True)
etiket.setGeometry(100,satirBoyu,500,satirBoyu+50)
etiket.setText(''+str(i) + ". " + ab.text.strip() + '')
etiket.show()
i+=1
satirBoyu += 10
uygulama = QApplication(sys.argv)
pencere = ButonluPencere()
pencere.show()
uygulama.exec_()
If you want to click on URLs of your QLabel, then you would need to set a few more properties for it (aside from setOpenExternalLinks) like setTextInteractionFlags to Qt::TextBrowserInteraction and setTextFormat to Qt::RichText
Edit (add these lined to every label that you need to click URL on etiket as example):
etiket.setOpenExternalLinks(True)
etiket.setTextInteractionFlags(Qt.TextBrowserInteraction)
etiket.setTextFormat(Qt.RichText)
QLabel is not clickable generally, but you can overload QLabel.mousePressEvent or QLabel.mouseReleaseEvent method to recognize clicked signal; like this:
class Form(QWidget):
def __init__(self):
QDialog.__init__(self)
self.t=QLabel('Click Me',self)
self.t.mousePressEvent=self.labelClick
def labelClick(self,e):
if e.button() == Qt.LeftButton:
print('Clicked')
QLabel.mousePressEvent(self.t,e)

Stop the error when trying to manipulate the items of a Listbox, but no item is selected?

I am a beginner in tkinter. I am making a list of names. You can delete, select and edit it, but if I don't select anything in the list and click these buttons, it says:
Exception in Tkinter callback Traceback (most recent call last): File
"C:\Python34\lib\tkinter\__init__.py", line 1533, in __call__ return
self.func(*args) File "C:\Users\user\Desktop\HOW_TOUGH - NEW\Change_user.py",
line 60, in Edit (idx, ) = d ValueError: need more than 0 values to unpack'''
I am planning to disable the buttons if the user doesn't click anything but I am not expert enough. Here's my code (it's a child window)
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
class Nick:
def __init__(self, master ):
self.master = master
self.window = Toplevel(master)
self.window.title('Change User')
self.window.geometry('300x300')
self.window.minsize(300, 300)
self.window.maxsize(300, 300)
self.nickname = StringVar()
self.lb = Listbox(self.window, selectmode = 'SINGLE')
f= open('users.txt','r')
rec = f.readlines()
f.close()
for i in rec:
p = i.find('|')
nickname = i[:p]
self.lb.insert(END, nickname)
self.lb.pack()
self.Ed = ttk.Button(self.window, text = 'Edit', command = self.Edit).pack()
self.Del = ttk.Button(self.window, text = 'Delete', command = self.Delete).pack()
self.Bac = ttk.Button(self.window, text = 'Back', command = self.Back).pack()
self.Okay = ttk.Button(self.window, text = 'Ok', command = self.Ok).pack()
def Back(self):
self.window.destroy()
def Delete(self):
d = self.lb.curselection()
(idx, ) = d
self.lb.delete(idx)
f = open('users.txt','r')
r = f.readlines()
f.close()
rec = r[idx]
r.remove(rec)
f = open('users.txt','w')
new = ''.join(r)
r = f.write(new)
f.close()
messagebox.showinfo(title='Success', message = 'Delete successful')
def Edit(self):
d = self.lb.curselection()
(idx, ) = d
import Edit as Edet
Edet.Edit(self.master, idx)
def Ok(self):
d = self.lb.curselection()
(idx, ) = d
get = self.lb.get(idx)
self.window.destroy()
print (get)
print (d)
The method curselection() returns an empty tuple when nothing is selected. You can skip those methods just by adding a
if not d:
return
If you want to gray out your buttons, you can do this:
button["state"] = DISABLED
Note that this won't work currently with your code as you did this:
self.button = ttk.Button(...).pack()
The problem lies in the call of pack() which returns None, effectively binding self.button to None. Just assign the button object to the variable first and then pack it. Furthermore, it's not recommended to import * from Tkinter because you're dropping ~190 names in your namespace. Just use
import tkinter as tk

How do I make a QTreeView always sort items of a certain category first?

I would like to display "folders" and "files" in a QTreeView. Folders are meant to be able to contain files, and due to this relationship I wish for folder items to be displayed above file items in the tree view. The view should be sortable. How do I make sure that folder items are displayed above file items at all times in the tree view?
The below code provides an example of a tree view with folder and file items:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
def _create_item(text, is_folder):
item = QStandardItem(text)
item.setData(is_folder, Qt.UserRole)
return item
def _folder_row(name, date):
return [_create_item(text, True) for text in (name, date)]
def _file_row(name, date):
return [_create_item(text, False) for text in (name, date)]
class _Window(QMainWindow):
def __init__(self):
super().__init__()
widget = QWidget()
self.__view = QTreeView()
layout = QVBoxLayout(widget)
layout.addWidget(self.__view)
self.setCentralWidget(widget)
model = QStandardItemModel()
model.appendRow(_file_row('File #1', '01.09.2014'))
model.appendRow(_folder_row('Folder #1', '01.09.2014'))
model.appendRow(_folder_row('Folder #2', '02.09.2014'))
model.appendRow(_file_row('File #2', '03.09.2014'))
model.setHorizontalHeaderLabels(['Name', 'Date'])
self.__view.setModel(model)
self.__view.setSortingEnabled(True)
app = QApplication([])
w = _Window()
w.show()
app.exec_()
A solution is to wrap the model in a QSortFilterProxyModel, and reimplement the proxy's lessThan method to make it so that folder items are always placed before file items:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
def _create_item(text, is_folder):
item = QStandardItem(text)
item.setData(is_folder, Qt.UserRole)
return item
def _folder_row(name, date):
return [_create_item(text, True) for text in (name, date)]
def _file_row(name, date):
return [_create_item(text, False) for text in (name, date)]
class _SortProxyModel(QSortFilterProxyModel):
"""Sorting proxy model that always places folders on top."""
def __init__(self, model):
super().__init__()
self.setSourceModel(model)
def lessThan(self, left, right):
"""Perform sorting comparison.
Since we know the sort order, we can ensure that folders always come first.
"""
left_is_folder = left.data(Qt.UserRole)
left_data = left.data(Qt.DisplayRole)
right_is_folder = right.data(Qt.UserRole)
right_data = right.data(Qt.DisplayRole)
sort_order = self.sortOrder()
if left_is_folder and not right_is_folder:
result = sort_order == Qt.AscendingOrder
elif not left_is_folder and right_is_folder:
result = sort_order != Qt.AscendingOrder
else:
result = left_data < right_data
return result
class _Window(QMainWindow):
def __init__(self):
super().__init__()
widget = QWidget()
self.__view = QTreeView()
layout = QVBoxLayout(widget)
layout.addWidget(self.__view)
self.setCentralWidget(widget)
model = QStandardItemModel()
model.appendRow(_file_row('File #1', '01.09.2014'))
model.appendRow(_folder_row('Folder #1', '01.09.2014'))
model.appendRow(_folder_row('Folder #2', '02.09.2014'))
model.appendRow(_file_row('File #2', '03.09.2014'))
model.setHorizontalHeaderLabels(['Name', 'Date'])
proxy_model = _SortProxyModel(model)
self.__view.setModel(proxy_model)
self.__view.setSortingEnabled(True)
app = QApplication([])
w = _Window()
w.show()
app.exec_()

Resources