How to Build a Python GUI Application With wxPython?

by | Nov 22, 2019 | Python Programming

21 Min Read. |

WxPython was created by Robin Dunn and Harri Pasanen, an open-source cross-platform toolkit for the creation of Python programming language graphical user interface (GUI) applications. There are many GUI toolkits that can use Python programming language, with PyQt, wxPython, and Tkinter being the most common.

They can run on most Windows, macOS, and Linux operating systems. WxPython is a C++ wrapper for wxWidgets, a free website that can be easily downloaded from the internet.

Python is a programming language that was developed and published in 1991 by Guido van Rossum. It is a vibrant language inspired by programming languages from ABC and Haskell.

Download Detailed Curriculum and Get Complimentary access to Orientation Session

Date: 08th Aug, 2020 (Saturday)
Time: 10:30 AM - 11:30 AM (IST/GMT +5:30)
  • This field is for validation purposes and should be left unchanged.

Python is a high-level, particular-purpose, cross-platform language and is widely used worldwide as a minimalist language that is easy to understand. Python’s most amazing feature is that it uses no semicolon or brackets instead of an indentation.

wxPython Modules 

wxPython Modules 

wxPython Modules  Source – Tutorials Point

There are five major wxPython modules namely:

1. Controls: It is a collection of all the widgets used in a Graphical application. Widgets are also known as controls in the Windows operating system. For example, static text, button, toolbar, editable text control, etc.

2. Windows: This module contains all the window classes such as a frame, a panel, a dialogue, a scrolled window, etc.

3. Misc: This module contains various functions and classes of modules that help in various functions such as system settings, the configuration of applications, logging in or controlling the display.

4. Core: This module contains all the principle class used for the development of graphical user interface-based applications. These classes consist of wxObject class which is the foundation of all other classes, Sizers used for widget layout, basic geometry classes and events.

5. GDI: wxPython API has a Graphical device interface module which is a collection of classes used for drawing onto the widgets. This contains classes such as color, brush, fonts, images, or pens, etc.

Download Detailed Curriculum and Get Complimentary access to Orientation Session

Date: 08th Aug, 2020 (Saturday)
Time: 10:30 AM - 11:30 AM (IST/GMT +5:30)
  • This field is for validation purposes and should be left unchanged.

wxPython API 

wxPython is a library that is used by programmers to code applications. Since wxPython is a wrapper around wxWidgets, therefore, it is not a native API and hence is not written directly in Python. wxPython has numerous widgets, they are the elementary base of any GUI application. The widgets can be grouped into five categories

  • 1. Base widgets: They help the other derived widgets to function through it. They are also known as ancestors and they generally do not function directly. Some of its examples are wxControl, wxWindows, etc.
  • 2. Top-level widgets: They do not depend on other widgets, such as wxFrame, wxScrolledWindow, wxDialog, wxMDIParentFrame, etc. 
  • 3. Dynamic widgets: These provide users to edit widgets, like wxListBox, wxScrollBar, wxButton, wxRadioButton, wxToggleButton, wxChoice, wxBitmapButton, wxSlider , wxRadioBox, wxGrid, wxSpinButton, wxCheckBox, wxTextCtrl, etc.
  • Containers: These widgets contain other widgets such as wxPanel, wxSplitterWindow, wxscrolledWindow, wxNotebook, etc.
  • Other widgets: These widgets are wxStatusBar, wxToolbar, wxMenubar, etc.

Inheritance

There is a connection between widgets in wxPython, such a relation is formed on the base of inheritance. This is a vital part of the object-oriented programming language. Widgets derive functionality from the other widgets and hence creating inheritance. The widgets which provide the functionality to other widgets are known as parent widgets or base widgets or ancestors, whereas the widgets that inherit are known as child widgets, derived widgets or descendants. 

Let us understand this if we use a button widget in our application that derives from four other base widgets. The first one being the wx.Control class. A button widget is similar to a tiny window and basically, mostly all widgets that appear on the screen are windows. Thus they derive from wx.Window class. Some of the objects are invisible such as sizers, device context or locale objects. There exist classes which are visible but they are not windows, for example, a colour object, caret object or a cursor object. wx.A dialog is not any type of control, thus not every object a control. The controls are placed on the widgets which are known as containers. The reason due to which it has a separate wx. Control base class.

