Python access to window controls



  • I've been trying to access the TreeCtrl on the HeirarchyEditor window. I've used the children of the main pane, retrieved from the AUI manager but I can never get to the base controls only ever a Pane. I realise that Panes are usually children of Sizers rather than windows and so I tried to use the wx Inspection tool, but that refused to work complaining about a lack of TopLevelWindow, so i took the source of the tool and changed the top level parent and managed to open a tree view of all the windows in the app. However this still failed to reveal the underlying controls, even though I could see the heavily nested BoxSizers terminating in a pane. Using the find function of the inspection tool, if I click on the tree control on the Heirarchy window I still end up at Pane not the actual control.

    I'm assuming, from looking at the xml files in the ui folder of Poser that the tree control is created as a wx control but I just can't find it by walking the heirarchy of windows. Does anyone have any thoughts on how I could accomplish this?



  • @amethystpendant You are on the right way: Sizers contains what you are looking for. But indirect.

    for child in sizer.GetChildren():
        widget = child.getWindow()
        if isinstance(widget, wx.TreeCtrl):
           print "Found it!"
    

    Or something similar – I wrote it without testing and I'm not deep into wxPython. I just analysed Poser's windows some moth ago.



  • @adp That's what I ws trying but drawing a blank. This is the inspection tool view of the Heirarchy window

    0_1553197238858_a8eaff76-2073-4069-8c2b-55d778c63f84-image.png

    The end of the chain is a panel, with no further children whether widgets or sizers



  • @amethystpendant I found time to make a snippet.

    import wx
    seen_ctrls = set()
    def list_ctrl(ctrl):
        path = list()
        path.append(ctrl.__class__.__name__)
        while ctrl.Parent:
            ctrl = ctrl.Parent
            path.append(ctrl.__class__.__name__)
        path.reverse()
        print("/".join(path))
    
    def find_ctrl(ctrl):
        if isinstance(ctrl, wx.TextCtrl):
            return list_ctrl(ctrl)
    
        for child in ctrl.GetChildren():
            if hasattr(child, "GetChildren"):
                if child not in seen_ctrls:
                    find_ctrl(child)
                    seen_ctrls.add(child)
    
    if __name__ == "__main__":
        man = poser.WxAuiManager()
        root = man.GetManagedWindow()
    
        find_ctrl(root)
    
    

    Result is:

    Frame/Panel/AuiFloatingFrame/Panel/Panel/TextCtrl
    Frame/Panel/AuiFloatingFrame/Panel/Panel/Panel/Panel/TextCtrl
    Frame/Panel/AuiFloatingFrame/Panel/Panel/TextCtrl
    Frame/Panel/AuiFloatingFrame/Panel/Panel/TextCtrl
    Frame/Panel/AuiFloatingFrame/Panel/Panel/TextCtrl
    Frame/Panel/Panel/Panel/TextCtrl
    Frame/Panel/Panel/Panel/TextCtrl
    Frame/Panel/Panel/Panel/TextCtrl
    Frame/Panel/Panel/Panel/TextCtrl
    

    So it seams your tool does it wrong or you checked the wrong window/pane ;)



  • To learn where Posers windows are, use this short snippet:

    man = poser.WxAuiManager()
    root = man.GetManagedWindow()
    
    for pane in man.AllPanes:
        w = pane.window
        print("Window Name:",w.Name)
    

    If you have opened the hirarchy editor, you should get something like this:

    Window Name: HairRoomGrowthGroups
    Window Name: HairRoomGrowthControls
    Window Name: HairRoomStylingControls
    Window Name: HairRoomDynControls
    Window Name: ClothRoomSimulators
    Window Name: ClothRoomCloths
    Window Name: ClothRoomClothGroups
    Window Name: ClothRoomDynControls
    Window Name: ScenePalette
    Window Name: RecentRenderPalette
    Window Name: PhysicsControls
    Window Name: FittingPalette
    Window Name: WeightPaintingPalette
    Window Name: SmallAnim
    Window Name: MorphPuttyTool
    Window Name: HairTool
    Window Name: GroupTool
    Window Name: JointPalette
    Window Name: DependentParmsEditor
    Window Name: FlexLibraryPalette
    Window Name: PythonPalette
    Window Name: WalkDesignPalette
    Window Name: Path3DPalette
    Window Name: AnimationPalette
    Window Name: ParmNPropsPalette
    Window Name: HierarchyEditor    <---------------!!!
    Window Name: LightToolsPalette
    Window Name: TalkDesignPalette
    Window Name: CameraToolsPalette
    Window Name: MemoryDotsPalette
    Window Name: MainToolsPalette
    Window Name: LogPalette
    Window Name: Notifications
    Window Name: DisplayStylePalette
    Window Name: CategoryEditor
    Window Name: RayTracePreview
    Window Name: MaterialFrame
    Window Name: PhotoMatching
    Window Name: FacePreview
    Window Name: FaceTexturePreview
    Window Name: FaceParmNPropsFrame
    Window Name: FaceTexture
    Window Name: stcwindow
    Window Name: stcwindow
    


  • @adp Hi I tried that and if I extend it

    man=poser.WxAuiManager()
    root=man.GetManagedWindow()
    for pane in man.AllPanes:
    	w=pane.window
    	if w.Name == "HierarchyEditor":
    		print("Window Name:",w.Name)       
    		r=w
    print r.GetId()
    for p in r.GetChildren():
    	print p.Name,p.__class__
            print p.GetChildren()
    

    I get

    ('Window Name:', u'HierarchyEditor')
    -31922
    panel <class 'wx._windows.Panel'>
    wxWindowList: []
    

    So I am still not getting any controls on the hierarchy window



  • @amethystpendant
    How is that for the other classic Poser windows?
    Is that maybe non-Wx stuff wrapped in a WxWindow container, with Wx only haning the function to show i and re-arrange it?



  • You got a Window-ID. A negative one. Means, it is auto-generated.

    try: wx.FindWindowById(-31922)
    or: wx.FindWindowById(r.GetId())

    AUI-Manager is just a frame wrapped around a bunch of windows. And wxPython is just a wrapper around C-Code (swig-interface). The "real" controls aren't python objects if they aren't explicitly stored somewhere as such (self.myCtrl = wx.Ctrl(...)). And nobody knows for sure if Poser's interface isn't written in C insteed of Python.

    Means, wxPython carries only Window-IDs around. So you have to look for a somewhere referenced window representing a kind of wx.ListCtrl. Better use ctrl.GetClassName() (not just ctrl.Name) to find Controls. Most of them are shadowed because of overloading and you see just "window" or something as name (each wx.control is a subclass of wx.window).

    By the way: The whole UI system is based on events (messages), not windows :).
    type(poser.WxApp()) says: <class 'wx._core.EvtHandler'>

    Depending on what you want to do it may be easier to use events.



  • @f_verbaas Don't think so. It's a very generic wx.ListCtrl as I see it. But maybe the whole UI is written in C insteed of Python. If that is the case, he will not find something familar and has to deal with window-id's for reverse-engineering.



  • To find the window-handle for window " HierarchyEditor", one can use:

    window = wx.FindWindowByName("HierarchyEditor")
    

    Then try:

    window.Name
    > u'HierarchyEditor'
    window.ClassName
    > u'PUIWXHierarchyFrame'
    
    


  • @adp I have no problem finding the HierarchyEditor window, my problem is finding the TreeCtrl hosted on that window



  • @amethystpendant You found it allready.

    If I do:

    window = wx.FindWindowByName("HierarchyEditor")
    ctrl = window.Children[0]
    ctrl.Bind(wx.EVT_LEFT_DOWN, ev_handler)
    

    ...the hirarchy editor gets no mousepress anymore.
    But, because Poser's UI is obviously not written in Python, the eventhandler must be careful not to destroy Poser.
    With the following I got some results:

    def ev_handler(event):
        def p():
            print("Mouse pressed")
        wx.CallLater(100, p)
    

    "Mouse pressed" appears now if I click into Hirarchy window. And nothing else happens, because the original handler isn't called anymore.
    Means to me that this is the right window (it's another story where the content of this list is stored).

    Use events if you want to manipulate this window. Anything in wx (or wxPython) is done with or based on events. Resizing windows, acting on controls, adding/removing content to controls, anything.



  • @adp Yes that is the pane, so that holds the checkboxes, buttons and the TreeCtrl but it isn't the TreeCtrl, looks like I can't grammatically do anything with the tree control. Putting this to bed :(



  • As far as I understand, Poser moved from Tkinter to Wx at some point in time. In any case a lot of tk-stuff is still in the delivery.
    I ran into this thread: https://stackoverflow.com/questions/25622764/import-a-tkinter-widget-in-wxpython-application-as-a-panel. I can imagine that rather than re-constructing each and every pane of the interface they followed a route as indicated in the thread, with the extant Tkinter code making the content of the panel and feed that as an image to the WxPanel, or constructed another way to show tk stuff in a wx environment, and re-construct the panel in Wx could then be done as and when necessary.
    So, question is: how difficult is it to make a replacement for the hierarchy editor in Wx



  • @fverbaas Not impossible. But such a lot of work for something simple like a Tree-Control?

    I bed one can manage the whole control with events. There is no need to have a handle to the attached window.

    One has to understand that wx (not wxPython) is a completly event-driven framework. wxPython is just a wrapper around it. A pythonic like structure is only available if the UI is written in Python.

    Here is what wxPython's documentation says:
    (https://wxpython.org/Phoenix/docs/html/wx.EvtHandler.html)

    wx.EvtHandler
    A class that can handle events from the windowing system.

    wx.Window is (and therefore all window classes are) derived from this class.

    When events are received, wx.EvtHandler invokes the method listed in the event table using itself as the object. When using multiple inheritance it is imperative that the wx.EvtHandler (-derived) class is the first class inherited such that the this pointer for the overall object will be identical to the this pointer of the wx.EvtHandler portion.

    And windows can be asked for their EventHandler via: window.GetEventHandler()
    With a pointer to this handler one may send any event – even events for Tree Controls. To receive events, the handler must be redirected.

    On the other hand: It may be less complicated and probably faster to write a new and better Editor directly with wxPython :)



  • @adp said in Python access to window controls:

    On the other hand: It may be less complicated and probably faster to write a new and better Editor directly with wxPython :)

    Well possible, but of course the interface part is just a small aspect of the whole task. That editor must also manipulate data in the Poser structure, and geting that right may well be the most difficult bit of all.

    From that aspect I can imagine a developer chooses to use a more laborious but standardized and verifyable way to move an existing app to a new platform. Staff doing this only needs to know the laborious trick, maybe that can even be automated. It can be done withot knowledge of the actual workings of the program.



  • @f_verbaas The „business-logic" hasn't changed. Just the Toolkit and how to handle it. So there is not that mutch work to do while moving to a new GUI.

    Beside of that, the staffmust support TkInter and wxPython in the future. A very bad decision.

    Your mentioned method to reuse TKInter has a lot of problems to solve. Example: Double-click an entry in the Hierarchy-Editor. A Textedit field will open exact on the right position and you can change the entry. How will you do that if there is only an image in the background? In a wx.ListCtrl it is only a few lines of code. Not to mention all the trouble one is confronted with while trying to use two independent event-systems in one app. It slows the app down and makes it instable. And one has to support two GUI-Frameworks if something must be changed.

    Maybe it's ok to try something like that in a private piece of software. But not in a commercial App.

    I'm not the big wxPython Guru, but I think I would be able to write a new Hierarchy-Editor with just Poser-Python in a reasonable timeframe. No internal knowledge required.
    But soon SM will give us a new Poser anyway :)



  • @adp said in Python access to window controls:

    It slows the app down and makes it instable.

    Sounds like Poser all right. LOL!

    I'm not the big wxPython Guru, but I think I would be able to write a new Hierarchy-Editor with just Poser-Python in a reasonable timeframe. No internal knowledge required.

    Never say 'easy' when it comes to software, but I tend to agree with you there.
    @h-elwood-gilliland: are you reading?

    But soon SM will give us a new Poser anyway :)
    We will probably have to pay for it, but yes who knows.



  • @adp said in Python access to window controls:

    Your mentioned method to reuse TKInter has a lot of problems to solve. Example: Double-click an entry in the Hierarchy-Editor. A Textedit field will open exact on the right position and you can change the entry. How will you do that if there is only an image in the background? In a wx.ListCtrl it is only a few lines of code. Not to mention all the trouble one is confronted with while trying to use two independent event-systems in one app. It slows the app down and makes it instable. And one has to support two GUI-Frameworks if something must be changed.

    That is exactly what happens, if you click to edit a name, a textCtrl (ID 3000) is exposed as soon as you finish editing, the control is removed.



  • @fverbaas said in Python access to window controls:

    Never say 'easy' when it comes to software, but I tend to agree with you there.
    @h-elwood-gilliland: are you reading?

    But soon SM will give us a new Poser anyway :)
    We will probably have to pay for it, but yes who knows.

    Yes, when you @ me.
    I wouldn't say "soon" but "as soon as possible" .. and though SM will be an important part of it, it's probably going to largely be a personal effort from yours truly. =D You're going to get a Poser 12 WITH AMAZING NEW FEATURES THAT ARE TOTALLY WORTH BUYING, I PROMISE!!!!! :D =D

    Unfortunately, thus far, we've not really built out any new Python features. I think it's probably a good idea to include some, however, so that new features can be automated. Can't promise anything yet, but it seems important.

    You guys might want to try AutoIT or RoboTask if you are clicking things