[WxPython][Tutorial] Notespad W.I.P.

Post here if you need help with creating a Graphical User Interface in Python.

[WxPython][Tutorial] Notespad W.I.P.

Postby Yoriz » Fri Mar 01, 2013 1:22 am

NOTE: This is still W.I.P.

In this tutorial I'm going to attempted to to build a notespad in stages and describe what each part of the code is doing.

I am using python version 2.7.3, WxPython version 2.9.4.0, and windowsXp.
WxPython site link

Links to existing WxPython tutorials that are probably way better then mine :lol:
http://wiki.wxpython.org/Getting%20Started
http://zetcode.com/wxpython/
http://wiki.wxpython.org/AnotherTutorial

Lets get started.
Code: Select all
import wx  # 1

if __name__ == '__main__':  # 2
    wx_app = wx.App(False)  # 3
    wx_app.MainLoop()  # 4

  1. Imports the wxPython module.
  2. This means only run the following code when you run this module directly, if you import this module, the following code will not run.
  3. Every Wxpython application needs one wx.App object, the False parameter means don't redirect stdout and stderr to a window.
  4. The method call MainLoop on the App object starts an infinite loop that checks for 'events', all the while there is a wx window in existence.
    we will get to events later, running this code as it is, will just exit the loop as there is no window created yet.

Lets create a Frame so we can actually see something happen, Running this code we should now actually see a very basic window, the mainloop is now infinitely looping, by clicking on the frames X an event will be sent to the App telling it that this window has been closed and as there are no other windows in existence will also exit the mainloop.
Code: Select all
import wx

if __name__ == '__main__':
    wx_app = wx.App(False)
    frame = wx.Frame(None)  # 1
    frame.Show()  # 2
    wx_app.MainLoop()

  1. To start with we create a wx.Frame, a Frame is a container object that we can add other wx objects to, calling None tells the frame that it has no parent.
  2. To make the frame visible we have to call its Show method.

Lets make it a little more interesting, rather then just using the basic wx Frame we'll make our own class of Frame and call it Notepad.
Code: Select all
import wx


class Notespad(wx.Frame):  # 1
    def __init__(self, *args, **kwargs):  # 2
        super(Notespad, self).__init__(*args, **kwargs)  # 3
        self.SetTitle('Untitled - Notespad')  # 4

        panel = wx.Panel(self)  # 5

       sizer = wx.BoxSizer(wx.VERTICAL)  # 6

        sizer.Add(panel, 1, wx.EXPAND)  # 7

        self.SetSizer(sizer)  # 8

        self.Layout()  # 9


if __name__ == '__main__':
    wx_app = wx.App(False)
    frame = Notespad(None)  # 10
    frame.Show()
    wx_app.MainLoop()

  1. Creates a new class named Notespad which inherits from a wxFrame.
  2. Allows us to pass in any attributes.
  3. The attributes we passed in are given to the wxFrame.
  4. Sets the title of the frame.
  5. Creates a wxPanel object, this is a container object that sits on the frame as a background, we tell it that the wxFrame is its parent by giving it the attribute self.
  6. Creates a sizer, this is a kind of invisible container that automatically arranges its objects on the sizer's owner.
  7. Adds the panel we created to the sizer, the second attribute number 1 is the proportion of the of the space in the sizer it will take, and as we set the sizer to be a vertical that's the direction the proportion relates to, for instance if we was to add another object to the sizer with the same proportion, both objects would take up half of the available space each in the vertical direction. The third attribute wxExpand tells the sizer to stretch the object to take up the full width in the opposite direction, the Horizontal in this case.
  8. the wxFrame is told to let the sizer we created, stretch into the available space it has.
  9. Calling this tells any sizer to arrange the objects there are controlling the position and size of.
  10. Now we are creating an instance of our own wxFrame and again passing in the attribute None to indicate that it has no parent

Its not a very good Notespad yet as we cant even type any text into it, lets fix that.
Code: Select all
import wx


