diff --git a/DemoPrograms/Demo_Email_Send.py b/DemoPrograms/Demo_Email_Send.py index 9d443f3ae..70515de75 100644 --- a/DemoPrograms/Demo_Email_Send.py +++ b/DemoPrograms/Demo_Email_Send.py @@ -1,38 +1,36 @@ import PySimpleGUI as sg -# import PySimpleGUIWeb as sg -# import PySimpleGUIWx as sg -# import PySimpleGUIQt as sg ''' - Copyright 2019 PySimpleGUI.org + Learn how to send emails from PySimpleGUI using the smtplib and email modules + + The GUI portion is simple + Based on a send-email script originally written by by Israel Dryer + (Thank you Israel for figuring out the hard part of the stmp and email module calls!) + + Copyright 2019, 2022 PySimpleGUI + ''' +# If you are using a mail service that's not gmail, hotmail, live or yahoo: +# then you can enter the smtp server address here so you don't have to keep typing it into the GUI +smtp_host_default = '' + # used for sending the email import smtplib as smtp # used to build the email from email.message import EmailMessage # create and send email -def send_an_email(from_address, to_address, subject, message_text, user, password): - # SMTP Servers for popular free services... add your own if needed. Format is: address, port - google_smtp_server = 'smtp.gmail.com', 587 - microsoft_smtp_server = 'smtp.office365.com', 587 - yahoo_smtp_server = 'smtp.mail.yahoo.com', 587 # or port 465 - - # open the email server connection - if 'gmail' in user: - smtp_host, smtp_port = google_smtp_server - elif 'hotmail' in user or 'live' in user: - smtp_host, smtp_port = microsoft_smtp_server - elif 'yahoo' in user: - smtp_host, smtp_port = yahoo_smtp_server - else: - sg.popup('Username does not contain a supported email provider') - return +def send_an_email(from_address, to_address, subject, message_text, user, password, smtp_host, smtp_port): server = smtp.SMTP(host=smtp_host, port=smtp_port) server.starttls() - server.login(user=user, password=password) + try: + server.login(user=user, password=password) + except Exception as e: + sg.popup_error('Error authenticaing your email credentials', e, image=sg.EMOJI_BASE64_WEARY) + server.close() + return # create the email message headers and set the payload msg = EmailMessage() @@ -42,9 +40,15 @@ def send_an_email(from_address, to_address, subject, message_text, user, passwor msg.set_payload(message_text) # open the email server and send the message - server.send_message(msg) + try: + server.send_message(msg) + except Exception as e: + sg.popup_error('Error sending your email', e, image=sg.EMOJI_BASE64_WEARY) + server.close() + return server.close() + sg.popup('Email sent successfully!', image=sg.EMOJI_BASE64_HAPPY_JOY) ''' important notes about using gmail @@ -61,33 +65,47 @@ def send_an_email(from_address, to_address, subject, message_text, user, passwor ''' def main(): + smtp_server_dict = {'gmail.com':'smtp.gmail.com','hotmail.com':'smtp.office365.com', 'live.com': 'smtp.office365.com', 'yahoo.com':'smtp.mail.yahoo.com'} + sg.theme('Dark Blue 3') layout = [[sg.Text('Send an Email', font='Default 18')], [sg.T('From:', size=(8,1)), sg.Input(key='-EMAIL FROM-', size=(35,1))], [sg.T('To:', size=(8,1)), sg.Input(key='-EMAIL TO-', size=(35,1))], [sg.T('Subject:', size=(8,1)), sg.Input(key='-EMAIL SUBJECT-', size=(35,1))], [sg.T('Mail login information', font='Default 18')], - [sg.T('User:', size=(8,1)), sg.Input(key='-USER-', size=(35,1))], + [sg.T('User:', size=(8,1)), sg.Input(key='-USER-', size=(35,1), enable_events=True)], [sg.T('Password:', size=(8,1)), sg.Input(password_char='*', key='-PASSWORD-', size=(35,1))], + [sg.T('SMTP Server Info', font='_ 14')], + [sg.T('SMTP Hostname'), sg.Input(smtp_host_default, s=20, key='-SMTP HOST-'), sg.T('SMTP Port'), sg.In(587, s=4, key='-SMTP PORT-') ], [sg.Multiline('Type your message here', size=(60,10), key='-EMAIL TEXT-')], [sg.Button('Send'), sg.Button('Exit')]] window = sg.Window('Send An Email', layout) - while True: # Event Loop + while True: event, values = window.read() if event in (sg.WIN_CLOSED, 'Exit'): break if event == 'Send': - if sg.__name__ != 'PySimpleGUIWeb': # auto close popups not yet supported in PySimpleGUIWeb + if values['-SMTP HOST-']: sg.popup_quick_message('Sending your message... this will take a moment...', background_color='red') - send_an_email(from_address=values['-EMAIL FROM-'], - to_address=values['-EMAIL TO-'], - subject=values['-EMAIL SUBJECT-'], - message_text=values['-EMAIL TEXT-'], - user=values['-USER-'], - password=values['-PASSWORD-']) + send_an_email(from_address=values['-EMAIL FROM-'], + to_address=values['-EMAIL TO-'], + subject=values['-EMAIL SUBJECT-'], + message_text=values['-EMAIL TEXT-'], + user=values['-USER-'], + password=values['-PASSWORD-'], + smtp_host=values['-SMTP HOST-'], + smtp_port = values['-SMTP PORT-']) + else: + sg.popup_error('Missing SMTP Hostname... you have to supply a hostname (gmail, hotmail, live, yahoo are autofilled)') + elif event == '-USER-': # as the email sender is typed in, try to fill in the smtp hostname automatically + for service in smtp_server_dict.keys(): + if service in values[event].lower(): + window['-SMTP HOST-'].update(smtp_server_dict[service]) + break window.close() -main() \ No newline at end of file +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/PySimpleGUI.py b/PySimpleGUI.py index 3282efa00..2bfda6657 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -version = __version__ = "4.60.3.70 Unreleased" +version = __version__ = "4.60.3.71 Unreleased" _change_log = """ Changelog since 4.60.0 released to PyPI on 8-May-2022 @@ -181,6 +181,9 @@ 4.60.3.70 Debug Print - fix for bug caused by no_button being set with non_blocking... a lesson in thorough testing... assumption was either blocking OR no_button (or else app would close without seeing the output... unless something else blocked. (DOH) + 4.60.3.71 + "Window closed" check added to update methods for elements. This will prevent a crash and instead show an error popup + Will be helpful for users that forget to check for closed window event in their event loop and try to call update after window closed. """ __version__ = version.split()[0] # For PEP 396 and PEP 345 @@ -1616,6 +1619,12 @@ def _ClickHandler(self, event): """ self._generic_callback_handler('') + def _this_elements_window_closed(self): + if self.ParentForm is not None: + return self.ParentForm.is_closed() + + return True + def _user_bind_callback(self, bind_string, event, propagate=True): """ Used when user binds a tkinter event directly to an element @@ -2202,6 +2211,11 @@ def update(self, value=None, disabled=None, select=None, visible=None, text_colo """ if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Input.update - The window was closed') + return + if disabled is True: self.TKEntry['state'] = 'readonly' if self.UseReadonlyForDisable else 'disabled' elif disabled is False: @@ -2400,6 +2414,12 @@ def update(self, value=None, values=None, set_to_index=None, disabled=None, read if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Combo.update - The window was closed') + return + + if values is not None: try: self.TKCombo['values'] = values @@ -2579,6 +2599,11 @@ def update(self, value=None, values=None, disabled=None, visible=None, size=(Non if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in OptionMenu.update - The window was closed') + return + + if values is not None: self.Values = values self.TKOptionMenu['menu'].delete(0, 'end') @@ -2779,6 +2804,10 @@ def update(self, values=None, disabled=None, set_to_index=None, scroll_to_index= if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Listbox.update - The window was closed') + return + if disabled is True: self.TKListbox.configure(state='disabled') elif disabled is False: @@ -3007,6 +3036,11 @@ def update(self, value=None, text=None, background_color=None, text_color=None, if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Radio.update - The window was closed') + return + + if value is not None: try: if value is True: @@ -3213,6 +3247,11 @@ def update(self, value=None, text=None, background_color=None, text_color=None, if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Checkbox.update - The window was closed') + return + + if value is not None: value = bool(value) try: @@ -3386,6 +3425,10 @@ def update(self, value=None, values=None, disabled=None, readonly=None, visible= if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Spin.update - The window was closed') + return + if values != None: old_value = self.TKStringVar.get() self.Values = values @@ -3652,6 +3695,11 @@ def update(self, value=None, disabled=None, append=False, font=None, text_color= if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Multiline.update - The window was closed') + return + + if autoscroll is not None: self.Autoscroll = autoscroll @@ -3991,6 +4039,11 @@ def update(self, value=None, background_color=None, text_color=None, font=None, if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Text.update - The window was closed') + return + if value is not None: self.DisplayText = str(value) self.TKStringVar.set(str(value)) @@ -4332,6 +4385,10 @@ def update(self, value=None, background_color=None, text_color=None, font=None, if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in StatusBar.update - The window was closed') + return + if value is not None: self.DisplayText = value stringvar = self.TKStringVar @@ -5175,6 +5232,11 @@ def update(self, text=None, button_color=(None, None), disabled=None, image_data if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Button.update - The window was closed') + return + + if self.UseTtkButtons: style_name = self.ttk_style_name # created when made initial window (in the pack) # style_name = str(self.Key) + 'custombutton.TButton' @@ -5438,6 +5500,11 @@ def update(self, menu_definition=None, visible=None, image_source=None, image_si if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in ButtonMenu.update - The window was closed') + return + + if menu_definition is not None: self.MenuDefinition = copy.deepcopy(menu_definition) top_menu = self.TKMenu = tk.Menu(self.TKButtonMenu, tearoff=self.Tearoff, font=self.ItemFont, tearoffcommand=self._tearoff_menu_callback) @@ -5638,6 +5705,11 @@ def update(self, current_count=None, max=None, bar_color=None, visible=None): if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return False + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in ProgressBar.update - The window was closed') + return + + if self.ParentForm.TKrootDestroyed: return False @@ -5783,6 +5855,11 @@ def update(self, source=None, filename=None, data=None, size=(None, None), subsa if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Image.update - The window was closed') + return + + if source is not None: if isinstance(source, bytes): data = source @@ -6010,6 +6087,10 @@ def update(self, background_color=None, visible=None): if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Canvas.update - The window was closed') + return + if background_color not in (None, COLOR_SYSTEM_DEFAULT): self._TKCanvas.configure(background=background_color) if visible is False: @@ -6530,6 +6611,10 @@ def update(self, background_color=None, visible=None): if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Graph.update - The window was closed') + return + if background_color is not None and background_color != COLOR_SYSTEM_DEFAULT: self._TKCanvas2.configure(background=background_color) @@ -6994,6 +7079,10 @@ def update(self, value=None, visible=None): if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Frame.update - The window was closed') + return + if visible is False: self._pack_forget_save_settings() # self.TKFrame.pack_forget() @@ -7309,6 +7398,11 @@ def update(self, title=None, disabled=None, visible=None): if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Tab.update - The window was closed') + return + + state = 'normal' if disabled is not None: self.Disabled = disabled @@ -7781,6 +7875,11 @@ def update(self, value=None, range=(None, None), disabled=None, visible=None): if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Slider.update - The window was closed') + return + + if range != (None, None): self.TKScale.config(from_=range[0], to_=range[1]) if value is not None: @@ -8189,6 +8288,10 @@ def update(self, visible=None): if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Column.update - The window was closed') + return + if self.expand_x and self.expand_y: expand = tk.BOTH elif self.expand_x: @@ -8319,6 +8422,11 @@ def update(self, visible=None): """ if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Pane.update - The window was closed') + return + if visible is False: self._pack_forget_save_settings() elif visible is True: @@ -8660,6 +8768,10 @@ def update(self, menu_definition=None, visible=None): if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Menu.update - The window was closed') + return + if menu_definition is not None: self.MenuDefinition = copy.deepcopy(menu_definition) @@ -8911,6 +9023,10 @@ def update(self, values=None, num_rows=None, visible=None, select_rows=None, alt if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Table.update - The window was closed') + return + if values is not None: for id in self.tree_ids: self.TKTreeview.item(id, tags=()) @@ -9344,6 +9460,10 @@ def update(self, values=None, key=None, value=None, text=None, icon=None, visibl if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow return + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Tree.update - The window was closed') + return + if values is not None: children = self.TKTreeview.get_children() for i in children: @@ -25492,4 +25612,4 @@ def main(): exit(0) main() exit(0) -#1fc04cbae65e4e9bbfd8c882d7db778995052e8af750268b2de6803c6ea81ab1faa4f416e47959fb828e1a43da8b6a876289a2426ef816f95867d500d9a34bca3fea0b5c8d8986d2b93aa36b0ff234e583500215d0bad9dea0b650625e04c7dffefdd38f21f8d2eeb0a5cf86793ff51f739482df8a9c32d89ff105de042ddcac20e06f12dd4a11f8b3ced779ce5a67cd7f1a2324dfcf1881eb8fd8379eebd42949107917c216d9c3e2a1f8e9b00c9c35e3b91a5eb05f05397bc4c465a6feff1842494c3224e9f3d148faf8422db3d1c8265e7240fe95eb3b1e5ac08bc09adac34d97ebdb4409cffa935319bacc200dd793ec0947b49d7394cd98d86e584bec113c5bee7c95ff4717a7d656c55bf9bcf635abcea9c3367510a970cd71177b232c571579794b2e291608fc06c9227fe995599c4dec829c1da207d439cc75ab11aeed649791a3c8df8cc1445ef16e2f0616859c7a0febc1126e6a2ca57c5cdf89a3d30d3cecbba1b1d3a451869cdae59a3cf441c44efe0d7988555fea6202e083a3af873c0a40a426fc32e0bf95c7c41dc05c0b7eea27b9609d96849299ce6f4f314d278d17cd31eabc4f8aad66c2381126f1f90f286c0b8e23fabe53ed7f4219224298d6d8459899e9090fa600f75e27079c0e46abde66a7e30e43dd9fee96fef4be95de9a7ce38208c5284e1227f74aba642bf5c7d00e1828901ab154c904c3ca \ No newline at end of file +#51534a560e555e10adba7036dc5457298451e1d08ec3e361101d2e89ccb1892ed85a4b93bbfd0b3c8037029ce7e1f5860f8d9f5e082830f262ff8e586421e16de491ce0bb9da6d81d7a79f4f1c721186345be685f44d4ab03742b7aceb8168b5aa292c31fb33f938634f39f103c5bfbd8e98fd944f9bc9573251928c0cf49031043b5f13b410faaa43ccc94426cf1ea38506d074effd107cd9d2ab4a486cf0476db2bf06e6d19e492e5092e9c6bcabd4515584c0d044c5fcb66202a31882fea2098a1b7d0e0c55d1dbd2e798dcd274595335c8876a97f063493d05abc35b41bba7afba2037776c9bc45e4bf027bde0773dfc48afc0d5a046b713330c8eebbd47f5555072c93ad08fb4fdbb743caa18007c05d1c6393ce2d7e375bc38f986670578c84fa3b7597c0743ef757fe2277492ab72bdf5bc4d97083387e794f6a70b5fb6440878fdd42b516495e94a92511e4621f7f09f4b213d1085b29ec7254cf8464b310c4a1a88f7b996d7913bd3a6aa7d5f8673ecbccf8451811f7db5d8b2b87ddf8ad2d6e61ff89aff034ca3538359b1dff8dacf1063bc2a339b8e11b3e02706b039f4342ba0d2168a9ad5546fd4ffab7c8c4b2ac1f85941c070bcc75b87e67077a1caae0f23919c196663005ce948b1e7d21ffc16ac8f033871cec2b4d39b4d49bd47c789fc5a74c8d9ba94f2cf8d4d11e950d92fc88a777180bc7be8f86d01 \ No newline at end of file