Setting window Z-order to Z_top cannot be undone

When I create a second Panda window and I change its Z-order to WindowProperties.Z_top, it stays on top of all other windows, even after trying to restore its normal order using WindowProperties.Z_normal.
The funny thing is, when checking the current Z-order with WindowProperties.getZOrder() it claims that the window does have a normal order again, even though I clearly see that it hasn’t.

Try the following on Windows to see for yourself:

from panda3d.core import *
from direct.showbase.ShowBase import ShowBase, DirectObject



class App(ShowBase):

  def __init__(self):

    ShowBase.__init__(self)

    self.win.setCloseRequestEvent("close_window_event")
    self.accept("close_window_event", self.__onCloseWindowEvent)
    self.accept("n", self.__setZNormal)
    self.accept("t", self.__setZTop)
    self.accept("p", self.__printZOrder)

    winprops = WindowProperties.getDefault()
    winprops.setForeground(False)
    winprops.setSize(640, 480)
    self._win2 = self.openWindow(props=winprops, name="win2")

    self.run()


  def __onCloseWindowEvent(self, *args):

##    self.closeWindow(self._win2)
##    self.closeWindow(self.win)
    self.userExit()


  def __setZNormal(self):

    winprops = WindowProperties()
    winprops.setZOrder(WindowProperties.Z_normal)
    self._win2.requestProperties(winprops)
    print "2nd Window should have normal Z-order..."


  def __setZTop(self):

    winprops = WindowProperties()
    winprops.setZOrder(WindowProperties.Z_top)
    self._win2.requestProperties(winprops)
    print "2nd Window should have top Z-order..."


  def __printZOrder(self):

    winprops = self._win2.getProperties()

    if winprops.getZOrder() == WindowProperties.Z_normal:
      print "2nd Window has normal Z-order."
    elif winprops.getZOrder() == WindowProperties.Z_top:
      print "2nd Window has top Z-order."




App()

Press to set the 2nd window on top of all others, to try and set it back to its normal Z-order and

to print out its current Z-order.

Tested with Panda 1.10.0 (Panda3D-SDK-1.10.0pre-9868d87.exe) on Windows XP Home, Windows 7 Home and Windows 8.1.

What I can do is first set the order to Z_bottom and then to Z_normal, but it’s an extra step I’d like to avoid.

However, even if this did work properly, what I’m really after is a way to keep a secondary Panda window floating on top of the main Panda window only, not on top of all other windows (similar to wxPython’s wx.FRAME_FLOAT_ON_PARENT frame style). Would it be possible to implement something like this?

if your program is only targeted at Windows platform, you can try calling SetWindowPos function from Windows through Python ctypes.
search “msdn setwindowpos” in case you need more info about the function.

Hmmm, that’s actually the problem. For myself that might be a good solution, but the project I’m working on is something I would ultimately like to share with the community and therefore it is important that it works cross-platform. That is also the reason I am now trying to get rid of wxPython and develop my own 2D GUI in Panda itself. Not just because Panda integration in wxPython is handled differently on Windows than it is on Linux, but especially since some changes in the new wxPython 3 are messing up my efforts to make my GUI skinnable. Rather than spending time trying to figure out how to fix it all, I’d rather just do it all in Panda from now on.

Anyway thanks for trying to help, it’s appreciated :slight_smile: .

Do you see a message on the console saying “SetWindowPos failed.” when switching back?

I get that message after quitting the application, when the last Z-ordering before quitting is either Z_top or Z_bottom, but not when it is Z_normal.

Thanks for reporting. I checked in a fix for this issue.

Thank you so much! Just tested the latest dev build (Panda3D-SDK-1.10.0pre-8c259d4.exe) and it works great! :slight_smile:
(The “SetWindowPos failed.” message is still there, but that’s probably just Windows :wink: .)

It actually allows me to emulate (in a very hackish way) wxPython’s wx.FRAME_FLOAT_ON_PARENT frame style:

from panda3d.core import *
from direct.showbase.ShowBase import ShowBase



class App(ShowBase):

  def __init__(self):

    ShowBase.__init__(self)
    
    self._win_has_focus = True
    self._win2_has_focus = True
    self._raised_win = False
    self._raised_win2 = False
    self._win_focus_reset = False
    self._win2_focus_reset = False
    self._win2_z_reset = False

    winprops = WindowProperties.getDefault()
    winprops.setSize(640, 480)
    self._win2 = self.openWindow(props=winprops, name="win2")
    self._win2.setWindowEvent("window2-event")
    self._win_has_focus = False

    self.win.setCloseRequestEvent("close_window_event")
    self.accept("close_window_event", self.__onCloseWindowEvent)
    self.accept("window-event", self.__onMainWindowEvent)
    self.accept("window2-event", self.__onWindow2Event)

    self.run()


  def __onCloseWindowEvent(self, *args):

