Drawing in toolbar?

Help needed drawing in toolbar

Hi, i am trying to draw inside the toolbar dc but nothing happens :frowning: i moved drawing code at the begining, at the end but in both cases nothing happens, i must have missed something, i need help !

I even tried to draw inside my side_panel, but even there, impossible :frowning:

I am new to wxPython (and python), if someone could help me it would be much appreciated!
here is my noob code :

I am trying to achieve this :

import wx

class MyFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title="My App", size=(800, 600))

        # Create the toolbar
        toolbar = self.CreateToolBar(wx.TB_HORIZONTAL | wx.NO_BORDER)

        # Create the side menu panel on the left
        side_panel = wx.Panel(self)
        side_panel.SetBackgroundColour(toolbar.GetBackgroundColour())  # Set background color

        # Create the preview pane on the right
        preview_panel = wx.Panel(self)
        preview_panel.SetBackgroundColour(wx.Colour(200, 200, 200))  # Set background color

        # Create the main sizer for the frame
        main_sizer = wx.BoxSizer(wx.HORIZONTAL)

        # Add the side menu and preview panels to the main sizer
        main_sizer.Add(side_panel, 1, wx.EXPAND)
        main_sizer.Add(preview_panel, 2, wx.EXPAND)

        new_icon = wx.ArtProvider.GetBitmap(wx.ART_NEW, wx.ART_TOOLBAR)
        open_icon = wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR)

        toolbar.CreateSeparator()

        toolbar.AddTool(wx.ID_NEW, "New", new_icon, "Create new file")
        toolbar.AddTool(wx.ID_OPEN, "Open", open_icon, "Open existing file")

        toolbar.AddControl(wx.StaticText(toolbar, label="Search: "))
        search_field = wx.TextCtrl(toolbar)
        toolbar.AddControl(search_field)

        toolbar.Realize()

        # Draw a custom separator line in the toolbar with corrected coordinates
        dc = wx.ClientDC(toolbar)
        dc.SetPen(wx.Pen(wx.Colour(255, 0, 0, 128), 5, wx.SOLID))  # Red color separator with thickness 5

        # Draw a custom separator line aligned with the window control buttons and left side panel border
        side_panel_width = side_panel.GetSize()[0]  # Obtain the width of the side panel
        toolbar_button_height = toolbar.GetToolBitmapSize().GetHeight()  # Get the height of the toolbar buttons

        # Calculate the separator line position to align with the extended border of the side panel
        separator_x = side_panel_width
        separator_y_start = toolbar_button_height  # Start the separator line from the top of the toolbar buttons
        separator_y_end = toolbar.GetSize()[1]  # Draw the line up to the bottom of the toolbar

        # Draw the separator line with corrected coordinates
        dc.DrawLine((separator_x, separator_y_start), (separator_x, separator_y_end))

        # Add the toolbar to the frame
        self.SetToolBar(toolbar)
        self.SetSizer(main_sizer)

        
app = wx.App()
frame = MyFrame()
frame.Show()


app.MainLoop()

That’s not how GUIs are working.
You need to do the painting inside a PaintEvent handler, i.e. when the operating system requests you to re-draw your window.
See e.g. wx.PaintEvent — wxPython Phoenix 4.2.2 documentation
I have never tried this with a toolbar. You need to experiment whether the toolbar receives this event or the frame.

I’m not 100% sure it’s a good idea to try to override the wx.EVT_PAINT event for a native control like a toolbar… assuming it’s even possible.

I believe a better idea would be to create a panel inside the toolbar and add controls/static lines/whatever to that panel. Or, if you need super customised look, to draw stuff yourself in that panel - although it can be hard to do in some cases, especially if you need custom controls inside that panel…

I tried drawing inside Frame.OnPaint() but line is still not visible :frowning: Any clue ?

import wx

