Related
I have put together a GUI which searches for a specific subject matter in your email within a while loop and continues to do so until it finds an unread message with that subject. Once it finds that email, it does some other functions and when all those functions are complete, it loops back to the beginning and searches once again for an unread message containing that subject. I am wanting to implement a "stop" button which will allow the user to break out of this infinite loop of search the inbox but I've had no success and am unsure on how to tackle this problem. I have tried searching for comparable examples but have been unable to implement them to my specific situation. I've truncated the code as much as possible to still allow for interpretation.
class App(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.desktop_path = os.path.join(os.environ['USERPROFILE'],'Desktop')
#create labelframe for directory and browse button
label_frame = tk.LabelFrame(self, text=" Search Inbox:")
label_frame.grid(row=1, columnspan=15, sticky='WE', padx=5, pady=5, ipadx=5, ipady=5)
#create label frame for send report and button
label_frame2 = tk.LabelFrame(self, text=" Interrupt Search:")
label_frame2.grid(row=2, columnspan=15, sticky='WE', padx=5, pady=5, ipadx=5, ipady=5)
#create label frame for send report and button
self.label_frame3 = tk.LabelFrame(self, text=" Totals:")
self.label_frame3.grid(row=3, columnspan=15, sticky='WE', padx=5, pady=5, ipadx=5, ipady=5)
#create browse button and place on grid
label_frame.button = ttk.Button(label_frame, text="Start",command=self.start, width=8)
label_frame.button.grid(row=4, column=7, sticky="EW", padx=100, ipadx=10)
#create send report button and place on grid
label_frame2.button = ttk.Button(label_frame2, text='Stop', command=self.stop, width=8)
label_frame2.button.grid(row=4, column=7, sticky='EW', padx=100, ipadx=10)
self.label_frame3.entry = tk.Entry(self.label_frame3)
self.label_frame3.entry.grid(row=4, column=7, stick='W', padx=50, ipadx=25)
def SaveAttachments(self, subject='subject matter goes here'):
#create outlook application object
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
inbox = outlook.GetDefaultFolder(6) #create an inbox object
messages = inbox.Items #create a message object
try:
inbox.Folders.Add('Extracts') #create folder in inbox, if already exsists, nothing happens.
except:
pass
#iterate through messages to find message that matches subject and is unread
for message in messages:
if message.Subject == subject and message.Unread:
attachments = message.Attachments
attachment = attachments.Item(1)
for attachment in message.Attachments:
if not os.path.isdir(self.desktop_path): #check if the path exsists, and if it doesn't, create it
os.makedirs(self.desktop_path)
attachment.SaveAsFile(os.path.join(self.desktop_path, str(attachment)))
if message.Subject == subject and message.Unread:
message.Unread = False #change message status from unread to read
message.Move(inbox.Folders('Extracts')) #move read messages to Extracts Folder
#print(attachment)
break
else:
attachment = None
return os.path.join(self.desktop_path, str(attachment))
def start(self, event=None):
self.cancel_id = None
self.main()
def main(self, event=None):
variable = True
while variable:
file = self.SaveAttachments()
if 'None' not in file:
variable = False
self.CreatePivots(file)
self.CreateExcel()
self.SendEmail()
self.label_frame3.entry.delete(0, 'end')
self.label_frame3.entry.insert(0, f'{self.table.iloc[-1,0]}')
self.cancel_id = self.label_frame3.entry.after(1000, self.main)
variable = True
def stop(self, event=None):
if self.cancel_id is not None:
self.label_frame3.entry.after_cancel(self.cancel_id)
self.cancel_id = None
You should do something liket this using threading.
This is the simplest example that comes to my mind for reproduce the behaviour that you want
import tkinter as tk
import threading as th
import time
class App:
def __init__(self):
self.root = tk.Tk()
self.run = True
self.counter = 0
def build(self):
self.counter_label = tk.Label(text=self.counter)
self.counter_label.pack()
self.stop_button = tk.Button(text='stop', command=self.stop_counter)
self.stop_button.pack()
def start_counter(self):
while self.run:
self.counter += 1
self.counter_label.configure(text=self.counter)
time.sleep(1)
def run_counter(self):
self.my_thread = th.Thread(target=self.start_counter) # create new thread that runs the self.start_counter() function
self.my_thread.start() # start the threading
def stop_counter(self):
self.counter_label.configure(text="counter stopped")
self.run = False # set the variable to false so that the while loop inside the threading stops
self.my_thread.join() # this destoy the created threading
def start_loop(self):
self.root.mainloop() # starts the tkinter mainloop
app = App()
app.build()
app.run_counter()
app.start_loop()
By the way, its better to call the variables with a significant name that will help you remind for what they are used.
In this program I want a user to enter credentials and then based on the inputs validate whether it is correct. I am using tkinter to provide a GUI. I want to be able to take the auth function outside of the class so I can shut the tkinter dialog once the account has been logged in, however, the problem here is that the auth function is within the class, I've tried various ways to retrieve the variable but I've had no luck.
from tkinter import *
import tkinter.messagebox as tm
class LoginFrame(Frame):
def __init__(self, master):
super().__init__(master)
self.label_Email = Label(self, text="Email")
self.label_password = Label(self, text="Password")
self.entry_Email = Entry(self)
self.entry_password = Entry(self, show="*")
self.label_Email.grid(row=0, sticky=E)
self.label_password.grid(row=1, sticky=E)
self.entry_Email.grid(row=0, column=1)
self.entry_password.grid(row=1, column=1)
self.checkbox = Checkbutton(self, text="Keep me logged in")
self.checkbox.grid(columnspan=2)
self.logbtn = Button(self, text="Login", command=self._login_btn_clicked)
self.logbtn.grid(columnspan=2)
self.pack()
def _login_btn_clicked(self):
# print("Clicked")
Email = self.entry_Email.get()
password = self.entry_password.get()
# print(Email, password)
self.answer = auth(Email, password)
root = Tk()
lf = LoginFrame(root)
if 'Bearer' in lf.answer:
root.quit()
root.mainloop()
My auth function will return a bearer token for the next stage if the login is successful, therefore I am checking whether or not the answer variable has returned it. If it has then I will shut the tkinter dialog
You're accessing the instance variable correctly, just in the wrong "order". Meaning, the answer must be checked only after the button is clicked. Basically, when your GUI loads, or directly after making the frame, the button isn't clicked, so the variable isn't defined, yet you're trying to access it immediately
One simple option is to not access the instance variable, and just use the passed in Tk object of the master
def _login_btn_clicked(self):
# print("Clicked")
Email = self.entry_Email.get()
password = self.entry_password.get()
# print(Email, password)
answer = auth(Email, password)
if 'Bearer' in answer:
self.master.quit()
I am trying to make a toggle button class by deriving from the tkinter.Button object. To that end, I am using this StackOverflow answer and these code examples.
The problem is that I get my desired toggle behavior from the button only after I click it twice; the first two clicks, it does not enact the self.config(relief="sunken"). I tried using the command keyword argument sample from this answer and that works from the start.
import tkinter as tk
class ToggleButton(tk.Button):
def __init__(self, parent=None, toggle_text="Toggled", toggle_bg_color="green", **kwargs):
tk.Button.__init__(self, parent, **kwargs)
self.toggled = False
self.default_bg_color = self['bg']
self.default_text = self["text"]
self.toggle_bg_color = toggle_bg_color
self.toggle_text = toggle_text
self.bind("<Button-1>", self.toggle, add="+")
def toggle(self, *args):
if self["relief"] == "sunken":
self["bg"] = self.default_bg_color
self["text"] = self.default_text
self.config(relief="raised")
# self["relief"] = "raised"
self.toggled = False
else:
self["bg"] = self.toggle_bg_color
self["text"] = self.toggle_text
# self["relief"] = "sunken"
self.config(relief="sunken")
self.toggled = True
def button_placeholder():
print("TO BE IMPLEMENTED")
root = tk.Tk()
button = ToggleButton(parent=root,
toggle_text="ON", toggle_bg_color="green",
text="OFF", command=button_placeholder)
button.pack()
root.mainloop()
Here are screenshots of the behavior of the buttons after numerous clicks
After the first two clicks on the button, the expected behavior occurs. However, if the user focuses on another window (for instance by minimizing the tkinter window) and then back, again the first two clicks do not cause the desired behavior.
Can some explain this? If not, can someone provide a solution where I can have consistent behavior on toggling my button?
Information about my system
Windows 10; 64 bit
Python 3.7.3 (64 bit)
Tkinter 8.6
The problem you seem to have is that the bg parameter is not defined when you first create the button; it only gets a value assigned upon the first button press.
Then, the logic to toggle is hard to follow: you have a self.toggled boolean, yet you are testing if the button is sunken or not to differentiate between states...
I reorganized the logic to make it easier to follow; after all, toggle is a binary change from one state to another. I therefore placed the definition of the ON and OFF states in the body of the class (into two class dictionaries), and the code swaps the two configs upon toggling.
On Windows:
import tkinter as tk
class ToggleButton(tk.Button):
ON_config = {'bg': 'green',
'text': 'button is ON',
'relief': 'sunken',
}
OFF_config = {'bg': 'white',
'text': 'button is OFF',
'relief': 'raised',
}
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.toggled = False
self.config = self.OFF_config
self.config_button()
self.bind("<Button-1>", self.toggle)
def toggle(self, *args):
if self.toggled: # True = ON --> toggle to OFF
self.config = self.OFF_config
else:
self.config = self.ON_config
self.toggled = not self.toggled
return self.config_button()
def config_button(self):
self['bg'] = self.config['bg']
self['text'] = self.config['text']
self['relief'] = self.config['relief']
return "break"
def __str__(self):
return f"{self['text']}, {self['bg']}, {self['relief']}"
def button_placeholder():
print('toggling now!')
if __name__ == '__main__':
root = tk.Tk()
button = ToggleButton(root)
button.pack()
root.mainloop()
On OSX:
Where the buttons aspect is fixed, using a tk.Label can mimic the desired behavior:
import tkinter as tk
class ToggleButtonLBL(tk.Label):
ON_config = {'bg': 'green',
'text': 'button is ON',
'relief': 'sunken',
}
OFF_config = {'bg': 'white',
'text': 'button is OFF',
'relief': 'raised',
}
def __init__(self, parent, *args, command=None, **kwargs):
super().__init__(parent, *args, **kwargs)
self.toggled = False
self.config = self.OFF_config
self.config_button()
self.bind("<Button-1>", self._toggle_helper)
self.bind("<ButtonRelease-1>", self._toggle)
self.command = command
def _toggle_helper(self, *args):
return 'break'
def _toggle(self, dummy_event):
self.toggle()
self.cmd()
def toggle(self, *args):
if self.toggled: # True = ON --> toggle to OFF
self.config = self.OFF_config
else:
self.config = self.ON_config
self.toggled = not self.toggled
self.config_button()
return 'break'
def config_button(self):
self['bg'] = self.config['bg']
self['text'] = self.config['text']
self['relief'] = self.config['relief']
return "break"
def __str__(self):
return f"{self['text']}, {self['bg']}, {self['relief']}"
def cmd(self):
self.command()
def button_placeholder():
print('toggling now!')
if __name__ == '__main__':
root = tk.Tk()
button = ToggleButtonLBL(root, command=button_placeholder)
button.pack()
root.mainloop()
Is there any way to run code after the OK button is pressed but before the dialog is closed in a GTK dialog? I want to be able to syntax check some code entered into the dialog after the OK button is pressed, with the option to keep the dialog open if the code doesn't compile. After a bit of googling I was able to find How to avoid closing of Gtk.Dialog in Python?, but the answer was regrettably short of details, so I couldn't figure out how to implement this. How does one go about doing this?
EDIT: Although the linked question asks about Python specifically, I don't actually care about any particular language. I'm using the Haskell bindings, but I'm fine with answers in any language with GTK+ bindings.
EDIT: If you find this question trying to figure out how to do validation, but don't have the complex requirements I have, I highly recommend looking at #AlexanderDmitriev's answer below.
I'm adding another answer in order to leave previous answer valid.
I see 2 ways to achieve desired behaviour.
Use deprecated gtk_dialog_get_action_area and pack a button there
Stop signal emission to prevent GtkDialog from "seeing" that the response button was pressed.
Both ways can be found in code below. Find deprecated for 1st approach and awesome for 2nd
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class DialogExample(Gtk.Dialog):
button_state = True
def awesome_cb (button, de):
if de.button_state:
print("Awesome ok")
else:
print("Awesome Not allowed")
button.stop_emission_by_name ("clicked")
def deprecated_cb (button, de):
if de.button_state:
print("Deprecated ok")
de.response(11)
else:
print("Deprecated Not allowed");
def switch_state(button, de):
de.button_state = not de.button_state
de.dialog_ok_btn.set_sensitive (de.button_state)
def __init__(self, parent):
Gtk.Dialog.__init__(self, "My Dialog", parent, 0,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK))
self.set_default_size(150, 100)
label = Gtk.Label("This is a dialog to display additional information")
box = self.get_content_area()
state_switcher_btn = Gtk.Button ("Switch")
state_switcher_btn.connect ("clicked", DialogExample.switch_state, self)
box.add(label)
box.add(state_switcher_btn)
hard_work_button = Gtk.Button ("deprec")
hard_work_button.connect ("clicked", DialogExample.deprecated_cb, self)
carea = self.get_action_area()
carea.add (hard_work_button)
tfb = Gtk.Button ("awesome");
tfb.connect("clicked", DialogExample.awesome_cb, self)
self.add_action_widget(tfb, 12)
self.dialog_ok_btn = self.get_widget_for_response (Gtk.ResponseType.OK)
self.show_all()
def do_response (self, response_id):
print ("Response! ID is ", response_id)
class DialogWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Dialog Example")
self.set_border_width(6)
button = Gtk.Button("Open dialog")
button.connect("clicked", self.on_button_clicked)
self.add(button)
def on_button_clicked(self, widget):
dialog = DialogExample(self)
response = dialog.run()
if response == Gtk.ResponseType.OK:
print("The OK button was clicked")
elif response == Gtk.ResponseType.CANCEL:
print("The Cancel button was clicked")
dialog.destroy()
win = DialogWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
Looks like GtkDialog itself doesn't allow to cancel button press (which is OK from user's point of view). However, every time user changes something, you can check it and make buttons sensitive or not. I've extended code from answer to mentioned question
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class DialogExample(Gtk.Dialog):
#this variable controls, whether OK is sensitive
button_state = True
def switch_state(button, de):
print ("switcher")
de.button_state = not de.button_state
de.set_response_sensitive (Gtk.ResponseType.OK, de.button_state)
def __init__(self, parent):
Gtk.Dialog.__init__(self, "My Dialog", parent, 0,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK))
self.set_default_size(150, 100)
label = Gtk.Label("This is a dialog to display additional information")
box = self.get_content_area()
# a button to switch OK's sensitivity
state_switcher_btn = Gtk.Button ("Switch")
state_switcher_btn.connect ("clicked", DialogExample.switch_state, self)
box.add(label)
box.add(state_switcher_btn)
self.show_all()
def do_response (self, response_id):
print ("Override! ID is ", response_id)
class DialogWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Dialog Example")
self.set_border_width(6)
button = Gtk.Button("Open dialog")
button.connect("clicked", self.on_button_clicked)
self.add(button)
def on_button_clicked(self, widget):
dialog = DialogExample(self)
response = dialog.run()
if response == Gtk.ResponseType.OK:
print("The OK button was clicked")
elif response == Gtk.ResponseType.CANCEL:
print("The Cancel button was clicked")
dialog.destroy()
win = DialogWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
Based on the Alexander Dmitriev's hint to use button.stop_emission_by_name, I came up with this solution which is probably what you were asking for:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class MyDialog(Gtk.Dialog):
def __init__(self, *args, **kwargs):
super(MyDialog, self).__init__(*args, **kwargs)
self.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK)
self.connect("response", self._cb_response)
def _cb_response(self, widget, response_id):
if response_id == Gtk.ResponseType.OK and self._check_invalid():
msg = Gtk.MessageDialog(
parent=self,
text="There are errors in what you entered.\n\n"
"Are you sure you want to continue?",
message_type=Gtk.MessageType.QUESTION,
buttons=Gtk.ButtonsType.YES_NO,
)
response = msg.run()
msg.destroy()
if response == Gtk.ResponseType.NO:
widget.stop_emission_by_name("response")
return True
return False
def _check_invalid(self):
"""Placeholder for checking for problems"""
return True
dialog = MyDialog()
dialog.run()
I once had this as well. I decided to catch the response signal. I had a function that would handle the validation. However, the function that handles the response signal always returns True to show GTK that the signal has already been handled and the dialog doesn't close. If the dialog needed closing, I did so manually.
myDialogWindow.connect("response", validate_response)
def validate_response(dialog, response_id):
# validate
if correct:
dialog.destroy()
else:
print("Something went wrong")
return True
Though this gets the job done, I'm not certain this the most GTK'ish solution.
This is my python login system I have attempted to built when I run the check on the user name and password I receive this error : http://pastebin.com/0DPAWx18
I was wondering if it because it is in a another function but I then put it in main as well and that just gave me errors
import tkinter
import time
def main():
global window
window = tkinter.Tk()
window.title("Login")
window.minsize(300,150)
window.configure(background="#7AC5CD")
userlbl = tkinter.Label(window, text="Username")
userlbl.pack()
userinp = tkinter.Entry(window)
userinp.pack()
pwlbl = tkinter.Label(window, text="Password")
pwlbl.pack()
userpw = tkinter.Entry(window)
userpw.pack()
submitbtn = tkinter.Button(text="Submit username and password here", command=check)
submitbtn.pack()
def check():
username = userinp.get()
password = userpw.get()
if username == 1234:
GD_USER = tkinter.Label(window, text="Correct user name")
GD_USER.pack()
else:
BD_USER = tkinter.Label(window, text="Bad username")
BD_USER.pack()
if password == 'test':
GD_PASS = tkinter.Label(window, text="Correct password")
GD_PASS.pack()
entry_YES()
return
else:
BD_PASS = tkinter.Label(window, text="wrong password")
window.mainloop()
def entry_NO():
print("access denied")
time.sleep(5)
close_window
return
def entry_YES():
print("Access granted please wait")
def close_window():
window.destry()
enter code here
That's is because you define userinp in the main function's scope, so it isn't defined the check function. You could make userinp and userpw global, or you could make your app into a class like this, which makes variable passing a lot easier through self.
The __init__ function is called when the class is called, so that can be used as 'main'.
I've put pretty much everything in self, which isn't necessary, but can be useful if you'd want to change any of the created widgets further on in a new function.
The update() function is needed to pack the labels before sleeping.
import tkinter as tk
import time
class App():
def __init__(self):
self.window = tk.Tk()
self.window.title("Login")
self.window.minsize(300,150)
self.window.configure(background="#7AC5CD")
self.userlbl = tk.Label(self.window, text="Username")
self.userlbl.pack()
self.userinp = tk.Entry(self.window)
self.userinp.pack()
self.pwlbl = tk.Label(self.window, text="Password")
self.pwlbl.pack()
self.userpw = tk.Entry(self.window)
self.userpw.pack()
self.submitbtn = tk.Button(text="Submit username and password here", command=self.check)
self.submitbtn.pack()
self.window.mainloop()
def check(self):
self.username = self.userinp.get()
self.password = self.userpw.get()
if self.username == '1234':
self.GD_USER = tk.Label(self.window, text="Correct user name")
self.GD_USER.pack()
else:
self.BD_USER = tk.Label(self.window, text="Bad username")
self.BD_USER.pack()
if self.password == 'test':
self.GD_PASS = tk.Label(self.window, text="Correct password")
self.GD_PASS.pack()
self.entry_YES()
else:
self.BD_PASS = tk.Label(self.window, text="Wrong password")
self.BD_PASS.pack()
self.window.update()
self.entry_NO()
def entry_NO(self):
print("Access denied, wait 5 seconds to try again")
time.sleep(5)
def entry_YES(self):
print("Access granted please wait")
def close_window(self):
window.destroy()
App()
For more info on making your app into a class read this and this question.