Every window and button widgets revert to events. By clicking on the button, the wx.EVT_COMMAND_BUTTON_CLICKED event is launched. The button widget derives the wx.EvtHandler through the wx.Window class. Every widget that reacts to events should inherit from wx.EvtHandler class and thus all objects inherit from wx.Object class.

Installing wxPython

wxPython 4 the Phoenix release being the latest one should be used here. The wxPython 3 and wxPython 2 are suited only for wxPython 2. Robin Dunn, the developer of wxPython cleared a lot of codes to make wxPython 4 easier to understand and even more Pythonic, it is even suitable for both wxPython 2.7 and wxPython 3.

Following links must be considered if you are migrating from an older version of wxPython to wxPython 4 (Phoenix):

1. Classic vs Phoenix

2. wxPython Project Phoenix Migration Guide

A pip can be used to install wxPython 4 as following:

$ pip install wxpython

Note: A compiler will be needed to install it on a macOS such as Xcode, while some dependencies must be installed in Linux before so that it is installed successfully.

To our advantage, the pip displays an error message which helps us to determine the problem and the ways it could be fixed. The prerequisite section on the wxPython Github page provides certain information that may be useful to install wxPython Linux.

The problem in installing it on Linux can also be solved by using the wheels in the ExtraLinux section with GTK2 and GTK3 versions with the help of the following command:

$ pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04/ wxPython

The above command must be altered as per the version of your Linux.

What is a GUI? 

What is a GUI? 

What is a GUI?  Source – Slideshare

Graphical User Interface i.e GUI is an interface which is a collection of objects and methods to build a user-friendly screen for interaction.

Graphical User interfaces have some basic components which are:

  • Main window
  • Menu
  • Toolbar
  • Buttons
  • Text Entry
  • Labels

The above items are mainly known as widgets, such widgets are arranged logically on the window by the developer so the user can interact with it.

Event Loops

An event occurs when any button is pressed on a widget or anything is typed on the screen, thus the graphical user interface works with events.

The event loop which is an infinite loop is running in the background by the GUI toolkit. Whenever any event is created, the event loop performs the functions coded by the developer for such a specific event on the application, and in case there is no occurrence of any event the event loop simply ignores it.

Each of the widgets must be assigned an event handler for the application to run during the creation of the program for the application.

One must be very careful while working with the event loops as the GUI tend to get blocked and stop responding which may make the user think as the system is stuck.

To prevent the GUI from freezing one must consider the following if any process implemented is taking more than a quarter second then a separate thread or process must be assigned for it giving the user a better experience. 

A special thread-safe method is provided by wxPython to communicate back to the thread, or provide information of its completion or to give an update.

A skeleton application can be used to show how events work.

Creating a Skeleton Application

A skeleton application in a Graphical User Interface has widgets without any event handlers, it is kind of a prototype without a logical back end.

Let us take a look at  the Hello World application with wxPython:

import wx



app = wx.App()

frame = wx.Frame(parent=None, title='Hello World')

frame.Show()

app.MainLoop()

Now let us write the same code as a class:

import wx

 

class MyFrame(wx.Frame):    

    def __init__(self):

        super().__init__(parent=None, title='Hello World')

        self.Show()

 

if __name__ == '__main__':

    app = wx.App()

    frame = MyFrame()

    app.MainLoop()

The above code can be used as a guide for other applications.Similarly even more widgets could be added to the application to add presentability to it, so let us gather more information on the widgets.

Widgets

The wxPython has numerous widgets to choose from each one is unique on its own, but the problem arises as to what kind of widget must be used and where to use it? Selection of widgets could be a tedious task so how does one solve this? To resolve such issues wxPython comes with an option known as Demo, it provides a search filter where one can choose the type of widget that is right for their application.

Generally, in a GUI application, a widget responds simply by typing any text or pressing any button. Let us have a look at how such widgets are added: 