class MyFrame(wx.Frame):
def __init__(self):
    super().__init__(None, title="My App", size=(800, 600))

    # Create the toolbar
    toolbar = self.CreateToolBar(wx.TB_HORIZONTAL | wx.NO_BORDER)

    # Create the side menu panel on the left as a class attribute
    self.side_panel = wx.Panel(self)
    self.side_panel.SetMinSize((300,-1))
    self.side_panel.SetMaxSize((300,-1))
    self.side_panel.SetSize((300,-1))

    self.side_panel.SetBackgroundColour(toolbar.GetBackgroundColour())  # Set background color

    # Create the preview pane on the right
    preview_panel = wx.Panel(self)
    preview_panel.SetBackgroundColour(wx.Colour(200, 200, 200))  # Set background color

    # Create the main sizer for the frame
    main_sizer = wx.BoxSizer(wx.HORIZONTAL)

    # Add the side menu and preview panels to the main sizer
    main_sizer.Add(self.side_panel, 1, wx.EXPAND)
    main_sizer.Add(preview_panel, 2, wx.EXPAND)

    new_icon = wx.ArtProvider.GetBitmap(wx.ART_NEW, wx.ART_TOOLBAR)
    open_icon = wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR)

    #toolbar.CreateSeparator()

    toolbar.AddTool(wx.ID_NEW, "New", new_icon, "Create new file")
    toolbar.AddTool(wx.ID_OPEN, "Open", open_icon, "Open existing file")

    toolbar.AddControl(wx.StaticText(toolbar, label="Search: "))
    search_field = wx.TextCtrl(toolbar)
    toolbar.AddControl(search_field)

    toolbar.Realize()

    self.SetToolBar(toolbar)
    self.SetSizer(main_sizer)

def DrawLineSeparator(self, dc):
            
    dc.SetPen(wx.Pen(wx.Colour(255, 255, 255, 255), 5, wx.SOLID))  # Red color separator with thickness 5
    
    toolbar_height = self.GetToolBar().GetClientSize()[1]
    side_panel_width = self.side_panel.GetSize()[0]
    
    separator_x = side_panel_width
    separator_y_start = 2
    separator_y_end = toolbar_height-2
    
    dc.DrawLine(int(separator_x), int(separator_y_start), int(separator_x), int(separator_y_end))

def OnPaint(self, event):

    dc = wx.PaintDC(self)
    self.DrawLineSeparator(self, dc)


app = wx.App()
frame = MyFrame()
frame.Bind(wx.EVT_PAINT, frame.OnPaint) # Bind the PaintEvent handler to the frame
frame.Show(True)

app.MainLoop()

I tried several different ways to draw on a toolbar, but none of them worked.

Do you really need a toolbar?

Below is a quick attempt to use a wxPanel as a substitute for a toolbar. When you resize the frame, the separator adjusts its position automatically. If you reduce the width sufficiently, it goes behind the controls.

import wx

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((800, 600))
        self.SetTitle("My App")

        self.main_panel = wx.Panel(self, wx.ID_ANY)
        main_sizer = wx.BoxSizer(wx.VERTICAL)

        self.tool_panel = wx.Panel(self.main_panel, wx.ID_ANY)
        main_sizer.Add(self.tool_panel, 0, wx.EXPAND, 0)

        tool_sizer = wx.BoxSizer(wx.HORIZONTAL)

        new_icon = wx.ArtProvider.GetBitmap(wx.ART_NEW, wx.ART_TOOLBAR, (24, 24))
        self.new_bitmap_button = wx.BitmapButton(self.tool_panel, wx.ID_NEW, new_icon)
        self.new_bitmap_button.SetSize(self.new_bitmap_button.GetBestSize())
        tool_sizer.Add(self.new_bitmap_button, 0, wx.RIGHT, 10)

        open_icon = wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, (24, 24))
        self.file_open_bitmap_button = wx.BitmapButton(self.tool_panel, wx.ID_OPEN, open_icon)
        self.file_open_bitmap_button.SetSize(self.file_open_bitmap_button.GetBestSize())
        tool_sizer.Add(self.file_open_bitmap_button, 0, wx.RIGHT, 10)

        search_label = wx.StaticText(self.tool_panel, wx.ID_ANY, "Search: ")
        tool_sizer.Add(search_label, 0, wx.ALIGN_CENTER_VERTICAL, 0)

        self.search_text_ctrl = wx.TextCtrl(self.tool_panel, wx.ID_ANY, "")
        tool_sizer.Add(self.search_text_ctrl, 0, wx.ALIGN_CENTER_VERTICAL, 0)

        bottom_sizer = wx.BoxSizer(wx.HORIZONTAL)
        main_sizer.Add(bottom_sizer, 1, wx.EXPAND, 0)

        self.side_panel = wx.Panel(self.main_panel, wx.ID_ANY)
        bottom_sizer.Add(self.side_panel, 1, wx.EXPAND, 0)

        self.preview_panel = wx.Panel(self.main_panel, wx.ID_ANY)
        self.preview_panel.SetBackgroundColour(wx.Colour(200, 200, 200))
        bottom_sizer.Add(self.preview_panel, 2, wx.EXPAND, 0)

        self.tool_panel.SetSizer(tool_sizer)
        self.main_panel.SetSizer(main_sizer)
        self.Layout()

        self.tool_panel.Bind(wx.EVT_PAINT, self.OnPaint)


    def OnPaint(self, _event):
        dc = wx.PaintDC(self.tool_panel)
        dc.SetPen(wx.Pen(wx.Colour(255, 0, 0, 128), 5, wx.SOLID))
        side_panel_width = self.side_panel.GetSize()[0]
        toolbar_height = self.tool_panel.GetSize()[1]
        separator_x = side_panel_width
        separator_y_start = 3
        separator_y_end = toolbar_height - 3
        dc.DrawLine((separator_x, separator_y_start), (separator_x, separator_y_end))


