Автор оригинала: Mike Driscoll.
Я думал, что было бы хорошей идеей, чтобы написать приложение образца в Wxpython, чтобы показать, как поставить все куски вместе и сделать что-то полезное. На моей дневной работе я создал небольшую программу для отправки электронных писем, потому что у нас было много пользователей, которые пропустили функциональность Mailto, которую мы проиграли, когда мы переключились с Exchange/Outlook в Зимбру. Следует отметить, что это только приложение Windows только в настоящее время, но не должно быть слишком сложно, чтобы сделать его больше ОС-Агностики.
Я разделим эту статью на три части: сначала создает интерфейс; Во-вторых устанавливает обработку данных, и в третьем будет создание исполняемого окна и подключение к обработчику MailTo. Когда мы закончим, графический интерфейс будет выглядеть что-то подобное:
Чтобы следовать, вам понадобится следующее:
Создание интерфейса
Давайте перейдем по коду ниже. Как видите, я основываю это приложение на объекте wx.frame и экземпляром wx.pysimpleapp, чтобы сделать приложение.
import os import sys import urllib import wx import mail_ico class SendMailWx(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'New Email Message (Plain Text)', size=(600,400)) self.panel = wx.Panel(self, wx.ID_ANY) # set your email address here self.email = 'myEmail@email.com' self.filepaths = [] self.currentDir = os.path.abspath(os.path.dirname(sys.argv[0])) self.createMenu() self.createToolbar() self.createWidgets() try: print sys.argv self.parseURL(sys.argv[1]) except Exception, e: print 'Unable to execute parseURL...' print e self.layoutWidgets() self.attachTxt.Hide() self.editAttachBtn.Hide() def createMenu(self): menubar = wx.MenuBar() fileMenu = wx.Menu() send_menu_item = fileMenu.Append(wx.NewId(), '&Send', 'Sends the email') close_menu_item = fileMenu.Append(wx.NewId(), '&Close', 'Closes the window') menubar.Append(fileMenu, '&File') self.SetMenuBar(menubar) # bind events to the menu items self.Bind(wx.EVT_MENU, self.onSend, send_menu_item) self.Bind(wx.EVT_MENU, self.onClose, close_menu_item) def createToolbar(self): toolbar = self.CreateToolBar(wx.TB_3DBUTTONS|wx.TB_TEXT) toolbar.SetToolBitmapSize((31,31)) bmp = mail_ico.getBitmap() sendTool = toolbar.AddSimpleTool(-1, bmp, 'Send', 'Sends Email') self.Bind(wx.EVT_MENU, self.onSend, sendTool) toolbar.Realize() def createWidgets(self): p = self.panel font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD) self.fromLbl = wx.StaticText(p, wx.ID_ANY, 'From', size=(60,-1)) self.fromTxt = wx.TextCtrl(p, wx.ID_ANY, self.email) self.toLbl = wx.StaticText(p, wx.ID_ANY, 'To:', size=(60,-1)) self.toTxt = wx.TextCtrl(p, wx.ID_ANY, '') self.subjectLbl = wx.StaticText(p, wx.ID_ANY, ' Subject:', size=(60,-1)) self.subjectTxt = wx.TextCtrl(p, wx.ID_ANY, '') self.attachBtn = wx.Button(p, wx.ID_ANY, 'Attachments') self.attachTxt = wx.TextCtrl(p, wx.ID_ANY, '', style=wx.TE_MULTILINE) self.attachTxt.Disable() self.editAttachBtn = wx.Button(p, wx.ID_ANY, 'Edit Attachments') self.messageTxt = wx.TextCtrl(p, wx.ID_ANY, '', style=wx.TE_MULTILINE) self.Bind(wx.EVT_BUTTON, self.onAttach, self.attachBtn) self.Bind(wx.EVT_BUTTON, self.onAttachEdit, self.editAttachBtn) self.fromLbl.SetFont(font) self.toLbl.SetFont(font) self.subjectLbl.SetFont(font) def layoutWidgets(self): mainSizer = wx.BoxSizer(wx.VERTICAL) fromSizer = wx.BoxSizer(wx.HORIZONTAL) toSizer = wx.BoxSizer(wx.HORIZONTAL) subjSizer = wx.BoxSizer(wx.HORIZONTAL) attachSizer = wx.BoxSizer(wx.HORIZONTAL) fromSizer.Add(self.fromLbl, 0) fromSizer.Add(self.fromTxt, 1, wx.EXPAND) toSizer.Add(self.toLbl, 0) toSizer.Add(self.toTxt, 1, wx.EXPAND) subjSizer.Add(self.subjectLbl, 0) subjSizer.Add(self.subjectTxt, 1, wx.EXPAND) attachSizer.Add(self.attachBtn, 0, wx.ALL, 5) attachSizer.Add(self.attachTxt, 1, wx.ALL|wx.EXPAND, 5) attachSizer.Add(self.editAttachBtn, 0, wx.ALL, 5) mainSizer.Add(fromSizer, 0, wx.ALL|wx.EXPAND, 5) mainSizer.Add(toSizer, 0, wx.ALL|wx.EXPAND, 5) mainSizer.Add(subjSizer, 0, wx.ALL|wx.EXPAND, 5) mainSizer.Add(attachSizer, 0, wx.ALL|wx.EXPAND, 5) mainSizer.Add(self.messageTxt, 1, wx.ALL|wx.EXPAND, 5) self.panel.SetSizer(mainSizer) self.panel.Layout() def parseURL(self, url): ''' Parse the URL passed from the mailto link ''' sections = 1 mailto_string = url.split(':')[1] if '?' in mailto_string: sections = mailto_string.split('?') else: address = mailto_string if len(sections) > 1: address = sections[0] new_sections = urllib.unquote(sections[1]).split('&') for item in new_sections: if 'subject' in item.lower(): Subject = item.split('=')[1] self.subjectTxt.SetValue(Subject) if 'body' in item.lower(): Body = item.split('=')[1] self.messageTxt.SetValue(Body) self.toTxt.SetValue(address) def onAttach(self, event): ''' Displays a File Dialog to allow the user to choose a file and then attach it to the email. ''' print "in onAttach method..." def onAttachEdit(self, event): ''' Allow the editing of the attached files list ''' print "in onAttachEdit method..." def onSend(self, event): ''' Send the email using the filled out textboxes. Warn the user if they forget to fill part of it out. ''' print "in onSend event handler..." def onClose(self, event): self.Close() ####################### # Start program if __name__ == '__main__': app = wx.PySimpleApp() frame = SendMailWx() frame.Show() app.MainLoop()
Я уже объяснил, как создавать панели инструментов, меню и сизеров на предыдущих постах, поэтому я собираюсь сосредоточиться на новом магазине здесь. Я импортирую модуль Urllib, чтобы помочь в расстановке данных, отправленных из ссылки MailTo на веб-странице. В настоящее время я поддерживаю поля To, субъект и телосложение протокола Mailto. Соответствующие текстовые ящики устанавливаются в зависимости от количества разделов, которые передаются в метод ParseURL (). Вы могли легко продлить это нужно. Я также получаю каталог, в котором сценарий бежит с помощью этой строки кода:
self.currentDir = os.path.abspath(os.path.dirname(sys.argv[0]))
Наконец, есть три охранника событий: «Онаттач», «ОНАТАХЕДИТИТЬ» и «Onsend». Давайте пойдем вперед и немного немного.
Прикрепление электронной почты
Первый метод, Onattach (), позволяет пользователю прикреплять файлы к их электронной почте. Я использую WX.FileDialog, чтобы получить выбор пользователя. Вот где находится собственность «FilePaths». Я также называю новый метод, GetFileSize, который рассчитает размер файла. Смотрите код ниже:
def onAttach(self, event): ''' Displays a File Dialog to allow the user to choose a file and then attach it to the email. ''' attachments = self.attachTxt.GetLabel() filepath = '' # create a file dialog wildcard = "All files (*.*)|*.*" dialog = wx.FileDialog(None, 'Choose a file', self.currentDir, '', wildcard, wx.OPEN) # if the user presses OK, get the path if dialog.ShowModal() == wx.ID_OK: self.attachTxt.Show() self.editAttachBtn.Show() filepath = dialog.GetPath() print filepath # Change the current directory to reflect the last dir opened os.chdir(os.path.dirname(filepath)) self.currentDir = os.getcwd() # add the user's file to the filepath list if filepath != '': self.filepaths.append(filepath) # get file size fSize = self.getFileSize(filepath) # modify the attachment's label based on it's current contents if attachments == '': attachments = '%s (%s)' % (os.path.basename(filepath), fSize) else: temp = '%s (%s)' % (os.path.basename(filepath), fSize) attachments = attachments + '; ' + temp self.attachTxt.SetLabel(attachments) dialog.Destroy() def getFileSize(self, f): ''' Get the file's approx. size ''' fSize = os.stat(f).st_size if fSize >= 1073741824: # gigabyte fSize = int(math.ceil(fSize/1073741824.0)) size = '%s GB' % fSize elif fSize >= 1048576: # megabyte fSize = int(math.ceil(fSize/1048576.0)) size = '%s MB' % fSize elif fSize >= 1024: # kilobyte fSize = int(math.ceil(fSize/1024.0)) size = '%s KB' % fSize else: size = '%s bytes' % fSize return size
Вы также заметите, что сохраняете последний каталог, который входит в пользователь. Я все еще сталкиваюсь с программами, которые не делают этого или не делают это последовательно. Надеюсь, моя реализация будет работать в большинстве случаев. Предполагается, что метод GetFileize () должен рассчитать размер прикрепленного файла. Это только отображает ближайший размер и не показывает фракции. Кроме того, я думаю, что это красиво самоуверенно.
Редактирование ваших вложений
Метод ONATTACHEDIT () довольно похож, за исключением того, что он вызывает пользовательский диалог, позволяющий пользователю редактировать, какие файлы включены в случай, если они выбрали один ошибочно.
def onAttachEdit(self, event): ''' Allow the editing of the attached files list ''' print 'in onAttachEdit...' attachments = '' dialog = EditDialog(self.filepaths) dialog.ShowModal() self.filepaths = dialog.filepaths print 'Edited paths:\n', self.filepaths dialog.Destroy() if self.filepaths == []: # hide the attachment controls self.attachTxt.Hide() self.editAttachBtn.Hide() else: for path in self.filepaths: # get file size fSize = self.getFileSize(path) # Edit the attachments listed if attachments == '': attachments = '%s (%s)' % (os.path.basename(path), fSize) else: temp = '%s (%s)' % (os.path.basename(path), fSize) attachments = attachments + '; ' + temp self.attachTxt.SetLabel(attachments) class EditDialog(wx.Dialog): def __init__(self, filepaths): wx.Dialog.__init__(self, None, -1, 'Edit Attachments', size=(190,150)) self.filepaths = filepaths instructions = 'Check the items below that you no longer wish to attach to the email' lbl = wx.StaticText(self, wx.ID_ANY, instructions) deleteBtn = wx.Button(self, wx.ID_ANY, 'Delete Items') cancelBtn = wx.Button(self, wx.ID_ANY, 'Cancel') self.Bind(wx.EVT_BUTTON, self.onDelete, deleteBtn) self.Bind(wx.EVT_BUTTON, self.onCancel, cancelBtn) mainSizer = wx.BoxSizer(wx.VERTICAL) btnSizer = wx.BoxSizer(wx.HORIZONTAL) mainSizer.Add(lbl, 0, wx.ALL, 5) self.chkList = wx.CheckListBox(self, wx.ID_ANY, choices=self.filepaths) mainSizer.Add(self.chkList, 0, wx.ALL, 5) btnSizer.Add(deleteBtn, 0, wx.ALL|wx.CENTER, 5) btnSizer.Add(cancelBtn, 0, wx.ALL|wx.CENTER, 5) mainSizer.Add(btnSizer, 0, wx.ALL|wx.CENTER, 5) self.SetSizer(mainSizer) self.Fit() self.Layout() def onCancel(self, event): self.Close() def onDelete(self, event): print 'in onDelete' numberOfPaths = len(self.filepaths) for item in range(numberOfPaths): val = self.chkList.IsChecked(item) if val == True: path = self.chkList.GetString(item) print path for i in range(len(self.filepaths)-1,-1,-1): if path in self.filepaths[i]: del self.filepaths[i] print 'new list => ', self.filepaths self.Close()
Главное, чтобы заметить в коде выше, состоит в том, что редакторDialog является подклассом WX.Dialog. Причина, по которой я выбрал это через WX.Frame, потому что я хотел, чтобы мой диалог был не модальным, и я думаю, что используя класс WX.Dialog имеет наибольшее значение для этого. Вероятно, самая интересная часть этого класса – мой метод OnDelete, в котором я петлю по пути назад. Я делаю это, поэтому я могу удалить элементы в любом порядке, не содержащий целостность списка. Например, если я неоднократно удалил элемент 2, я бы, вероятно, в конечном итоге удаляю элемент, который я не хотел.
Отправка электронной почты
Мой последний метод – Onsend () один. Я думаю, что это, вероятно, самый сложный, и тот, который потребуется рефакторинг больше всего. В этой реализации все SMTP-элементы жестко закодированы. Давайте посмотрим и посмотрим, как это работает:
def OnSend(self, event): ''' Send the email using the filled out textboxes. Warn the user if they forget to fill part of it out. ''' From = self.fromTxt.GetValue() To = self.toTxt.GetValue() Subject = self.subjectTxt.GetValue() text = self.messageTxt.GetValue() colon = To.find(';') period = To.find(',') if colon != -1: temp = To.split(';') To = self.sendStrip(temp) #';'.join(temp) elif period != -1: temp = To.split(',') To = self.sendStrip(temp) #';'.join(temp) else: pass if To == '': print 'add an address to the "To" field!' dlg = wx.MessageDialog(None, 'Please add an address to the "To" field and try again', 'Error', wx.OK|wx.ICON_EXCLAMATION) dlg.ShowModal() dlg.Destroy() elif Subject == '': dlg = wx.MessageDialog(None, 'Please add a "Subject" and try again', 'Error', wx.OK|wx.ICON_EXCLAMATION) dlg.ShowModal() dlg.Destroy() elif From == '': lg = wx.MessageDialog(None, 'Please add an address to the "From" field and try again', 'Error', wx.OK|wx.ICON_EXCLAMATION) dlg.ShowModal() dlg.Destroy() else: msg = MIMEMultipart() msg['From'] = From msg['To'] = To msg['Subject'] = Subject msg['Date'] = formatdate(localtime=True) msg.attach( MIMEText(text) ) if self.filepaths != []: print 'attaching file(s)...' for path in self.filepaths: part = MIMEBase('application', "octet-stream") part.set_payload( open(path,"rb").read() ) Encoders.encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(path)) msg.attach(part) # edit this to match your mail server (i.e. mail.myserver.com) server = smtplib.SMTP('mail.myserver.org') # open login dialog dlg = LoginDlg(server) res = dlg.ShowModal() if dlg.loggedIn: dlg.Destroy() # destroy the dialog try: failed = server.sendmail(From, To, msg.as_string()) server.quit() self.Close() # close the program except Exception, e: print 'Error - send failed!' print e else: if failed: print 'Failed:', failed else: dlg.Destroy()
Большинство из этого вы видели раньше, поэтому я собираюсь поговорить о звонках модуля электронной почты. Tha Основная часть ключей в том, что для создания сообщения электронной почты с вложениями вы захотите использовать mimemultiPart czll. Я использовал его, чтобы добавить поля «из», «на», «тему» и «дату». Чтобы прикрепить файлы, вам нужно использовать MimeBase. Наконец, отправлять электронное письмо, вам нужно будет установить SMTP-сервер, который я использовал с помощью библиотеки SMPTLIB и вход в систему, именно для которого является логинлг. Я пойду на это следующий, но прежде чем я буду хотеть бы порекомендовать оба соответствующей документации по полной информации, поскольку они намного больше функциональности, которые я не использую в этом примере.
Вход в систему
Я заметил, что мой код не работал за пределами моей организации, и мне потребовалось некоторое время, чтобы выяснить, почему. Оказывается, когда я вошел в работу на работе, я также вошел в нашу систему Webmail, поэтому мне не нужно аутентифицироваться с ним. Когда я снаружи, я делаю. Поскольку это на самом деле довольно нормальная процедура для SMTP-серверов, я включил довольно простой диалог входа в систему. Давайте посмотрим на код:
class LoginDlg(wx.Dialog): def __init__(self, server): wx.Dialog.__init__(self, None, -1, 'Login', size=(190,150)) self.server = server self.loggedIn = False # widgets userLbl = wx.StaticText(self, wx.ID_ANY, 'Username:', size=(50, -1)) self.userTxt = wx.TextCtrl(self, wx.ID_ANY, '') passwordLbl = wx.StaticText(self, wx.ID_ANY, 'Password:', size=(50, -1)) self.passwordTxt = wx.TextCtrl(self, wx.ID_ANY, '', size=(150, -1), style=wx.TE_PROCESS_ENTER|wx.TE_PASSWORD) loginBtn = wx.Button(self, wx.ID_YES, 'Login') cancelBtn = wx.Button(self, wx.ID_ANY, 'Cancel') self.Bind(wx.EVT_BUTTON, self.OnLogin, loginBtn) self.Bind(wx.EVT_TEXT_ENTER, self.OnTextEnter, self.passwordTxt) self.Bind(wx.EVT_BUTTON, self.OnClose, cancelBtn) # sizer / layout userSizer = wx.BoxSizer(wx.HORIZONTAL) passwordSizer = wx.BoxSizer(wx.HORIZONTAL) btnSizer = wx.BoxSizer(wx.HORIZONTAL) mainSizer = wx.BoxSizer(wx.VERTICAL) userSizer.Add(userLbl, 0, wx.ALL, 5) userSizer.Add(self.userTxt, 0, wx.ALL, 5) passwordSizer.Add(passwordLbl, 0, wx.LEFT|wx.RIGHT, 5) passwordSizer.Add(self.passwordTxt, 0, wx.LEFT, 5) btnSizer.Add(loginBtn, 0, wx.ALL, 5) btnSizer.Add(cancelBtn, 0, wx.ALL, 5) mainSizer.Add(userSizer, 0, wx.ALL, 0) mainSizer.Add(passwordSizer, 0, wx.ALL, 0) mainSizer.Add(btnSizer, 0, wx.ALL|wx.CENTER, 5) self.SetSizer(mainSizer) self.Fit() self.Layout() def OnTextEnter(self, event): ''' When enter is pressed, login method is run. ''' self.OnLogin('event') def OnLogin(self, event): ''' When the "Login" button is pressed, the credentials are authenticated. If correct, the email will attempt to be sent. If incorrect, the user will be notified. ''' try: user = self.userTxt.GetValue() pw = self.passwordTxt.GetValue() res = self.server.login(user, pw) self.loggedIn = True self.OnClose('') except: message = 'Your username or password is incorrect. Please try again.' dlg = wx.MessageDialog(None, message, 'Login Error', wx.OK|wx.ICON_EXCLAMATION) dlg.ShowModal() dlg.Destroy() def OnClose(self, event): self.Close()
По большей части мы видели это раньше. Основная часть для замечания состоит в том, что я добавил два стиля к моему паролю TextCtrl: wx.te_process_enter и wx.te_password. Первый позволит вам нажать Введите вхожу в логин, а не на нажатии Вход кнопка явно. Стиль TE_PASSWORD скрывает текст, набранный в TextCtrl с черными кругами или звездочками.
Также вы должны отметить, что ваше имя пользователя может включать URL вашей электронной почты. Например, а не просто Имя пользователя это может быть username@hotmail.com Отказ К счастью, если логин неверный, программа бросит ошибку и отображает диалог, позволяющий пользователю знать.
Взломать реестр
Последняя вещь, которую нужно сделать в Windows, это установить его для использования этого сценария, когда пользователь нажимает на ссылку MailTo. Для этого вам нужно будет связываться с реестром Windows. Прежде чем делать что-нибудь с реестром, обязательно обратитесь к этому, так как всегда есть шанс, что вы можете что-то сломать, включая ОС.
Начать, перейти к началу, запустить и введите Реддит Отказ Теперь перейдите к следующему расположению:
Hkey_classes_root \ mailto \ shell \ open \ command
Просто расширяйте дерево справа, чтобы навигаться по дереву. Один там, вам нужно редактировать ключ (по умолчанию) справа. Это должно быть типа reg_sz. Просто дважды щелкните по нему, чтобы редактировать его. Вот что вы хотите поставить туда:
CMD/C “Set: \ Путь \ to \ python24 && c: \ pathon24 && c: \ path \ to \ python24 \ python.exe c: \ path \ to \ wxpymail.py% 1”
В принципе, это говорит Windows для установки домашнего каталога Python и путь к Python.exe в качестве экологической переменной, которая, я думаю, является только временным. Затем он проходит скрипт wxpymail.py, который мы создали в указанном python.exe. «% 1» – это аргументы, переданные ссылкой Mailto. Как только вы попадаете на кнопку ОК, она сохраняется и просто начать работать.
Обертывание
Теперь вы знаете, как сделать полностью функциональное приложение с WxPython. В моем следующем посте я покажу, как упаковать его как исполняемый файл, чтобы вы могли распространять его.
Некоторые возможные улучшения, которые вы могли бы добавить:
- Хранить информацию о профиле (то есть ваше имя, электронная почта, подпись и т. Д.)
- Хранить адреса электронной почты в SQLite
- Создайте мастер для настройки профиля (ы)
- Добавить шифрование на электронные письма.
Дальнейшее чтение:
Скачать Источник:
- WXPYMail Source (.zip)