import wx



class MyFrame(wx.Frame):    

    def __init__(self):

        super().__init__(parent=None, title='Hello World')

        panel = wx.Panel(self)

 

        self.text_ctrl = wx.TextCtrl(panel, pos=(5, 5))

        my_btn = wx.Button(panel, label='Press Me', pos=(5, 55))



        self.Show()

 

if __name__ == '__main__':

    app = wx.App()

    frame = MyFrame()

    app.MainLoop()

After running this code you will get this application as a final result:

wx.The panel is the first widget to be used, it is a window on which all controls are placed.This widget if not used then the Tab traversal is disabled. It is not compulsory to use this widget but it is suggested to be used as else one may not get the right shade of gray for the background panel.

When the panel is the only derivative of the frame then the panel covers up the whole frame to take up all the space in the frame.

Now we add wx. Textctrl to the panel, since the first widget should be a parent widget, therefore, one has to specify a parent widget. The right choice would be a button widget or a textctrl widget.

the pos parameter is used to designate appropriate positions for the widgets on the frame of wxPython. Then you add your button to the panel and give it a label. To prevent the widgets from overlapping, you need to set the y-coordinate to 55 for the button’s position.

Absolute Positioning

When you provide exact coordinates for your widget’s position, the technique that you used is called absolute positioning. Most GUI toolkits provide this capability, but it’s not actually recommended.

As your application becomes more complex, it becomes difficult to keep track of all the widget locations and if you have to move the widgets around. Resetting all those positions becomes a nightmare.

Fortunately, all modern GUI toolkits provide a solution for this, which is what you will learn about next.

Download Detailed Curriculum and Get Complimentary access to Orientation Session

Date: 08th Aug, 2020 (Saturday)
Time: 10:30 AM - 11:30 AM (IST/GMT +5:30)
  • This field is for validation purposes and should be left unchanged.

Sizers (Dynamic Sizing)

The wxPython toolkit includes sizers, which are used for creating dynamic layouts. They manage the placement of your widgets for you and will adjust them when you resize the application window. Other GUI toolkits will refer to sizers as layouts, which is what PyQt does.

Here are the primary types of sizers that you will see used most often:

  • wx.BoxSizer
  • wx.GridSizer
  • wx.FlexGridSizer

Let’s add a wx.BoxSizer to your example and see if we can make it work a bit more nicely:

import wx



class MyFrame(wx.Frame):    

    def __init__(self):

        super().__init__(parent=None, title='Hello World')

        panel = wx.Panel(self)        

        my_sizer = wx.BoxSizer(wx.VERTICAL)        

        self.text_ctrl = wx.TextCtrl(panel)

        my_sizer.Add(self.text_ctrl, 0, wx.ALL | wx.EXPAND, 5)        

        my_btn = wx.Button(panel, label='Press Me')

        my_sizer.Add(my_btn, 0, wx.ALL | wx.CENTER, 5)        

        panel.SetSizer(my_sizer)        

        self.Show()



if __name__ == '__main__':

    app = wx.App()

    frame = MyFrame()

    app.MainLoop()

Here you create an instance of a wx.BoxSizer and pass it wx.VERTICAL, which is the orientation that widgets are added to the sizer.

In this case, the widgets will be added vertically, which means they will be added one at a time from top to bottom. You may also set a BoxSizer’s orientation to wx.HORIZONTAL. When you do that, the widgets would be added from left to right.

To add a widget to a sizer, you will use .Add(). It accepts up to five arguments:

  • window (the widget)
  • proportion
  • flag
  • border
  • userDataThe widget to be added is defined by the window argument. 

