I am developing a GUI app in PyQt.
For the buttons, I have used QWidget and for a QSqlTable I have used a QMainWindow.
I know I can add QWidget by adding setCentralwidget in QMainWindow.
But in order to show the Table, I need the table to be central widget.
Individually both work, but I am unable to have both at the same time.
My code is given below:
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QGridLayout, QPushButton,QLabel.
from PyQt5.QtGui import QPixmap
from PyQt5 import QtGui
from gwWindow import groundwaterWindow
from swWindow import surfacewaterWindow
from kwWindow import karstwaterWindow
from PyQt5.QtCore import Qt, QModelIndex
from PyQt5.QtSql import QSqlDatabase, QSqlTableModel, QSqlQuery
from PyQt5.QtWidgets import (QApplication, QMainWindow, QMessageBox, QTableView, QPushButton, QGridLayout, QGroupBox, QVBoxLayout)
from PyQt5 import QtCore
from PyQt5.QtCore import pyqtSignal
class MainWindow(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.title = "PPCP Selection"
self.top = 100
self.left = 100
self.width = 1200
self.height = 1200
self.InitWindow()
def InitWindow(self):
self.setWindowTitle(self.title)
self.setGeometry(self.top, self.left, self.width, self.height)
self.createLayout()
vBoxLayout = QVBoxLayout()
vBoxLayout.addWidget(self.groupBox1)
# vBoxLayout.addWidget(self.groupBox2)
self.setLayout(vBoxLayout)
# self.showMaximized()
def createLayout(self):
self.groupBox1 = QGroupBox("PPCP Selection")
self.groupBox1.setFont(QtGui.QFont("Arial", 30))
gridLayout = QGridLayout()
gridLayout.setRowStretch(2, 2)
self.button1 = QPushButton("Evaluate")
gridLayout.addWidget(self.button1, 1, 0, 1, 1)
#self.button1.clicked.connect(self.switch1)
gridLayout.addWidget(self.button1)
self.button1.setFocusPolicy(QtCore.Qt.NoFocus)
self.groupBox1.setLayout(gridLayout)
class RealMainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("QTableView Example")
self.resize(415, 200)
# Set up the model
self.model = TableModel(self)
self.model.setTable("Table2")
self.model.setEditStrategy(QSqlTableModel.OnFieldChange)
#self.model.setHeaderData(0, Qt.Horizontal, "ID")
self.model.select()
# Set up the view
self.view = QTableView()
self.view.setModel(self.model)
#self.view.resizeColumnsToContents()
self.another = MainWindow()
# I want both of the following
# self.setCentralWidget(self.view)
# self.setCentralWidget(self.another)
def secondWindow(self):
self.another = MainWindow()
self.another.show()
class TableModel(QSqlTableModel):
def __init__(self, *args, **kwargs):
QSqlTableModel.__init__(self, *args, **kwargs)
self.checkeable_data = {}
def flags(self, index):
fl = QSqlTableModel.flags(self, index)
if index.column() == 0:
fl |= Qt.ItemIsUserCheckable
return fl
def data(self, index, role=Qt.DisplayRole):
if role == Qt.CheckStateRole and (
self.flags(index) & Qt.ItemIsUserCheckable != Qt.NoItemFlags
):
if index.row() not in self.checkeable_data.keys():
self.setData(index, Qt.Unchecked, Qt.CheckStateRole)
return self.checkeable_data[index.row()]
else:
return QSqlTableModel.data(self, index, role)
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.CheckStateRole and (
self.flags(index) & Qt.ItemIsUserCheckable != Qt.NoItemFlags
):
self.checkeable_data[index.row()] = value
self.dataChanged.emit(index, index, (role,))
return True
return QSqlTableModel.setData(self, index, value, role)
def createConnection():
con = QSqlDatabase.addDatabase("QSQLITE")
con.setDatabaseName("ppcp_database.db")
if not con.open():
QMessageBox.critical(
None,
"QTableView Example - Error!",
"Database Error: %s" % con.lastError().databaseText(),
)
return False
return True
app = QApplication(sys.argv)
if not createConnection():
sys.exit(1)
win = RealMainWindow()
win.show()
sys.exit(app.exec_())
Use a Qt layout:
class RealMainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("QTableView Example")
self.resize(415, 200)
# Set up the model
self.model = TableModel(self)
self.model.setTable("Table2")
self.model.setEditStrategy(QSqlTableModel.OnFieldChange)
self.model.select()
self.view = QTableView()
self.view.setModel(self.model)
self.another = MainWindow()
central_widget = QWidget()
self.setCentralWidget(central_widget)
lay = QVBoxLayout(central_widget)
lay.addWidget(self.view)
lay.addWidget(self.another)
Related
this time my problem is with the login credentials, I want to be able to use them in my mainwindow to continue consulting my database, the usage code is as follows:
import sys
from sqlprograma import *
from PyQt6.QtCore import QSize, QAbstractTableModel, Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QDialog, QDialogButtonBox, QGridLayout, QLabel,QLineEdit, QWidget, QMessageBox, QStatusBar, QVBoxLayout, QTableView, QComboBox
from PyQt6.QtGui import QPalette, QColor, QAction
app = QApplication(sys.argv)
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def data(self, index, role = Qt.ItemDataRole.DisplayRole):
if index.isValid():
if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
value = self._data.iloc[index.row(), index.column()]
return str(value)
def rowCount(self, index):
return self._data.shape[0]
def columnCount(self, index):
return self._data.shape[1]
def headerData(self, section, orientation, role):
# section is the index of the column/row.
if role == Qt.ItemDataRole.DisplayRole:
if orientation == Qt.Orientation.Horizontal:
return str(self._data.columns[section])
if orientation == Qt.Orientation.Vertical:
return str(self._data.index[section])
class Login(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Impulsa OE")
self.setFixedSize(400,300)
self.login_dialog()
def login_dialog(self):
button = QPushButton("Ingresar",self)
button.clicked.connect(self.open_login)
button.setGeometry(160,130,80,50)
def open_login(self):
dialogin.exec()
class LoginDialog(QDialog):
usuario = null
contrasena = null
def __init__(self, parent=Login):
super().__init__()
self.setWindowTitle("¡Bienvenida!")
self.setFixedSize(QSize(300, 200))
self.titulo = QLabel("Ingrese sus datos para iniciar", self).move(80,45)
self.dato1 = QLineEdit(self)
self.dato1.setPlaceholderText("Usuario")
self.dato1.move(85,80)
self.dato2 = QLineEdit(self, echoMode=QLineEdit.EchoMode.Password)
self.dato2.setPlaceholderText("Contraseña")
self.dato2.move(85,110)
self.botonconectar = QPushButton("Conectar", self)
self.botonconectar.clicked.connect(self.conectar_sql)
self.botonconectar.move(115,150)
def conectar_sql(self):
alerta = QMessageBox(QMessageBox.Icon.Critical,"¡Ups!","Usuario y/o contraseña incorrectos")
self.usuario = self.dato1.text()
self.contrasena = self.dato2.text()
self.dato1.clear()
self.dato2.clear()
if app_login(self.usuario,self.contrasena) == "public":
self.conta = Acc()
self.conta.show()
self.hide()
window.close()
elif app_login(self.usuario,self.contrasena) == "fact":
pass
self.factu = Acc()
self.factu.show()
self.hide()
window.close()
elif app_login(self.usuario,self.contrasena) == "prod":
pass
self.produ = Acc()
self.produ.show()
self.hide()
window.close()
else: alerta.exec()
class Acc(QMainWindow):
def __init__(self):
super().__init__()
self.setFixedSize(700,500)
self.setWindowTitle("Impulsa OE - Contabilidad")
menu = self.menuBar()
self.table = QTableView()
tablaentidades = entidades(dialogin.usuario,dialogin.contrasena)
#data = data.astype({'numero_documento':'int'})
self.model = TableModel(tablaentidades)
self.table.setModel(self.model)
self.setCentralWidget(self.table)
window = Login()
dialogin = LoginDialog()
def main():
window.show()
sys.exit(app.exec())
if __name__ == '__main__':
main()
In this way I can use the input data contained in a variable in my main window
Also correcting certain bad practices mentioned by musicamante, removing and ordering my code
I want to be able to detect if the slider has moved to a new position and then do some actions in another function called sl. Here is my code:
import sys
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QLabel, QLineEdit, QScrollBar, QMainWindow, QMenu, QWidget, QGridLayout, \
QMessageBox, QListWidget, QPlainTextEdit, QTableWidget, QTableWidgetItem, QHeaderView
from PyQt5.QtWidgets import QMenuBar, QHBoxLayout, QVBoxLayout, QSlider, QPushButton, QDial, QBoxLayout, QSpacerItem
from PyQt5.QtGui import QFont, QColor, QPixmap, QResizeEvent, QPen
import PyQt5.QtGui as QtGui
import PyQt5.QtCore as QtCore
class ControlWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
self.layout = QVBoxLayout()
self.layout.setContentsMargins(10, 10, 10, 10)
widget3 = QLabel("Manual")
big_font = QFont('Arial')
big_font.setPointSize(15)
widget3.setFont(big_font)
self.layout.addWidget(widget3, stretch=1)
widget2 = ManualWidget()
self.layout.addWidget(widget2, stretch=4)
self.setLayout(self.layout)
class ManualWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
self.layout = QHBoxLayout()
right_widget = QWidget()
right_layout = QVBoxLayout()
right_top_widget = QWidget()
self.sld1 = self.slider('V1:', 0, 100)
right_top_layout = QVBoxLayout()
right_top_layout.setContentsMargins(0, 0, 0, 0)
right_top_layout.addWidget(self.sld1, stretch=1)
right_top_widget.setLayout(right_top_layout)
right_layout.addWidget(right_top_widget)
right_widget.setLayout(right_layout)
self.layout.addWidget(right_widget, stretch=1)
self.setLayout(self.layout)
self.layout.addWidget(self.name)
self.layout.addWidget(self.label)
self.layout.addWidget(self.slider)
print(self.sl())
def slider(self, name, low, high, step=10):
self.name = QLabel(str(name))
self.label = QLabel(str(low))
self.slider = QSlider(Qt.Horizontal, self)
self.slider.setMinimum(low)
self.slider.setMaximum(high*step)
self.slider.setValue(low)
self.slider.valueChanged.connect(self.change_value)
self.action = False
self.slider.sliderMoved.connect(self.act1)
def change_value(self):
self.set_point = (float(self.slider.value())) / 10
self.label.setText(str(self.set_point))
def act1(self):
self.action = True
return self.action
def sl(self):
if self.action == True:
x = 3
else:
x = 6
return x
class MainWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
self.layout = QVBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
big_font = QFont('Arial')
big_font.setPointSize(10)
bottom_widget = ControlWidget()
self.layout.addWidget(bottom_widget, stretch=10)
self.setLayout(self.layout)
class Window(QMainWindow):
def __init__(self, widget):
QMainWindow.__init__(self)
self.setWindowTitle("Flow test rig")
self.menu = self.menuBar()
self.setCentralWidget(widget)
self.status = self.statusBar()
widget.parent = self
app = QApplication(sys.argv)
main_widget = MainWidget()
win = Window(main_widget)
win.show()
sys.exit(app.exec_())
I tried to detect it with the act1 function, however, self.action is always False, or when it becomes True, it does not reset to False after the first move of the slider. I appreciate it if someone would help me.
I'm studying some code I've found here in SO, but I can't understand why this happens: when I move all the sub-items from a branch in my treeview, if I try to drop a branch into it, the branch simply disappears. It seems that it's not a visualization problem, the branch is not reachable by any means. What am I doing wrong?
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import (Qt, QModelIndex, QMimeData, QByteArray)
from PyQt5.QtWidgets import (QApplication, QMainWindow, QAbstractItemView, QPushButton, QVBoxLayout, QWidget)
from PyQt5.QtGui import QStandardItemModel, QStandardItem
class DragDropTreeModel(QStandardItemModel):
def __init__(self, parent=None):
super(DragDropTreeModel, self).__init__(parent)
def supportedDropActions(self):
return Qt.MoveAction
def flags(self, index):
defaultFlags = QStandardItemModel.flags(self, index)
if index.isValid():
return Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | defaultFlags
else:
return Qt.ItemIsDropEnabled | defaultFlags
class MyTreeView(QtWidgets.QTreeView):
def __init__(self, parent=None):
super(MyTreeView, self).__init__(parent)
self.setAcceptDrops(True)
self.setDragEnabled(True)
def dropEvent(self, event):
index = self.indexAt(event.pos())
model = self.model()
dest_node = model.itemFromIndex(index)
if dest_node is None:
return
source_index = self.currentIndex()
source_node = model.itemFromIndex(source_index)
# source_node: Node
sourse_parent = source_node.parent()
if sourse_parent != None:
taken_row = sourse_parent.takeRow(source_index.row())
else:
taken_row = None
dest_parent = dest_node
if dest_node != sourse_parent:
dest_parent = dest_node.parent()
if dest_parent is None:
print('dest_parent is none')
print('dest_node', dest_node.text())
dest_parent = dest_node
if taken_row == None:
return
print('dest_parent',dest_parent.text())
dest_parent.insertRow(index.row(), taken_row)
class DemoDragDrop(QWidget):
def __init__(self, parent=None):
super(DemoDragDrop, self).__init__(parent)
self.resize(480, 320)
self.initUi()
def initUi(self):
self.vLayout = QVBoxLayout(self)
self.TreeView = MyTreeView(self)# QtWidgets.QTreeView(self)#
self.TreeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.TreeView.setDragEnabled(True)
self.TreeView.setAcceptDrops(True)
self.TreeView.setDropIndicatorShown(True)
self.ddm = DragDropTreeModel()
self.TreeView.setDragDropMode(QAbstractItemView.InternalMove)
self.TreeView.setDefaultDropAction(Qt.MoveAction)
self.TreeView.setDragDropOverwriteMode(False)
# self.root_node = Node('root')
# self.ddm.appendRow(self.root_node)
node_1 = QStandardItem('1')
self.ddm.appendRow(node_1)
node_2 = QStandardItem('2')
font = QtGui.QFont()
font.setBold(True)
node_2.setFont(font)
self.ddm.appendRow(node_2)
node_d = QStandardItem('d')
node_2.appendRow(node_d)
node_a = QStandardItem('a')
node_1.appendRow(node_a)
node_b = QStandardItem('b')
node_1.appendRow(node_b)
node_c = QStandardItem('c')
node_1.appendRow(node_c)
node_3 = QStandardItem('3')
self.ddm.appendRow(node_3)
node_e = QStandardItem('e')
node_3.appendRow(node_e)
node_f = QStandardItem('f')
node_3.appendRow(node_f)
self.TreeView.setModel(self.ddm)
self.vLayout.addWidget(self.TreeView)
self.TreeView.expandAll()
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyle('fusion')
window = DemoDragDrop()
window.show()
sys.exit(app.exec_())
In my application I have a QTreeview. I have a folder named "test" that contains many subfolders. The treeview only shows the subfolders not the test forlder it self!
def create_treeview(self):
self.treeView = QTreeView()
self.treeView.setMinimumSize(QSize(250, 0))
self.treeView.setMaximumSize(QSize(250, 16777215))
self.treeView.setObjectName("treeView")
self.dirModel = QFileSystemModel()
self.dirModel.setRootPath(QDir.rootPath())
self.dirModel.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs)
self.treeView.setModel(self.dirModel)
self.treeView.setRootIndex(self.dirModel.index("/home/data/test"))
self.treeView.setHeaderHidden(True)
self.treeView.clicked.connect(self.tree_click)
return self.treeView
The rootIndex of the QTreeView is hidden so it is not shown. One possible solution is to pass the parent of the path and use a QSortFilterProxyModel to hide the other directories and files.
import os
from PyQt5.QtCore import pyqtSlot, QDir, QModelIndex, QSize, QSortFilterProxyModel
from PyQt5.QtWidgets import QApplication, QFileSystemModel, QMainWindow, QTreeView
class ProxyModel(QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self._root_path = ""
def filterAcceptsRow(self, source_row, source_parent):
source_model = self.sourceModel()
if self._root_path and isinstance(source_model, QFileSystemModel):
root_index = source_model.index(self._root_path).parent()
if root_index == source_parent:
index = source_model.index(source_row, 0, source_parent)
return index.data(QFileSystemModel.FilePathRole) == self._root_path
return True
#property
def root_path(self):
return self._root_path
#root_path.setter
def root_path(self, p):
self._root_path = p
self.invalidateFilter()
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.create_treeview()
self.setCentralWidget(self.treeView)
def create_treeview(self):
path = "/home/data/test"
self.treeView = QTreeView()
self.treeView.setMinimumSize(QSize(250, 0))
self.treeView.setMaximumSize(QSize(250, 16777215))
self.treeView.setObjectName("treeView")
self.dirModel = QFileSystemModel()
self.dirModel.setRootPath(QDir.rootPath())
self.dirModel.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs)
root_index = self.dirModel.index(path).parent()
self.proxy = ProxyModel(self.dirModel)
self.proxy.setSourceModel(self.dirModel)
self.proxy.root_path = path
self.treeView.setModel(self.proxy)
proxy_root_index = self.proxy.mapFromSource(root_index)
self.treeView.setRootIndex(proxy_root_index)
self.treeView.setHeaderHidden(True)
self.treeView.clicked.connect(self.tree_click)
#pyqtSlot(QModelIndex)
def tree_click(self, index):
ix = self.proxy.mapToSource(index)
print(
ix.data(QFileSystemModel.FilePathRole),
ix.data(QFileSystemModel.FileNameRole),
)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
I am trying to figure out a way to customize the scrollbars for QListWidget to have the scrollbars above and below the QListWidget instead of the normal vertical and horizontal scrollbars.
Please check out my example below if you don't understand what I mean.
In the example below I use QPushButtons with QTimers controlling the scrolling in place of the scrollbars but what I am looking for are scrollbars like the ones in QMenu when menu scrolling is enabled.
If that is not an option, I am wondering if there is a scrollbar signal or something that I could try to use to know when the scrollbars are normally activated? That way I can show/hide the buttons as needed. Thanks.
import sys
from PyQt5.QtCore import pyqtSignal, QTimer, Qt
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout, \
QApplication, QStyle, QListWidget, QStyleOptionButton, QListWidgetItem
class UpBtn(QPushButton):
mouseHover = pyqtSignal()
def __init__(self):
QPushButton.__init__(self)
self.setMouseTracking(True)
self.timer = QTimer()
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
opt = QStyleOptionButton()
self.initStyleOption(opt)
self.style().drawControl(QStyle.CE_ScrollBarSubLine, opt, painter, self)
painter.end()
def startScroll(self):
self.mouseHover.emit()
def enterEvent(self, event):
self.timer.timeout.connect(self.startScroll)
self.timer.start(120)
def leaveEvent(self, event):
self.timer.stop()
class DwnBtn(QPushButton):
mouseHover = pyqtSignal()
def __init__(self):
QPushButton.__init__(self)
self.setMouseTracking(True)
self.timer = QTimer()
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
opt = QStyleOptionButton()
self.initStyleOption(opt)
self.style().drawControl(QStyle.CE_ScrollBarAddLine, opt, painter, self)
painter.end()
def startScroll(self):
self.mouseHover.emit()
def enterEvent(self, event):
self.timer.timeout.connect(self.startScroll)
self.timer.start(120)
def leaveEvent(self, event):
self.timer.stop()
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.layout = QVBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
self.upBtn = UpBtn()
self.upBtn.setFixedWidth(230)
self.layout.addWidget(self.upBtn)
self.listWidget = QListWidget()
self.listWidget.setFixedWidth(230)
self.listWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.listWidget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.layout.addWidget(self.listWidget)
self.downBtn = DwnBtn()
self.downBtn.setFixedWidth(230)
self.layout.addWidget(self.downBtn)
self.setLayout(self.layout)
self.upBtn.clicked.connect(self.upBtnClicked)
self.upBtn.mouseHover.connect(self.upBtnClicked)
self.downBtn.clicked.connect(self.downBtnClicked)
self.downBtn.mouseHover.connect(self.downBtnClicked)
for i in range(100):
item = QListWidgetItem()
item.setText("list item " + str(i))
self.listWidget.addItem(item)
def upBtnClicked(self):
cur = self.listWidget.currentRow()
self.listWidget.setCurrentRow(cur - 1)
def downBtnClicked(self):
cur = self.listWidget.currentRow()
self.listWidget.setCurrentRow(cur + 1)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
EDIT:
Here is an example image for what I am talking about. This is a scrollable QMenu.
EDIT:
Scrollable QMenu code.
Uncomment the commented parts to get a fixed size like in the image. Normally Qmenu scrolling only works when the menu items exceed the screen height. I am just looking for the top and bottom hover style scrolling but to be used in QListWidget.
import sys
from PyQt5.QtCore import QPoint, QEvent
from PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout, \
QApplication, QAction, QMenu, QProxyStyle, QStyle
class MyMenu(QMenu):
def event(self, event):
if event.type() == QEvent.Show:
self.move(self.parent().mapToGlobal(QPoint(-108, 0)))
return super(MyMenu, self).event(event)
# class CustomStyle(QProxyStyle):
# def pixelMetric(self, QStyle_PixelMetric, option=None, widget=None):
# if QStyle_PixelMetric == QStyle.PM_MenuScrollerHeight:
# return 15
# if QStyle_PixelMetric == QStyle.PM_MenuDesktopFrameWidth:
# return 290
# else:
# return QProxyStyle.pixelMetric(self, QStyle_PixelMetric, option, widget)
class MainWindow(QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.layout = QVBoxLayout()
self.btn = QPushButton("Button")
self.btn.setFixedHeight(30)
self.btn.setFixedWidth(100)
self.myMenu = MyMenu("Menu", self.btn)
self.btn.setMenu(self.myMenu)
self.layout.addWidget(self.btn)
self.setLayout(self.layout)
menus = []
for _ in range(5):
myMenus = QMenu("Menu"+str(_+1), self.btn)
# myMenus.setFixedHeight(120)
myMenus.setStyleSheet("QMenu{menu-scrollable: 1; }")
menus.append(myMenus)
for i in menus:
self.btn.menu().addMenu(i)
for item in range(100):
action = QAction("item" + str(item), self)
i.addAction(action)
if __name__ == '__main__':
app = QApplication(sys.argv)
# app.setStyle(CustomStyle())
w = MainWindow()
w.show()
app.exec_()
The idea is to obtain the row of the upper and lower element that will decide whether the buttons are hidden or not, for that we use the method itemAt () that returns the item given the geometrical coordinates. On the other hand I have improved this calculation has to do every time they change the number of items in the QListView for that we use the signals of the internal model.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Button(QtWidgets.QPushButton):
moveSignal = QtCore.pyqtSignal()
def __init__(self, *args, **kwargs):
super(Button, self).__init__(*args, **kwargs)
self.m_timer = QtCore.QTimer(self, interval=120)
self.m_timer.timeout.connect(self.moveSignal)
self.setMouseTracking(True)
self.setFixedHeight(20)
def mouseReleaseEvent(self, e):
super(Button, self).mousePressEvent(e)
self.setDown(True)
def enterEvent(self, e):
self.setDown(True)
self.m_timer.start()
super(Button, self).enterEvent(e)
def leaveEvent(self, e):
self.setDown(False)
self.m_timer.stop()
super(Button, self).leaveEvent(e)
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.setFixedWidth(230)
icon = self.style().standardIcon(QtWidgets.QStyle.SP_ArrowUp)
self.upBtn = Button(icon=icon)
self.upBtn.moveSignal.connect(self.moveUp)
icon = self.style().standardIcon(QtWidgets.QStyle.SP_ArrowDown)
self.downBtn = Button(icon=icon)
self.downBtn.moveSignal.connect(self.moveDown)
self.listWidget = QtWidgets.QListWidget()
self.listWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.listWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(self.upBtn)
layout.addWidget(self.listWidget)
layout.addWidget(self.downBtn)
self.adjust_buttons()
self.create_connections()
def create_connections(self):
self.listWidget.currentItemChanged.connect(self.adjust_buttons)
model = self.listWidget.model()
model.rowsInserted.connect(self.adjust_buttons)
model.rowsRemoved.connect(self.adjust_buttons)
model.rowsMoved.connect(self.adjust_buttons)
model.modelReset.connect(self.adjust_buttons)
model.layoutChanged.connect(self.adjust_buttons)
#QtCore.pyqtSlot()
def adjust_buttons(self):
first = self.listWidget.itemAt(QtCore.QPoint())
r = self.listWidget.row(first)
self.upBtn.setVisible(r != 0 and r!= -1)
last = self.listWidget.itemAt(self.listWidget.viewport().rect().bottomRight())
r = self.listWidget.row(last)
self.downBtn.setVisible( r != (self.listWidget.count() -1) and r != -1)
#QtCore.pyqtSlot()
def moveUp(self):
ix = self.listWidget.moveCursor(QtWidgets.QAbstractItemView.MoveUp, QtCore.Qt.NoModifier)
self.listWidget.setCurrentIndex(ix)
#QtCore.pyqtSlot()
def moveDown(self):
ix = self.listWidget.moveCursor(QtWidgets.QAbstractItemView.MoveDown, QtCore.Qt.NoModifier)
self.listWidget.setCurrentIndex(ix)
#QtCore.pyqtSlot(str)
def add_item(self, text):
item = QtWidgets.QListWidgetItem(text)
self.listWidget.addItem(item)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
for i in range(100):
window.add_item("item {}".format(i))
window.show()
sys.exit(app.exec_())