如何正确更改DataViewModel

时间:2016-05-24 12:10:58

标签: python wxpython wxwidgets

wxPython最近给我带来了很多麻烦,所以我再一次在这里问你们:)

我的设置

给定代码是我实际应用程序的一个非常简化的版本。实际上,我有一个大模型,以不同的方式显示在不同的控件中。 因此,我有一个模型,它是代码示例中的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,这是一个非常大的痛苦(因为我必须对删除,编辑和移动项目做同样的事情)。 此外,我不知道新添加的对象的项ID,因为每个DataViewModel中的项ID都不同。因此,我使用pub向所有DataViewModel发送消息,然后DataViewModel搜索该新对象并分别调用ItemAdded。 由于这没有成功,我试图直接致电ItemAdded,这也不起作用。 您可以通过更改VARIANT变量的值来在两种实现之间切换。最终目标是让VARIANT 'viaMessage'起作用。

问题

以下是如何重现奇怪行为的描述:

  1. 启动应用程序。您应该只能看到折叠的根项目(旁边有' +')。无需触摸树状视图,只需单击"添加孩子"按钮几次。
  2. 现在展开根项目。您将看到其中有几个孩子(与您点击的频率一样多)。但是,在计划开始时,添加了两个孩子,现在缺少这是不可取的,我认为这是错误的行为。
    Screenshot of wrong behavior
    • 编辑:好的,事情变得更加怪异了:
      我编辑了代码并实现了一个额外的删除按钮。当我重复所有内容直到第2步,然后删除所有添加的孩子时,孩子1和2突然神奇地再次出现! (在添加2个孩子后离开,然后在删除两个添加的孩子后再扩展root ||并再次扩展root) Screenshot of wrong behavior with Del button Screenshot of weird behavior after deleting added children
  3. 无论如何,现在请重新启动应用程序(关闭窗口并再次运行脚本)。现在展开根项目并单击&#34;添加子项&#34;。 <哇>哇,突然间它起作用了。
    Screenshot of correct behavior
  4. 好的,让我们尝试另一个:重启应用程序。再次展开和折叠根项目。现在点击&#34;添加儿童&#34;几次。现在再次展开根项目。
  5. 再次,它似乎工作。所有孩子,开头添加的孩子以及按钮添加的孩子都在那里。

    因此,只有在您扩展父项目之前添加子项时,才会显示该错误。

    这是什么样的巫术?

    我的印象是,我想要实现的并不是什么特别的事情,我想知道错误在哪里以及我无法通过谷歌找到这个问题,所以我不得不假设错误在于我的身边,但我无法找到它。

    仅为了证明这个问题的标题:删除项目时遇到类似的问题。因此,更一般地说,问题是如何正确更改DataViewModel的内容(例如删除,添加和更改项目的值),而不仅仅是添加项目。

    我的尝试

    • 我试图谷歌"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 *')之前收到异常。

    如果这些问题也可以回答,那会很好,但对我来说,接受你的答案作为解决方案既不必要也不够;)

    感谢任何帮助。

1 个答案:

答案 0 :(得分:1)

您的PyDataViewModel代码比您的应用程序所需的代码更复杂。不是Update你的DVC模型,而是完全可以清除它(使模型本身弄清楚数据如何变化并根据它发送信息给DVC)。这项工作没有明显的延迟百件(我没有测试过几千件)。

请执行以下操作:

    # remove subscription, no longer needed
    # pub.subscribe(self.OnItemAdded, 'ITEM_ADDED')

    # remove OnItemAdded and Update
    #-------------------- CUSTOM FUNCTIONS -----------------------------

简化为:

def DeleteLastItemFromRoot(*ignoreEvent):
    global childNum
    if modelRoot.children != []:
        obj = modelRoot.children[-1] #select last item
        modelRoot.RemoveChild(obj)
        # no longer required, handled my model.Cleared() 
        # model.ItemDeleted(model.ObjectToItem(modelRoot), model.ObjectToItem(obj))
        # Forcing a synchronisation python model/PyDataViewModel/DVC
        model.Cleared()

def AddItemToRoot(*ignoreEvent):
    global childNum
    newObject = DvcTreeItem('child' + str(childNum))
    modelRoot.AddChild(newObject)
    childNum += 1
    # syncing
    model.Cleared()