class Notespad(wx.Frame):
    def __init__(self, *args, **kwargs):
        super(Notespad, self).__init__(*args, **kwargs)
        self.SetTitle('Untitled - Notepad')
        panel = wx.Panel(self)

        txt_ctrl = wx.TextCtrl(panel, style=wx.TE_MULTILINE)  # 1

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(panel, 1, wx.EXPAND)

        p_sizer = wx.BoxSizer(wx.VERTICAL)  # 2

        p_sizer.Add(txt_ctrl, 1, wx.EXPAND)  # 3

        panel.SetSizer(p_sizer)  # 4

        self.SetSizer(sizer)
        self.Layout()


if __name__ == '__main__':
    wx_app = wx.App(False)
    frame = Notespad(None)
    frame.Show()
    wx_app.MainLoop()

  1. Creates a wxTextCtrl and set its parent as the panel the style parameter makes us able to type on more then just the first row
  2. Because the panel now has controls it needs a sizer to keep them under control just the the frame needed a sizer for the panel
  3. Adding the wxTextCtrl to the panels sizer with the same parameters used in the previous sizer's add method
  4. tells the panel to use this sizer
New Users, Read This
Join the #python-forum IRC channel on irc.freenode.net!
Image
User avatar
Yoriz
 
Posts: 1049
Joined: Fri Feb 08, 2013 1:35 am
Location: UK

Adding the beginings of the menu and our first event

Postby Yoriz » Sun Mar 03, 2013 6:56 pm

We want to be able to open existing txt files and save any new ones so let get started on adding a Menubar and a Statusbar.
Code: Select all
import wx


class Notespad(wx.Frame):
    def __init__(self, *args, **kwargs):
        super(Notespad, self).__init__(*args, **kwargs)
        self.SetTitle('Untitled - Notespad')

        self.CreateStatusBar()  # 1

        menubar = wx.MenuBar()  # 2

        self.SetMenuBar(menubar)  # 3

        file_menu = wx.Menu()  # 4

        menu_exit = file_menu.Append(-1, "Exit", "Exit the Application")  # 5

        menubar.Append(file_menu, "&File")  # 6

        panel = wx.Panel(self)
        txt_ctrl = wx.TextCtrl(panel, style=wx.TE_MULTILINE)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(panel, 1, wx.EXPAND)

        p_sizer = wx.BoxSizer(wx.VERTICAL)
        p_sizer.Add(txt_ctrl, 1, wx.EXPAND)
        panel.SetSizer(p_sizer)

        self.SetSizer(sizer)
        self.Layout()

        self.Bind(wx.EVT_MENU, self.on_menu_exit, menu_exit)  # 7

    def on_menu_exit(self, event):  # 8
        self.Close()  # 9
        event.Skip()  # 10


if __name__ == '__main__':
    wx_app = wx.App(False)
    frame = Notespad(None)
    frame.Show()
    wx_app.MainLoop()

  1. Creates a statusbar that shows information about whats happening along the bottom of the frame.
  2. Creates an empty menubar.
  3. Adds the menubar to our frame.
  4. Creates an empty menubar item.
  5. Creates a menu item named 'Exit', the first parameter is for an ID but we don't need one here so just using -1, the second parameter is the text that will be displayed for this menu item, the third parameter is the text that will be shown in the statusbar when the mouse cursor is over this menu item.
  6. Adds the file_menu item to the menubar and set its display text as File, the '&' Makes the letter following it to be a keyboard shortcut to this menu item.
  7. Creates an event, the first parameter is the type of event, the second is what to call when this event happens and the third is which menu item of the menu this event applies to.
  8. This is our event call it must receive one argument.
  9. This tells our frame to close.
  10. This line tells the app to continue processing events of this type

