I have some QML below where I want to always make sure that values up to the hundreth decimal place is always displayed. Currently, when you click on the big red button the number at the top will go up by 1. If the button is pressed and held, there is a timer that will help repeatedly increment the number while the button is held.
I am working with a float data type, and every time there is some sort of increment called, the trailing 0s will disappear. However, I need those 0s to be displayed. So I tried to insert some Javascript into the onClicked and onPressAndHold signals, but the line I have commented out displayVal.text = result will cause the numbers to stop incrementing. Is my logic faulty, or is there a better way to add trailing 0s? I might as well also mention that just appending ".00" to the end of everything would not work because in the future, I will be dealing with decimal numbers that aren't whole, like 10.20 or 11.89. I am not too familiar with Javascript either so my current code snippet to try to add 0's came from another forum post. I know it partially works at least because of the print statement.
If possible, I would like to avoid as much complex Javascript as possible; complex as in needing functions or something. If this could even be handled in the Python code that would be even better.
main.qml:
import QtQuick 2.14
import QtQuick.Layouts 1.12
import QtQuick.Controls.Material 2.15
Item {
visible: true
width: 600; height: 400
id: itemView
signal startIncrement()
signal stopIncrement()
signal incrementOnce()
ColumnLayout{
Repeater{
model: modelManager.model
ColumnLayout{
Text {
id: displayVal
text: model.display
font.pixelSize: 30
}
Rectangle{
id: incrementButton
color: "red"
width: 250
height: 100
MouseArea {
anchors.fill: parent
onClicked: {
incrementOnce()
var num = parseFloat(displayVal.text)
var result = num.toFixed(Math.max(2, (num.toString().split('.')[1] || []).length));
// displayVal.text = result
console.log("OnClick Formatted #: " + result + " DisplayVal: " + displayVal.text)
}
onPressAndHold:{
startIncrement()
var num = parseFloat(displayVal.text)
var result = num.toFixed(Math.max(2, (num.toString().split('.')[1] || []).length));
// displayVal.text = result
console.log("Started increment")
}
onReleased:{
stopIncrement()
console.log("Stopped increment")
}
}
}
}
}
}
}
main.py:
import os
import sys
from pathlib import Path
sys.path.append(os.path.join(os.path.dirname(sys.path[0]), ".."))
from PySide6.QtCore import QUrl, Qt, QCoreApplication, QTimer, QObject, Property
from PySide6.QtGui import QGuiApplication, QStandardItemModel, QStandardItem
from PySide6.QtQuick import QQuickView
CURRENT_DIRECTORY = Path(__file__).resolve().parent
DisplayRole = Qt.UserRole
class ModelManager(QObject):
def __init__(self):
super().__init__()
self._model = QStandardItemModel()
self._model.setItemRoleNames({DisplayRole: b"display"})
item = QStandardItem()
item.setData("10.00", DisplayRole)
self._model.appendRow(item)
self.timer = QTimer()
self.timer.setInterval(100)
#Property(QObject, constant=True)
def model(self):
return self._model
def start_increment(self):
self.timer.timeout.connect(self.increment)
self.timer.start()
def increment(self):
presentVal = float(self._model.item(0).data(DisplayRole)) +1.0
self._model.item(0).setData(presentVal, DisplayRole)
print(presentVal)
def stop_increment(self):
if self.timer.isActive():
self.timer.stop()
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
view = QQuickView()
view.setResizeMode(QQuickView.SizeRootObjectToView)
url = QUrl.fromLocalFile(os.fspath(CURRENT_DIRECTORY / "main.qml"))
def handle_status_changed(status):
if status == QQuickView.Error:
QCoreApplication.exit(-1)
modelManager = ModelManager()
view.rootContext().setContextProperty("modelManager", modelManager)
view.statusChanged.connect(handle_status_changed, Qt.ConnectionType.QueuedConnection)
view.setSource(url)
root = view.rootObject()
root.startIncrement.connect(modelManager.start_increment)
root.stopIncrement.connect(modelManager.stop_increment)
root.incrementOnce.connect(modelManager.increment)
view.show()
sys.exit(app.exec())
The problem is that when you do displayVal.text = result you stop the ability to automatically update the displayed text from the model. The result is that, while the model is updated, the display text is not, as you set a static value for that.
The solution is to use toFixed directly in the text definition:
Text {
id: displayVal
text: model.display.toFixed(2)
font.pixelSize: 30
}
And set the value of the item as a number, not a string:
item.setData(10., DisplayRole)
This obviously means that you must remove all the computation you're trying to achieve after incrementOnce and startIncrement, since it's unnecessary any more.
Note that:
you shouldn't overwrite the existing display role, and you should probably use another name if absolutely necessary; in reality, you can avoid setting the role names and use the standard Qt.DisplayRole to set the number (again, not as a string);
connecting the timer in the start_increment function is an error, as it will result in calling increment everytime you connect it: if you press the button twice, the increment will be done twice, if you press it three times, it will increment by 3, etc. Move the connection in the __init__;
Related
This bit of Code here creates a scrollview of buttons to allow me select from a list of different "tickets"
SearchResultButton(Button(text=str(Datarray2[i]),id=str(Datarray2[i]),on_press=self.Pressbtn ))
self.ids.SearchResult.add_widget(SearchResultButton)
From there it opens this function, which should set a variable (In this case "UpdateTicketNum") which will be used in another function to set the label text in another screen.
def Pressbtn(self, SearchResultButton):
global UpdateTicket
UpdateTicket = OPENTicket
woo = SearchResultButton.text
print(SearchResultButton.text)
wow = [blank.strip() for blank in woo.split(',')]
print("\n\n\n\n")
global UpdateTicketNum
UpdateTicketNum = (wow[0])
self.manager.get_screen('UpdateTicket').UpdateOpen()
At this point it opens up the sqlite DB and double checks that the TicketNumber is valid. The issue comes in when trying to access the label inside the kv build
def UpdateOpen(self):
print("TESTSETST")
conn = sqlite3.connect('TicketData.db', timeout=10)
UD = conn.cursor()
UD.execute('SELECT TicketNumber FROM TicketData WHERE TicketNumber = ?',(UpdateTicketNum,))
tips = UD.fetchone()
print(tips[0])
tipsy = tips[0]
UpdatedLabelTexT = tipsy
sm.current=('UpdateTicket')
UpdateTicket.ids.UpdateLabels['text']=(UpdatedLabelTexT)
The UpdateTicket.ids.UpdateLabels['text']=UpdatedLabelText] field always claims to be a property of the scrollview buttons even though I am initializing it inside another class, and with different parameters. apologies if this question is poorly formatted. but 3 days of dying trying to figure this out and I snapped.
Here is the bit in KV
<UpdateTicket>
name: 'UpdateTicket'
on_enter:
root.UpdateOpen()
orientation: "vertical"
FloatLayout:
canvas.before:
Color:
rgba: .0, .6, 1, 1
Rectangle:
pos: self.pos
size: self.size
source: "lights.jpg"
Label:
id: UpdateLabels
text: "filler"
multiline: False
size_hint: (None, None)
size: (root.width/5,root.height/20)
pos_hint:{'center_x': .5,'center_y': .5 }
and how I initialize the screen
sm.add_widget(UpdateTicket(name='UpdateTicket'))
I found the solution that worked for me. when first initializing the app
class SampleApp(App)
return sm(to build the application)
I needed to replace that return SM with
global root
return sm
root = ScreenManager()
return root
And that appeared to fix my issue. thanks for reading
I have my own "Virtual Keyboard". I already got to transform buttons clicked into KeyEvents and deliver it to QTextEdit and so on. My problem now is that I want to do the same for writable areas inside a QWebEngineView.
For example, I use my keyboard to edit my QLineEdit, and request a website. DONE
Let's say I requested google. Now I have the Google website right in front of me. I need to send KeyEvents from my Keyboard to it's search box.(Box that is inside my QWebEngineView.
Let's now point a few points:
I am using PyQt5
As I've read, the API says me that it's parent should consume the KeyEvent to the corect place. here
This snippet says "...like it was possible with QtWebKit."
I've seen now that there is no more QtWebKit, and so Chromium instead.(Maybe that's the reason I'm not getting to post these events)
This is what I have for example to simulate KeyEvents to my QEditText and whatever..
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtCore import QEvent
from PyQt5.QtCore import QSize
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtGui import QKeyEvent
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QPushButton
class KeyboardKey(QPushButton):
__path = ""
__size = [30, 30]
__style = ""
__icon_on = ""
__icon_off = ""
__auto_repeat = True
__receiver = None
__key = None
__str_key = None
def __init__(self, style, str_icon_on, str_icon_off, auto_repeat, size, receiver, key, str_key):
super(KeyboardKey, self).__init__()
self.__size = size
self.__style = style
self.__icon_on = str_icon_on
self.__icon_off = str_icon_off
self.__auto_repeat = auto_repeat
self.__receiver = receiver
self.__key = key
self.__str_key = str_key
self.set_up_button(style, str_icon_on, str_icon_off, auto_repeat, size, receiver, key, str_key)
def set_up_button(self, style, str_icon_on, str_icon_off, auto_repeat, size, receiver, key, str_key):
self.__size = size
self.__style = style
self.__icon_on = str_icon_on
self.__icon_off = str_icon_off
self.__auto_repeat = auto_repeat
self.__receiver = receiver
self.__key = key
self.__str_key = str_key
self.setText(str_key)
self.setFixedSize(size[0], size[1])
self.setStyleSheet(style)
self.setIconSize(QSize(size[0], size[1]))
self.setIcon(QIcon(self.__path + str_icon_off + ".png"))
self.setAutoRepeat(auto_repeat)
pixmap = QPixmap(self.__path + str_icon_off + ".png")
self.setMask(pixmap.mask())
self.pressed.connect(self.key_pressed)
self.released.connect(self.key_released)
def set_receiver(self, receiver):
self.__receiver = receiver
def key_pressed(self):
self.setStyleSheet("""
border-width: 5px;
border-color: rgb(37,43,52);
color: white;
background-color: rgb(0,187,255);
""",)
def key_released(self):
event = QKeyEvent(QEvent.KeyPress, Qt.Key_A, Qt.NoModifier,
"a", False)
# self.__receiver is my QEditText/QLineEdit/...
self.__receiver.keyPressEvent(event)
This Last part is the one that I post my events on the "self.__receiver". This receiver is set always for the "QWidget" that invoke it.
I have tried just to say something like:
def key_released(self):
event = QKeyEvent(QEvent.KeyPress, Qt.Key_A, Qt.NoModifier,
"a", False)
# web_view is my QWebEngineView... but it won't consume. Or maybe it's not consuming to the right place.
self.web_view.keyPressEvent(event)
This should work when you send the events to the QWebEngineViews focusProxy instead - something like this should work:
recipient = self.web_view.focusProxy()
QApplication.postEvent(recipient, event)
Got a listview where items need to make use of bold text, so a html delegate is the way to go. I looked around and found several solutions and out of them I made this code, but it has the issue that you can see in the gif, which shows the results with the use of the delegate and without.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
class HTMLDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super(HTMLDelegate, self).__init__(parent)
self.doc = QTextDocument(self)
def paint(self, painter, option, index):
painter.save()
options = QStyleOptionViewItemV4(option)
self.initStyleOption(options, index)
self.doc.setHtml(options.text)
options.text = ""
style = QApplication.style() if options.widget is None \
else options.widget.style()
style.drawControl(QStyle.CE_ItemViewItem, options, painter)
ctx = QAbstractTextDocumentLayout.PaintContext()
if option.state & QStyle.State_Selected:
ctx.palette.setColor(QPalette.Text, option.palette.color(
QPalette.Active, QPalette.HighlightedText))
textRect = style.subElementRect(QStyle.SE_ItemViewItemText, options)
painter.translate(textRect.topLeft())
self.doc.documentLayout().draw(painter, ctx)
painter.restore()
if __name__ == '__main__':
app = QApplication(sys.argv)
data = ['1','2','3','4','5','6','7','8','9']
main_list = QListView()
main_list.setItemDelegate(HTMLDelegate())
main_list.setModel(QStringListModel(data))
main_list.show()
sys.exit(app.exec_())
Here are PyQt5 and PySide versions
I dont use sizeHint, since through my testing it didnt feel like it was affecting this behavior.
Some attribute is positioning the text wrongly, I have been able to partly counter it with:
self.doc.setDocumentMargin(0)
the default margin is 4, and then move the whole item bit to the right
rect = options.rect
options.rect = QRect(rect.x()+3, rect.y(), rect.width(), rect.height())
But I dont think it addresses the cause of this issue, it just masks it and it becomes a problem again once you try add icons for example
this one is now the one I use
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import sys
class HTMLDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__()
self.doc = QTextDocument(self)
def paint(self, painter, option, index):
painter.save()
options = QStyleOptionViewItem(option)
self.initStyleOption(options, index)
self.doc.setHtml(options.text)
options.text = ""
style = QApplication.style() if options.widget is None \
else options.widget.style()
style.drawControl(QStyle.CE_ItemViewItem, options, painter)
ctx = QAbstractTextDocumentLayout.PaintContext()
print(QStyle.State_Selected)
if option.state & QStyle.State_Selected:
ctx.palette.setColor(QPalette.Text, option.palette.color(
QPalette.Active, QPalette.HighlightedText))
else:
ctx.palette.setColor(QPalette.Text, option.palette.color(
QPalette.Active, QPalette.Text))
textRect = style.subElementRect(
QStyle.SE_ItemViewItemText, options)
if index.column() != 0:
textRect.adjust(5, 0, 0, 0)
thefuckyourshitup_constant = 4
margin = (option.rect.height() - options.fontMetrics.height()) // 2
margin = margin - thefuckyourshitup_constant
textRect.setTop(textRect.top() + margin)
painter.translate(textRect.topLeft())
painter.setClipRect(textRect.translated(-textRect.topLeft()))
self.doc.documentLayout().draw(painter, ctx)
painter.restore()
def sizeHint(self, option, index):
return QSize(self.doc.idealWidth(), self.doc.size().height())
if __name__ == '__main__':
app = QApplication(sys.argv)
data = ['1','2','3','4','5','6','7','8','9']
main_list = QListView()
main_list.setItemDelegate(HTMLDelegate())
main_list.setModel(QStringListModel(data))
main_list.show()
sys.exit(app.exec_())
Well after some more digging and trying, adjusting the text rectangle seems to give the feel that everything is allright and seems to behave consistently in various DEs
textRect.adjust(-1, -4, 0, 0)
I am not sure if its a bug, but one would expect that SE_ItemViewItemText should have give the correct position on its own
An old question, but since I ran across this with a similar problem I thought I'd address some problems with your code. Mostly, it is the self.doc member. It is not going to work as you had hoped. The sizeHint() method is called (multiple times) before paint() is called (multiple times) and will be getting an empty document (which is why you "didnt feel like it was affecting this behavior".
You might think well, just initialize the document in sizeHint() vs paint() then, but that would also be wrong. There is just one delegate and document for your list, so the document would always be based on the last one sized or painted, so having self.doc as a member only saves you the document construction which is probably not worth it considering the confusion it causes.
I' m looking for an Event or Signal like "row height changed" that is called if the user changes the height of a row in my QTableView. I want to use this signal to resize all other rows in the QTableView to the new height. I didn' t find such an Event or Signal so I reckon there must be some kind of handy workaround.
Any suggestions will be appreciated.
Row resizing is performed by the vertical QHeaderView. The object emmiting it is QTableView::verticalHeader()
the signal you are interested in is
void QHeaderView::sectionResized ( int logicalIndex, int oldSize, int newSize )
This signal is emitted when a section is resized. The section's logical number is specified by logicalIndex, the old size by oldSize, and the new size by newSize.
Use QHeaderView (You can get an instance by calling QTableView::horizontalHeader()/QTableView::verticalHeader() ) and connect to geometriesChanged() or sectionResized()
The solution with QHeaderView and sectionResized works very well for me. So for resizing all
rows to the same height as the edited row I use this code now:
class MyWidget(QWidget):
def __init__(self, parent=None):
#Do some other init things
self.myTable.verticalHeader().sectionResized.connect(
self.row_resized)
def row_resized(self, index, old_size, new_size):
for i in range(self.myTable.verticalHeader().count()):
self.myTable.setRowHeight(i, new_size)
I wanted to save data after a section was resized, but found sectionResized fired a signal continuously while adjusting, rather than once at the end.
The simplest solution I could think of was to subclass the QHeaderView.
class CustomHeader(QtWidgets.QHeaderView):
"""
Custom header class to return when a section has been resized
Only after mouse released, opposed to continuously while resizing
"""
on_section_resized = QtCore.Signal(int)
def __init__(self, *args, **kwargs):
super(CustomHeader, self).__init__(*args, **kwargs)
self._has_resized = False
self._index = 0
self.sectionResized.connect(self.section_resized)
def section_resized(self, index, old_size, new_size):
self._has_resized = True
self._index = index
return
def mouseReleaseEvent(self, event):
super(CustomHeader, self).mouseReleaseEvent(event)
if self._has_resized:
self._has_resized = False
self.on_section_resized.emit(self._index)
return
In my main class I assigned the header to the table widget:
self.table_widget.setVerticalHeader(CustomHeader(QtCore.Qt.Vertical, self.table_widget))
Then connected the custom signal to my function within the original class:
self.table_widget.verticalHeader().on_section_resized.connect(self.save_section_height)
Part of the Save function:
def save_section_height(self, row):
"""
Updates the edited row's section height
:param row: int, row that has been changed
:return: None
"""
new_section_height = self.table_widget.verticalHeader().sectionSize(row)
I expect some things could be optimised better, but this at least fires one save signal opposed to ten plus, or something!
when the contents of the QFileSystemModel is displayed in a QTableView , the alignment of the text in the first row header section is right-aligned ,while the others is left-aligned,I wonder why ?
how to make the alignment of the text in each header section to be left-aligned?
setDefaultSectionSize() seems doesn't work here
my code
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
if __name__ == '__main__':
app =QApplication(sys.argv)
ui =QMainWindow()
model= QFileSystemModel ()
model.setRootPath(QDir.currentPath())
model.sort(3)
table = QTableView()
#print(table.verticalHeader().defaultAlignment()) #
table.verticalHeader().setDefaultAlignment(Qt.AlignRight)
table.setModel(model);
table.setRootIndex(model.index(QDir.currentPath())) #
ui.setCentralWidget(table)
ui.resize(800, 600)
ui.show()
app.exec_()
I'm using the QFileSystemModel in my own code and was surprised to see you get this strange behaviour. Then I dug deeper and saw that I had actually subclassed the QFileSystemModel and overridden the headerData method.
It seems that when role is Qt.DecorationRole and section==0 the default headerData function returns a QImage which messes things up. Also, setDefaultAlignment doesn't seem to actually set the default alignment.
In your case the problem will go away if you use the class given below. You can specify the alignment in the constructor to MyFileSystemModel (e.g. model= MyFileSystemModel(h_align = Qt.AlignRight))
class MyFileSystemModel(QFileSystemModel):
def __init__(self, h_align = Qt.AlignLeft, v_align = Qt.AlignLeft, parent = None):
super(MyFileSystemModel, self).__init__(parent)
self.alignments = {Qt.Horizontal:h_align, Qt.Vertical:v_align}
def headerData(self, section, orientation, role):
if role==Qt.TextAlignmentRole:
return self.alignments[orientation]
elif role == Qt.DecorationRole:
return None
else:
return QFileSystemModel.headerData(self, section, orientation, role)