##    self.closeWindow(self._win2)
##    self.closeWindow(self.win)
    self.userExit()


  def __onMainWindowEvent(self, *args):

    # When the main window receives focus, the secondary window should remain on top
    # of it; this can be done by making it the topmost window, but doing so gives *it*
    # the focus, so this causes another window-event to be thrown for the main window
    # (since it loses the focus again), so this becomes quite complicated...
    # When the main window loses focus, the secondary window should should get a
    # normal Z-order again.

    winprops = self.win.getProperties()
    has_focus = winprops.getForeground()
    
    if self._win_has_focus != has_focus:
      
      if has_focus:
        if self._raised_win:
          self._raised_win = False
        elif self._win_focus_reset:
          self._win_focus_reset = False
        else:
          winprops = WindowProperties()
          winprops.setZOrder(WindowProperties.Z_top)
          self._win2.requestProperties(winprops)
          self._raised_win2 = True
      else:
        if self._raised_win2:
          winprops = WindowProperties()
          winprops.setForeground(True)
          self.win.requestProperties(winprops)
          self._win_focus_reset = True
        elif not self._win2_focus_reset:
          winprops = WindowProperties()
          winprops.setZOrder(WindowProperties.Z_normal)
          self._win2.requestProperties(winprops)
          self._win2_z_reset = True

      self._win_has_focus = has_focus


  def __onWindow2Event(self, *args):

    # When the secondary window receives focus, it should remain just above the
    # main window (again, easier said than done...)

    winprops = self._win2.getProperties()
    has_focus = winprops.getForeground()
    
    if self._win2_has_focus != has_focus:
      
      if has_focus:
        if self._raised_win2:
          self._raised_win2 = False
        elif self._win2_z_reset:
          self._win2_z_reset = False
        elif self._win2_focus_reset:
          self._win2_focus_reset = False
        else:
          winprops = WindowProperties()
          winprops.setZOrder(WindowProperties.Z_top)
          self.win.requestProperties(winprops)
          self._raised_win = True
          self.graphicsEngine.openWindows() # apply changes immediately
          winprops = WindowProperties()
          winprops.setZOrder(WindowProperties.Z_normal)
          self.win.requestProperties(winprops)
      else:
        if self._raised_win:
          winprops = WindowProperties()
          winprops.setForeground(True)
          self._win2.requestProperties(winprops)
          self._win2_focus_reset = True
          
      self._win2_has_focus = has_focus




App()

Sometimes it doesn’t work as expected, but I couldn’t find a better way to do it.

Hmm, depending on what you’re trying to do, you could also tell Panda to parent the child window to the parent window, using WindowProperties.setParentWindow. This will make sure that the child window is always on top of and inside your parent window (without decorations), which may or may not be what you want.

In case it’s just a small temporary dialog (like for saving or loading), then that would probably be good enough. But sometimes I want a second window that is almost as big as the main window, and I want the user to be able to work in both windows at the same time. If the second window became covered by other windows, the user might forget about it and think he closed it already.
It’s also convenient - when there are other (non-Panda) windows open - that when either Panda window is brought to the foreground, the other one also becomes visible automatically.

Would it perhaps be possible to extend the functionality of WindowProperties.setParentWindow to allow this, for example by adding an optional parameter to it, like so:

WindowProperties.setParentWindow(parent_window, embed=True)

such that if you pass False for embed (True would be the default to ensure backward compatibility), the child window would not be embedded but always remain directly on top of its parent window instead?
Or perhaps by adding Z_onparent as a new WindowProperties::ZOrder (making it stay on top of the previously created window, or have the same effect as Z_normal in the absence of a previous window)?

In short, it’s mostly about convenience and my program can work just fine without it, but it would still be nice to have such a feature :slight_smile: .

If it’s unlikely to happen, I would like to know if my code above actually works on Linux? The reason it’s so convoluted is that changing the Z-order of a window also gives it the focus. At least on Windows, but maybe not on other platforms?

Hmm, this does make sense, and it seems there are APIs to do this on at least Linux and Windows.

I’ll put it on my todo list.

As for whether your code works on Linux: I’m not sure. Certainly not for me, because I use a tiling window manager (so windows are never on top of each other). Note that on Linux, window managers are always free to reject window properties (or what X11 calls “window manager hints”), including even size and position. In theory we do support Z-ordering on all platforms, though.

Great, thanks in advance :slight_smile: !

Good to know, thank you for that information also.