【发布时间】:2016-05-24 12:10:58
【问题描述】:
wxPython最近让我很头疼,所以我再次在这里问你们:)
我的设置
- Windows 7
- 便携式 Python v 2.7.6.1 (http://portablepython.com/wiki/PortablePython2.7.6.1/)
- wxPython 3.0.2.0 (http://www.wxpython.org/)
给定的代码是我实际应用程序的简化版本。实际上,我有一个大模型,它以不同的方式显示在不同的控件中。
因此,我有一个模型,即代码示例中的modelRoot,我从中为不同的DataViewCtrls 构建不同的DataViewModels (MyDvcModel)。在代码示例中,我只有一个 DataViewModel 和一个 DataViewCtrl,因为它足以说明我的问题。
我试图贴近 https://github.com/svn2github/wxPython/blob/master/trunk/demo/DVC_DataViewModel.py 中的 DataViewModel 示例
代码
这是我的最小工作示例:
import wx
import wx.dataview
from wx.lib.pubsub import pub
#class for a single item
class DvcTreeItem(object):
def __init__(self, value='item'):
self.parent = None
self.children = []
self.value = value
def AddChild(self, dvcTreeItem):
self.children.append(dvcTreeItem)
dvcTreeItem.parent = self
def RemoveChild(self, dvcTreeItem):
self.children.remove(dvcTreeItem)
dvcTreeItem.parent = None
#class for the model
class MyDvcModel(wx.dataview.PyDataViewModel):
def __init__(self, root):
wx.dataview.PyDataViewModel.__init__(self)
self.root = root
pub.subscribe(self.OnItemAdded, 'ITEM_ADDED')
#-------------------- REQUIRED FUNCTIONS -----------------------------
def GetColumnCount(self):
return 1
def GetChildren(self, item, children):
if not item:
children.append(self.ObjectToItem(self.root))
return 1
else:
objct = self.ItemToObject(item)
for child in objct.children:
#print "GetChildren called. Items returned = " + str([child.value for child in objct.children])
children.append(self.ObjectToItem(child))
return len(objct.children)
def IsContainer(self, item):
if not item:
return True
else:
return (len(self.ItemToObject(item).children) != 0)
return False
def GetParent(self, item):
if not item:
return wx.dataview.NullDataViewItem
parentObj = self.ItemToObject(item).parent
if parentObj is None:
return wx.dataview.NullDataViewItem
else:
return self.ObjectToItem(parentObj)
def GetValue(self, item, col):
if not item:
return None
else:
return self.ItemToObject(item).value
#-------------------- CUSTOM FUNCTIONS -----------------------------
def OnItemAdded(self, obj):
self.Update(obj) #for some weird reason, the update function cannot be used directly as event handler for pub (?).
def Update(self, obj, currentItem=wx.dataview.DataViewItem()):
children = []
self.GetChildren(currentItem, children)
for child in children:
self.Update(obj, child) #recursively step through the tree to find the item that belongs to the added object
if self.ItemToObject(child) == obj:
self.ItemAdded(self.GetParent(child), child)
print "item " + obj.value + " was added!"
break
#class for the frame
class wxTreeAddMini(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DLIGHT))
self.myDVC = wx.dataview.DataViewCtrl(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0)
self.myButton = wx.Button(self, wx.ID_ANY, u"Add Child", wx.DefaultPosition, wx.DefaultSize, 0)
self.myDelButton = wx.Button(self, wx.ID_ANY, u"Del Child", wx.DefaultPosition, wx.DefaultSize, 0)
mySizer = wx.BoxSizer(wx.VERTICAL)
mySizer.Add(self.myDVC, 1, wx.ALL|wx.EXPAND, 5)
mySizer.Add(self.myButton, 0, wx.ALL, 5)
mySizer.Add(self.myDelButton, 0, wx.ALL, 5)
self.SetSizer(mySizer)
app = wx.App(False)
modelRoot = DvcTreeItem('root')
child1 = DvcTreeItem('child1 - the forgotten one')
child1.AddChild(DvcTreeItem('even complete subtrees'))
child1.AddChild(DvcTreeItem('disappear'))
modelRoot.AddChild(child1)
modelRoot.AddChild(DvcTreeItem('child2 - the forgotten brother'))
childNum = 3
model = MyDvcModel(modelRoot)
frame = wxTreeAddMini(None)
frame.myDVC.AssociateModel(model)
frame.myDVC.AppendTextColumn("stuff", 0, width=250, mode=wx.dataview.DATAVIEW_CELL_INERT)
frame.Show()
def DeleteLastItemFromRoot(*ignoreEvent):
global childNum
if modelRoot.children != []:
obj = modelRoot.children[-1] #select last item
modelRoot.RemoveChild(obj)
model.ItemDeleted(model.ObjectToItem(modelRoot), model.ObjectToItem(obj))
def AddItemToRoot(*ignoreEvent):
global childNum
newObject = DvcTreeItem('child' + str(childNum))
modelRoot.AddChild(newObject)
childNum += 1
VARIANT = 'callItemAdded'
if VARIANT == 'viaMessage':
wx.CallAfter(pub.sendMessage, 'ITEM_ADDED', obj=newObject)
elif VARIANT == 'callItemAdded':
model.ItemAdded(model.ObjectToItem(modelRoot), model.ObjectToItem(newObject))
frame.myButton.Bind(wx.EVT_BUTTON, AddItemToRoot)
frame.myDelButton.Bind(wx.EVT_BUTTON, DeleteLastItemFromRoot)
app.MainLoop()
我的目标
我的最终目标是仅更新低级模型(modelRoot 及其后代/子代)并由此更新所有 DataViewModel。不幸的是,我必须在每个模型上调用ItemAdded,这是一个相当大的痛苦(因为我必须为删除、编辑和移动项目做同样的事情)。
另外,我不知道新添加的对象的item ID,因为每个DataViewModel中的item ID都不一样。因此,我使用 pub 向所有 DataViewModel 发送消息,然后它们搜索该新对象并分别调用 ItemAdded。
由于这没有正常工作,我尝试直接调用ItemAdded,这也不起作用。
您可以通过更改VARIANT 变量的值在两种实现之间切换。最终目标是让 VARIANT 'viaMessage' 工作。
问题
这里是如何重现怪异行为的描述:
- 启动应用程序。您应该只看到折叠的根项目(旁边带有“+”)。无需触摸树视图,只需点击几次“添加子项”按钮即可。
- 现在展开根项目。您会看到其中有几个孩子(与您单击的频率一样多)。但是,在程序开始时,添加了两个 children,现在它们丢失了。 这是不希望的,我认为这是错误的行为。
- 无论如何,现在请重新启动应用程序(关闭窗口并再次运行脚本)。现在展开根项目并单击“添加子项”。 哇,突然就可以了。
- 好的,让我们尝试另一个:重新启动应用程序。再次展开并折叠根项目。现在点击“添加孩子”几次。现在再次展开根项。
再次,它似乎工作。所有的孩子,开头添加的和按钮添加的都在那里。
所以这个 bug 显然只有在你扩展父项之前添加子项时才会出现。
这是什么魔法?
我的印象是,我想要实现的目标并没有什么特别的,我想知道错误在哪里,我无法通过谷歌找到那个问题,所以我不得不假设错误在我这边,但是没找到。
只是为了证明这个问题的标题:我在删除项目时遇到了类似的问题。因此,问题更普遍地是关于如何正确更改 DataViewModel 的内容(例如删除、添加和更改项目的值),而不仅仅是添加项目。
我的尝试
- 我尝试用 Google 搜索
"wxwidgets dataviewmodel itemadded collapsed",但结果不是我想要的。 - 我有一个想法,到目前为止我还没有尝试过,因为它只是一种解决方法:在程序启动时,我可以一次以编程方式展开和折叠所有子树。不过,我想避免这种解决方法。
- 我尝试调试它,但没有发现任何可疑之处。
- 我检查了原始的 wxWidgets 代码,但没有完全掌握。
我的问题
- 怎么了?为什么它不能按预期工作?这是 wxPython 错误还是我的代码中的错误?
- 我该如何解决?
支线任务
- 我有比我实现目标更好的方法来实现我的目标吗?
- 您在我的代码中发现任何其他缺陷或缺点吗? (除了它是一个精简版,我尽量避免像
if __name__ == '__main__': main()和 MVC 设计(至少缺少 C)等样板。) - 为什么我不能直接使用
MyDvcModel.Update作为消息处理程序而我必须通过OnItemAdded()使用间接寻址?如果我使用MyDvcModel.Update,我会在应用程序实际启动之前遇到异常(TypeError: in method 'DataViewItem___cmp__', expected argument 2 of type 'wxDataViewItem *')。
如果这些问题也能得到解答就好了,但我接受你的回答作为解决方案既没有必要也不够;)
感谢任何帮助。
【问题讨论】: