Related
I'm using solution in the related answer for How to auto-dump modified values in nested dictionaries using ruamel.yaml, which uses the default (round-trip) loader/dumper.
I believe, it is a hard problem since additinal dict should keep comments as well.
=> Would it be possible to prevent comments to be removed if we use modify values in nested dictionaries using ruamel.yaml approach?
Example:
config.yaml:
c: # my comment
b:
f: 5
a:
z: 4
b: 4 # my comment
code (same code from How to auto-dump modified values in nested dictionaries using ruamel.yaml
), which changed to use the default (round-trip) loader/dumper:
#!/usr/bin/env python3
from pathlib import Path
from ruamel.yaml import YAML, representer
class SubConfig(dict):
def __init__(self, parent):
self.parent = parent
def updated(self):
self.parent.updated()
def __setitem__(self, key, value):
if isinstance(value, dict):
v = SubConfig(self)
v.update(value)
value = v
super().__setitem__(key, value)
self.updated()
def __getitem__(self, key):
try:
res = super().__getitem__(key)
except KeyError:
super().__setitem__(key, SubConfig(self))
self.updated()
return super().__getitem__(key)
return res
def __delitem__(self, key):
res = super().__delitem__(key)
self.updated()
def update(self, *args, **kw):
for arg in args:
for k, v in arg.items():
self[k] = v
for k, v in kw.items():
self[k] = v
self.updated()
return
_SR = representer.RoundTripRepresenter
_SR.add_representer(SubConfig, _SR.represent_dict)
class Config(dict):
def __init__(self, filename, auto_dump=True):
self.filename = filename if hasattr(filename, "open") else Path(filename)
self.auto_dump = auto_dump
self.changed = False
self.yaml = YAML()
self.yaml.indent(mapping=4, sequence=4, offset=2)
self.yaml.default_flow_style = False
if self.filename.exists():
with open(filename) as f:
self.update(self.yaml.load(f) or {})
def updated(self):
if self.auto_dump:
self.dump(force=True)
else:
self.changed = True
def dump(self, force=False):
if not self.changed and not force:
return
with open(self.filename, "w") as f:
self.yaml.dump(dict(self), f)
self.changed = False
def __setitem__(self, key, value):
if isinstance(value, dict):
v = SubConfig(self)
v.update(value)
value = v
super().__setitem__(key, value)
self.updated()
def __getitem__(self, key):
try:
res = super().__getitem__(key)
except KeyError:
super().__setitem__(key, SubConfig(self))
self.updated()
return super().__getitem__(key)
def __delitem__(self, key):
res = super().__delitem__(key)
self.updated()
def update(self, *args, **kw):
for arg in args:
for k, v in arg.items():
self[k] = v
for k, v in kw.items():
self[k] = v
self.updated()
cfg = Config(Path("config.yaml"))
=> config.yaml file is updated as follows, where its comments are removed:
c:
b:
f: 5
a:
z: 4
b: 4
Yes, it is possible to prevent comments from being lost. The object returned from self.yaml.load() in your Config.__init__() method is not a dict, but a subclass thereof (ruamel.yaml.comments.CommentedMap) that includes all of the comment information (in its .ca attribute. And that CommentedMap will have values that are in itself again CommentedMap instances (at least with your input.
So what you need to do is change your classes:
class Config(ruamel.yaml.comments.CommentedMap):
and do the same for SubConfig. Then during the update routine you should try to copy the .ca attribute (it will be created empty on the CommentedMap, but not be available on {})
Make sure you all add a the representer for Config, and don't cast to dict in your Config.dump() method.
If you also copy the .fa attribute of the loaded data (also on Subconfig), you'll preserve the flow/block style of the original, and you can do a away with the self.yaml.default_flow_style = False.
The above is the theory, in practise there are a few more issues.
Your config.yaml changes, although you do not explicitly dump. That is
because your auto_dump is True by default. But that also means it
dumps for every change, i.e. your config.yaml gets dumped 10 (ten) times while the
Config/SubConfig data structure gets build.
I changed this to dump only once if auto_dump is True, but even that I would not
recommend, instead only dump if changed after loading.
A dict doesn't need initializing, but a CommentedMap does.
If you don't you get an attribute error at some point. So you'll have to
call super().__init__(self) in each the __init__ of each class.
from pathlib import Path
import ruamel.yaml
_SR = ruamel.yaml.representer.RoundTripRepresenter
class SubConfig(ruamel.yaml.comments.CommentedMap):
def __init__(self, parent):
self.parent = parent
super().__init__(self)
def updated(self):
self.parent.updated()
def __setitem__(self, key, value):
if isinstance(value, dict):
v = SubConfig(self)
v.update(value)
value = v
super().__setitem__(key, value)
self.updated()
def __getitem__(self, key):
try:
res = super().__getitem__(key)
except KeyError:
super().__setitem__(key, SubConfig(self))
self.updated()
return super().__getitem__(key)
return res
def __delitem__(self, key):
res = super().__delitem__(key)
self.updated()
def update(self, *args, **kw):
for arg in args:
for k, v in arg.items():
self[k] = v
for attr in [ruamel.yaml.comments.Comment.attrib, ruamel.yaml.comments.Format.attrib]:
if hasattr(arg, attr):
setattr(self, attr, getattr(arg, attr))
for k, v in kw.items():
self[k] = v
self.updated()
return
_SR.add_representer(SubConfig, _SR.represent_dict)
class Config(ruamel.yaml.comments.CommentedMap):
def __init__(self, filename, auto_dump=True):
super().__init__(self)
self.filename = filename if hasattr(filename, "open") else Path(filename)
self.auto_dump = False # postpone setting during loading of config
self.changed = False
self.yaml = ruamel.yaml.YAML()
self.yaml.indent(mapping=4, sequence=4, offset=2)
# self.yaml.default_flow_style = False
if self.filename.exists():
with open(filename) as f:
self.update(self.yaml.load(f) or {})
self.auto_dump = auto_dump
if auto_dump:
self.dump()
def updated(self):
if self.auto_dump:
self.dump(force=True)
else:
self.changed = True
def dump(self, force=False):
if not self.changed and not force:
return
# use the capability of dump to take a Path. It will open the file 'wb' as
# is appropriate for a YAML file, which is UTF-8
self.yaml.dump(self, self.filename)
self.changed = False
def __setitem__(self, key, value):
if isinstance(value, dict):
v = SubConfig(self)
v.update(value)
value = v
super().__setitem__(key, value)
self.updated()
def __getitem__(self, key):
try:
res = super().__getitem__(key)
except KeyError:
super().__setitem__(key, SubConfig(self))
self.updated()
return super().__getitem__(key)
def __delitem__(self, key):
res = super().__delitem__(key)
self.updated()
def update(self, *args, **kw):
for arg in args:
for k, v in arg.items():
self[k] = v
for attr in [ruamel.yaml.comments.Comment.attrib, ruamel.yaml.comments.Format.attrib]:
if hasattr(arg, attr):
setattr(self, attr, getattr(arg, attr))
for k, v in kw.items():
self[k] = v
self.updated()
_SR.add_representer(Config, _SR.represent_dict)
fn = Path('config.yaml')
fn.write_text("""
c: # my comment
b:
f: 5
x: {g: 6}
a:
z: 4
b: 4 # my comment
""")
cfg = Config(fn)
print(Path(fn).read_text())
which gives:
c: # my comment
b:
f: 5
x: {g: 6}
a:
z: 4
b: 4 # my comment
Since the input changes, I write the config file to test out on every run.
I also added a flow style dict, to make clear that original formatting is performed.
import random
from tkinter import *
class Spinner(object):
#staticmethod
def getSpin():
newSpin = random.randint(1,6)
return newSpin
class Player(object):
def __init__(self,name):
self.position = 1
self.name = name
def setName(self,name):
self.name = name
def changePosition(self,number):
self.position = self.position + number
def setPosition(self,pos):
self.position = pos
return self.position
def getPosition(self):
return self.position
def getName(self):
return self.name
def spin(self):
newSpin = Spinner.getSpin()
self.position = self.position + newSpin
print(str(self.name) + "'s spin was: " + str(newSpin))
class Path(object):
#staticmethod
def buildLadders():
ladders = [[0 for x in range(2)] for x in range(9)]
ladders[0][0] = 2
ladders[0][1] = 9
ladders[1][0] = 8
ladders[1][1] = 11
return ladders
#staticmethod
def buildChutes():
chutes = [[0 for x in range(2)] for x in range(10)]
chutes[0][0] = 9
chutes[0][1] = 3
chutes[1][0] = 12
chutes[1][1] = 6
return chutes
class Check(Player):
def __init__(self):
super(Check,self).__init__()
def checkLadders(self):
ladders = Path.buildLadders()
for i in range(0,len(ladders),1):
if self.getPosition() == ladders[i][0]:
self.position = self.setPosition(ladders[i][1])
print(str(self.name) + " Landed on a Ladder! from " + \
str(ladders[i][0]) +" to " + str(ladders[i][1]))
def newPosition(self):
return self.position
def checkChutes(self):
chutes = Path.buildChutes()
for i in range(0,len(chutes),1):
if self.getPosition() == chutes[i][0]:
self.position = self.setPosition(chutes[i][1])
print(str(self.name) + " Landed on a Chutes!")
class Match_Position(Player):
def __init__(self,name):
super(Match_Position,self).__init__(name)
self.match = [[70,235],
[180,235],
[290,235],
[400, 235],
[400, 140],
[290, 140],
[180, 140],
[70, 140],
[70, 45],
[180, 45],
[290, 45],
[400, 45]]
self.name = name
self.players = Player(self.name)
self.pos = self.players.getPosition()
self.position_actual = []
self.__str__()
self.actualpos()
def __str__(self):
for j in range(len(self.match)):
if self.pos == (j+1):
self.position_actual.append(self.match[j][0])
self.position_actual.append(self.match[j][1])
def actualpos(self):
return self.position_actual
class Display(object):
def __init__(self,master,img,name):
canvas_width = 650
canvas_height = 300
self.name = name
print(self.name)
self.pos = Match_Position(self.name).actualpos()
print(self.pos)
self.canvas = Canvas(master, width = canvas_width, height = canvas_height, bg = "yellow")
self.canvas.grid(padx=0, pady=0)
self.canvas.create_image(300,150,anchor=CENTER, image = img)
self.animate(master)
def animate(self,master):
Button(master, text= "ROLL", command=self.say_hello(self.name[0])).grid( row=3, column=0, sticky=E)
Button(master, text= "ROLL", command=self.say_hello1(self.name[1])).grid( row=3, column=1, sticky=E)
def say_hello(self,name):
self.name = name
self.name = Player(self.name)
self.name.spin()
Check.checkLadders(self.name)
Check.checkChutes(self.name)
x = self.pos[0]
y = self.pos[1]
self.canvas.create_oval(x,y,x+20,y+20, fill='blue')
def say_hello1(self,name):
self.name = name
self.name = Player(self.name)
self.name.spin()
Check.checkLadders(self.name)
Check.checkChutes(self.name)
x = self.pos[0]
y = self.pos[1]
self.canvas.create_oval(x,y,x+20,y+20, fill='red')
class BounceController(object):
def __init__(self):
master = Tk()
master.title("Snake and Ladder")
master.geometry("700x350")
img = PhotoImage( file = "puzzlor-chutes-and-ladders.gif" )
name = ['n','s']
Display(master,img,name).animate(master)
master.mainloop()
def main():
BounceController()
main()
It printed out this, but the error:
"'Player' object does not support indexing" pop out.
What is object does not support indexing error is?
And when I click the button, the oval does not actually move.
And using tkinter Button, I can call the method, right?
But by doing so, if I want to make for example, clicking the button, result to the oval moving to different location, how do I achieve that? Since as my code, above, the oval does not move eventhough the button is clicked.
Thanks. Since I'm quite new to python and programming, any help would be such a gratitude.
Indexing is when you suffix an object with [n], where n is an int. Here is how to reproduce the message.
>>> class C: pass
>>> C()[1]
Traceback (most recent call last):
File "<pyshell#21>", line 1, in <module>
C()[1]
TypeError: 'C' object does not support indexing
The File entry above the error message tells you the line number and expression that gave the error. Someplace in your code, as indicated by the traceback you did not show us, you have x[n], where x in an instance of the Player class you defined.
An object is subscriptable if and only if it has a __getitem__ method, which Player does not have and should not have.
I am to create a simple image viewer using wxPython. I am using a panel and a button upon which by clicking a picture would be shown from a directory (Images). Below is the code. I have taken pieces from different websites.
My question is if I maximize the window, the picture viewer as well as the button remain same. I want the picture viewer to increase as well.
Another thing is, after I press the "Load" button, it moves to the left. How do I fix these two problems?
I understand for those 2 above questions, this so big program might be unnecessary. I am a newbie to Python.
Thanks in advance.
import wx, os
class Panel1(wx.Panel):
def __init__(self, *args, **kwds):
wx.Panel.__init__(self, *args, **kwds)
self.jpgs = self.GetJpgList("./Images")
self.CurrentJpg = 0
self.MaxImageSize = 200
self.img = wx.EmptyImage(self.MaxImageSize,self.MaxImageSize)
self.imageCtrl = wx.StaticBitmap(self, wx.ID_ANY, wx.BitmapFromImage(self.img))
self.button_1 = wx.Button(self, wx.ID_ANY, "Load")
self.Bind(wx.EVT_BUTTON, self.OnLoad, self.button_1)
self.__do_layout()
self.Layout()
def GetJpgList(self, dir):
jpgs = [f for f in os.listdir(dir) if f[-4:] == ".JPG"]
return [os.path.join(dir, f) for f in jpgs]
def __do_layout(self):
sizer_1 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2 = wx.BoxSizer(wx.VERTICAL)
sizer_1.Add((1,1),1)
sizer_1.Add(self.imageCtrl, 0, wx.ALL|wx.EXPAND, 5)
sizer_1.Add((1,1),1)
sizer_1.Add(wx.StaticLine(self, wx.ID_ANY), 0, wx.ALL|wx.EXPAND, 5)
sizer_2.Add(self.button_1, 0, wx.ALL|wx.TOP, 5)
sizer_1.Add(sizer_2, 0, wx.ALL, 5)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
def OnNext(self, event):
path = self.jpgs[self.CurrentJpg]
self.Img = wx.Image(path, wx.BITMAP_TYPE_ANY)
W = self.Img.GetWidth()
H = self.Img.GetHeight()
if W > H:
NewW = self.MaxImageSize
NewH = self.MaxImageSize * H / W
else:
NewH = self.MaxImageSize
NewW = self.MaxImageSize * W / H
self.Img = self.Img.Scale(NewW,NewH)
self.imageCtrl.SetBitmap(wx.BitmapFromImage(self.Img))
#self.Fit()
#self.Layout()
self.Refresh()
self.CurrentJpg += 1
if self.CurrentJpg > len(self.jpgs) -1:
self.CurrentJpg = 0
# end of class Panel1
class Frame1(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.__set_properties()
self.__do_layout()
def __set_properties(self):
self.SetTitle("Picture")
def __do_layout(self):
panel1 = Panel1(self)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(panel1, 1, wx.EXPAND)
self.SetSizer(self.sizer)
self.sizer.Fit(self)
self.Layout()
self.Centre()
# end of class Frame1
class Game1(wx.App):
def OnInit(self):
wx.InitAllImageHandlers()
frame1 = Frame1(None, wx.ID_ANY, "")
self.SetTopWindow(frame1)
frame1.Show()
return 1
# end of class Game1
if __name__ == "__main__":
game1 = Game1(0)
game1.MainLoop()
To make the image resize itself on maximize, you will need to catch wx.EVT_MAXIMIZE. Then in your event handler, you'll need to call your function that updates the image control you created. You may need to use the Frame's size to help you determine what your MaxImageSize should be.
For the load button issue, you may need to call the sizer or the widget's parent's Layout() method.
my purpose is to display an different icon in treeview list when a specific postion(row, column) match a value. eg: (row, 2) is dir or file, will diplay different icon. because this is not used in local filesystem, QDir or Qfilesystem model not suite for this.
i know a bit on MVC that controller display in view, make model as data interface api. but i do not how to make it works on specific position(row ,col) as my expect.
i have tried to add args in ImageDelegate(like pass icon file name to it), but failed maybe due to its parent Class not accept more args.
hope someone can give me some light.
class ImageDelegate(QtGui.QStyledItemDelegate):
def __init__(self, parent=None):
QtGui.QStyledItemDelegate.__init__(self, parent)
#self.icon =icon
def paint(self, painter, option, index):
#painter.fillRect(option.rect, QtGui.QColor(191,222,185))
# path = "path\to\my\image.jpg"
path = "icon1.png"
image = QtGui.QImage(str(path))
pixmap = QtGui.QPixmap.fromImage(image)
pixmap.scaled(16, 16, QtCore.Qt.KeepAspectRatio)
painter.drawPixmap(option.rect.x(), option.rect.y(), pixmap)
and i can use this delegate in my view. but it will change all line in specific column.
def init_remotetreeview(self):
self.model = myModel(self.remote_Treeview)
for therow in range(self.model.rowCount(QModelIndex())) :
print self.model.data(self.model.index(therow, 2, QtCore.QModelIndex()),Qt.DisplayRole).toString() # i do check the value will used to load correct icon.
self.remote_Treeview.setItemDelegate(ImageDelegate(self)) # this change all lines
self.remote_Treeview.setModel(self.model)
In fact, your have some light in your code, isn't it ? (Just kidding.)
Your have right way to use QtGui.QStyledItemDelegate. I have reference how to implement it (But, C++ only). 'Star Delegate Example', 'QItemDelegate Class Reference C++' and 'QItemDelegate Class Reference PyQt4';
Keyword : Your have to implement paint draw your element what your want (I think this is your want.)
Little example, Hope is help;
import sys
from PyQt4 import QtCore, QtGui
from functools import partial
class QCustomDelegate (QtGui.QItemDelegate):
signalNewPath = QtCore.pyqtSignal(object)
def createEditor (self, parentQWidget, optionQStyleOptionViewItem, indexQModelIndex):
column = indexQModelIndex.column()
if column == 0:
editorQWidget = QtGui.QPushButton(parentQWidget)
editorQWidget.released.connect(partial(self.requestNewPath, indexQModelIndex))
return editorQWidget
else:
return QtGui.QItemDelegate.createEditor(self, parentQWidget, optionQStyleOptionViewItem, indexQModelIndex)
def setEditorData (self, editorQWidget, indexQModelIndex):
column = indexQModelIndex.column()
if column == 0:
textQString = indexQModelIndex.model().data(indexQModelIndex, QtCore.Qt.EditRole).toString()
editorQWidget.setText(textQString)
else:
QtGui.QItemDelegate.setEditorData(self, editorQWidget, indexQModelIndex)
def setModelData (self, editorQWidget, modelQAbstractItemModel, indexQModelIndex):
column = indexQModelIndex.column()
if column == 0:
textQString = editorQWidget.text()
modelQAbstractItemModel.setData(indexQModelIndex, textQString, QtCore.Qt.EditRole)
else:
QtGui.QItemDelegate.setModelData(self, editorQWidget, modelQAbstractItemModel, indexQModelIndex)
def updateEditorGeometry(self, editorQWidget, optionQStyleOptionViewItem, indexQModelIndex):
column = indexQModelIndex.column()
if column == 0:
editorQWidget.setGeometry(optionQStyleOptionViewItem.rect)
else:
QtGui.QItemDelegate.updateEditorGeometry(self, editorQWidget, optionQStyleOptionViewItem, indexQModelIndex)
def requestNewPath (self, indexQModelIndex):
self.signalNewPath.emit(indexQModelIndex)
def paint (self, painterQPainter, optionQStyleOptionViewItem, indexQModelIndex):
column = indexQModelIndex.column()
if column == 0:
textQString = indexQModelIndex.model().data(indexQModelIndex, QtCore.Qt.EditRole).toString()
painterQPainter.drawPixmap (
optionQStyleOptionViewItem.rect.x(),
optionQStyleOptionViewItem.rect.y(),
QtGui.QPixmap(textQString).scaled(180, 180, QtCore.Qt.KeepAspectRatio))
else:
QtGui.QItemDelegate.paint(self, painterQPainter, optionQStyleOptionViewItem, indexQModelIndex)
class QCustomTreeWidget (QtGui.QTreeWidget):
def __init__(self, parent = None):
super(QCustomTreeWidget, self).__init__(parent)
self.setColumnCount(1)
myQCustomDelegate = QCustomDelegate()
self.setItemDelegate(myQCustomDelegate)
myQCustomDelegate.signalNewPath.connect(self.getNewPath)
def addMenu (self, path, parentQTreeWidgetItem = None):
if parentQTreeWidgetItem == None:
parentQTreeWidgetItem = self.invisibleRootItem()
currentQTreeWidgetItem = QtGui.QTreeWidgetItem(parentQTreeWidgetItem)
currentQTreeWidgetItem.setData(0, QtCore.Qt.EditRole, path)
currentQTreeWidgetItem.setFlags(currentQTreeWidgetItem.flags() | QtCore.Qt.ItemIsEditable)
for i in range(self.columnCount()):
currentQSize = currentQTreeWidgetItem.sizeHint(i)
currentQTreeWidgetItem.setSizeHint(i, QtCore.QSize(currentQSize.width(), currentQSize.height() + 200))
def getNewPath (self, indexQModelIndex):
currentQTreeWidgetItem = self.itemFromIndex(indexQModelIndex)
pathQStringList = QtGui.QFileDialog.getOpenFileNames()
if pathQStringList.count() > 0:
textQString = pathQStringList.first()
currentQTreeWidgetItem.setData(indexQModelIndex.column(), QtCore.Qt.EditRole, textQString)
print textQString
class QCustomQWidget (QtGui.QWidget):
def __init__ (self, parent = None):
super(QCustomQWidget, self).__init__(parent)
self.myQCustomTreeWidget = QCustomTreeWidget(self)
self.allQHBoxLayout = QtGui.QHBoxLayout()
self.allQHBoxLayout.addWidget(self.myQCustomTreeWidget)
self.setLayout(self.allQHBoxLayout)
self.myQCustomTreeWidget.addMenu(r'''C:\Users\Kitsune Meyoko\Desktop\twitter01.jpg''')
self.myQCustomTreeWidget.addMenu(r'''C:\Users\Kitsune Meyoko\Desktop\twitter02.jpg''')
self.myQCustomTreeWidget.addMenu(r'''C:\Users\Kitsune Meyoko\Desktop\twitter04.jpg''')
self.myQCustomTreeWidget.addMenu(r'''C:\Users\Kitsune Meyoko\Desktop\twitter05.jpg''')
app = QtGui.QApplication([])
myQCustomQWidget = QCustomQWidget()
myQCustomQWidget.show()
sys.exit(app.exec_())
Note: In same way to implement QTreeView, but different is set values only.
If your want to show image by path in some index (In this case : 2nd). Your can find it by using QModelIndex QAbstractItemModel.index (self, int row, int column, QModelIndex parent = QModelIndex()), And do want your want.
Example;
import sys
from PyQt4 import QtCore, QtGui
class QCustomDelegate (QtGui.QItemDelegate):
def paint (self, painterQPainter, optionQStyleOptionViewItem, indexQModelIndex):
column = indexQModelIndex.column()
if column == 3:
currentQAbstractItemModel = indexQModelIndex.model()
iconQModelIndex = currentQAbstractItemModel.index(indexQModelIndex.row(), 1, indexQModelIndex.parent())
pathQString = currentQAbstractItemModel.data(iconQModelIndex, QtCore.Qt.EditRole).toString()
iconQPixmap = QtGui.QPixmap(pathQString)
if not iconQPixmap.isNull():
painterQPainter.drawPixmap (
optionQStyleOptionViewItem.rect.x(),
optionQStyleOptionViewItem.rect.y(),
iconQPixmap.scaled(20, 20, QtCore.Qt.KeepAspectRatio))
else:
QtGui.QItemDelegate.paint(self, painterQPainter, optionQStyleOptionViewItem, indexQModelIndex)
myQApplication = QtGui.QApplication([])
myQTreeView = QtGui.QTreeView()
headerQStandardItemModel = QtGui.QStandardItemModel()
headerQStandardItemModel.setHorizontalHeaderLabels([''] * 4)
myQTreeView.setModel(headerQStandardItemModel)
# Set delegate
myQCustomDelegate = QCustomDelegate()
myQTreeView.setItemDelegate(myQCustomDelegate)
# Append data row 1
row1QStandardItem = QtGui.QStandardItem('ROW 1')
row1QStandardItem.appendRow([QtGui.QStandardItem(''), QtGui.QStandardItem('1.jpg'), QtGui.QStandardItem(''), QtGui.QStandardItem('')])
headerQStandardItemModel.appendRow(row1QStandardItem)
# Append data row 2
row2QStandardItem = QtGui.QStandardItem('ROW 2')
row2QStandardItem.appendRow([QtGui.QStandardItem(''), QtGui.QStandardItem('2.png'), QtGui.QStandardItem(''), QtGui.QStandardItem('')])
headerQStandardItemModel.appendRow(row2QStandardItem)
myQTreeView.show()
sys.exit(myQApplication.exec_())
experimental result:
Note: I have image 1.jpg, 2.png.
Following is the system and software info
Platforms: Windows XP and OSX Lion
Activestate Python 2.7.2
wxPython2.9-osx-cocoa-py2.7 (for OSX)
wxPython2.9-win32-py27 (for Windows XP)
I am trying to create a UltimateListCtrl using ULC_VIRTUAL and ULC_REPORT mode. I would like to know how can I put a checkbox beside the first column of every row and catch the event when a user checks the box. I was able to do the same using UltimateListCtrl without VIRTUAL mode. But, with the ULC_VIRTUAL flag ON, I don't know how to proceed. Following is the code I created, but this still doesn't allow me to check the boxes associated with the first column. Please help.
import wx
import images
import random
import os, sys
from wx.lib.agw import ultimatelistctrl as ULC
class TestUltimateListCtrl(ULC.UltimateListCtrl):
def __init__(self, parent, log):
ULC.UltimateListCtrl.__init__(self, parent, -1, agwStyle=ULC.ULC_VIRTUAL|ULC.ULC_REPORT|ULC.ULC_SINGLE_SEL|ULC.ULC_VRULES|ULC.ULC_HRULES)
self.SetItemCount(1000)
self.table_fields=['First','Second','Third']
field_index=0
for field in self.table_fields:
info = ULC.UltimateListItem()
info._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT | ULC.ULC_MASK_CHECK
info._image = []
info._format = wx.LIST_FORMAT_CENTER
info._kind = 1
info._text = field
info._font= wx.Font(13, wx.ROMAN, wx.NORMAL, wx.BOLD)
self.InsertColumnInfo(field_index, info)
self.SetColumnWidth(field_index,175)
field_index += 1
def getColumnText(self, index, col):
item = self.GetItem(index, col)
return item.GetText()
def OnGetItemText(self, item, col):
return "Item %d, Column %d" % (item,col)
def OnGetItemColumnImage(self, item, col):
return []
def OnGetItemImage(self, item):
return []
def OnGetItemAttr(self, item):
return None
def OnGetItemTextColour(self, item, col):
return None
#def OnGetItemColumnCheck(self, item, col):
#return True
#def OnGetItemCheck(self, item):
#return True
def OnGetItemToolTip(self, item, col):
return None
def OnGetItemKind(self, item):
return 1
def OnGetItemColumnKind(self, item, col):
if col==0:
return self.OnGetItemKind(item)
return 0
class TestFrame(wx.Frame):
def __init__(self, parent, log):
wx.Frame.__init__(self, parent, -1, "UltimateListCtrl in wx.LC_VIRTUAL mode", size=(700, 600))
panel = wx.Panel(self, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
listCtrl = TestUltimateListCtrl(panel, log)
sizer.Add(listCtrl, 1, wx.EXPAND)
panel.SetSizer(sizer)
sizer.Layout()
self.CenterOnScreen()
self.Show()
if __name__ == '__main__':
import sys
app = wx.PySimpleApp()
frame = TestFrame(None, sys.stdout)
frame.Show(True)
app.MainLoop()
Btw, following is the code I used to create the same thing without the VIRTUAL mode. And in this case, I can check the boxes beside the first column data in every row. But, I will be working with tens of thousands of items and I cannot rely on loading the items like below because it is very slow. Hence, I want to use the Virtual List, but I don't know how to get the same functionality in it.
import wx
import images
import random
import os, sys
from wx.lib.agw import ultimatelistctrl as ULC
class TestUltimateListCtrl(ULC.UltimateListCtrl):
def __init__(self, parent, log):
ULC.UltimateListCtrl.__init__(self, parent, -1, agwStyle=ULC.ULC_REPORT|ULC.ULC_SINGLE_SEL|ULC.ULC_VRULES|ULC.ULC_HRULES)
self.table_fields=['First','Second','Third']
field_index=0
for field in self.table_fields:
info = ULC.UltimateListItem()
info._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT | ULC.ULC_MASK_CHECK
info._image = []
info._format = wx.LIST_FORMAT_CENTER
info._kind = 1
info._text = field
info._font= wx.Font(13, wx.ROMAN, wx.NORMAL, wx.BOLD)
self.InsertColumnInfo(field_index, info)
self.SetColumnWidth(field_index,175)
field_index += 1
for record_index in range(0,1000):
for field in self.table_fields:
if self.table_fields.index(field)==0:
self.InsertStringItem(record_index, 'Item %d, Column %d' % (record_index,self.table_fields.index(field)),it_kind=1)
else:
self.SetStringItem(record_index, self.table_fields.index(field), 'Item %d, Column %d' % (record_index,self.table_fields.index(field)))
class TestFrame(wx.Frame):
def __init__(self, parent, log):
wx.Frame.__init__(self, parent, -1, "UltimateListCtrl in wx.LC_VIRTUAL mode", size=(700, 600))
panel = wx.Panel(self, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
listCtrl = TestUltimateListCtrl(panel, log)
sizer.Add(listCtrl, 1, wx.EXPAND)
panel.SetSizer(sizer)
sizer.Layout()
self.CenterOnScreen()
self.Show()
if __name__ == '__main__':
import sys
app = wx.PySimpleApp()
frame = TestFrame(None, sys.stdout)
frame.Show(True)
app.MainLoop()
This question has been here for a while and I've been trying to find a solution to this problem around the web (to no avail). I've now solved mine and I'm posting here just in case someone might find the solution useful, or may have an more appropriate one.
Short answer: you will need to manually keep track of checked and unchecked items. To detect which are being checked (clicked), you can bind to the EVT_LIST_ITEM_CHECKING event.
Long answer: First, you will need a way to keep track of which items are checked or not. Then use that for determining what to return for OnGetItemColumnCheck. You could, for example, use a list of item+columns like so:
def __init__(...):
...
self.checked = []
...
def OnGetItemColumnCheck(self, item, column):
item_column = (item, column)
if item_column in self.checked:
return True
else:
return False
You will now need a way to populate that list. To do that, you will need to bind to the EVT_LIST_ITEM_CHECKING and do the appropriate actions:
def __init__(...):
...
self.checked = []
self.Bind(ULC.EVT_LIST_ITEM_CHECKING, self.OnCheck)
...
def OnCheck(self, event):
item_column = (event.m_itemIndex, event.m_item.GetColumn())
try:
idx = self.checked.index(item_column)
except ValueError:
idx = None
if idx == None:
self.checked.append(item_column)
else:
del(self.checked[idx])
self.Refresh()
The self.Refresh() call is essential as sometimes the checkbox won't get redrawn. After this, you should now be able to check and uncheck items (and that information is easily accessible to boot!). Here is your complete code with the above modifications:
import wx
import random
import os, sys
from wx.lib.agw import ultimatelistctrl as ULC
class TestUltimateListCtrl(ULC.UltimateListCtrl):
def __init__(self, parent, log):
ULC.UltimateListCtrl.__init__(self, parent, -1, agwStyle=ULC.ULC_VIRTUAL|ULC.ULC_REPORT|ULC.ULC_SINGLE_SEL|ULC.ULC_VRULES|ULC.ULC_HRULES)
self.SetItemCount(1000)
self.table_fields=['First','Second','Third']
field_index=0
for field in self.table_fields:
info = ULC.UltimateListItem()
info._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT | ULC.ULC_MASK_CHECK
info._image = []
info._format = wx.LIST_FORMAT_CENTER
info._kind = 1
info._text = field
info._font= wx.Font(13, wx.ROMAN, wx.NORMAL, wx.BOLD)
self.InsertColumnInfo(field_index, info)
self.SetColumnWidth(field_index,175)
field_index += 1
self.checked = []
self.Bind(ULC.EVT_LIST_ITEM_CHECKING, self.OnCheck)
def OnCheck(self, event):
item_column = (event.m_itemIndex, event.m_item.GetColumn())
try:
idx = self.checked.index(item_column)
except ValueError:
idx = None
if idx == None:
self.checked.append(item_column)
else:
del(self.checked[idx])
self.Refresh()
def getColumnText(self, index, col):
item = self.GetItem(index, col)
return item.GetText()
def OnGetItemText(self, item, col):
return "Item %d, Column %d" % (item,col)
def OnGetItemColumnImage(self, item, col):
return []
def OnGetItemImage(self, item):
return []
def OnGetItemAttr(self, item):
return None
def OnGetItemTextColour(self, item, col):
return None
def OnGetItemToolTip(self, item, col):
return None
def OnGetItemKind(self, item):
return 1
def OnGetItemColumnKind(self, item, col):
if col==0:
return self.OnGetItemKind(item)
return 0
def OnGetItemColumnCheck(self, item, column):
item_column = (item, column)
if item_column in self.checked:
return True
else:
return False
class TestFrame(wx.Frame):
def __init__(self, parent, log):
wx.Frame.__init__(self, parent, -1, "UltimateListCtrl in wx.LC_VIRTUAL mode", size=(700, 600))
panel = wx.Panel(self, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
listCtrl = TestUltimateListCtrl(panel, log)
sizer.Add(listCtrl, 1, wx.EXPAND)
panel.SetSizer(sizer)
sizer.Layout()
self.CenterOnScreen()
self.Show()
if __name__ == '__main__':
import sys
app = wx.PySimpleApp()
frame = TestFrame(None, sys.stdout)
frame.Show(True)
app.MainLoop()