Lets now add another menu item to enable us to open files
Code: Select all
class Notespad(wx.Frame):
    def __init__(self, *args, **kwargs):
        super(Notespad, self).__init__(*args, **kwargs)
        self.SetTitle('Untitled - Notespad')

        self.CreateStatusBar()
        menubar = wx.MenuBar()
        self.SetMenuBar(menubar)

        file_menu = wx.Menu()

        menu_open = file_menu.Append(wx.ID_OPEN, "&Open",
                                     "Open an existing file")  # 1

        file_menu.AppendSeparator()  # 2

        menu_exit = file_menu.Append(-1, "Exit", "Exit the Application")
        menubar.Append(file_menu, "&File")

        panel = wx.Panel(self)
        self.txt_ctrl = wx.TextCtrl(panel, style=wx.TE_MULTILINE)  # 3

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(panel, 1, wx.EXPAND)

        p_sizer = wx.BoxSizer(wx.VERTICAL)
        p_sizer.Add(self.txt_ctrl, 1, wx.EXPAND)  # 4
        panel.SetSizer(p_sizer)

        self.SetSizer(sizer)
        self.Layout()

        self.Bind(wx.EVT_MENU, self.on_menu_exit, menu_exit)

        self.Bind(wx.EVT_MENU, self.on_menu_open, menu_open)  # 5

    def on_menu_exit(self, event):
        self.Close()
        event.Skip()

    def on_menu_open(self, event):  # 6
        self.file_open()  # 7
        event.Skip()  # 8

    def file_open(self):  # 9
        wildcard = "Text Documents (*.txt)|*.txt|Python Documents (*.py)|*.py"  # 10
        with wx.FileDialog(self, message='Open', wildcard=wildcard,
                           style=wx.OPEN) as dlg:  # 11
            if dlg.ShowModal() == wx.ID_OK:  # 12
                directory, filename = dlg.GetDirectory(), dlg.GetFilename()  # 13
                self.txt_ctrl.LoadFile('/'.join((directory, filename)))  # 14
                self.SetTitle('{} - Notespad'.format(filename))  # 15

if __name__ == '__main__':
    wx_app = wx.App(False)
    frame = Notespad(None)
    frame.Show()
    wx_app.MainLoop()

  1. Creates the Open menu item
  2. Adds a seperator to the menu
  3. Changed txt_ctrl to an instance variable so it can be accessed in methods
  4. Altered to account for the txt_ctrl name change
  5. Creates the Open event
  6. Creates the open event method
  7. Calls the file_open method
  8. Tell the app to continue processing events of this type
  9. method definition
  10. Creates a variable with the file types that can be opened
  11. Opens a FileDialog by using with will destroy the dialog when its finished with
  12. Shows the dialog and waits for a result back, if the result is a filename it will continue
  13. Grabs the directory and filename that was selected and stores them in variables
  14. Loads the text from the selected file into our txt_ctrl
  15. Alters the frame title to the name of the file that was opened
New Users, Read This
Join the #python-forum IRC channel on irc.freenode.net!
Image
User avatar
Yoriz
 
Posts: 1049
Joined: Fri Feb 08, 2013 1:35 am
Location: UK

Adding the ability to save, open a new file and refactoring

Postby Yoriz » Thu Mar 07, 2013 12:43 am

Time to add the ability to save a file and to open a new file, as im making this up as i go there have been some lines deleted or modified as well as new lines added
Code: Select all
import wx


