wx.SearchCtrl on Empty MenuBar Space

I have a menubar on my frame with only a few menus. The frame has a minimum size due to child panel requirements, so there is extra space left over on the menubar on its right. I’d like to put a wx.SearchCtrl there in the otherwise wasted space, but I’m not sure how to go about this or if it’s even possible.

I was thinking one option would be to create two panels at the top of the window and add a menubar to the left one, but it doesn’t look like I can add a menubar to a panel from some googling.

Another option would be to create a custom menubar from scratch using static text and popup menus.

Maybe a child frame with wx.FRAME_FLOAT_ON_PARENT could hold the menubar… but how to keep it’s position perfectly aligned? Lag / flicker issues with this?

Any ideas on the best way to do this?

https://docs.wxpython.org/wx.SearchCtrl.html

For example:

from functools import partial
import wx

class TitleBar(wx.TopLevelWindow):
    def __init__(self, parent, **kwargs):
        super().__init__(parent,
            style=wx.FRAME_FLOAT_ON_PARENT|wx.NO_BORDER, **kwargs)
        
        self.btn = wx.TextCtrl(self)
        self.Fit()
        
        def resize(evt):
            w, h = self.Size
            W, H = self.Parent.Size
            self.Position = self.Parent.ScreenPosition + (W-10-w, 50-h)
            evt.Skip()
        self.Parent.Bind(wx.EVT_SIZE, resize)
        self.Parent.Bind(wx.EVT_MOVE, resize)

class TestFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.MenuBar = wx.MenuBar()
        
        menu = wx.Menu()
        self.Bind(wx.EVT_MENU,
                  lambda v: self.Close(),
                  menu.Append(wx.ID_EXIT, 'Exit\tCtrl-w'))
        self.MenuBar.Append(menu, 'File')
        
        self.tb = TitleBar(self)
        self.tb.Show()

if __name__ == "__main__":
    app = wx.App()
    frm = TestFrame(None)
    frm.Show()
    app.MainLoop()

image

Another example similar to this is How to place menu bar on frame's title bar and keep "minimize, maximize and close" buttons & and dragging? - #14 by komoto48g
image

One more option, if the control is small, you can embed it in the status bar.
See wxdemo > core Windows/Controls > StatusBar:
image

1 Like

Hey, that’s pretty slick!
I gave it a whirl, and it works pretty well; I bet I can incorporate this into my IDE project.

Thanks much! I was going to get started on this in the next week or so, and you’ve really sped up the process. :slightly_smiling_face:

@komoto48g On Linux you may have to add style wx.STAY_ON_TOP and give the item a size.

I did have to give it a size, but works fine. :+1:

Really? :roll_eyes:
On Windows STAY_ON_TOP causes a window to stay on top of all other windows.
I don’t think there is any difference between Windows and Linux about this.

class TitleBar(wx.TopLevelWindow):
    def __init__(self, parent, **kwargs):
        super().__init__(parent,
            style=wx.FRAME_FLOAT_ON_PARENT|wx.NO_BORDER|wx.STAY_ON_TOP, **kwargs)

image

You can roll your eyes as much as you want to but running the code suggests eye rolling is inappropriate.

The posted code with a wx.SearchCtrl rather than TextCtrl
(‘4.2.1 gtk3 (phoenix) wxWidgets 3.2.2.1’)

import wx

class TitleBar(wx.TopLevelWindow):
    def __init__(self, parent, **kwargs):
        super().__init__(parent,
            style=wx.FRAME_FLOAT_ON_PARENT|wx.NO_BORDER, **kwargs)

        self.btn = wx.SearchCtrl(self, -1, "Search...")
        self.Fit()

        def resize(evt):
            w, h = self.Size
            W, H = self.Parent.Size
            self.Position = self.Parent.ScreenPosition + (W-10-w, 50-h)
            evt.Skip()
        self.Parent.Bind(wx.EVT_SIZE, resize)
        self.Parent.Bind(wx.EVT_MOVE, resize)

class TestFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.MenuBar = wx.MenuBar()

        menu = wx.Menu()
        self.Bind(wx.EVT_MENU,
                  lambda v: self.Close(),
                  menu.Append(wx.ID_EXIT, 'Exit\tCtrl-w'))
        self.MenuBar.Append(menu, 'File')

        self.tb = TitleBar(self)
        self.tb.Show()

