Pages

How to Build a Python GUI Application With WX Python

 In our previous blog, we learned to design a basic mobile application with the Kivy Python Framework, and in this blog, we will learn to build a Python GUI application using the wxPython GUI toolkit. 


There are many different cross-platform GUI toolkits for the Python programming language, and wxPython is one of them. Apart from this, python developers can also use Tkinter and PyQt to build a python application. You can make apps for Windows, macOS, and Linux with all three GUI toolkits. However, PyQt has some extra features that also work on mobile platforms. 


GUI stands for "Graphical User Interface," which is a type of interface that allows users to interact with electronic devices through visual indicators and elements, such as icons and buttons, rather than just text-based commands. It is the way that most modern computer operating systems, web applications, and mobile devices let people use them. It makes it easier and more natural for people to use these technologies.


To design a python app using wxPython first of all, we have to install wxPython on our device. 


  1. Installing wxPython

You can install wxPython by running "pip install wxPython" in your terminal or command prompt.


$ pip install wxpython


To install wxpython on Mac OS X you will need a compiler installed on your device such as XCode for a successful installation. For Linux users to be able to use the pip installer successfully, they may also need to install other dependencies.


In the Extras Linux section of this site, you can find Python wheels that work with the most popular Linux distributions. These Python wheels come in both GTK2 and GTK3 flavorings. You would use this command in order to install one of these wheels on your Device:


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



  1. Creating a Skeleton App

In your Python code, import the necessary wxPython modules such as wx, wx.Frame, wx.Button, etc.


In the context of a graphical user interface (GUI), a user interface with widgets that do not have any event handlers is referred to as an application skeleton. These are helpful for creating prototypes. Before devoting a significant amount of time to the backend logic, you essentially just need to create the graphical user interface (GUI) and show it to your stakeholders so they can give you their approval. 



import wx


app = wx.App()

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

frame.Show()

app.MainLoop()



In this code, we have created a class MyApp that inherits from wx.App. The OnInit method is called when the application starts, and it creates a frame with the title "Hello World". The frame is then shown on the screen. Finally, the MainLoop method is called to start the main event loop, which processes user events until the application is closed.


Also, there are two parts of this code. The first one is wx.App and the second is wx.Frame. If you want to get your GUI up and running, you'll need to use wxPython's application object, which is called wx.App and it starts with a .MainLoop(). 


The second part of code is wx.Frame that creates a window for the user to interact with. Here, you've instructed wxPython that the frame is standalone and given it the title "Hello World." Here is what it looks like when you run the code:


Image 1


This is how our code looks when we rewrite it 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()




  1. Adding Widgets

One can pick from over a hundred different widgets in the wxPython toolkit. This lets you make sophisticated applications, but it can be difficult to know which widget to employ. In order to find the widgets that might be useful for your project, you can use the search filter provided by the wxPython Demo.


The vast majority of GUI programs have text entry fields and buttons. We can add those widgets using the below code:


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()



  1. Adjust Sizers 

There are sizers in the wxPython toolkit that can be used to make custom layouts on the fly. If you resize the application window, the widgets will automatically reposition themselves. PyQt is not alone in renaming sizers to layouts; this is common practice among GUI toolkits.


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()



  1. Adding Event

Your application looks great and is aesthetically appealing, but it still doesn't do very much. You can see this in action by pressing the button and seeing nothing happen.


Allow the button to do some work for us:


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()


If you want a wxPython widget to act in response to a certain event, you can bind it to that event.


The goal is to have the button trigger an action when the user presses it. For this you have to use the button’s .Bind() method. .Bind() function initiates as soon as the user click on the buttons. 


  1. Making a Functional Program

Discovering your intended end result is the first step in the creative process. Here we will create an MP3 tag editor. Once we decide what to create, the second step is to find out what additional resources you will need to accomplish your task.


To design an MP3 tag editor, we will use the eyeD3 tool, which is a Python tool. This tool is used for working with MP3 audio files containing ID3 metadata


eyeD3 has a nice API and you can install it using the below pip command:



$ pip install eyed3


  1. Designing the User Interface

Before designing the interface, its better to make a rough sketch how you want to make your interface look like.


To access a file or folder, the user typically selects it from a list or presses a button. The File menu works well for this purpose. You'll want to find a widget that displays tags for multiple MP3 files elegantly, as you'll likely want to do this.


There are different ways to writing a new application and you will get the whole information about it gradually with time. However, today we can start creating user interface with two classes


wx.Panel class

wx.Frame class


It could be argued that a controller-like module would also be useful here, but that's not the case. While it's possible to separate out each class into its own module, you'll likely just write everything in one giant Python file instead.


Here is how your code look like after 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, we have imported the eyed3 package, the wx package, and Python’s glob package for the user interface. 


Next, we will add a subclass of wx.Panel and create our user interface. We also need a dictionary to store MP3 data, and we will name it row_obj_dict.



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()


Compared to the original class, all you have to do to use this one is give the frame a title and create an instance of the panel class Mp3Panel. When finished, your interface should look something like this:


Image 2


  1. Make a Functioning Application

Adding an MP3 file to your project is the first step in getting your app up and running, so make sure you update it with a File menu. Modifying the wx.Frame class is typically how menus are implemented.


You can use the below code to add a menu bar in your app:


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()


Now call self.Bind(), to create event binding. This command binds the frame to wx.EVT_MENU. While using self.Bind() for a menu event, you don’t need to instruct wxPython which handler to use, but also which source to bind the handler to.



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()



As we want the user to choose a folder that contains MP3s, we have to use wxPython’s wx.DirDialog. This command allows the user to only open directories.


Additionally, we can set the dialog's title and numerous style flags. You must call in order to display the dialog .ShowModal()


Now we will update Mp3Panel class using below code

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 can select a new working directory and then clear the active list. This way, the list control is always up-to-date and only displays the MP3s you're currently editing. That also means you have to re-add the columns.


Now we have to update the .on_edit() to 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()



Now, firstly we have to get the user’s selection by calling the list control’s .GetFocusedItem.().


  1. Creating an Editing Dialog

The final step is to develop a user interface for editing MP3 tags. Since this interface consists of only a string of rows with labels and text controls, a sketch is unnecessary. Existing tag data should be pre-populated in the text controls. By instantiating wx.StaticText, we can make a label to go along with the text controls.


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)



Now 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)



The last step is to combine the vertical sizer at the very top with the horizontal sizer below it. Sizers can be nested within each other to facilitate the development of sophisticated software.


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()




Final thoughts:

Building a Python GUI application with wxPython can be a straightforward and efficient process. With its easy-to-use API and comprehensive library of widgets and components, wxPython provides developers with a powerful toolkit for creating visually appealing and functional applications. Whether you are a beginner or an experienced developer, wxPython offers the flexibility and customization options needed to build the application you desire. Overall, wxPython is a highly recommended choice for building GUI applications in Python.




No comments:

Post a Comment

Make new Model/Controller/Migration in Laravel

  In this article, we have included steps to create a model and controller or resource controller with the help of command line(CLI). Here w...