class Notespad(wx.Frame):

    UNTITLED = 'Untitled'  #
    WILDCARD = 'Text Documents (*.txt)|*.txt|Python Documents (*.py)|*.py'  #

    def __init__(self, *args, **kwargs):
        #----------------------------------------------------------- Attributes
        self.file_directory = None  #
        self.file_name = self.UNTITLED  #
        self.title_string = '{}{} - NotesPad'  #

        #---------------------------------------------------------- Frame Setup
        super(Notespad, self).__init__(*args, **kwargs)
        self.CreateStatusBar()

        #----------------------------------------------------------- Frame Menu
        menubar = wx.MenuBar()
        self.SetMenuBar(menubar)

        file_menu = wx.Menu()
        menu_open = file_menu.Append(wx.ID_OPEN, '&Open',
                                     'Open an existing document')

        menu_new = file_menu.Append(wx.ID_NEW, '&New',
                                    'Creates a new document')  #
        menu_save = file_menu.Append(wx.ID_SAVE, '&Save',
                                     'Saves the active document')  #
        menu_saveas = file_menu.Append(wx.ID_SAVEAS, 'Save &As',
                                'Saves the active document with a new name')  #

        file_menu.AppendSeparator()
        menu_exit = file_menu.Append(-1, 'Exit', 'Exit the Application')
        menubar.Append(file_menu, '&File')

        #--------------------------------------------------- Panel And Controls
        panel = wx.Panel(self)
        self.txt_ctrl = wx.TextCtrl(panel, style=wx.TE_MULTILINE)

        #------------------------------------------------------- Sizer Creation
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(panel, 1, wx.EXPAND)

        p_sizer = wx.BoxSizer(wx.VERTICAL)
        p_sizer.Add(self.txt_ctrl, 1, wx.EXPAND)

        #------------------------------------------------------- Setting Sizers
        panel.SetSizer(p_sizer)
        self.SetSizer(sizer)
        self.Layout()

        #---------------------------------------------------------- Event Binds
        self.Bind(wx.EVT_MENU, self.on_menu_exit, menu_exit)
        self.Bind(wx.EVT_MENU, self.on_menu_open, menu_open)

        self.Bind(wx.EVT_MENU, self.on_menu_new, menu_new)  #
        self.Bind(wx.EVT_MENU, self.on_menu_save, menu_save)  #
        self.Bind(wx.EVT_MENU, self.on_menu_saveas, menu_saveas)  #

        #-------------------------------------------------------- Initial State
        self.set_title()  #

    #----------------------------------------------------------- Event Handlers
    def on_menu_exit(self, event):
        self.Close()
        event.Skip()

    def on_menu_open(self, event):
        self.file_open()
        event.Skip()

    def on_menu_new(self, event):  #
        self.file_new()  #
        event.Skip()  #

    def on_menu_save(self, event):  #
        self.file_save()  #
        event.Skip()  #

    def on_menu_saveas(self, event):  #
        self.file_saveas()  #
        event.Skip()  #

    #------------------------------------------------------------------ Actions
    def file_open(self):
        with wx.FileDialog(self, message='Open', wildcard=self.WILDCARD,
                           style=wx.OPEN) as dlg:
            if dlg.ShowModal() == wx.ID_OK:
                self.file_directory = dlg.GetDirectory()  #
                self.file_name = dlg.GetFilename()  #
                self.file_load()  #

    def file_load(self):  #
        full_path = '/'.join((self.file_directory, self.file_name))  #
        self.txt_ctrl.LoadFile(full_path)  #
        self.set_title()  #

    def file_new(self):  #
        self.file_directory = None  #
        self.file_name = self.UNTITLED  #
        self.txt_ctrl.Clear()  #
        self.set_title()  #

    def file_saveas(self):  #
        with wx.FileDialog(self, message='Save as', wildcard=self.WILDCARD,
                           style=wx.SAVE) as dlg:  #
            if dlg.ShowModal() == wx.ID_OK:  #
                self.file_directory = dlg.GetDirectory()  #
                self.file_name = dlg.GetFilename()  #
                self.file_save()  #

    def file_save(self):  #
        if self.file_name == self.UNTITLED:  #
            self.file_saveas()  #
        else:  #
            full_path = '/'.join((self.file_directory, self.file_name))  #
            self.txt_ctrl.SaveFile(full_path)  #
            self.set_title()  #

    def set_title(self):  #
        is_modified = '*' if self.txt_ctrl.IsModified() else ''  #
        self.SetTitle(self.title_string.format(is_modified, self.file_name))  #


if __name__ == '__main__':
    wx_app = wx.App(False)
    frame = Notespad(None)
    frame.Show()
    wx_app.MainLoop()

  1. The list of modifications will follow for the time being new/changed line end with #

Is any one following this and have any questions or comments so far :?:
New Users, Read This
Join the #python-forum IRC channel on irc.freenode.net!
Image
User avatar
Yoriz
 
Posts: 1049
Joined: Fri Feb 08, 2013 1:35 am
Location: UK

Re: [WxPython][Tutorial] Notespad W.I.P.

Postby Yoriz » Sun Mar 24, 2013 1:06 pm

Doesn't look like anyone is really interested in following this and i was starting to get bogged down in listing the changes to the code.
I might still add to it at some point but just mark the changes with a # and if there are any question on any particular bits try and answer them.
New Users, Read This
Join the #python-forum IRC channel on irc.freenode.net!
Image
User avatar
Yoriz
 
Posts: 1049
Joined: Fri Feb 08, 2013 1:35 am
Location: UK


Return to GUI

Who is online

Users browsing this forum: No registered users and 1 guest