I'm having trouble when modifying the source model of a QSortFilterProxyModel from another thread, while the proxy model is connected to a view. The proxy model seems to add "ghost" items to the model being displayed, blank items that cannot be interacted with (apparently)
This example code demonstrates the problem I'm experiencing, reduced down to a bare-basic example where there's a view for each of the proxy model and the source model:
from PySide2 import QtCore, QtGui, QtWidgets
import random
WordList = ["apples", "pears", "down", "stairs", "mother", "hubord", "isn't", "cupboard", "lemon", "tango", "apricot", "nuke"]
class Searcher(QtCore.QThread):
def __init__(self, model):
super(Searcher, self).__init__()
self.model = model
def run(self):
populateModel(self.model)
def populateModel(model):
for i in range(5):
item1 = QtGui.QStandardItem()
model.invisibleRootItem().appendRow(item1)
majorWord = random.choice(WordList)
item1.setData(majorWord, QtCore.Qt.DisplayRole)
for i in range(random.randint(2, 8)):
item2 = QtGui.QStandardItem()
item1.appendRow(item2)
item2.setData(os.path.join(majorWord, random.choice(WordList)), QtCore.Qt.DisplayRole)
if __name__ == '__main__':
tv = QtWidgets.QTreeView()
tv.setWindowTitle("Source Model, Threaded")
tv.show()
tv2 = QtWidgets.QTreeView()
tv2.setWindowTitle("Proxy Model, Threaded")
tv2.show()
tv3 = QtWidgets.QTreeView()
tv3.setWindowTitle("Proxy Model 2, No Threading")
tv3.show()
sourceModel = QtGui.QStandardItemModel()
tv.setModel(sourceModel)
proxyModel = QtCore.QSortFilterProxyModel()
proxyModel.setDynamicSortFilter(True)
proxyModel.setSourceModel(sourceModel)
tv2.setModel(proxyModel)
s = Searcher(sourceModel)
s.start()
sm3 = QtGui.QStandardItemModel()
pm3 = QtCore.QSortFilterProxyModel()
pm3.setSourceModel(sm3)
proxyModel.setDynamicSortFilter(True)
tv3.setModel(pm3)
populateModel(sm3)
I thought that maybe the setDynamicSortFilter property of the proxy model could be useful, but it didn't seem to have any effect.
Any help or pointers appreciated!
Related
I've a form and I wanted to know how I was supposed to hide the field "conditionalWeb" until the user choose "Web application" for the typeOfTheproject field?
I've made my research online but I absolutely don't know how to proceed... Any help would be nice :)
from django import forms
from configurator import models
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Layout
from .models import TypeOfProgram, Language, Framework, Database
from crispy_forms.bootstrap import (PrependedAppendedText, PrependedText, FormActions)
class ConfiguratorForm(forms.Form):
helper = FormHelper()
helper.form_method = 'POST'
helper.form_show_labels = False
queryOfProject = TypeOfProgram.objects.values_list('name')
queryOfFramework = Framework.objects.values_list('name','version')
queryOfDatabase = Database.objects.values_list('name','version')
listFramework = []
listProject = []
conditionalWeb=[]
listFramework=[((q[0],q[1]),q[0]+" version "+q[1])for q in queryOfFramework]
listProject=[(q[0],q[0])for q in queryOfProject]
listDatabase = [((q[0],q[1]),q[0]+" version "+q[1])for q in queryOfDatabase]
typeOfTheproject = forms.ChoiceField(choices = listProject)
conditionalWeb = forms.ChoiceField (choices = [('nothing', '----'),("Only Backend","Only Backend"),("Only Frontend","Only Frontend")])
wantedFramework = forms.MultipleChoiceField(choices = listFramework)
wantedDatabase = forms.MultipleChoiceField(choices = listDatabase)
helper.layout = Layout(
'typeOfTheproject',
'wantedFramework',
'wantedDatabase',
FormActions(Submit('Finalize and find the result','Finalize and find the result', css_class="btn btn-success"))
)
#Not Working
if typeOfTheproject is 'Web application':
helper.layout.append('conditionalWeb')
Thank you :)
You can add a simple javascript for the task:
$(document).ready(function(){
hideShow()
})
// call hideShow when the user clicks on the project_type dropdownlist
$('#id_typeoftheproject').click(function(){
hideShow()
});
function hideShow(){
if(document.getElementById('id_typeoftheproject').value == "7")
{
$('#id_conditionalweb').show();
}
else
{
$('#id_conditionalweb').hide();
}
}
You need to find the actual ids of the fields from database and replace #id_typeoftheproject and #id_conditionalweb . Also the value '7' needs to be replaced with id of web application.
I boiled the original code down to a small section that still reproduces the issue. The below code works fine with action = raw_input('next action? ') instead of action = self.fake(). WHY??!! Specifically, the 'label' window will hang and crash using the class function, but will display the two overlaid images no problem using the user input. I cannot fathom how the two are impacting PyQt, especially since the changes are being made AFTER the image update.
import time
import sys
from PyQt4 import QtGui
class Basement(object):
def __init__(self):
self.app = QtGui.QApplication(sys.argv)
self.label = QtGui.QLabel()
def update_image(self):
self.im = QtGui.QImage('n-wall.png')
painter = QtGui.QPainter()
c_image = QtGui.QImage('bed.png')
painter.begin(self.im)
painter.drawImage(10, 10, c_image)
painter.end()
self.label.setPixmap(QtGui.QPixmap.fromImage(self.im))
self.label.show()
def fake(self):
return 'left'
def play_game(self):
### Update graphics / text
self.update_image()
### Decide action
action = self.fake()
#action = raw_input('next action? ')
time.sleep(5)
B = Basement()
B.play_game()
How can I databind the value self.text in MyModel to the controls in the wx.Frame?
When the text control changes the text I would like the label to change aswell as the contents of the other text control automatically. (ideally without having to write any code if that's possible)
Note: this is a dummy example below to illustrate the general problem which may get more complicated as more fields are added to both the view and the model
import wx
class MyModel(object):
def __init__(self):
self.text = "hello"
class MyRegion(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="My Region")
self.model = MyModel()
self.label = wx.StaticText(self, label=self.model.text)
self.textbox = wx.TextCtrl(self, value=self.model.text)
self.textbox2 = wx.TextCtrl(self, value=self.model.text)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.label, 0, wx.ALL, 5)
sizer.Add(self.textbox, 0, wx.ALL, 5)
sizer.Add(self.textbox2, 0, wx.ALL, 5)
self.SetSizer(sizer)
if __name__ == "__main__":
app = wx.App()
frame = MyRegion(None)
frame.Show()
app.MainLoop()
Is there a data/model-centric approach to this as opposed to having to bind the views events to code so that that code update the model which then triggers some more code which updates the rest?
If there isn't then what mechanisms/recommended approaches are there which would be used to trigger updates to the view once the model has been updated?
Define a handler for wxEVT_UPDATE_UI event for the control whose text you'd like to change and call event.SetText() from it. This allows you to declaratively specify the text that the control should have, without bothering with updating it whenever something else changes -- instead it will be always up to date.
I have the custom item delegate set for one column for QTableView. In some cases I need to remove it (i.e. set default item delegate). But it seems that QT does not allow this. The old delegate is used even after setting the new one.
According to QT documentation for QItemDelegate all handling should be done in the same delegate, but this may bring to performance issues. Is there any way to remove/reset to default the item delegate for QTableView.
I tried it in PyQt5 (sorry, im not able to write C++). I could set the standard itemGelegate to the view and then set a custom itemDelegate to one column. By using the „clicked“-signal i could replace the custom delegate by the standard itemDelegate for this column and vice versa.
This is my code, perhaps it helps:
import sys
from PyQt5 import QtGui, QtCore, QtWidgets
class MyDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self):
QtWidgets.QStyledItemDelegate.__init__(self)
self.AlignmentFlag = QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter
self.abstand = 2
def paint(self, painter, item, index):
rahmen = item.rect.adjusted(self.abstand, self.abstand, -self.abstand, -self.abstand)
eintrag = index.data()
painter.save()
painter.drawText(rahmen,self.AlignmentFlag, eintrag)
painter.restore()
class MyModel(QtCore.QAbstractTableModel):
def __init__(self,):
QtCore.QAbstractTableModel.__init__(self)
self.items = [['a0','a1','a2','a3','a4'],['b0','b1','b2','b3','b4'],['c0','c1','c2','c3','c4']]
def columnCount(self,items):
cc = len(self.items[0])
return cc
def rowCount(self,items):
rc = len(self.items)
return rc
def data(self, index, role=2):
return self.items[index.row()][index.column()]
class MyWidget(QtWidgets.QTableView):
def __init__(self):
QtWidgets.QTableView.__init__(self)
self.setModel(MyModel())
self.setGeometry(200,200,530,120)
self.delegate_1 = MyDelegate()
self.delegate_2 = QtWidgets.QStyledItemDelegate()
self.setItemDelegate(self.delegate_2)
self.setItemDelegateForColumn(0,self.delegate_1)
self.clicked.connect(self.changeDelegate)
def changeDelegate(self,index):
if index.column() == 0:
delegate_new = self.delegate_2 if self.itemDelegateForColumn(index.column()) == self.delegate_1 else self.delegate_1
self.setItemDelegateForColumn(index.column(),delegate_new)
else:
pass
app = QtWidgets.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
I'm using Python 2.7 with PyQt 4.0.
I'm trying to make a QGraphicsRectItem move 10 px up in a animation. I have read the documentation and several tutorials but I can't get it to work. What is wrong with my code?
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import random
class TestWidget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.scene = QGraphicsScene()
self.view = QGraphicsView(self.scene)
self.button1 = QPushButton("Do test")
self.button2 = QPushButton("Move forward 10")
layout = QVBoxLayout()
buttonLayout = QHBoxLayout()
buttonLayout.addWidget(self.button1)
buttonLayout.addWidget(self.button2)
buttonLayout.addStretch()
layout.addWidget(self.view)
layout.addLayout(buttonLayout)
self.setLayout(layout)
self.button1.clicked.connect(self.do_test)
self.button2.clicked.connect(self.move_forward)
def do_test(self):
self.turtle = self.scene.addRect(0,0,10,20)
def move_forward(self):
animation = QGraphicsItemAnimation()
timeline = QTimeLine(1000)
timeline.setFrameRange(0,100)
animation.setTimeLine(timeline)
animation.setItem(self.turtle)
animation.setPosAt(1.0, QPointF(self.turtle.x(),self.turtle.y()+10))
timeline.start()
Thanks for the help!
The reason why your example doesn't work, is that you are not keeping a reference to the QGraphicsItemAnimation created in the move_forward method, and so it gets garbage-collected before it has a chance to do anything.
I would suggest you create the animation in __init__ so that you can access it later as an instance attribute:
def __init__(self, parent=None):
...
self.animation = QGraphicsItemAnimation()
def move_forward(self):
timeline = QTimeLine(1000)
timeline.setFrameRange(0, 100)
self.animation.setTimeLine(timeline)
self.animation.setItem(self.turtle)
self.animation.setPosAt(
1.0, QPointF(self.turtle.x(), self.turtle.y() + 10))
timeline.start()
try this small change (in function move_forward).
replace
animation = QGraphicsItemAnimation()
with
animation = QGraphicsItemAnimation(self)
that changes the behaviour for me.