[WxPython] [Tutorial] Notespad - Create a text editor - W.I.P. - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: General (https://python-forum.io/forum-1.html) +--- Forum: Tutorials (https://python-forum.io/forum-4.html) +---- Forum: GUI tutorials (https://python-forum.io/forum-34.html) +---- Thread: [WxPython] [Tutorial] Notespad - Create a text editor - W.I.P. (/thread-68.html) Pages:
1
2
|
[Tutorial] Notespad - Create a text editor - W.I.P. - Yoriz - Sep-17-2016 NOTE: This is still W.I.P. In this tutorial, I'm going to attempt to build a notespad in WxPython in stages and describe what each part of the code is doing. Updated to work with python 3 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/ Lets get started. import wx # 1 if __name__ == '__main__': # 2 wx_app = wx.App(False) # 3 wx_app.MainLoop() # 4
import wx if __name__ == '__main__': wx_app = wx.App(False) frame = wx.Frame(None) # 1 frame.Show() # 2 wx_app.MainLoop()
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()
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()
Adding the beginings of the menu and our first event - Yoriz - Sep-17-2016 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. 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()
import wx 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()
RE: [WxPython][Tutorial] Notespad W.I.P. - Yoriz - Sep-17-2016 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 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.FD_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.FD_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()
RE: [WxPython][Tutorial] Notespad W.I.P. - llanitedave - Sep-27-2016 Sizers were always my bane in wxPython. I've never been able to figure out their associated attributes. RE: [WxPython][Tutorial] Notespad W.I.P. - j.crater - Sep-27-2016 (Sep-27-2016, 05:50 AM)llanitedave Wrote: Sizers were always my bane in wxPython. I've never been able to figure out their associated attributes. Same here, even when I was still tinkering with wxWidgets in C++, everything went rather well except the part with sizers =)) RE: [Tutorial] Notespad W.I.P. - Yoriz - Mar-27-2019 I've taken this back a step, updated to python 3 and refactored the code a bit. The only change i needed to update to python 3 was style=wx.OPENto style=wx.FD_OPENI've also changed self.SetTitle('{} - Notespad'.format(filename))to use new string formating self.SetTitle(f'{filename} - Notespad')The new code import wx class Notespad(wx.Frame): def __init__(self, *args, **kwargs): super(Notespad, self).__init__(*args, **kwargs) self.frame_settings() self.create_menu() self.create_gui_items() self.create_sizers() def frame_settings(self): self.SetTitle('Untitled - Notespad') self.CreateStatusBar() def create_menu(self): menubar = wx.MenuBar() self.SetMenuBar(menubar) file_menu = wx.Menu() menu_open = file_menu.Append(wx.ID_OPEN, '&Open', 'Open an existing file') file_menu.AppendSeparator() menu_exit = file_menu.Append(-1, 'E&xit', 'Exit the Application') menubar.Append(file_menu, '&File') self.Bind(wx.EVT_MENU, self.on_menu_exit, menu_exit) self.Bind(wx.EVT_MENU, self.on_menu_open, menu_open) def create_gui_items(self): self.frame_panel = wx.Panel(self) self.txt_ctrl = wx.TextCtrl(self.frame_panel, style=wx.TE_MULTILINE | wx.BORDER_NONE) def create_sizers(self): sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.frame_panel, 1, wx.EXPAND) p_sizer = wx.BoxSizer(wx.VERTICAL) p_sizer.Add(self.txt_ctrl, 1, wx.EXPAND) self.frame_panel.SetSizer(p_sizer) self.SetSizer(sizer) self.Layout() def on_menu_exit(self, event): self.Close() event.Skip() def on_menu_open(self, event): wildcard = 'Text Documents (*.txt)|*.txt|Python Documents (*.py)|*.py' with wx.FileDialog(self, message='Open', wildcard=wildcard, style=wx.FD_OPEN) as dlg: if dlg.ShowModal() == wx.ID_OK: directory, filename = dlg.GetDirectory(), dlg.GetFilename() self.load_file_to_txt_ctrl(directory, filename) event.Skip() def load_file_to_txt_ctrl(self, directory, filename): self.txt_ctrl.LoadFile('/'.join((directory, filename))) self.SetTitle(f'{filename} - Notespad') if __name__ == '__main__': wx_app = wx.App(False) frame = Notespad(None) frame.Show() wx_app.MainLoop() RE: [Tutorial] Notespad W.I.P. - Yoriz - Mar-28-2019 Adding the opening of a new file to the file menu. In the method create_menu adding the following will create a new menu item.docs.wxpython.org - wx.Menu menu_new = file_menu.Append(wx.ID_NEW, '&New', 'Creates a new document') # 1Parameters
The following binds a method to be called when the New menu item is clicked.docs.wxpython.org - Events and Event Handling self.Bind(wx.EVT_MENU, self.on_menu_new, menu_new) # 2Parameters
Create the method on_menu_new def on_menu_new(self, event): # 3 self.txt_ctrl.Clear() # 4 self.SetTitle('Untitled - Notespad') # 5 event.Skip() # 6
The full code now. import wx class Notespad(wx.Frame): def __init__(self, *args, **kwargs): super(Notespad, self).__init__(*args, **kwargs) self.frame_settings() self.create_menu() self.create_gui_items() self.create_sizers() def frame_settings(self): self.SetTitle('Untitled - Notespad') self.CreateStatusBar() def create_menu(self): menubar = wx.MenuBar() self.SetMenuBar(menubar) file_menu = wx.Menu() menu_open = file_menu.Append(wx.ID_OPEN, '&Open', 'Open an existing file') menu_new = file_menu.Append(wx.ID_NEW, '&New', 'Creates a new document') # 1 file_menu.AppendSeparator() menu_exit = file_menu.Append(-1, 'E&xit', 'Exit the Application') menubar.Append(file_menu, '&File') 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) # 2 def create_gui_items(self): self.frame_panel = wx.Panel(self) self.txt_ctrl = wx.TextCtrl(self.frame_panel, style=wx.TE_MULTILINE | wx.BORDER_NONE) def create_sizers(self): sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.frame_panel, 1, wx.EXPAND) p_sizer = wx.BoxSizer(wx.VERTICAL) p_sizer.Add(self.txt_ctrl, 1, wx.EXPAND) self.frame_panel.SetSizer(p_sizer) self.SetSizer(sizer) self.Layout() def on_menu_exit(self, event): self.Close() event.Skip() def on_menu_open(self, event): wildcard = 'Text Documents (*.txt)|*.txt|Python Documents (*.py)|*.py' with wx.FileDialog(self, message='Open', wildcard=wildcard, style=wx.FD_OPEN) as dlg: if dlg.ShowModal() == wx.ID_OK: directory, filename = dlg.GetDirectory(), dlg.GetFilename() self.load_file_to_txt_ctrl(directory, filename) event.Skip() def on_menu_new(self, event): # 3 self.txt_ctrl.Clear() # 4 self.SetTitle('Untitled - Notespad') # 5 event.Skip() # 6 def load_file_to_txt_ctrl(self, directory, filename): self.txt_ctrl.LoadFile('/'.join((directory, filename))) self.SetTitle(f'{filename} - Notespad') if __name__ == '__main__': wx_app = wx.App(False) frame = Notespad(None) frame.Show() wx_app.MainLoop() RE: [Tutorial] Notespad W.I.P. - Yoriz - Apr-30-2019 At the moment, if there is a file already open that has been modified but not saved and you open another file or a new file, the changes will be lost. To keep track of the file that is open i'm going to use pathlib from pathlib import PathI added self.path = None to the Notespad __init__ to indicate that no file is open when the program starts.and added a constant UNTITLED = 'Untitled' for an unsaved file name.The following new method when called will set the frames title based on the paths name or set it to untitled def update_title(self): name = UNTITLED if self.path: name = self.path.name self.SetTitle(f'{name} - Notespad')The following new method uses a wx.MessageDialog to ask if changes are to be saved def save_changes_msg_dialog(self): path = self.path or UNTITLED dlg = wx.MessageDialog( self, (f'Do you want to save changes to {path}?'), 'Notespad', wx.YES_NO | wx.CANCEL | wx.CENTER) dlg.SetYesNoLabels('Save', 'Don\'t Save') return dlg.ShowModal()The following new method uses self.txt_ctrl.IsModified to find out if the contents of the text control has been altereddef save_if_modified(self): saved_or_dont_save = True if self.txt_ctrl.IsModified(): dlg_result = self.save_changes_msg_dialog() if dlg_result == wx.ID_CANCEL: saved_or_dont_save = False elif dlg_result == wx.ID_YES: self.save() if not self.path: saved_or_dont_save = False return saved_or_dont_saveIf it has been altered it uses the self.save_changes_msg_dialog method to save the changes if required and to return true if its OK to precede or false if saving was cancelled or it was decided to cancel the action.Here is the save and saveas methods def save(self): if not self.path: # 1 self.save_as() #1 else: # self.txt_ctrl.SaveFile(str(self.path)) self.update_title() def save_as(self): with wx.FileDialog(self, message='Save as', wildcard=WILDCARD, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as dlg: if dlg.ShowModal() == wx.ID_OK: directory, filename = dlg.GetDirectory(), dlg.GetFilename() self.path = Path(directory).joinpath(filename) self.save() save calls save_as if there is no path, otherwise the contents of the text control is saved to the path and the title updated.save_as opens up a wx.FileDialog that asks where to save the file to, if a file is chosen, self.path is set to the chosen file and save is calledon_menu_new and on_menu_open have been changed to call self.save_if_modified and only carry out opening a new file or opening an existing file if ok to do so.def on_menu_new(self, event): if self.save_if_modified(): self.new() def on_menu_open(self, event): if self.save_if_modified(): self.open() def on_menu_save(self, event): self.save() def on_menu_saveas(self, event): self.save_as()The new method is quite straightforward def new(self): self.txt_ctrl.Clear() self.path = None self.update_title()The open methid is making use of the pathlib Path def open(self): with wx.FileDialog(self, message='Open', wildcard=WILDCARD, style=wx.FD_OPEN) as dlg: if dlg.ShowModal() == wx.ID_OK: directory, filename = dlg.GetDirectory(), dlg.GetFilename() self.path = Path(directory).joinpath(filename) # self.txt_ctrl.LoadFile(str(self.path)) # self.update_title()A new event bind has been added self.Bind(wx.EVT_CLOSE, self.on_close_evt)this is triggered by either the close method called from the menu close or from clicking the X on the frame. The event itself def on_close_evt(self, event): if event.CanVeto(): if not self.save_if_modified(): event.Veto() return self.Destroy()checks if it can interrupt the closing of the frame, it will then also check if unsaved changes should be saved. the window will only be destroyed if there where no changes or if changes were made they were saved or decided to not save. The menu items save and savas where added, the binds made and the events to call the save and saveas methods self.menu_save = file_menu.Append(wx.ID_SAVE, '&Save', 'Saves the active document') self.menu_saveas = file_menu.Append(wx.ID_SAVEAS, 'Save &As', 'Saves the active document with a new name') self.Bind(wx.EVT_MENU, self.on_menu_save, self.menu_save) self.Bind(wx.EVT_MENU, self.on_menu_saveas, self.menu_saveas) def on_menu_save(self, event): self.save() def on_menu_saveas(self, event): self.save_as()Here is the new full code from pathlib import Path import wx WILDCARD = 'Text Documents (*.txt)|*.txt|Python Documents (*.py)|*.py' UNTITLED = 'Untitled' class Notespad(wx.Frame): def __init__(self, *args, **kwargs): super(Notespad, self).__init__(*args, **kwargs) self.path = None self.frame_settings() self.create_menu() self.create_gui_items() self.create_sizers() self.create_binds() def frame_settings(self): self.update_title() self.CreateStatusBar() self.SetMinSize((600, 400)) self.CentreOnScreen() def create_menu(self): menubar = wx.MenuBar() self.SetMenuBar(menubar) file_menu = wx.Menu() self.menu_new = file_menu.Append(wx.ID_NEW, '&New', 'Creates a new document') self.menu_open = file_menu.Append(wx.ID_OPEN, '&Open', 'Open an existing file') self.menu_save = file_menu.Append(wx.ID_SAVE, '&Save', 'Saves the active document') self.menu_saveas = file_menu.Append(wx.ID_SAVEAS, 'Save &As', 'Saves the active document with a new name') file_menu.AppendSeparator() self.menu_exit = file_menu.Append(-1, 'E&xit', 'Exit the Application') menubar.Append(file_menu, '&File') def create_gui_items(self): self.frame_panel = wx.Panel(self) self.txt_ctrl = wx.TextCtrl(self.frame_panel, style=wx.TE_MULTILINE | wx.BORDER_NONE) def create_sizers(self): sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.frame_panel, 1, wx.EXPAND) p_sizer = wx.BoxSizer(wx.VERTICAL) p_sizer.Add(self.txt_ctrl, 1, wx.EXPAND) self.frame_panel.SetSizer(p_sizer) self.SetSizer(sizer) self.Layout() def create_binds(self): self.Bind(wx.EVT_MENU, self.on_menu_new, self.menu_new) self.Bind(wx.EVT_MENU, self.on_menu_open, self.menu_open) self.Bind(wx.EVT_MENU, self.on_menu_save, self.menu_save) self.Bind(wx.EVT_MENU, self.on_menu_saveas, self.menu_saveas) self.Bind(wx.EVT_MENU, self.on_menu_exit, self.menu_exit) self.Bind(wx.EVT_CLOSE, self.on_close_evt) def on_menu_new(self, event): if self.save_if_modified(): self.new() def on_menu_open(self, event): if self.save_if_modified(): self.open() def on_menu_save(self, event): self.save() def on_menu_saveas(self, event): self.save_as() def on_menu_exit(self, event): self.Close() def on_close_evt(self, event): if event.CanVeto(): if not self.save_if_modified(): event.Veto() return self.Destroy() def update_title(self): name = UNTITLED if self.path: name = self.path.name self.SetTitle(f'{name} - Notespad') def save_changes_msg_dialog(self): path = self.path or UNTITLED dlg = wx.MessageDialog( self, (f'Do you want to save changes to {path}?'), 'Notespad', wx.YES_NO | wx.CANCEL | wx.CENTER) dlg.SetYesNoLabels('Save', 'Don\'t Save') return dlg.ShowModal() def save_if_modified(self): saved_or_dont_save = True if self.txt_ctrl.IsModified(): dlg_result = self.save_changes_msg_dialog() if dlg_result == wx.ID_CANCEL: saved_or_dont_save = False elif dlg_result == wx.ID_YES: self.save() if not self.path: saved_or_dont_save = False return saved_or_dont_save def new(self): self.txt_ctrl.Clear() self.path = None self.update_title() def open(self): with wx.FileDialog(self, message='Open', wildcard=WILDCARD, style=wx.FD_OPEN) as dlg: if dlg.ShowModal() == wx.ID_OK: directory, filename = dlg.GetDirectory(), dlg.GetFilename() self.path = Path(directory).joinpath(filename) self.txt_ctrl.LoadFile(str(self.path)) self.update_title() def save(self): if not self.path: self.save_as() else: # self.txt_ctrl.SaveFile(str(self.path)) self.update_title() def save_as(self): with wx.FileDialog(self, message='Save as', wildcard=WILDCARD, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as dlg: if dlg.ShowModal() == wx.ID_OK: directory, filename = dlg.GetDirectory(), dlg.GetFilename() self.path = Path(directory).joinpath(filename) self.save() if __name__ == '__main__': wx_app = wx.App(False) frame = Notespad(None) frame.Show() wx_app.MainLoop() RE: [Tutorial] Notespad W.I.P. - Yoriz - Apr-30-2019 Added a help menu that opens this forum thread to ask for help or any questions. add an import import webbrowserA url constant is added HELP_URL = 'https://python-forum.io/Thread-WxPython-Tutorial-Notespad-W-I-P'A new menu is created and a help item is added to it help_menu = wx.Menu() self.menu_help = help_menu.Append(wx.ID_HELP, '&Help', 'Get Help from python-forum.io') menubar.Append(help_menu, '&Help')A simple bind self.Bind(wx.EVT_MENU, self.on_menu_help, self.menu_help)A simple event calls the open_forum_page methoddef on_menu_help(self, event): self.open_forum_page()And this open a web browser to this forum thread def open_forum_page(self): webbrowser.open(HELP_URL, 2)Full code from pathlib import Path import webbrowser import wx WILDCARD = 'Text Documents (*.txt)|*.txt|Python Documents (*.py)|*.py' UNTITLED = 'Untitled' HELP_URL = 'https://python-forum.io/Thread-WxPython-Tutorial-Notespad-W-I-P' class Notespad(wx.Frame): def __init__(self, *args, **kwargs): super(Notespad, self).__init__(*args, **kwargs) self.path = None self.frame_settings() self.create_menu() self.create_gui_items() self.create_sizers() self.create_binds() def frame_settings(self): self.update_title() self.CreateStatusBar() self.SetMinSize((600, 400)) self.CentreOnScreen() def create_menu(self): menubar = wx.MenuBar() self.SetMenuBar(menubar) file_menu = wx.Menu() self.menu_new = file_menu.Append(wx.ID_NEW, '&New', 'Creates a new document') self.menu_open = file_menu.Append(wx.ID_OPEN, '&Open', 'Open an existing file') self.menu_save = file_menu.Append(wx.ID_SAVE, '&Save', 'Saves the active document') self.menu_saveas = file_menu.Append(wx.ID_SAVEAS, 'Save &As', 'Saves the active document with a new name') file_menu.AppendSeparator() self.menu_exit = file_menu.Append(-1, 'E&xit', 'Exit the Application') menubar.Append(file_menu, '&File') help_menu = wx.Menu() self.menu_help = help_menu.Append(wx.ID_HELP, '&Help', 'Get Help from python-forum.io') menubar.Append(help_menu, '&Help') def create_gui_items(self): self.frame_panel = wx.Panel(self) self.txt_ctrl = wx.TextCtrl(self.frame_panel, style=wx.TE_MULTILINE | wx.BORDER_NONE) def create_sizers(self): sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.frame_panel, 1, wx.EXPAND) p_sizer = wx.BoxSizer(wx.VERTICAL) p_sizer.Add(self.txt_ctrl, 1, wx.EXPAND) self.frame_panel.SetSizer(p_sizer) self.SetSizer(sizer) self.Layout() def create_binds(self): self.Bind(wx.EVT_MENU, self.on_menu_new, self.menu_new) self.Bind(wx.EVT_MENU, self.on_menu_open, self.menu_open) self.Bind(wx.EVT_MENU, self.on_menu_save, self.menu_save) self.Bind(wx.EVT_MENU, self.on_menu_saveas, self.menu_saveas) self.Bind(wx.EVT_MENU, self.on_menu_help, self.menu_help) self.Bind(wx.EVT_MENU, self.on_menu_exit, self.menu_exit) self.Bind(wx.EVT_CLOSE, self.on_close_evt) def on_menu_new(self, event): if self.save_if_modified(): self.new() def on_menu_open(self, event): if self.save_if_modified(): self.open() def on_menu_save(self, event): self.save() def on_menu_saveas(self, event): self.save_as() def on_menu_help(self, event): self.open_forum_page() def on_menu_exit(self, event): self.Close() def on_close_evt(self, event): if event.CanVeto(): if not self.save_if_modified(): event.Veto() return self.Destroy() def update_title(self): name = UNTITLED if self.path: name = self.path.name self.SetTitle(f'{name} - Notespad') def save_changes_msg_dialog(self): path = self.path or UNTITLED dlg = wx.MessageDialog( self, (f'Do you want to save changes to {path}?'), 'Notespad', wx.YES_NO | wx.CANCEL | wx.CENTER) dlg.SetYesNoLabels('Save', 'Don\'t Save') return dlg.ShowModal() def save_if_modified(self): saved_or_dont_save = True if self.txt_ctrl.IsModified(): dlg_result = self.save_changes_msg_dialog() if dlg_result == wx.ID_CANCEL: saved_or_dont_save = False elif dlg_result == wx.ID_YES: self.save() if not self.path: saved_or_dont_save = False return saved_or_dont_save def new(self): self.txt_ctrl.Clear() self.path = None self.update_title() def open(self): with wx.FileDialog(self, message='Open', wildcard=WILDCARD, style=wx.FD_OPEN) as dlg: if dlg.ShowModal() == wx.ID_OK: directory, filename = dlg.GetDirectory(), dlg.GetFilename() self.path = Path(directory).joinpath(filename) self.txt_ctrl.LoadFile(str(self.path)) self.update_title() def save(self): if not self.path: self.save_as() else: # self.txt_ctrl.SaveFile(str(self.path)) self.update_title() def save_as(self): with wx.FileDialog(self, message='Save as', wildcard=WILDCARD, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as dlg: if dlg.ShowModal() == wx.ID_OK: directory, filename = dlg.GetDirectory(), dlg.GetFilename() self.path = Path(directory).joinpath(filename) self.save() def open_forum_page(self): webbrowser.open(HELP_URL, 2) if __name__ == '__main__': wx_app = wx.App(False) frame = Notespad(None) frame.Show() wx_app.MainLoop() RE: [Tutorial] Notespad W.I.P. - Yoriz - May-03-2019 Similar to the other menu items, adding a edit menu with undo, cut, copy, paste and delete edit_menu = wx.Menu() self.menu_undo = edit_menu.Append(wx.ID_UNDO, '&Undo', 'Undo change') edit_menu.AppendSeparator() self.menu_cut = edit_menu.Append(wx.ID_CUT, 'Cu&t', 'Cut the selected text') self.menu_copy = edit_menu.Append(wx.ID_COPY, '&Copy', 'Copy the selected text') self.menu_paste = edit_menu.Append(wx.ID_PASTE, '&Paste', 'Paste from the clipboard') self.menu_delete = edit_menu.Append(wx.ID_DELETE, 'De&lete', 'Delete selected text') self.menubar.Append(edit_menu, '&Edit')binds for the new menu items self.Bind(wx.EVT_MENU, self.on_menu_undo, self.menu_undo) self.Bind(wx.EVT_UPDATE_UI, self.on_menu_undo_update, self.menu_undo) self.Bind(wx.EVT_MENU, self.on_menu_cut, self.menu_cut) self.Bind(wx.EVT_UPDATE_UI, self.on_menu_cut_update, self.menu_cut) self.Bind(wx.EVT_MENU, self.on_menu_copy, self.menu_copy) self.Bind(wx.EVT_UPDATE_UI, self.on_menu_copy_update, self.menu_copy) self.Bind(wx.EVT_MENU, self.on_menu_paste, self.menu_paste) self.Bind(wx.EVT_UPDATE_UI, self.on_menu_paste_update, self.menu_paste) self.Bind(wx.EVT_MENU, self.on_menu_delete, self.menu_delete) self.Bind( wx.EVT_UPDATE_UI, self.on_menu_delete_update, self.menu_delete)These have a type of event that has not been used so far wx.EVT_UPDATE_UI https://wxpython.org/Phoenix/docs/html/wx.UpdateUIEvent.html Wrote:This class is used for pseudo-events which are called by wxWidgets to give an application the chance to update various user interface elements. .... see link for more details Here are the event handlers for undo def on_menu_undo(self, event): self.txt_ctrl.Undo() def on_menu_undo_update(self, event): event.Enable(self.txt_ctrl.CanUndo()) on_menu_undo is straight forward, on_menu_undo_update will enable or disable the menu item depending on if the text control can be undone or not.The events for cut, copy and paste are similar to undo For the delete events def on_menu_delete(self, event): self.txt_ctrl.Remove(*self.txt_ctrl.GetSelection()) def on_menu_delete_update(self, event): event.Enable(bool(self.txt_ctrl.GetStringSelection())) on_menu_delete uses the text controls remove method which removes the text starting at the first given position up to (but not including) the character at the last position.The values for this are obtained by using text controls GetSelection method.on_menu_delete_update enables or disables the delete menu item by using bool to get a true or false from the result of method GetSelection which gets the text currently selected in the control or if there is no selection, the returned string is empty.Full Code from pathlib import Path import webbrowser import wx WILDCARD = 'Text Documents (*.txt)|*.txt|Python Documents (*.py)|*.py' UNTITLED = 'Untitled' HELP_URL = 'https://python-forum.io/Thread-WxPython-Tutorial-Notespad-W-I-P' class Notespad(wx.Frame): def __init__(self, *args, **kwargs): super(Notespad, self).__init__(*args, **kwargs) self.path = None self.frame_settings() self.create_menus() self.create_gui_items() self.create_sizers() self.add_binds() def frame_settings(self): self.update_title() self.CreateStatusBar() self.SetMinSize((600, 400)) self.CentreOnScreen() def create_menus(self): self.menubar = wx.MenuBar() self.SetMenuBar(self.menubar) self.create_file_menu() self.create_edit_menu() self.create_help_menu() def create_file_menu(self): file_menu = wx.Menu() self.menu_new = file_menu.Append(wx.ID_NEW, '&New', 'Creates a new document') self.menu_open = file_menu.Append(wx.ID_OPEN, '&Open', 'Open an existing file') self.menu_save = file_menu.Append(wx.ID_SAVE, '&Save', 'Saves the active document') self.menu_saveas = file_menu.Append(wx.ID_SAVEAS, 'Save &As', 'Saves the active document with a new name') file_menu.AppendSeparator() self.menu_exit = file_menu.Append(-1, 'E&xit', 'Exit the Application') self.menubar.Append(file_menu, '&File') def create_edit_menu(self): edit_menu = wx.Menu() self.menu_undo = edit_menu.Append(wx.ID_UNDO, '&Undo', 'Undo change') edit_menu.AppendSeparator() self.menu_cut = edit_menu.Append(wx.ID_CUT, 'Cu&t', 'Cut the selected text') self.menu_copy = edit_menu.Append(wx.ID_COPY, '&Copy', 'Copy the selected text') self.menu_paste = edit_menu.Append(wx.ID_PASTE, '&Paste', 'Paste from the clipboard') self.menu_delete = edit_menu.Append(wx.ID_DELETE, 'De&lete', 'Delete selected text') self.menubar.Append(edit_menu, '&Edit') def create_help_menu(self): help_menu = wx.Menu() self.menu_help = help_menu.Append(wx.ID_HELP, '&Help', 'Get Help from python-forum.io') self.menubar.Append(help_menu, '&Help') def create_gui_items(self): self.frame_panel = wx.Panel(self) self.txt_ctrl = wx.TextCtrl(self.frame_panel, style=wx.TE_MULTILINE | wx.BORDER_NONE) def create_sizers(self): sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.frame_panel, 1, wx.EXPAND) p_sizer = wx.BoxSizer(wx.VERTICAL) p_sizer.Add(self.txt_ctrl, 1, wx.EXPAND) self.frame_panel.SetSizer(p_sizer) self.SetSizer(sizer) self.Layout() def add_binds(self): self.add_frame_binds() self.add_fle_menu_binds() self.add_edit_menu_binds() self.add_help_menu_binds() def add_frame_binds(self): self.Bind(wx.EVT_CLOSE, self.on_close_evt) def add_fle_menu_binds(self): self.Bind(wx.EVT_MENU, self.on_menu_new, self.menu_new) self.Bind(wx.EVT_MENU, self.on_menu_open, self.menu_open) self.Bind(wx.EVT_MENU, self.on_menu_save, self.menu_save) self.Bind(wx.EVT_MENU, self.on_menu_saveas, self.menu_saveas) self.Bind(wx.EVT_MENU, self.on_menu_exit, self.menu_exit) def add_edit_menu_binds(self): self.Bind(wx.EVT_MENU, self.on_menu_undo, self.menu_undo) self.Bind(wx.EVT_UPDATE_UI, self.on_menu_undo_update, self.menu_undo) self.Bind(wx.EVT_MENU, self.on_menu_cut, self.menu_cut) self.Bind(wx.EVT_UPDATE_UI, self.on_menu_cut_update, self.menu_cut) self.Bind(wx.EVT_MENU, self.on_menu_copy, self.menu_copy) self.Bind(wx.EVT_UPDATE_UI, self.on_menu_copy_update, self.menu_copy) self.Bind(wx.EVT_MENU, self.on_menu_paste, self.menu_paste) self.Bind(wx.EVT_UPDATE_UI, self.on_menu_paste_update, self.menu_paste) self.Bind(wx.EVT_MENU, self.on_menu_delete, self.menu_delete) self.Bind( wx.EVT_UPDATE_UI, self.on_menu_delete_update, self.menu_delete) def add_help_menu_binds(self): self.Bind(wx.EVT_MENU, self.on_menu_help, self.menu_help) def on_menu_exit(self, event): self.Close() def on_menu_new(self, event): if self.save_if_modified(): self.new() def on_menu_open(self, event): if self.save_if_modified(): self.open() def on_menu_save(self, event): self.save() def on_menu_saveas(self, event): self.save_as() def on_menu_undo(self, event): self.txt_ctrl.Undo() def on_menu_undo_update(self, event): event.Enable(self.txt_ctrl.CanUndo()) def on_menu_cut(self, event): self.txt_ctrl.Cut() def on_menu_cut_update(self, event): event.Enable(self.txt_ctrl.CanCut()) def on_menu_copy(self, event): self.txt_ctrl.Copy() def on_menu_copy_update(self, event): event.Enable(self.txt_ctrl.CanCopy()) def on_menu_paste(self, event): self.txt_ctrl.Paste() def on_menu_paste_update(self, event): event.Enable(self.txt_ctrl.CanPaste()) def on_menu_delete(self, event): self.txt_ctrl.Remove(*self.txt_ctrl.GetSelection()) def on_menu_delete_update(self, event): event.Enable(bool(self.txt_ctrl.GetStringSelection())) def on_menu_help(self, event): self.open_forum_page() def on_close_evt(self, event): if event.CanVeto(): if not self.save_if_modified(): event.Veto() return self.Destroy() def update_title(self): name = UNTITLED if self.path: name = self.path.name self.SetTitle(f'{name} - Notespad') def save_changes_msg_dialog(self): path = self.path or UNTITLED dlg = wx.MessageDialog( self, (f'Do you want to save changes to {path}?'), 'Notespad', wx.YES_NO | wx.CANCEL | wx.CENTER) dlg.SetYesNoLabels('Save', 'Don\'t Save') return dlg.ShowModal() def save_if_modified(self): saved_or_dont_save = True if self.txt_ctrl.IsModified(): dlg_result = self.save_changes_msg_dialog() if dlg_result == wx.ID_CANCEL: saved_or_dont_save = False elif dlg_result == wx.ID_YES: self.save() if not self.path: saved_or_dont_save = False return saved_or_dont_save def new(self): self.txt_ctrl.Clear() self.path = None self.update_title() def open(self): with wx.FileDialog(self, message='Open', wildcard=WILDCARD, style=wx.FD_OPEN) as dlg: if dlg.ShowModal() == wx.ID_OK: directory, filename = dlg.GetDirectory(), dlg.GetFilename() self.path = Path(directory).joinpath(filename) self.txt_ctrl.LoadFile(str(self.path)) self.update_title() def save(self): if not self.path: self.save_as() else: # self.txt_ctrl.SaveFile(str(self.path)) self.update_title() def save_as(self): with wx.FileDialog(self, message='Save as', wildcard=WILDCARD, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as dlg: if dlg.ShowModal() == wx.ID_OK: directory, filename = dlg.GetDirectory(), dlg.GetFilename() self.path = Path(directory).joinpath(filename) self.save() def open_forum_page(self): webbrowser.open(HELP_URL, 2) if __name__ == '__main__': wx_app = wx.App(False) frame = Notespad(None) frame.Show() wx_app.MainLoop() |