Related
I want to use QtreeView to organize the data shown by a QComboBox. As you can see in my example, creating the box and setting up data works so far.
But my problem is, that the combobox itself only shows the first argument and not the whole line. what I want to have is, that there is shown the whole row, not only the first item of the row.
Is this maybe related to the fact, that each cell is selectable? Do I have to prohibit to select items at the end of the tree branch?
How can I achieve this while adding the elements to the QtreeView-data?
minimal example:
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
data = [['a','b','c'],['d','e','f'],['g','h','i']]
class MainWindow(QMainWindow):
dispatcher = 0
def __init__(self):
super().__init__()
# buil UI
self.init_ui()
def init_ui(self):
# layout
self.box_window = QVBoxLayout()
# content
model = QStandardItemModel(len(data),len(data[0]))
row = 0
for r in data:
col = 0
for item in r:
model.setData(model.index(row, col), item)
col += 1
row += 1
tree_view = QTreeView()
tree_view.setHeaderHidden(True)
tree_view.setRootIsDecorated(False)
tree_view.setAlternatingRowColors(True)
combobox = QComboBox()
combobox.setMinimumSize(250,50)
combobox.setView(tree_view)
combobox.setModel(model)
self.box_window.addWidget(combobox)
self.box_window.addStretch()
# build central widget and select it
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.centralWidget().setLayout(self.box_window)
# show window
self.setGeometry(50,50,1024,768)
self.setWindowTitle("Test")
self.show()
def main():
app = QApplication(sys.argv)
main_window = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
A possible solution is to concatenate the texts in the row and set as the text to be painted:
class ComboBox(QComboBox):
def paintEvent(self, event):
painter = QStylePainter(self)
painter.setPen(self.palette().color(QPalette.Text))
# draw the combobox frame, focusrect and selected etc.
opt = QStyleOptionComboBox()
self.initStyleOption(opt)
values = []
for c in range(self.model().columnCount()):
index = self.model().index(self.currentIndex(), c, self.rootModelIndex())
values.append(index.data())
opt.currentText = " ".join(values)
painter.drawComplexControl(QStyle.CC_ComboBox, opt)
# draw the icon and text
painter.drawControl(QStyle.CE_ComboBoxLabel, opt)
I'm trying to add a label to the top right corner of the plot showing the most recent data value. I've tried using pg.LabelItem and adding this to pg.PlotWidget and updating the label with each new data update but I'm unable to get the label to appear. Here's some pictures of what I'm trying to do:
What I have:
What I'm trying to do:
I can't get the white label to appear on the plot. Here's my code:
from PyQt4 import QtCore, QtGui
from threading import Thread
import pyqtgraph as pg
import numpy as np
import random
import sys
import time
class SimplePlot(QtGui.QWidget):
def __init__(self, parent=None):
super(SimplePlot, self).__init__(parent)
# Desired Frequency (Hz) = 1 / self.FREQUENCY
# USE FOR TIME.SLEEP (s)
self.FREQUENCY = .004
# Frequency to update plot (ms)
# USE FOR TIMER.TIMER (ms)
self.TIMER_FREQUENCY = self.FREQUENCY * 1000
# Set X Axis range. If desired is [-10,0] then set LEFT_X = -10 and RIGHT_X = 0
self.LEFT_X = -10
self.RIGHT_X = 0
self.X_Axis = np.arange(self.LEFT_X, self.RIGHT_X, self.FREQUENCY)
self.buffer = int((abs(self.LEFT_X) + abs(self.RIGHT_X))/self.FREQUENCY)
self.data = []
# Create Plot Widget
self.simple_plot_widget = pg.PlotWidget()
# Enable/disable plot squeeze (Fixed axis movement)
self.simple_plot_widget.plotItem.setMouseEnabled(x=False, y=False)
self.simple_plot_widget.setXRange(self.LEFT_X, self.RIGHT_X)
self.simple_plot_widget.setTitle('Plot')
self.simple_plot_widget.setLabel('left', 'Value')
self.simple_plot_widget.setLabel('bottom', 'Time (s)')
self.simple_plot = self.simple_plot_widget.plot()
self.simple_plot.setPen(197,235,255)
self.label_value = pg.LabelItem('', **{'color': '#FFF'})
self.simple_plot_widget.addItem(self.label_value)
self.layout = QtGui.QGridLayout()
self.layout.addWidget(self.simple_plot_widget)
self.read_position_thread()
self.start()
# Update plot
def start(self):
self.position_update_timer = QtCore.QTimer()
self.position_update_timer.timeout.connect(self.plot_updater)
self.position_update_timer.start(self.get_simple_plot_timer_frequency())
# Read in data using a thread
def read_position_thread(self):
self.current_position_value = 0
self.old_current_position_value = 0
self.position_update_thread = Thread(target=self.read_position, args=())
self.position_update_thread.daemon = True
self.position_update_thread.start()
def read_position(self):
frequency = self.get_simple_plot_frequency()
while True:
try:
# Add data
self.current_position_value = self.current_position_value + random.uniform(-1, -5)
self.old_current_position_value = self.current_position_value
time.sleep(frequency)
except:
self.current_position_value = self.old_current_position_value
def plot_updater(self):
self.dataPoint = float(self.current_position_value)
if len(self.data) >= self.buffer:
del self.data[:1]
self.data.append(self.dataPoint)
self.simple_plot.setData(self.X_Axis[len(self.X_Axis) - len(self.data):], self.data)
# Update label value
self.label_value.setText(str(self.dataPoint))
def clear_simple_plot(self):
self.data[:] = []
def get_simple_plot_frequency(self):
return self.FREQUENCY
def get_simple_plot_timer_frequency(self):
return self.TIMER_FREQUENCY
def get_simple_plot_layout(self):
return self.layout
def get_current_position_value(self):
return self.current_position_value
def get_simple_plot_widget(self):
return self.simple_plot_widget
if __name__ == '__main__':
app = QtGui.QApplication([])
mw = QtGui.QMainWindow()
mw.setWindowTitle('Plot')
simple_plot_widget = SimplePlot()
cw = QtGui.QWidget()
ml = QtGui.QGridLayout()
cw.setLayout(ml)
mw.setCentralWidget(cw)
ml.addLayout(simple_plot_widget.get_simple_plot_layout(),0,0)
mw.show()
# Start Qt event loop unless running in interactive mode or using pyside
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
It may be because your plot is rescaling constantly, and the size of the LabelItem doesn't change with it, also it seems to be positioned on the positive side of the x-axis, so you cant visualize the text.
Pyqtgraph recommends here to use TextItem instead of LabelItem, to display text inside a scaled view.
I tried using the TextItem and it worked fine, but its default position is bad, maybe because your plot is in the negative quadrant. Just use the setPos() method like this:
# Update label value
self.label_value.setPos(QtCore.QPointF(-9, 0.6*min(self.data)))
self.label_value.setText(str(self.dataPoint))
And it should do what you want.
i used a class from stackoverflow that applies the drag and drop feature to table rows but it doesn't save images from being removed from cells when dragging and dropping any row i want some help figuring out how to fix the code to do this jop right !
the code of the whole project with database:
database used: https://gofile.io/d/c0srQ0
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import (QApplication, QComboBox, QDialog,
QDialogButtonBox, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout,
QLabel, QLineEdit, QMenu, QMenuBar, QPushButton, QSpinBox, QTextEdit,
QVBoxLayout, QStyledItemDelegate, QCheckBox, QTableWidgetItem, QAbstractItemView, QTableWidget)
import sqlite3
import random
import ast
#import qdarkstyle
class Main(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.cashed = []
self.setWindowTitle('Arabic Math Project')
self.setContentsMargins(20,20,20,20)
# create table:
#Options()
#self.setStyleSheet(qdarkstyle.load_stylesheet())
layout = QVBoxLayout()
self.searchBar = QtWidgets.QLineEdit()
self.searchBar.setAlignment(QtCore.Qt.AlignCenter)
self.searchBar.setPlaceholderText("search")
self.searchBar.textChanged.connect(self.startSearchThreading) #editingFinished()
#self.table = QtWidgets.QTableWidget()
self.table = TableWidgetDragRows()
conn = sqlite3.connect("math.db")
conn.text_factory = bytes
self.cur = conn.cursor()
data = self.cur.execute("select * from problems;").fetchall();conn.close()
self.dims = (lambda x: (len(x), len(x[0])))(data) #(rows number, col number)
[self.table.insertRow(i) for i in [i for i in range(self.dims[0])]]
[self.table.insertColumn(i) for i in [i for i in range(self.dims[1]+1)]]
#changing h-header names .
self.table.setHorizontalHeaderLabels(["Unique", "Image", "Test", "Year", "Lesson", "Page","Comment", "Options", "Options-advanced"])
for c in range(self.dims[1]):
for r in range(self.dims[0]):
if c!=1:self.table.setItem(r, c, QtWidgets.QTableWidgetItem(data[r][c].decode("utf-8")))
else:self.table.setCellWidget(r, c, self.getImage(data[r][c]))
for r in range(self.dims[0]):self.table.setCellWidget(r, self.dims[1], Options(ops=[data[r][self.dims[1]-1].decode("utf-8")], row=data[r]))
#table h & v auto - resizing .
header = self.table.horizontalHeader()
header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
header.setSectionResizeMode(2, QtWidgets.QHeaderView.Interactive)
header.setSectionResizeMode(7, QtWidgets.QHeaderView.Interactive)
headerv = self.table.verticalHeader()
headerv.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
#hide the options column cuz it doesn't concern user to see it.
self.table.setColumnHidden(7, True)
#layout setting up.
layout.addWidget(self.searchBar)
layout.addWidget(self.table)
self.setLayout(layout)
def getImage(self, image):
imageLabel = ScaledPixmapLabel()
pixmap = QtGui.QPixmap()
pixmap.loadFromData(image, "jpg")
imageLabel.setPixmap(pixmap)
return imageLabel
def startSearchThreading(self):
self.table.setRowCount(0)
self.update = updateTable(data= self.searchBar.text())
self.update.new_signal.connect(self.Search)
self.update.start()
def create(self):
self.createFormGroupBox()
return self.formGroupBox
#QtCore.pyqtSlot(int, int, bytes, bool, bytes)
def Search(self, r, c, d, newRowOrder, ops):
if newRowOrder:self.table.insertRow(self.table.rowCount()) # create new row.
if c!=1:self.table.setItem(r, c, QtWidgets.QTableWidgetItem(d.decode("utf-8")))
else:self.table.setCellWidget(r, c, self.getImage(d))
self.cashed.append(d)
if c ==self.dims[1]-1:
self.table.setCellWidget(r, self.dims[1], Options(ops=[ops.decode("utf-8")], row=self.cashed))
self.cashed = []
class updateTable(QtCore.QThread):
def __init__(self, parent=None, data=True):
super(QtCore.QThread, self).__init__()
self.data = data
new_signal = QtCore.pyqtSignal(int, int, bytes, bool, bytes)
def run(self):
currRow = 0
conn = sqlite3.connect("math.db")
conn.text_factory = bytes
self.cur = conn.cursor()
searched = self.cur.execute(" SELECT * FROM problems WHERE text LIKE ('%' || ? || '%') ", (self.data,)).fetchall()
dims = (lambda x: (len(x), len(x[0])))(searched) if searched!=[] else (0, 0)
for r in range(dims[0]):
for c in range(dims[1]):
self.new_signal.emit(r, c, searched[r][c], True if c==0 else False, searched[r][dims[1]-1])
class ScaledPixmapLabel(QtWidgets.QLabel):
def __init__(self):
super().__init__()
self.setScaledContents(True)
def paintEvent(self, event):
if self.pixmap():
pm = self.pixmap()
originalRatio = pm.width() / pm.height()
currentRatio = self.width() / self.height()
if originalRatio != currentRatio:
qp = QtGui.QPainter(self)
pm = self.pixmap().scaled(self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
rect = QtCore.QRect(0, 0, pm.width(), pm.height())
rect.moveCenter(self.rect().center())
qp.drawPixmap(rect, pm)
return
super().paintEvent(event)
class Options(QtWidgets.QWidget):
def __init__(self, parent=None,row=[], ops=[]):
super(Options, self).__init__(parent)
self.i = random.randint(1, 100)
self.row = [j.decode("utf-8") if i!=1 else "image" for i,j in enumerate(row)]
self.formLayout = QFormLayout()
self.setStyleSheet(open("styles.css").read())
ops = ast.literal_eval(ops[0]) if ops!=[] else {}
for i,j in ops.items():
widget = {"lineedit": QLineEdit(), "combobox": QComboBox(), "spinbox": QSpinBox(), "checkbox": QCheckBox()}
title = j.get("title", "")
combos = j.get("combos", [])
if i == "lineedit":widget[i].setText(j.get("default", ""))
elif i == "combobox":widget[i].addItems(combos)
elif i == "checkbox":
widget[i].setCheckState((lambda x: {1:2, 2:2, 0:0}[x])(j.get("default", 0)))
widget[i].setText(j.get("text", ""))
elif i == "spinbox":
widget[i].setMaximum(j.get("max", 100))
widget[i].setMinimum(j.get("min", 0))
self.formLayout.addRow(QLabel(title), widget[i])
self.btn = QtWidgets.QPushButton("Submit")
self.btn.clicked.connect(self.do)
self.formLayout.addWidget(self.btn)
self.formLayout.setFormAlignment(QtCore.Qt.AlignVCenter)
self.setLayout(self.formLayout)
def do(self):
#[STOPED HERE]
heads, tails = [], []
for i in range(self.formLayout.count()):
w = self.formLayout.itemAt(i).widget()
if i%2==0:heads.append(w.text())
elif isinstance(w, QLineEdit):tails.append(w.text())
elif isinstance(w, QComboBox):tails.append(w.currentText())
elif isinstance(w, QSpinBox):tails.append(w.value())
elif isinstance(w, QCheckBox):tails.append(w.checkState())
values = {i:j for i,j in zip(heads, tails)}
print(self.row[:-1]+[values])
class TableWidgetDragRows(QTableWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.viewport().setAcceptDrops(True)
self.setDragDropOverwriteMode(False)
self.setDropIndicatorShown(True)
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
#self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setDragDropMode(QAbstractItemView.InternalMove)
def dropEvent(self, event: QDropEvent):
if not event.isAccepted() and event.source() == self:
drop_row = self.drop_on(event)
rows = sorted(set(item.row() for item in self.selectedItems()))
print(rows)
rows_to_move = [[QTableWidgetItem(self.item(row_index, column_index)) for column_index in range(self.columnCount())]
for row_index in rows]
for row_index in reversed(rows):
self.removeRow(row_index)
if row_index < drop_row:
drop_row -= 1
for row_index, data in enumerate(rows_to_move):
row_index += drop_row
self.insertRow(row_index)
for column_index, column_data in enumerate(data):
self.setItem(row_index, column_index, column_data)
event.accept()
for row_index in range(len(rows_to_move)):
self.item(drop_row + row_index, 0).setSelected(True)
self.item(drop_row + row_index, 1).setSelected(True)
super().dropEvent(event)
def drop_on(self, event):
index = self.indexAt(event.pos())
if not index.isValid():
return self.rowCount()
return index.row() + 1 if self.is_below(event.pos(), index) else index.row()
def is_below(self, pos, index):
rect = self.visualRect(index)
margin = 2
if pos.y() - rect.top() < margin:
return False
elif rect.bottom() - pos.y() < margin:
return True
# noinspection PyTypeChecker
return rect.contains(pos, True) and not (int(self.model().flags(index)) & Qt.ItemIsDropEnabled) and pos.y() >= rect.center().y()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
main = Main()
main.resize(600,600)
main.show()
app.exec_()
You are setting images using QLabels set as cell widgets. Setting cell widgets is only a feature used by the table UI: it is completely unrelated to the underlying model and the QTableWidgetItems know nothing about them also. So, it's completely up to you to check if a cell has a widget set and move that widget accordingly.
Semi pseudo-code:
def dropEvent(self, event: QDropEvent):
if not event.isAccepted() and event.source() == self:
drop_row = self.drop_on(event)
rows = sorted(set(item.row() for item in self.selectedItems()))
print(rows)
rows_to_move = []
images = []
for row_index in rows:
items = []
for column_index in range(self.columnCount()):
items.append(QTableWidgetItem(
self.item(row_index, column_index)))
widget = self.cellWidget(row_index, column_index)
if isinstance(widget, ScaledPixmapLabel):
images.append((row_index, column_index, widget.pixmap()))
rows_to_move.append(items)
# ...
for row, column, pixmap in widgets:
self.setCellWidget(row + drop_row, column,
ScaledPixmapLabel(pixmap))
As you can see I recreated the label instance each time, that's because when you set a cell widget, the table takes its ownership. While you could reparent the widget, the result might be undefined, so it's better to create a brand new instance based on the pixmap.
Tip: avoid using inline if/else or for loops: there's absolutely no benefit in doing it, and the only result is that it makes your code much less readable
I want to select a column on a right click and afterwards a context menu has to provide a delete entry for deleting the selected column.
With the transportet QPoint arg the mouse position is sent to the slot. But i need the clicked column.
How to fix it?
To obtain the column you must use the pressed item that is obtained using the itemAt() method.
It is selected using the setRangeSelected method by passing it QTableWidgetSelectionRange, which has the column as data.
Then you create the QMenu and the exec_() method you pass the position of the signal but you must convert it to global using mapToGlobal from the viewport.
Then you delete the column using the removeColumn() method.
from PyQt5 import QtCore, QtGui, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.table_widget = QtWidgets.QTableWidget(20, 10)
for i in range(self.table_widget.rowCount()):
for j in range(self.table_widget.columnCount()):
it = QtWidgets.QTableWidgetItem("{}-{}".format(i, j))
self.table_widget.setItem(i, j, it)
vlay = QtWidgets.QVBoxLayout(self)
vlay.addWidget(self.table_widget)
self.table_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.table_widget.customContextMenuRequested.connect(self.on_customContextMenuRequested)
#QtCore.pyqtSlot(QtCore.QPoint)
def on_customContextMenuRequested(self, pos):
it = self.table_widget.itemAt(pos)
if it is None: return
c = it.column()
item_range = QtWidgets.QTableWidgetSelectionRange(0, c, self.table_widget.rowCount()-1 , c)
self.table_widget.setRangeSelected(item_range, True)
menu = QtWidgets.QMenu()
delete_column_action = menu.addAction("Delete column")
action = menu.exec_(self.table_widget.viewport().mapToGlobal(pos))
if action == delete_column_action:
self.table_widget.removeColumn(c)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
Ok - based on your solution (thanks a lot) - i tried a view. Mostly it does what i want. If i click on the right mouse button on a cell in the table body the cell should be selected and a context menu should be shown - ok - works. If a row or column head is under the mouse cursor the whole row (column) should be selected - works.
BUT the selection remains and that shouldn't be so!
Here's my code for the view:
import logging
import sys
from PyQt5.QtWidgets import \
QApplication, QWidget, QVBoxLayout, QMenu, \
QTableWidget, QTableWidgetItem, QTableWidgetSelectionRange
from PyQt5.QtCore import \
QPoint, Qt
class View(QWidget):
def __init__(self, app, parent=None):
super().__init__(parent)
self.app = app
# gui elements
self.layout = None
self.tbl = None
self.tbl_selection = None
self.tbl_item = None
self.tbl_item_pos = [-1, -1]
def _setup_layout(self):
logging.debug("<View._setup_layout>")
self.setMinimumHeight(600)
self.setMinimumWidth(800)
self.layout = QVBoxLayout()
self.setLayout(self.layout)
def _setup_table(self):
logging.debug("<View._setup_table()>")
if self.app.data:
data = self.app.data
rows, cols = len(data), len(data[0])
self.tbl = tbl = QTableWidget(rows, cols)
tbl.setHorizontalHeaderLabels(data.pop(0))
for r, ds in enumerate(data):
for c, f in enumerate(ds):
item = QTableWidgetItem(f)
tbl.setItem(r, c, item)
def _setup_widgets(self):
logging.debug("<View._setup_widgets()>")
if self.app is not None:
self._setup_table()
else:
self.tbl = QTableWidget()
tbl = self.tbl
tbl.verticalHeader().setContextMenuPolicy(
Qt.CustomContextMenu)
tbl.horizontalHeader().setContextMenuPolicy(
Qt.CustomContextMenu)
self.layout.addWidget(self.tbl)
def _connect_evt_with_handler(self):
logging.debug("<View._connect_evt_with_handler()>")
tbl = self.tbl
tbl.setContextMenuPolicy(Qt.CustomContextMenu)
tbl.customContextMenuRequested.connect(self.on_tbl_rightclick)
# event for click on row heads
tbl.verticalHeader().customContextMenuRequested.connect(
self.on_tbl_rhead_rightclick)
# event for click on col heads
tbl.horizontalHeader().customContextMenuRequested.connect(
self.on_tbl_chead_rightclick)
# protected
def _get_clicked_item(self, qpoint: QPoint) -> bool:
logging.debug("<View._get_clicked_item(qpoint={})>".format(qpoint))
self.tbl_item = item = self.tbl.itemAt(qpoint)
self.tbl_item_pos = [item.row(), item.column()]
return self.tbl_item is not None
def _set_selection(self):
logging.debug("<View._set_selection()>")
r, c = self.tbl_item_pos
logging.debug("... row={}, col={}".format(r, c))
if r >= 0 and c >= 0:
self.tbl_selection = QTableWidgetSelectionRange(
r, c, r, c)
elif c < 0:
# row head
self.tbl_selection = QTableWidgetSelectionRange(
r, 0, r, self.tbl.columnCount() - 1)
elif r < 0:
# col head
self.tbl_selection = QTableWidgetSelectionRange(
0, c, self.tbl.rowCount() - 1, c)
self.tbl.setRangeSelected(self.tbl_selection, True)
def _build_ctx_mnu(self, qpoint: QPoint):
logging.debug("<View._build_ctx_mnu(qpoint={})>".format(qpoint))
r, c = self.tbl_item_pos
logging.debug("... row={}, col={}".format(r, c))
# build menu
action_del_col = None
action_del_row = None
menu = QMenu()
if c < 0:
# row head
action_del_row = menu.addAction("Delete row")
elif r < 0:
# col head
action_del_col = menu.addAction("Delete column")
else:
# table body
action_del_col = menu.addAction("Delete column")
action_del_row = menu.addAction("Delete row")
action = menu.exec_(self.tbl.viewport().mapToGlobal(qpoint))
if action is not None and action == action_del_col:
logging.debug("... action: {}".format(action_del_col.text()))
self.tbl.removeColumn(c)
elif action is not None and action == action_del_row:
logging.debug("... action: {}".format(action_del_row.text()))
self.tbl.removeRow(r)
# UI
def setup(self):
logging.debug("<View.setup()>")
self._setup_layout()
self._setup_widgets()
self._connect_evt_with_handler()
# events
def on_tbl_rightclick(self, qpoint: QPoint):
logging.debug("<View.on_tbl_rightclick(qpoint={})>".format(qpoint))
if self._get_clicked_item(qpoint):
self._set_selection()
self._build_ctx_mnu(qpoint)
def on_tbl_rhead_rightclick(self, qpoint: QPoint):
logging.debug(
"<View.on_tbl_rhead_rightclick(qpoint={})>".format(qpoint))
if self._get_clicked_item(qpoint):
self.tbl_item_pos[1] = -1
self._set_selection()
self._build_ctx_mnu(qpoint)
def on_tbl_chead_rightclick(self, qpoint: QPoint):
logging.debug(
"<View.on_tbl_chead_rightclick(qpoint={})>".format(qpoint))
if self._get_clicked_item(qpoint):
self.tbl_item_pos[0] = -1
self._set_selection()
self._build_ctx_mnu(qpoint)
And here some date - once more:
data = [f.strip().split(",") for f in """\
id,first_name,last_name,email,gender,ip_address
1,Hillard,Tasseler,htasseler0#google.com.br,Male,104.153.237.243
2,Tyrus,Oley,toley1#ft.com,Male,163.73.24.45
3,Kora,Covil,kcovil2#privacy.gov.au,Female,158.44.166.87
4,Phineas,McEntee,pmcentee3#rambler.ru,Male,71.82.246.45
5,Dottie,Spraging,dspraging4#berkeley.edu,Female,226.138.241.22
6,Andria,Ivatts,aivatts5#about.com,Female,57.5.76.78
7,Missy,Featherstone,mfeatherstone6#unblog.fr,Female,9.56.215.203
8,Anstice,Sargant,asargant7#about.me,Female,36.115.185.109
9,Teresita,Trounce,ttrounce8#myspace.com,Female,240.228.133.166
10,Sib,Thomke,sthomke9#ibm.com,Female,129.191.2.7
11,Amery,Dallander,adallandera#elpais.com,Male,4.115.194.100
12,Rourke,Rowswell,rrowswellb#bloomberg.com,Male,48.111.190.66
13,Cloe,Benns,cbennsc#slideshare.net,Female,142.48.24.44
14,Enos,Fery,eferyd#pen.io,Male,59.19.200.235
15,Russell,Capelen,rcapelene#fc2.com,Male,38.205.20.141""".split()]
And for testing: the app object and the main function:
class App:
def __init__(self, data=()):
self.view = View(app=self)
self.data = []
if isinstance(data, (list, tuple)) and len(data) > 0:
self.set(data)
def set(self, data):
logging.debug("<App.set(data)>")
self.data = data
def setup(self):
logging.debug("<App.setup()>")
self.view.setup()
def show(self):
logging.debug("<App.show()>")
self.view.show()
def main():
logging.basicConfig(level=logging.DEBUG)
qapp = QApplication(sys.argv)
app = App(data=data)
app.setup()
app.show()
sys.exit(qapp.exec_())
main()
I'm trying to have text from feedparser scroll across the screen from right to left. I'm using PyQt5, I'm not sure how to go about adding this feature.
What I want to display is below
import feedparser
sports = feedparser.parse('http://rssfeeds.usatoday.com/UsatodaycomSports-TopStories')
for e in sports['entries']:
news = (e.get('title', ''))
I'm looking for a continuous scrolling until all the news headlines are read and then the page is reloaded to get the most recent headlines or just reread whats already there. Thanks!
You can use QTimeLine to show a continously scrolling slice of the news in a label. I implemented it in a little gui to try, if other functions in the app are blocked while QTimeLine is running:
import feedparser
import sys
from PyQt5 import QtWidgets, QtCore
class MyWidget(QtWidgets.QWidget):
def __init__(self, parent = None):
QtWidgets.QWidget.__init__(self, parent)
self.setGeometry(200, 200, 800, 600)
self.textLabel = QtWidgets.QLabel('') # label showing some text
self.uButton = QtWidgets.QPushButton('upper Button')
self.lButton = QtWidgets.QPushButton('lower Button')
self.label = QtWidgets.QLabel('') # label showing the news
self.label.setAlignment(QtCore.Qt.AlignRight) # text starts on the right
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.textLabel)
self.layout.addWidget(self.uButton)
self.layout.addWidget(self.lButton)
self.layout.addWidget(self.label)
self.layout.setStretch(0, 3)
self.layout.setStretch(1, 3)
self.layout.setStretch(2, 3)
self.layout.setStretch(3, 1)
self.setLayout(self.layout)
self.timeLine = QtCore.QTimeLine()
self.timeLine.setCurveShape(QtCore.QTimeLine.LinearCurve) # linear Timeline
self.timeLine.frameChanged.connect(self.setText)
self.timeLine.finished.connect(self.nextNews)
self.signalMapper = QtCore.QSignalMapper(self)
self.signalMapper.mapped[str].connect(self.setTlText)
self.uButton.clicked.connect(self.signalMapper.map)
self.signalMapper.setMapping(self.uButton, self.uButton.text())
self.lButton.clicked.connect(self.signalMapper.map)
self.signalMapper.setMapping(self.lButton, self.lButton.text())
self.feed()
def feed(self):
fm = self.label.fontMetrics()
self.nl = int(self.label.width()/fm.averageCharWidth()) # shown stringlength
news = []
sports = feedparser.parse('http://rssfeeds.usatoday.com/UsatodaycomSports-TopStories')
for e in sports['entries']:
news.append(e.get('title', ''))
appendix = ' '*self.nl # add some spaces at the end
news.append(appendix)
delimiter = ' +++ ' # shown between the messages
self.news = delimiter.join(news)
newsLength = len(self.news) # number of letters in news = frameRange
lps = 4 # letters per second
dur = newsLength*1000/lps # duration until the whole string is shown in milliseconds
self.timeLine.setDuration(dur)
self.timeLine.setFrameRange(0, newsLength)
self.timeLine.start()
def setText(self, number_of_frame):
if number_of_frame < self.nl:
start = 0
else:
start = number_of_frame - self.nl
text = '{}'.format(self.news[start:number_of_frame])
self.label.setText(text)
def nextNews(self):
self.feed() # start again
def setTlText(self, text):
string = '{} pressed'.format(text)
self.textLabel.setText(string)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
Add PySide2 version:
import feedparser
import sys
from PySide2 import QtWidgets, QtCore
class MyWidget(QtWidgets.QWidget):
def __init__(self, parent = None):
QtWidgets.QWidget.__init__(self, parent)
self.setGeometry(200, 200, 800, 600)
self.textLabel = QtWidgets.QLabel('') # label showing some text
self.uButton = QtWidgets.QPushButton('upper Button')
self.lButton = QtWidgets.QPushButton('lower Button')
self.label = QtWidgets.QLabel('') # label showing the news
self.label.setAlignment(QtCore.Qt.AlignRight) # text starts on the right
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.textLabel)
self.layout.addWidget(self.uButton)
self.layout.addWidget(self.lButton)
self.layout.addWidget(self.label)
self.layout.setStretch(0, 3)
self.layout.setStretch(1, 3)
self.layout.setStretch(2, 3)
self.layout.setStretch(3, 1)
self.setLayout(self.layout)
self.timeLine = QtCore.QTimeLine()
self.timeLine.setCurveShape(QtCore.QTimeLine.LinearCurve) # linear Timeline
self.timeLine.frameChanged.connect(self.setText)
self.timeLine.finished.connect(self.nextNews)
self.signalMapper = QtCore.QSignalMapper(self)
self.signalMapper.mapped[str].connect(self.setTlText)
self.uButton.clicked.connect(self.signalMapper.map)
self.signalMapper.setMapping(self.uButton, self.uButton.text())
self.lButton.clicked.connect(self.signalMapper.map)
self.signalMapper.setMapping(self.lButton, self.lButton.text())
self.feed()
def feed(self):
fm = self.label.fontMetrics()
self.nl = int(self.label.width()/fm.averageCharWidth()) # shown stringlength
news = []
sports = feedparser.parse('http://rssfeeds.usatoday.com/UsatodaycomSports-TopStories')
for e in sports['entries']:
news.append(e.get('title', ''))
appendix = ' '*self.nl # add some spaces at the end
news.append(appendix)
delimiter = ' +++ ' # shown between the messages
self.news = delimiter.join(news)
newsLength = len(self.news) # number of letters in news = frameRange
lps = 4 # letters per second
dur = newsLength*1000/lps # duration until the whole string is shown in milliseconds
self.timeLine.setDuration(dur)
self.timeLine.setFrameRange(0, newsLength)
self.timeLine.start()
def setText(self, number_of_frame):
if number_of_frame < self.nl:
start = 0
else:
start = number_of_frame - self.nl
text = '{}'.format(self.news[start:number_of_frame])
self.label.setText(text)
def nextNews(self):
self.feed() # start again
def setTlText(self, text):
string = '{} pressed'.format(text)
self.textLabel.setText(string)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())