if __name__ == "__main__":
    app = wx.App()
    frm = TestFrame(None)
    frm.Show()
    app.MainLoop()

Result:

Screenshot at 2023-10-08 18-52-20

The searchctrl is stuck in the bottom panel as a window that needs to be clicked on to be seen.

The posted code with wx.STAY_ON_TOP and a size

import wx

class TitleBar(wx.TopLevelWindow):
    def __init__(self, parent, **kwargs):
        super().__init__(parent,
            style=wx.FRAME_FLOAT_ON_PARENT|wx.STAY_ON_TOP|wx.NO_BORDER, **kwargs)

        self.btn = wx.SearchCtrl(self, -1, "Search...", size=(250,25))
        self.Fit()

        def resize(evt):
            w, h = self.Size
            W, H = self.Parent.Size
            self.Position = self.Parent.ScreenPosition + (W-10-w, 50-h)
            evt.Skip()
        self.Parent.Bind(wx.EVT_SIZE, resize)
        self.Parent.Bind(wx.EVT_MOVE, resize)

class TestFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.MenuBar = wx.MenuBar()

        menu = wx.Menu()
        self.Bind(wx.EVT_MENU,
                  lambda v: self.Close(),
                  menu.Append(wx.ID_EXIT, 'Exit\tCtrl-w'))
        self.MenuBar.Append(menu, 'File')

        self.tb = TitleBar(self)
        self.tb.Show()

if __name__ == "__main__":
    app = wx.App()
    frm = TestFrame(None)
    frm.Show()
    app.MainLoop()

Result:
Screenshot at 2023-10-08 19-02-39

and what if the TitleBar is shown first :thinking: like

        self.Parent.Bind(wx.EVT_SIZE, resize)
        self.Parent.Bind(wx.EVT_MOVE, resize)
        self.Show()

class TestFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.tb = TitleBar(self)

        self.MenuBar = wx.MenuBar()

Sadly, it makes no difference.

Clearly there’s some issue with wx.FRAME_FLOAT_ON_PARENT

Your hunch about the order things are shown in however, was in the right direction.
Showing the parent frame before attempting to float something on top of it, seems obvious now.

The following works as it should, without a wx.STAY_ON_TOP

How or why or it works in other environments is a mystery. :slight_smile:

import wx

class TitleBar(wx.TopLevelWindow):
    def __init__(self, parent, **kwargs):
        super().__init__(parent, **kwargs)

        self.btn = wx.SearchCtrl(self, -1, "Search...", size=(250,25))
        self.Fit()

        def resize(evt):
            w, h = self.Size
            W, H = self.Parent.Size
            self.Position = self.Parent.ScreenPosition + (W-10-w, 50-h)
            evt.Skip()
        self.Parent.Bind(wx.EVT_SIZE, resize)
        self.Parent.Bind(wx.EVT_MOVE, resize)
        self.Show()

class TestFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.MenuBar = wx.MenuBar()

        menu = wx.Menu()
        self.Bind(wx.EVT_MENU,
                  lambda v: self.Close(),
                  menu.Append(wx.ID_EXIT, 'Exit\tCtrl-w'))
        self.MenuBar.Append(menu, 'File')
        self.Show()

        self.tb = TitleBar(self, style=wx.FRAME_FLOAT_ON_PARENT|wx.NO_BORDER)

if __name__ == "__main__":
    app = wx.App()
    frm = TestFrame(None)
    app.MainLoop()

Thank you @rolfofsaxony and @da-dada, for some points.

I am not sure, but there also seems to be a difference regarding the initial sizing of controls.
I read Window Sizing Overview — wxPython Phoenix 4.2.1 documentation :eyes: again but still um :roll_eyes:

Anyway, It is certain that my code is tricky, and should not be recommended from a view of cross-platform. @avose You may have to consider a modern design such as a popup search control like Chrome.

windows likes a SizeEvent (otherwise the control is misplaced) :cowboy_hat_face:

        self.MenuBar.Append(menu, 'File')

        self.Show()



        self.tb = TitleBar(self, style=wx.FRAME_FLOAT_ON_PARENT|wx.NO_BORDER)
        self.SendSizeEvent()



if __name__ == "__main__":