Search code examples
pythonuser-interfaceparameter-passingwxpython

What is the code structure when integrating a GUI with wxpython


I've a probably pretty simple question about code structure

I've written an application connecting to a web socket to retrieve data and then do heavy computation

Basically the websocket is opened in one thread and the heavy computation in the mainloop followed by console display. To be noted I will probably have to split the code on several cores using multiprocessing.

I'm now trying to have the display in wxpython. I've made a separate project with a GUI which works fine itself. The mainloop is :

def main():
    app = wx.App()
    ex = MainWindows(rows)
    app.MainLoop()

if __name__ == '__main__':
    main()

and the MainWindows function starts like that :

    class MainWindows(wx.Frame):
    
        def __init__(self, *args, **kwargs):
            super(MainWindows, self).__init__(data=rows, *args, **kwargs)
    
            self.InitUI(rows)
    
        def InitUI(self, rows):
            # Set Frame options
            self.SetSize((800, 600))
            self.SetTitle('Title')
            self.Centre()
...

Just now wondering how to have my background heavy computation done and how to pass parameters to my MainWindows class. I've tried investigating on the *args and **kwargs but without success. Always compilation errors saying my class to not attend for more parameters.

So my questions are :

  1. What is the best way to integrate background computatations, websocket processing and so on (I'm thinking putting in threads would be the best)

  2. How to share data with my GUI (parameter passing)

  3. how to refresh widgets periodically (like every second) to update with date from the websocket which is processed in a separate thread (actually I'm trying to display my websocked processed content in a ListCtrl widget and would like to update it in real time

the full test GUI code where I'd like to display my data (see orders tab). The variable rows is an example of data coming from my background computation from websocket data :

Version_Major = 3
Version_Minor = 0
Version_patch = 0

#########
# IMPORTS
#########
import wx, sys
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np


#################
# Class OrderList
#################
class OrderList(wx.Panel):
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
        self.InitUI()

    def InitUI(self):

        self.SetBackgroundColour('#EEEEEE')
        rows = [('0', 'BTCUSDT', '31256.25', '31262.00', '0.21', '2.22', '3.16', '01d 11:15:12'),
                ('16', 'ADAUSDT', '1.25', '1.00', '0.21', '2.22', '3.16', '01d 11:15:12'),
                ('9', 'FOEM', '31256.25', '31262,00', '0.21', '2.22', '3.16', '01d 11:15:12'),
                ('36', 'DOGEUSDT', '31256.25', '31262,00', '0.21', '2.22', '3.16', '01d 11:15:12'),
                ('175', 'SHIBUSDT', '31256.25', '31262,00', '0.21', '2.22', '3.16', '01d 11:15:12'),
                ('216', 'MERDEUSDT', '31256.25', '31262,00', '0.21', '2.22', '3.16', '01d 11:15:12'),
                ('888', 'SLIPUSDT', '31256.25', '31262,00', '0.21', '2.22', '3.16', '01d 11:15:12')]

        self.list_ctrl = wx.ListCtrl(self, style=wx.LC_REPORT)

        self.list_ctrl.InsertColumn(0, "Number")
        self.list_ctrl.InsertColumn(1, "Crypto")
        self.list_ctrl.InsertColumn(2, "Buying")
        self.list_ctrl.InsertColumn(3, "Price")
        self.list_ctrl.InsertColumn(4, "Hull")
        self.list_ctrl.InsertColumn(5, "Gain")
        self.list_ctrl.InsertColumn(6, "Gain peak")
        self.list_ctrl.InsertColumn(7, "Duration")

        index = 0
        for row in rows:
            self.list_ctrl.InsertStringItem(index, row[0])
            self.list_ctrl.SetStringItem(index, 1, row[1])
            self.list_ctrl.SetStringItem(index, 2, row[2])
            self.list_ctrl.SetStringItem(index, 3, row[3])
            self.list_ctrl.SetStringItem(index, 4, row[4])
            self.list_ctrl.SetStringItem(index, 5, row[5])
            self.list_ctrl.SetStringItem(index, 6, row[6])
            self.list_ctrl.SetStringItem(index, 7, row[7])

            if index % 2:
                self.list_ctrl.SetItemBackgroundColour(index, "#F8F8F8")
            else:
                self.list_ctrl.SetItemBackgroundColour(index, (249, 246, 176))
            index += 1

        # Vbox for listctrl
        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(self.list_ctrl, 0, wx.ALL | wx.EXPAND, 3)
        self.SetSizer(vbox)

        # hbox for button
        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        b1 = wx.Button(self, label='OK', size=(70,30))
        hbox1.Add(b1, flag=wx.LEFT|wx.BOTTOM, border=5)
        vbox.Add((-1, 10))
        vbox.Add(hbox1, flag=wx.ALIGN_RIGHT|wx.RIGHT, border=10)
        self.SetSizer(vbox)

        # vbox for graph
        t = np.arange(0.0, 10, 1.0)
        s = [0, 1, 0, 1, 0, 2, 1, 2, 1, 0]
        t1 = np.arange(0.0, 10, 1.0)
        s1 = [3, 1, 2, 1, 0, 1, 1, 2, 3, 3]

        # hbox for graphs
        nbFigures = 3
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
        self.figs = [Figure(figsize=(2.5, 2)) for _ in range(nbFigures)]
        self.axes = [fig.add_subplot(111) for fig in self.figs]
        self.canvases = [FigureCanvas(self, -1, fig) for fig in self.figs]
        for canvas in self.canvases:
            hbox2.Add(canvas, 1, wx.ALL | wx.EXPAND , 5)

        vbox.Add(hbox2, flag=wx.ALIGN_LEFT | wx.RIGHT | wx.ALL | wx.EXPAND, border=10)
        self.SetSizer(vbox)
        # self.axes[0].plot(t,s)
        self.axes[0].bar(x=t, height=s, width=0.9, color='blue')
        self.figs[0].set_facecolor('#EEEEEE')
        self.axes[0].set_facecolor('#AEEAFF')

        self.axes[0].grid(alpha=0.5)
        self.axes[0].set_title('Graph1')

        self.axes[1].plot(t1,s1, color='red')
        self.axes[1].set_facecolor('#FFE0E0')
        self.figs[1].set_facecolor('#EEEEEE')
        self.axes[1].grid()
        self.axes[1].set_title('Graph2')

        self.axes[2].bar(x=t, height=s1, width=0.9, color='orange')
        self.axes[2].set_facecolor('#FFFFE0')
        self.figs[2].set_facecolor('#EEEEEE')
        self.axes[2].grid()
        self.axes[2].set_title('Graph3')


class TabPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent=parent)

        btn = wx.Button(self, label="Press Me")
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(btn, 0, wx.ALL, 10)
        self.SetSizer(sizer)


class MainWindows(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(MainWindows, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):
        # Set Frame options
        self.SetSize((800, 600))
        self.SetTitle('Binance Scalping')
        self.Centre()

        # NoteBook
        panel = wx.Panel(self)
        notebook = wx.Notebook(panel)
        tabSettings = TabPanel(notebook)
        tabAccount = TabPanel(notebook)
        tabCryptoList = TabPanel(notebook)
        tabOrders = OrderList(notebook)
        tabIndicators = TabPanel(notebook)
        tabStatistics = TabPanel(notebook)
        notebook.AddPage(tabSettings, 'Settings')
        notebook.AddPage(tabAccount, 'Account')
        notebook.AddPage(tabCryptoList, 'Cryptos List')
        notebook.AddPage(tabOrders, 'Orders')
        notebook.AddPage(tabIndicators, 'Indicators')
        notebook.AddPage(tabStatistics, 'Statistics')
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(notebook, 1, wx.ALL|wx.EXPAND, 5)
        panel.SetSizer(sizer)

        # Menus bar
        menubar = wx.MenuBar() # Menu bar
        # Menu items
        fileMenu = wx.Menu()
        viewMenu = wx.Menu()
        aboutMenu = wx.Menu()

        # viewMenu tick option
        self.shst = viewMenu.Append(wx.ID_ANY, 'Show statusbar', 'Show Statusbar', kind=wx.ITEM_CHECK)
        self.shtl = viewMenu.Append(wx.ID_ANY, 'Show toolbar', 'Show Toolbar', kind=wx.ITEM_CHECK)
        viewMenu.Check(self.shst.GetId(), True)
        viewMenu.Check(self.shtl.GetId(), True)
        self.Bind(wx.EVT_MENU, self.ToggleStatusBar, self.shst)
        self.Bind(wx.EVT_MENU, self.ToggleToolBar, self.shtl)

        # Create menu
        menubar.Append(fileMenu, '&File')
        menubar.Append(viewMenu, '&View')
        menubar.Append(aboutMenu, '&About')
        self.SetMenuBar(menubar)

        # Create Toolbar
        self.toolbar = self.CreateToolBar()
        self.toolbar.AddTool(1, '', wx.Bitmap('Z:/Crypto/Scripts Python/Go.png'))
        self.toolbar.Realize()

        # Create Status Bar
        self.statusbar = self.CreateStatusBar()
        self.statusbar.SetStatusText('Ready')

        self.Layout()
        self.Show()

    def ToggleStatusBar(self, e):

        if self.shst.IsChecked():
            self.statusbar.Show()
        else:
            self.statusbar.Hide()

    def ToggleToolBar(self, e):

        if self.shtl.IsChecked():
            self.toolbar.Show()
        else:
            self.toolbar.Hide()


def main():

    app = wx.App()
    ex = MainWindows(None)
    app.MainLoop()

if __name__ == '__main__':
    main()

Solution

  • You have many questions in one. I am not qualified to talk about your threading issues, but:

    2 How to share data with my GUI (parameter passing)

    Just add your data in the call to the mainframe class

    main_frame = MainFrame(config)
    ...
    class MainFrame(wx.Frame):
        """Create MainFrame class."""
        def __init__(self, config, *args, **kwargs):
            super().__init__(None, *args, **kwargs)
            self.Title = config.title
            ...
    

    3 how to refresh widgets periodically (like every second)

    wxPython comes with a timer class of its own:

    The wx.Timer class allows you to execute code at specified intervals. Its precision is platform-dependent, but in general will not be better than 1ms nor worse than 1s .

    listen for EVT_TIMER