if __name__ == "__main__":
    app = wx.App()
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()

Tested on wxPython 4.2.2 gtk3 (phoenix) wxWidgets 3.2.6 + Python 3.12.3 + Linux Mint 22

1 Like

I will have a look to your solution tomorrow
I guess i can reproduce toolbar with a panel
Thx

On my journey with wxPython and Python, i finally managed to put a vertical bar in the toolbar using a custom wxControl tool, just to discover that the vertical bar i was looking for is part of the system as the default toolbar separator and that for any unknown reason, i can only see it when the toolbar displayed as a statusbar at the bottom of the window, and never when on top (i am wondering if window caption prevents it from appearing).

So problem solved i would say. I will continue to investigate.
Now my new problem is that for unknown reasons, my toolbar tools buttons are all right aligned in the toolbar, and i have no idea why or how to left aligne them.

It depends on the OS how a separator is displayed.
On Windows I see just a gap.
With style TB_FLAT I see a bar:

I have been investigating how toolbar separators are displayed on Linux. I have discovered that is all depends on which theme you are using.

My tests were done using wxPython 4.2.2 gtk3 (phoenix) wxWidgets 3.2.6 + Python 3.12.3 + Linux Mint 22.

I am running the MATE desktop environment which uses Xorg, not Wayland.

The following code creates a Frame containing a toolbar with a “New” button and a “Quit” button. Between these 2 buttons there is a toolbar separator.

import wx

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((300, 200))
        self.SetTitle("Separator after New button")
        self.toolbar = wx.ToolBar(self, -1)
        bmp = wx.ArtProvider.GetBitmap(wx.ART_NEW, wx.ART_TOOLBAR, (24, 24))
        self.toolbar.AddTool(wx.ID_ANY, "New", bmp)
        self.toolbar.AddSeparator()
        bmp = wx.ArtProvider.GetBitmap(wx.ART_QUIT, wx.ART_TOOLBAR, (24, 24))
        self.toolbar.AddTool(wx.ID_ANY, "Quit", bmp)
        self.SetToolBar(self.toolbar)
        self.toolbar.Realize()
        self.main_panel = wx.Panel(self, wx.ID_ANY)
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        self.text_ctrl = wx.TextCtrl(self.main_panel, wx.ID_ANY, "", style=wx.TE_MULTILINE)
        main_sizer.Add(self.text_ctrl, 1, wx.ALL | wx.EXPAND, 4)
        self.main_panel.SetSizer(main_sizer)
        self.Layout()

if __name__ == "__main__":
    app = wx.App()
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()

Using one of the ‘Mint-X’ family of themes, the separator is shown as a space containing a vertical line.


[Mint-X theme]

Using one of the ‘Mint-L’ or ‘Mint-Y’ families of themes, the separator is just shown as a space.

Unlike on Windows, setting the toolbar’s style to wxTB_FLAT made no difference.


[Mint-L theme]


[Mint-Y theme]

My default theme is a customised version of the ‘TraditionalOK’ theme.

When using the original ‘TraditionalOK’ theme, the toolbar separator is not shown at all, not even by a space! It is identical in appearance to commenting out the call to self.toolbar.AddSeparator().


[TraditionalOK theme]

A theme consists of a directory containing mainly Cascading Style Sheet (CSS) files. In my modified version of ‘TraditionalOK’ I have added the following lines to the gtk-3.0/gtk-widgets.css file:

toolbar separator {
    border-style: solid;
    border-width: 1px;
    border-color: shade(@theme_bg_color, 0.85);
}

toolbar.horizontal separator { 
    margin: 3px 3px 3px 3px; 
}

toolbar.vertical separator { 
    margin: 3px 3px 3px 3px; 
}

Using my modified theme, the toolbar separator is now shown as a space containing a vertical grey line.


[TraditionalRT theme]

Note: The ‘TraditionalOK’ theme is part of the ‘mate-themes’ package which can be installed using sudo apt install mate-themes.

Just don’t decide the position of the buttons. Leave it to the OS, it’s the users’ choice, whichever way they like it. For years I set my layout to have the buttons on the left, and believe me, the two or three apps which don’t respect that, annoy me endlessly.