1. The space that this widget acquires relative to other widgets is specified by proportion argument. Its default value is zero, which suggests the wxPython to assign the widget at its default proportion. 

    2. You can add multiple flags as an argument separated by pipe character(‘|’). This pipe character represents a bitwise OR operator in the wxPython. In the above example, you have added the text control with the wx.ALL and wx.EXPAND flags. The wx.ALL specifies which side or sides of the widget, the border width is applied. The wx.EXPAND lets the widgets expand as much as to fill the spaces assigned to them within the sizer.

    3. The number of pixels that need to be added around the widget is specified by the parameter called border.

    4. The userData parameter is quite rarely used and is only incorporated when you wish to perform complex operations on the sizing of your widgets.

    5. You need to follow exactly the same steps to add the button to the sizer. But for the fun part, I have replaced the wx.EXPAND flag with wx.CENTER which places the button on the center of the screen.

    When you execute the above-discussed code, you get an application that appears as follows:

    If you’d like to learn more about sizers, the wxPython documentation has a nice page on the topic.

    Adding an Event

    While your application looks more interesting visually, it still doesn’t really do anything. For example, if you press the button, nothing really happens.

    Let’s give the button a job:

    import wx
    
    
    
    class MyFrame(wx.Frame):    
    
        def __init__(self):
    
            super().__init__(parent=None, title='Hello World')
    
            panel = wx.Panel(self)        
    
            my_sizer = wx.BoxSizer(wx.VERTICAL)        
    
            self.text_ctrl = wx.TextCtrl(panel)
    
            my_sizer.Add(self.text_ctrl, 0, wx.ALL | wx.EXPAND, 5)        
    
            my_btn = wx.Button(panel, label='Press Me')
    
            my_btn.Bind(wx.EVT_BUTTON, self.on_press)
    
            my_sizer.Add(my_btn, 0, wx.ALL | wx.CENTER, 5)        
    
            panel.SetSizer(my_sizer)        
    
            self.Show()
    
    
    
        def on_press(self, event):
    
            value = self.text_ctrl.GetValue()
    
            if not value:
    
                print("You didn't enter anything!")
    
            else:
    
                print(f'You typed: "{value}"')
    
    
    if __name__ == '__main__':
    
        app = wx.App()
    
        frame = MyFrame()
    
        app.MainLoop()

    The widgets in wxPython allow you to attach event bindings to them so that they can respond to certain types of events.

    Note: The code block above uses f-strings. You can read all about them in Python 3’s f-Strings: An Improved String Formatting Syntax (Guide).

    You want the button to perform some action when it is pressed. This is accomplished by using the function .Bind() in the button. This function .Bind() takes the following:

    • The event that you want to bind to
    • The handler to call when the event takes place
    • An optional source
    • Some optional ids.

    In the above example, the button is bound to the wx.EVT_BUTTON event and it is required to call the method on_press() when that event takes place.

    This is called the ‘firing’ of the event which takes place when the user performs the event that you have bound your button to. In the following case, wx.EVT_BUTTON is the event that you have set up your button with which is called when the button is pressed.

    The .on_press() method can take a second argument which can be called an event conventionally. You can refer to it with some other name as well if you wish. When this method is called, the event parameter ensures that its second argument is an event object of some particular kind.

    You can call the GetValue() method within .on_press() to get the text control’s content. Depending on the contents of the text control, you can print a string to stdout.

    We have already summarised the basics for you, now let’s proceed with the process to create an application that can perform some useful task.

    Create a functional Application

    The first and most important step is to identify the requirement, that is to specify what you want to create. Here, I leave that decision up to you to decide the function of your application. Here, we will discuss how to create an MP3 tag editor. The next step is to identify packages that will help you to accomplish your task. If you look out on Google for Python MP3 tagging, you will come across several options as follows:

    • Mutagen
    • Mp3-tagger
    • eyeD3

    After trying out these options for myself, I decided to go ahead with eyeD3 which has a good API that you can use and not dragged by the complications of the MP3’s ID3 specification. You can follow the following step to install eyeD3 using pip:

    $ pip install eyed3

    When installing this package on macOS, you may need to install libmagic using brew. Windows and Linux users shouldn’t have any issues installing eyeD3.

    Designing the user interface

    It is advisable to sketch out the look and feel of your application that you have planned before you proceed with the steps to create your application. You should be able to do the following:

    • Display the current MP3 tags
    • Edit an MP3 tag
    • Open up one or more MP3 files

    Most of the user interfaces incorporate a menu or a button for opening files and folders. You can proceed with a File menu to perform this operation. You want to see tags for different MP3 files, for this, you need to have a widget that can perform this task in a nice manner.

    A tabular option that has orderly rows and columns is a preferred choice because it can allow you to have labeled columns for the MP3 tags. There are some widgets available in the wxPython toolkit that can perform this. Following are the top two options:

    • wx.grid.Grid
    • wx.ListCtrl

    It is preferred to use wx.ListCtrl as the grid widget is more complicated to use. Lastly, you need to have a button that will be used to edit a selected MP3’s tag. Now that you know what you want, you can draw it up:

    The illustration above gives us an idea of how the application should look. Now that you know what you want to do, it’s time to code!

    Download Detailed Curriculum and Get Complimentary access to Orientation Session

    Date: 08th Aug, 2020 (Saturday)
    Time: 10:30 AM - 11:30 AM (IST/GMT +5:30)
    • This field is for validation purposes and should be left unchanged.

    Creating the User Interface

    You can refer to many options when you wish to write a new application. You can follow the Model-View-Controller design pattern as one of the options. You can incorporate ways to split up the classes. As you gain more experience with GUI design, you will be able to make the choices accordingly.

    In this case, you need only the following two classes:

    • wx.Frame class
    • wx.Panel class

    You might go for creating a controller type module but in this case, you don’t really need it. You will be creating a single Python file for all your code though you can make a case to put each class into its own module

    We start with the imports and panel class:

    import eyed3
    
    import glob
    
    import wx
    
    
    
    class Mp3Panel(wx.Panel):    
    
        def __init__(self, parent):
    
            super().__init__(parent)
    
            main_sizer = wx.BoxSizer(wx.VERTICAL)
    
            self.row_obj_dict = {}
    
    
    
            self.list_ctrl = wx.ListCtrl(
    
                self, size=(-1, 100), 
    
                style=wx.LC_REPORT | wx.BORDER_SUNKEN
    
            )
    
            self.list_ctrl.InsertColumn(0, 'Artist', width=140)
    
            self.list_ctrl.InsertColumn(1, 'Album', width=140)
    
            self.list_ctrl.InsertColumn(2, 'Title', width=200)
    
            main_sizer.Add(self.list_ctrl, 0, wx.ALL | wx.EXPAND, 5)        
    
            edit_button = wx.Button(self, label='Edit')
    
            edit_button.Bind(wx.EVT_BUTTON, self.on_edit)
    
            main_sizer.Add(edit_button, 0, wx.ALL | wx.CENTER, 5)        
    
            self.SetSizer(main_sizer)
    
        def on_edit(self, event):
    
            print('in on_edit')
    
    
    
        def update_mp3_listing(self, folder_path):
    
            print(folder_path)

    In the above code, you are importing the following packages for your user interface:

    • Eyed3 package
    • Wx package
    • Python’s glob package

    You create your user interface by subclassing wx.Panel. You need to define a dictionary to hold and store the data about your MP3s, here we are naming it as row_obj_dict. Next, you create a wx.ListCtrl and set it to report mode by the following code wx.LC_REPORT and set a sunken border wx.BORDER_SUNKEN. This list control depends on the style flag that you pass in and can take different forms accordingly, but the report flag is most widely used. You need to call the .InsertColumn() for each column header to make the ListCtrl have the correct headers. Then we pass the index of the column, its label and the width in pixels of the column. Lastly, we add an Edit button, an event handler and a method as discussed above. You can create a binding to this event and leave the method empty in this case. Now, we proceed with writing the code for the frame:

    class Mp3Frame(wx.Frame):    
    
        def __init__(self):
    
            super().__init__(parent=None,
    
                             title='Mp3 Tag Editor')
    
            self.panel = Mp3Panel(self)
    
            self.Show()
    
    
    if __name__ == '__main__':
    
        app = wx.App(False)
    
        frame = Mp3Frame()
    
        app.MainLoop()

    This class is much simpler than the first one in that all you need to do is set the title of the frame and instantiate the panel class, Mp3Panel. When you are all done, your user interface should look like this:

    The user interface looks almost right, but you don’t have a File menu. This makes it impossible to add MP3s to the application and edit their tags!

    Let’s fix that now.

    Make a Functioning Application

    The first step in making your application work is to update the application so that it has a File menu because then you can add MP3 files to your creation. Menus are almost always added to the wx.Frame class, so that is the class you need to modify.

    Note: Some applications have moved away from having menus in their applications. One of the first to do so was Microsoft Office when they added the Ribbon Bar. The wxPython toolkit has a custom widget that you can use to create ribbons in wx.lib.agw.ribbon.

    The other type of application that has dropped menus of late are web browsers, such as Google Chrome and Mozilla Firefox. They just use toolbars nowadays.

    Let’s learn how to add a menu bar to our application:

    class Mp3Frame(wx.Frame):

        def __init__(self):
    
            wx.Frame.__init__(self, parent=None, 
    
                              title='Mp3 Tag Editor')
    
            self.panel = Mp3Panel(self)
    
            self.create_menu()
    
            self.Show()
    
    
    
        def create_menu(self):
    
            menu_bar = wx.MenuBar()
    
            file_menu = wx.Menu()
    
            open_folder_menu_item = file_menu.Append(
    
                wx.ID_ANY, 'Open Folder', 
    
                'Open a folder with MP3s'
    
            )
    
            menu_bar.Append(file_menu, '&File')
    
            self.Bind(
    
                event=wx.EVT_MENU, 
    
                handler=self.on_open_folder,
    
                source=open_folder_menu_item,
    
            )
    
            self.SetMenuBar(menu_bar)
    
    
        def on_open_folder(self, event):
    
            title = "Choose a directory:"
    
            dlg = wx.DirDialog(self, title, 
    
                               style=wx.DD_DEFAULT_STYLE)
    
            if dlg.ShowModal() == wx.ID_OK:
    
                self.panel.update_mp3_listing(dlg.GetPath())
    
            dlg.Destroy()

    Here, you add a call to .create_menu() within the class’s constructor. Then in create_menu()itself, you will create a wx.MenuBar instance and a wx.Menu instance.

    To add a menu item to a menu, you call the menu instance’s .Append() and pass it the following:

    • A unique identifier
    • The label for the new menu item
    • A help string

    Next, you need to add the menu to the menu bar, so you will need to call the menubar’s .Append(). It takes the menu instance and the label for menu. This label is a bit odd in that you called it &File instead of File. The ampersand tells wxPython to create a keyboard shortcut of Alt+F to open the File menu using just your keyboard.

    Note: If you would like to add keyboard shortcuts to your application, then you will want to use an instance of wx.AcceleratorTable to create them. You can read more about Accerator Tables in the wxPython documentation.

    To create an event binding, you will need to call self.Bind(), which binds the frame to wx.EVT_MENU. When you use self.Bind() for a menu event, you need to not only tell wxPython which handler to use, but also which source to bind the handler to.

    Finally, you must call the frame’s .SetMenuBar() and pass it the menubar instance for it to be shown to the user.

    Now that you have the menu added to your frame, let’s go over the menu item’s event handler, which is reproduced again below:

    def on_open_folder(self, event):
    
        title = "Choose a directory:"
    
        dlg = wx.DirDialog(self, title, style=wx.DD_DEFAULT_STYLE)
    
        if dlg.ShowModal() == wx.ID_OK:
    
            self.panel.update_mp3_listing(dlg.GetPath())
    
        dlg.Destroy()

    Since you want the user to choose a folder that contains MP3s, you will want to use wxPython’s wx.DirDialog. The wx.DirDialog allows the user to only open directories.

    You can set the dialog’s title and various style flags. To show the dialog, you will need to call .ShowModal(). This will cause the dialog to show modally, which means that the user won’t be able to interact with your main application while the dialog is shown.

    If the user presses the dialog’s OK button, you can get the user’s path choice via the dialog’s .GetPath(). You will want to pass that path to your panel class, which you can do here by calling the panel’s .update_mp3_listing().

    Finally you need to close the dialog. To close the dialog, the recommended method is to call its .Destroy().

    Dialogs do have a .Close() method, but that basically just hides the dialog, and it will not destroy itself when you close your application, which can lead to weird issues such as your application now shutting down properly. It’s simpler to call .Destroy() on the dialog to prevent this issue.

    Now let’s update your Mp3Panel class. You can start by updating .update_mp3_listing():

    def update_mp3_listing(self, folder_path):
    
        self.current_folder_path = folder_path
    
        self.list_ctrl.ClearAll()
    
    
    
        self.list_ctrl.InsertColumn(0, 'Artist', width=140)
    
        self.list_ctrl.InsertColumn(1, 'Album', width=140)
    
        self.list_ctrl.InsertColumn(2, 'Title', width=200)
    
        self.list_ctrl.InsertColumn(3, 'Year', width=200)
    
    
    
        mp3s = glob.glob(folder_path + '/*.mp3')
    
        mp3_objects = []
    
        index = 0
    
        for mp3 in mp3s:
    
            mp3_object = eyed3.load(mp3)
    
            self.list_ctrl.InsertItem(index, 
    
                mp3_object.tag.artist)
    
            self.list_ctrl.SetItem(index, 1, 
    
                mp3_object.tag.album)
    
            self.list_ctrl.SetItem(index, 2, 
    
                mp3_object.tag.title)
    
            mp3_objects.append(mp3_object)
    
            self.row_obj_dict[index] = mp3_object
    
            index += 1

    Here you set the current directory to the specified folder and then you clear the list control. This keeps the list control fresh and only showing the MP3s that you are currently working on. That also means that you need to re-insert all the columns again.

    Next, you’ll want to take the folder that was passed in and use Python’s glob module to search for MP3 files.

    Then you can loop over the MP3s and turn them into eyed3 objects. You can do this by calling the .load() of eyed3. Assuming that the MP3s have the appropriate tags already, you can then add the artist, album, and the title of the MP3 to the list control.

    Interestingly, the method of adding a new row to a list control object is by calling .InsertItem() for the first column and SetItem() for all the subsequent columns.

    The last step is to save off your MP3 object to your Python dictionary, row_obj_dict.

    Now you need to update the .on_edit() event handler so that you can edit an MP3’s tags:

    def on_edit(self, event):
    
        selection = self.list_ctrl.GetFocusedItem()
    
        if selection >= 0:
    
            mp3 = self.row_obj_dict[selection]
    
            dlg = EditDialog(mp3)
    
            dlg.ShowModal()
    
            self.update_mp3_listing(self.current_folder_path)
    
            dlg.Destroy()

    The first thing you need to do is get the user’s selection by calling the list control’s .GetFocusedItem().

    If the user has not selected anything in the list control, it will return -1. Assuming that the user did select something, you will want to extract the MP3 object from your dictionary and open an MP3 tag editor dialog. This will be a custom dialog that you will use to edit the artist, album, and title tags of the MP3 file.

    As usual, show the dialog modally. When the dialog closes, the last two lines in .on_edit()will execute. These two lines will update the list control so it displays the current MP3 tag information that the user just edited and destroy the dialog.

    Download Detailed Curriculum and Get Complimentary access to Orientation Session

    Date: 08th Aug, 2020 (Saturday)
    Time: 10:30 AM - 11:30 AM (IST/GMT +5:30)
    • This field is for validation purposes and should be left unchanged.

    Creating an Editing Dialog

    The final piece of the puzzle is creating an MP3 tag editing dialog. For brevity, we will skip sketching out this interface as it is a series of rows that contains labels and text controls. The text controls should have the existing tag information pre-populated within them. You can create a label for the text controls by creating instances of wx.StaticText.

    When you need to create a custom dialog, the wx.Dialog class is your friend. You can use that to design the editor:

    class EditDialog(wx.Dialog):    
    
        def __init__(self, mp3):
    
            title = f'Editing "{mp3.tag.title}"'
    
            super().__init__(parent=None, title=title)        
    
            self.mp3 = mp3        
    
            self.main_sizer = wx.BoxSizer(wx.VERTICAL)        
    
            self.artist = wx.TextCtrl(
    
                self, value=self.mp3.tag.artist)
    
            self.add_widgets('Artist', self.artist)        
    
            self.album = wx.TextCtrl(
    
                self, value=self.mp3.tag.album)
    
            self.add_widgets('Album', self.album)        
    
            self.title = wx.TextCtrl(
    
                self, value=self.mp3.tag.title)
    
            self.add_widgets('Title', self.title)        
    
            btn_sizer = wx.BoxSizer()
    
            save_btn = wx.Button(self, label='Save')
    
            save_btn.Bind(wx.EVT_BUTTON, self.on_save)        
    
            btn_sizer.Add(save_btn, 0, wx.ALL, 5)
    
            btn_sizer.Add(wx.Button(
    
                self, id=wx.ID_CANCEL), 0, wx.ALL, 5)
    
            self.main_sizer.Add(btn_sizer, 0, wx.CENTER)        
    
            self.SetSizer(self.main_sizer)

    Here you want to start off by sub-classing wx.Dialog and giving it a custom title based on the title of the MP3 that you are editing.

    Next you can create the sizer you want to use and the widgets. To make things easier, you can create a helper method called .add_widgets() for adding the wx.StaticText widgets as rows with the text control instances. The only other widget here is the Save button.

    Let’s write the add_widgets method next:

       def add_widgets(self, label_text, text_ctrl):
    
            row_sizer = wx.BoxSizer(wx.HORIZONTAL)
    
            label = wx.StaticText(self, label=label_text,
    
                                  size=(50, -1))
    
            row_sizer.Add(label, 0, wx.ALL, 5)
    
            row_sizer.Add(text_ctrl, 1, wx.ALL | wx.EXPAND, 5)
    
            self.main_sizer.Add(row_sizer, 0, wx.EXPAND)

    add_widgets() takes the label’s text and the text control instance. It then creates a horizontally oriented BoxSizer.

    Next, you will create an instance of wx.StaticText using the passed-in text for its label parameter. You will also set its size to be 50 pixels wide and the default height is set with a -1. Since you want the label before the text control, you will add the StaticText widget to your BoxSizer first and then add the text control.

    Finally, you want to add the horizontal sizer to the top-level vertical sizer. By nesting the sizers in each other, you can design complex applications.

    Now you will need to create the on_save() event handler so that you can save your changes:

       def on_save(self, event):
    
            self.mp3.tag.artist = self.artist.GetValue()
    
            self.mp3.tag.album = self.album.GetValue()
    
            self.mp3.tag.title = self.title.GetValue()
    
            self.mp3.tag.save()
    
            self.Close()

    Here you set the tags to the contents of the text controls and then call the eyed3 object’s .save(). Finally, you call the .Close() of the dialog. The reason you call .Close() here instead of .Destroy() is that you already call .Destroy() in the .on_edit() of your panel subclass.

    Now your application is complete!

    Download Detailed Curriculum and Get Complimentary access to Orientation Session

    Date: 08th Aug, 2020 (Saturday)
    Time: 10:30 AM - 11:30 AM (IST/GMT +5:30)
    • This field is for validation purposes and should be left unchanged.

    Final Thoughts

    Python language is one of the most popular languages in programming that has seen a sharp increase in the number of users. It is the leading language of the data science industry. By taking a Python Programming course, you’re going to pave a way for far greater career opportunities than you ever had.

    Register for FREE Digital Marketing Orientation Class
    Date: 05th Aug, 2020 (Wed) Time: 3:00 PM to 4:30 PM (IST/GMT +5:30)
    • This field is for validation purposes and should be left unchanged.
    We are good people. We don't spam.

    You May Also Like…

    Top 21 DevOps Interview Questions

    Top 21 DevOps Interview Questions

    DevOps interview questions can be tricky and involve some prior preparation. One of the most profitable careers in...

    0 Comments

    Submit a Comment

    Your email address will not be published. Required fields are marked *