starts reorganise with tkinter
This commit is contained in:
parent
d7fd3c9bae
commit
adbdafef1e
|
@ -3837,977 +3837,3 @@ TODO add a countdown timer to the multiple choice game
|
|||
|
||||
* how apt does it's [progress bar](https://mdk.fr/blog/how-apt-does-its-fancy-progress-bar.html)
|
||||
|
||||
# GUI programming
|
||||
|
||||
TODO event loop introduction
|
||||
|
||||
## Tkinter helloworld
|
||||
|
||||
The absolute most basic way to have a *hello world* GUI program up and running with Tkinter is the following.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
|
||||
root = Tk()
|
||||
|
||||
MyLabel = Label(root,text="hello world")
|
||||
MyLabel.pack()
|
||||
|
||||
root.mainloop()
|
||||
```
|
||||
Tkinter have two popular architectures, the Tcl and Tk. This two architectures are different, they have their own functionality and their own official documentation.
|
||||
We are gonna use the Tk architecture.
|
||||
|
||||
The Tk architecture is used to create a GUI widget. He adds a lot of custom commands, so the widget is very customizable.
|
||||
|
||||
In the previous code,the `mainloop()` instruction allows us to open the main window and to not close immediately the window.
|
||||
And then, the `Label()` method creates label in a layout. This method has many parameters to customize a label. The instruction `pack()` will be always call , this instruction can
|
||||
add and adjust a widget.
|
||||
|
||||
While it works we know better by now.
|
||||
We should include the `if __name__ == "__main__"` statement.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
|
||||
MyLabel = Label(root,text="hello world")
|
||||
MyLabel.pack()
|
||||
|
||||
root.mainloop()
|
||||
```
|
||||
|
||||
The instance of `Tk()` is what will actually process our event loop and the `root` is the content of our window. We can customize `root` with instruction like geometry, title,etc.
|
||||
In the latter we will create our button and labels so should create our own class and inherit from it.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Label.__init__(self, text="hello world")
|
||||
self.pack()
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("title of the window")
|
||||
root.geometry("500x300")
|
||||
MainWindow()
|
||||
root.mainloop()
|
||||
```
|
||||
|
||||
We can add content to the *frame*, such as labels, input boxes and buttons as follows.
|
||||
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Label.__init__(self, text="hello world")
|
||||
#Label
|
||||
MyLabel = Label(self, text="This is a label")
|
||||
MyLabel.pack()
|
||||
|
||||
self.config(bg="yellow")
|
||||
self.pack()
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("title of the window")
|
||||
root.geometry("500x300")
|
||||
MainWindow()
|
||||
root.mainloop()
|
||||
```
|
||||
|
||||
Let's try to put multiple visual object into the same frame.
|
||||
|
||||
```python3
|
||||
from tkinter import *
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Label.__init__(self, text="hello world")
|
||||
#Label
|
||||
MyLabel = Label(self, text="This is a label")
|
||||
MyLabel.pack()
|
||||
#Button
|
||||
MyButton = Button(self, text="I'm clickable!")
|
||||
MyButton.pack()
|
||||
|
||||
self.config(bg="yellow")
|
||||
self.pack()
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("title of the window")
|
||||
root.geometry("500x300")
|
||||
MainWindow()
|
||||
root.mainloop()
|
||||
```
|
||||
|
||||
You see how they are *stacked* one on top of the other?
|
||||
We can overcome this problem with parameters of [pack()](https://wxpython.org/Phoenix/docs/html/sizers_overview.html).
|
||||
We can also use other geometry managers like [grid()](https://www.pythontutorial.net/tkinter/tkinter-grid/) and [place()](https://www.pythontutorial.net/tkinter/tkinter-place/).
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Frame.__init__(self, master=None)
|
||||
MyPanel = PanedWindow.__init__(self, bg="Blue")
|
||||
#Label
|
||||
MyLabel = Label(MyPanel, text="this is a label", bg= "yellow")
|
||||
MyLabel.pack(fill="x")
|
||||
#Bouton
|
||||
MyButton = Button(MyPanel, text="I'm clickable!")
|
||||
MyButton.place(x=10, y=50)
|
||||
self.pack(fill=BOTH,expand=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("this is the title of the window")
|
||||
root.geometry("500x300")
|
||||
win = MainWindow()
|
||||
root.mainloop()
|
||||
```
|
||||
|
||||
This is looking better!
|
||||
But it requires some explanation though.
|
||||
Let's break it down.
|
||||
The `Frame.__init__` is your **window** in which you create a `PanedWindow.__init__` which is used to draw thing to.
|
||||
To this panel you're adding two different *objects* (`Label()` and `Button()`) each with or without their own settings (such as a label or not).
|
||||
It probably looks a bit convoluted but this is how most GUI libraries work internally.
|
||||
|
||||
1. You create a frame
|
||||
2. Within this frame you create a *drawing area* and set some form of automatic layout to it.
|
||||
3. You create the visual elements you want and add them one by one to the drawing area.
|
||||
4. Success
|
||||
|
||||
Now how do we link **user input** to **code actions**?
|
||||
This is a complicated way of saying *actually do something when I click the damn button*!
|
||||
For this we'll need to create a function, or better yet a method to the `Frame.__init__` objects.
|
||||
Each time we click the button, that method will be called.
|
||||
Because it is a *method* it has access to *self* so it can modify *anything* within the scope of the instance.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Frame.__init__(self, master=None)
|
||||
MyPanel = PanedWindow.__init__(self, bg="Blue")
|
||||
#Label
|
||||
MyLabel = Label(MyPanel, text="this is a label", bg= "yellow")
|
||||
MyLabel.pack(fill="x")
|
||||
#Bouton
|
||||
MyButton = Button(MyPanel, text="I'm clickable!", command=lambda : self.ButtonEnable(MyLabel))
|
||||
MyButton.place(x=10, y=50)
|
||||
self.pack(fill=BOTH,expand=True)
|
||||
|
||||
def ButtonEnable(self, Label):
|
||||
global number
|
||||
number += 1
|
||||
counter = "You have press the button {} time".format(number)
|
||||
Label.config(text=counter)
|
||||
|
||||
|
||||
number=0
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("this is the title of the window")
|
||||
root.geometry("500x300")
|
||||
win = MainWindow()
|
||||
root.mainloop()
|
||||
|
||||
|
||||
```
|
||||
|
||||
We can use the same *idea* to grab input from the textbox.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Frame.__init__(self, master=None)
|
||||
MyPanel = PanedWindow.__init__(self, bg="Blue")
|
||||
#Label
|
||||
MyLabel = Label(MyPanel, text="this is a label", bg= "yellow")
|
||||
MyLabel.pack(fill="x")
|
||||
#TextBox
|
||||
MyEntry = Entry(MyPanel)
|
||||
MyEntry.place(x=200,y=50)
|
||||
#Bouton
|
||||
MyButton = Button(MyPanel, text="I'm clickable!", command=lambda : self.ButtonEnable(MyLabel,MyEntry))
|
||||
MyButton.place(x=10, y=50)
|
||||
self.pack(fill=BOTH,expand=True)
|
||||
|
||||
|
||||
def ButtonEnable(self, MyLabel, MyEntry):
|
||||
MyText = MyEntry.get()
|
||||
MyLabel.config(text=MyText)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("this is the title of the window")
|
||||
root.geometry("500x300")
|
||||
win = MainWindow()
|
||||
root.mainloop()
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Tkinter guess the number
|
||||
|
||||
```python
|
||||
import time
|
||||
from tkinter import *
|
||||
import random
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Frame.__init__(self, master=None, bg="white")
|
||||
MyPanel = PanedWindow.__init__(self)
|
||||
|
||||
MyNumber = random.randint(0, 100)
|
||||
|
||||
#Label
|
||||
self.MyLabel = Label(MyPanel, text="I have a number in mind...", bg= "blue")
|
||||
self.MyLabel.pack(fill="x", ipadx=25, ipady=20)
|
||||
#TextBox
|
||||
MyEntry = Entry(MyPanel)
|
||||
MyEntry.place(x=200,y=90)
|
||||
#Bouton
|
||||
MyButton = Button(MyPanel, text="I'm clickable!", command=lambda : self.ButtonEnable(MyEntry, MyNumber))
|
||||
MyButton.place(x=10, y=90)
|
||||
|
||||
self.pack(fill=BOTH,expand=True)
|
||||
|
||||
|
||||
def ButtonEnable(self, MyEntry, MyNumber):
|
||||
if self.IsCorrect(MyEntry.get()):
|
||||
number = int(MyEntry.get())
|
||||
if number != MyNumber:
|
||||
self.GameOver(number, MyNumber)
|
||||
else:
|
||||
self.Win()
|
||||
else:
|
||||
self.MyLabel.config(text="I need numbers!")
|
||||
|
||||
def GameOver(self, number, MyNumber):
|
||||
if number > MyNumber:
|
||||
self.MyLabel.config(text="My number is smaller")
|
||||
else:
|
||||
self.MyLabel.config(text="My number is bigger")
|
||||
|
||||
def Win(self):
|
||||
self.MyLabel.config(text="You WIN!")
|
||||
|
||||
def IsCorrect(self, MyEntry):
|
||||
x = str(MyEntry)
|
||||
if x.isdigit() == True:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("Guess the number")
|
||||
root.geometry("500x300")
|
||||
win = MainWindow()
|
||||
root.mainloop()
|
||||
|
||||
|
||||
```
|
||||
|
||||
## MVC design pattern
|
||||
|
||||
A simple console only MVC.
|
||||
We'll add the GUI view in a bit.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
|
||||
class ConsoleView(object):
|
||||
"""A view for console."""
|
||||
|
||||
def select_task(self):
|
||||
"""Asks which index to look up."""
|
||||
idx = input("which task do you want to see? ")
|
||||
return idx
|
||||
|
||||
def show(self, task):
|
||||
"""Displays the task to the console. This method is called from the
|
||||
controller."""
|
||||
print("your task: {}".format(task))
|
||||
|
||||
def error(self, msg):
|
||||
"""Prints error messages coming from the controller."""
|
||||
print("error: {}".format(msg))
|
||||
|
||||
class Model(object):
|
||||
"""The model houses add data and should implement all methods related to
|
||||
adding, modifying and deleting tasks."""
|
||||
|
||||
db = ["go to the shops", "dryhop beer", "drop of motorbike"]
|
||||
|
||||
def get_task(self, idx):
|
||||
"""Performs a task lookun into the database and returns it when found.
|
||||
If no task is found, it returns an error message that will be displayed
|
||||
in the view (via the controller)."""
|
||||
try:
|
||||
task = Model.db[idx]
|
||||
except IndexError:
|
||||
task = "task with {} not found!".format(idx)
|
||||
return task
|
||||
|
||||
|
||||
class Controller(object):
|
||||
"""Binds the model and the view together."""
|
||||
|
||||
def __init__(self, view):
|
||||
self.model = Model()
|
||||
self.view = view
|
||||
|
||||
def run(self):
|
||||
"""The controller's main function. Depending on what type of view is
|
||||
selected, a different event loop is setup. Do note that the ConsoleView
|
||||
is not a real event loop, just a basic flow of action."""
|
||||
if self.view is ConsoleView:
|
||||
self.view = self.view()
|
||||
self._run_console_view()
|
||||
elif self.view is TkinterView:
|
||||
root = Tk()
|
||||
root.title("Task Manager")
|
||||
root.geometry("500x300")
|
||||
self.view = self.view()
|
||||
self.view._set_controller(self)
|
||||
root.mainloop()
|
||||
|
||||
def get_task_from_model(self, idx):
|
||||
"""Needed for the TkinterView to communicate with the controller."""
|
||||
task = self.model.get_task(idx)
|
||||
self.view.show_task(task)
|
||||
|
||||
def _run_console_view(self):
|
||||
"""Super simple event loop."""
|
||||
while True:
|
||||
try:
|
||||
idx = self.view.select_task()
|
||||
idx = int(idx)
|
||||
except Exception as e:
|
||||
self.view.error(e)
|
||||
continue
|
||||
task = self.model.get_task(idx)
|
||||
self.view.show(task)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
view = ConsoleView
|
||||
app = Controller(view)
|
||||
app.run()
|
||||
```
|
||||
|
||||
And now with the implemented `TkinterView` class.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
|
||||
class ConsoleView(object):
|
||||
"""A view for console."""
|
||||
|
||||
def select_task(self):
|
||||
"""Asks which index to look up."""
|
||||
idx = input("which task do you want to see? ")
|
||||
return idx
|
||||
|
||||
def show(self, task):
|
||||
"""Displays the task to the console. This method is called from the
|
||||
controller."""
|
||||
print("your task: {}".format(task))
|
||||
|
||||
def error(self, msg):
|
||||
"""Prints error messages coming from the controller."""
|
||||
print("error: {}".format(msg))
|
||||
|
||||
|
||||
class TkinterView(Frame):
|
||||
"""A view using a wx.Dialog window"""
|
||||
|
||||
def __init__(self):
|
||||
Frame.__init__(self, master=None)
|
||||
#Panel
|
||||
self.panel = PanedWindow(self, bg="green")
|
||||
self.panel.pack(fill=BOTH, expand=True)
|
||||
|
||||
#Task Label
|
||||
self.task = Label(self.panel, text="your task")
|
||||
self.task.pack(expand=True)
|
||||
|
||||
#SpinBox
|
||||
self.idx = Spinbox(self.panel, from_=0, to=2, wrap=True )
|
||||
self.idx.pack(side= TOP)
|
||||
|
||||
#Button
|
||||
self.button = Button(self.panel, text="submit", command=lambda : self.select_task())
|
||||
self.button.pack(ipadx=60, ipady=30)
|
||||
|
||||
self.pack(fill=BOTH, expand=True)
|
||||
|
||||
def _set_controller(self, controller):
|
||||
"""Set the controller so the view can communicate it's requests to it
|
||||
and update it's values too."""
|
||||
self.controller = controller
|
||||
|
||||
def select_task(self):
|
||||
"""Gets the index to look up in the model and submits the request to
|
||||
the controller."""
|
||||
idx = self.idx.get()
|
||||
self.controller.get_task_from_model(idx)
|
||||
|
||||
def show_task(self, task):
|
||||
"""Updates the visual label in the view with the task. This method is
|
||||
called from the controller."""
|
||||
self.task.config(text=task)
|
||||
|
||||
|
||||
class Model(object):
|
||||
"""The model houses add data and should implement all methods related to
|
||||
adding, modifying and deleting tasks."""
|
||||
|
||||
db = ["go to the shops", "dryhop beer", "drop of motorbike"]
|
||||
|
||||
def get_task(self, idx):
|
||||
"""Performs a task lookun into the database and returns it when found.
|
||||
If no task is found, it returns an error message that will be displayed
|
||||
in the view (via the controller)."""
|
||||
try:
|
||||
task = Model.db[int(idx)]
|
||||
except IndexError:
|
||||
task = "task with {} not found!".format(idx)
|
||||
return task
|
||||
|
||||
|
||||
class Controller(object):
|
||||
"""Binds the model and the view together."""
|
||||
|
||||
def __init__(self, view):
|
||||
self.model = Model()
|
||||
self.view = view
|
||||
|
||||
def run(self):
|
||||
"""The controller's main function. Depending on what type of view is
|
||||
selected, a different event loop is setup. Do note that the ConsoleView
|
||||
is not a real event loop, just a basic flow of action."""
|
||||
if self.view is ConsoleView:
|
||||
self.view = self.view()
|
||||
self._run_console_view()
|
||||
elif self.view is TkinterView:
|
||||
root = Tk()
|
||||
root.title("Task Manager")
|
||||
root.geometry("500x300")
|
||||
self.view = self.view()
|
||||
self.view._set_controller(self)
|
||||
root.mainloop()
|
||||
|
||||
def get_task_from_model(self, idx):
|
||||
"""Needed for the TkinterView to communicate with the controller."""
|
||||
task = self.model.get_task(idx)
|
||||
self.view.show_task(task)
|
||||
|
||||
def _run_console_view(self):
|
||||
"""Super simple event loop."""
|
||||
while True:
|
||||
try:
|
||||
idx = self.view.select_task()
|
||||
idx = int(idx)
|
||||
except Exception as e:
|
||||
self.view.error(e)
|
||||
continue
|
||||
task = self.model.get_task(idx)
|
||||
self.view.show(task)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
view = TkinterView
|
||||
app = Controller(view)
|
||||
app.run()
|
||||
|
||||
```
|
||||
|
||||
For a GUI only login generator an implementation without MVC could look a bit like this.
|
||||
Note that the actual calculation is done inside the window itself.
|
||||
This is not a good idea because we should separate responsibilities into classes!
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
from tkinter import ttk
|
||||
import login_generator
|
||||
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Frame.__init__(self)
|
||||
self.full_window = PanedWindow(self,orient=VERTICAL)
|
||||
self.full_window.pack(fill=BOTH,expand=True)
|
||||
self.create_top_panel()
|
||||
self.create_bottom_panel()
|
||||
self.pack(fill=BOTH, expand=True)
|
||||
|
||||
def create_bottom_panel(self):
|
||||
bottom_panel = PanedWindow(self.full_window, bg="green")
|
||||
bottom_panel.pack(fill=BOTH, side=BOTTOM)
|
||||
#List
|
||||
self.login_list = ttk.Treeview(bottom_panel, columns=["username", "password"], show="headings", height=6)
|
||||
self.login_list.pack()
|
||||
self.login_list.heading("username", text="Username")
|
||||
self.login_list.heading("password", text="Password")
|
||||
self.login_list.bind("<ButtonRelease-1>", lambda e: self.show_popup())
|
||||
|
||||
def create_top_panel(self):
|
||||
|
||||
top_panel = PanedWindow(self.full_window, bg="red")
|
||||
self.login_amount = Text(top_panel, height=5, width=52)
|
||||
self.login_amount.place(x=10,y=10)
|
||||
self.complex = BooleanVar()
|
||||
self.complex.set(False)
|
||||
self.login_complex = Checkbutton(top_panel, text="complex",var=self.complex)
|
||||
self.login_complex.place(x=10,y=100)
|
||||
self.login_create = Button(top_panel, text="Create", command=lambda: self.add_login())
|
||||
self.login_create.place(x=100,y=100)
|
||||
top_panel.pack(expand=True, fill=BOTH)
|
||||
|
||||
def show_popup(self):
|
||||
global root
|
||||
menu = Menu()
|
||||
menu.add_command(label="Copy selected items", command=lambda : self.copy_items())
|
||||
root.config(menu=menu)
|
||||
|
||||
def copy_items(self):
|
||||
global root
|
||||
try:
|
||||
Item = self.login_list.item(self.login_list.focus())
|
||||
FunctionValue = Item.values()
|
||||
Array = list(FunctionValue)
|
||||
Login = Array[2]
|
||||
self.login_amount.insert(END, "Username : {} \nPassword : {} \n".format(Login[0],Login[1]))
|
||||
|
||||
except:
|
||||
Msg = Toplevel(root)
|
||||
l1 = Label(Msg, text="You have to take something")
|
||||
l1.pack(fill=BOTH, expand=True)
|
||||
|
||||
def add_login(self):
|
||||
self.username = login_generator.generate_username()
|
||||
self.password = login_generator.generate_password(12,self.complex.get())
|
||||
self.login_list.insert('', END,values=[self.username, self.password])
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("Login Generator")
|
||||
root.geometry("500x300")
|
||||
win = MainWindow()
|
||||
root.mainloop()
|
||||
|
||||
```
|
||||
|
||||
Now let's assume the generate username and password function take some calculation time.
|
||||
We'll add in a fake `time.sleep` to simulate.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
from tkinter import ttk
|
||||
import login_generator
|
||||
import time
|
||||
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Frame.__init__(self)
|
||||
self.full_window = PanedWindow(self,orient=VERTICAL)
|
||||
self.full_window.pack(fill=BOTH,expand=True)
|
||||
self.create_top_panel()
|
||||
self.create_bottom_panel()
|
||||
self.pack(fill=BOTH, expand=True)
|
||||
|
||||
def create_bottom_panel(self):
|
||||
bottom_panel = PanedWindow(self.full_window, bg="green")
|
||||
bottom_panel.pack(fill=BOTH, side=BOTTOM)
|
||||
#List
|
||||
self.login_list = ttk.Treeview(bottom_panel, columns=["username", "password"], show="headings", height=6)
|
||||
self.login_list.pack()
|
||||
self.login_list.heading("username", text="Username")
|
||||
self.login_list.heading("password", text="Password")
|
||||
self.login_list.bind("<ButtonRelease-1>", lambda e: self.show_popup())
|
||||
|
||||
def create_top_panel(self):
|
||||
top_panel = PanedWindow(self.full_window, bg="red")
|
||||
self.login_amount = Text(top_panel, height=5, width=52)
|
||||
self.login_amount.place(x=10,y=10)
|
||||
self.complex = BooleanVar()
|
||||
self.complex.set(False)
|
||||
self.login_complex = Checkbutton(top_panel, text="complex",var=self.complex)
|
||||
self.login_complex.place(x=10,y=100)
|
||||
self.login_create = Button(top_panel, text="Create", command=lambda: self.add_login())
|
||||
self.login_create.place(x=100,y=100)
|
||||
top_panel.pack(expand=True, fill=BOTH)
|
||||
|
||||
def show_popup(self):
|
||||
global root
|
||||
menu = Menu()
|
||||
menu.add_command(label="Copy selected items", command=lambda : self.copy_items())
|
||||
root.config(menu=menu)
|
||||
|
||||
def copy_items(self):
|
||||
global root
|
||||
try:
|
||||
Item = self.login_list.item(self.login_list.focus())
|
||||
FunctionValue = Item.values()
|
||||
Array = list(FunctionValue)
|
||||
Login = Array[2]
|
||||
self.login_amount.insert(END, "Username : {} \nPassword : {} \n".format(Login[0],Login[1]))
|
||||
time.sleep(1)
|
||||
|
||||
except:
|
||||
Msg = Toplevel(root)
|
||||
l1 = Label(Msg, text="You have to take something")
|
||||
l1.pack(fill=BOTH, expand=True)
|
||||
|
||||
def add_login(self):
|
||||
self.username = login_generator.generate_username()
|
||||
self.password = login_generator.generate_password(12,self.complex.get())
|
||||
self.login_list.insert('', END,values=[self.username, self.password])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("Login Generator")
|
||||
root.geometry("500x300")
|
||||
win = MainWindow()
|
||||
root.mainloop()
|
||||
|
||||
```
|
||||
|
||||
A clear separation of responsabilities can be acchieved via an MVC pattern and a login *library*.
|
||||
The library code is quite straightforward and goes as follows.
|
||||
It's basically the same code we did before but with added `try except` blocks.
|
||||
Can you tell me why I added those?
|
||||
|
||||
```python
|
||||
import random
|
||||
import string
|
||||
|
||||
|
||||
def load_file(filename):
|
||||
"""
|
||||
We load a file and make a list out of it. Note that the same function is
|
||||
used for both files (both adjectives and subjects). Functions should be
|
||||
made as generic as possible.
|
||||
|
||||
There IS a problem you can fix, some logins will have spaces in them. Try
|
||||
to remove them in this function!
|
||||
"""
|
||||
words = []
|
||||
with open(filename, "r") as fp:
|
||||
lines = fp.readlines()
|
||||
for line in lines:
|
||||
words.append(line.strip()) # what does strip() do, what does append() do? remember CTRL+Q!
|
||||
return words
|
||||
|
||||
|
||||
def generate_username():
|
||||
"""
|
||||
We'll generate a random pair of adjectives and subjects from two wordlists.
|
||||
You NEED to have both files in you python project for this to work! Note
|
||||
the capitalize method call to make it all prettier...
|
||||
"""
|
||||
try:
|
||||
adjectives = load_file("./adjectives.txt")
|
||||
except:
|
||||
adjectives = ["big", "funny", "normal", "red"]
|
||||
try:
|
||||
subjects = load_file("./subjects.txt")
|
||||
except:
|
||||
subjects = ["giraffe", "elephant", "cougar", "tiger"]
|
||||
adjective = random.choice(adjectives)
|
||||
subject = random.choice(subjects)
|
||||
username = adjective.capitalize() + subject.capitalize()
|
||||
return username
|
||||
|
||||
|
||||
def generate_password(length=10, complictated=True):
|
||||
"""
|
||||
We generate a password with default settings. You can overide these by
|
||||
changing the arguments in the function call.
|
||||
"""
|
||||
password = ""
|
||||
if complictated:
|
||||
chars = string.ascii_letters + string.digits + string.punctuation
|
||||
else:
|
||||
chars = string.ascii_letters
|
||||
for i in range(0, length):
|
||||
password += random.choice(chars)
|
||||
return password
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# let's do some testing!
|
||||
username_test = generate_username()
|
||||
print(username_test)
|
||||
password_test = generate_password()
|
||||
print(password_test)
|
||||
```
|
||||
|
||||
And now the GUI code nicely split up in a **model**, **controller** and a **view**.
|
||||
The overhead is quite large but it makes the code a lot more scalable.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
from tkinter import ttk
|
||||
import login_generator
|
||||
import time
|
||||
import threading
|
||||
import queue
|
||||
|
||||
|
||||
class View(Frame):
|
||||
def __init__(self, controller):
|
||||
Frame.__init__(self)
|
||||
self.controller = controller
|
||||
self.full_window = PanedWindow(self, orient=VERTICAL)
|
||||
self.full_window.pack(fill=BOTH, expand=True)
|
||||
self.create_top_panel()
|
||||
self.create_bottom_panel()
|
||||
self.pack(fill=BOTH, expand=True)
|
||||
|
||||
def create_bottom_panel(self):
|
||||
bottom_panel = PanedWindow(self.full_window, bg="green")
|
||||
bottom_panel.pack(fill=BOTH, side=BOTTOM)
|
||||
# List
|
||||
self.login_list = ttk.Treeview(bottom_panel, columns=["username", "password"], show="headings", height=6)
|
||||
self.login_list.pack()
|
||||
self.login_list.heading("username", text="Username")
|
||||
self.login_list.heading("password", text="Password")
|
||||
self.login_list.bind("<ButtonRelease-1>", lambda e: self.show_popup())
|
||||
|
||||
def create_top_panel(self):
|
||||
top_panel = PanedWindow(self.full_window, bg="red")
|
||||
self.login_amount = Text(top_panel, height=5, width=52)
|
||||
self.login_amount.place(x=10, y=10)
|
||||
self.complex = BooleanVar()
|
||||
self.complex.set(False)
|
||||
self.login_complex = Checkbutton(top_panel, text="complex", var=self.complex)
|
||||
self.login_complex.place(x=10, y=100)
|
||||
self.login_create = Button(top_panel, text="Create", command=lambda: self.submit_request())
|
||||
self.login_create.place(x=100, y=100)
|
||||
top_panel.pack(expand=True, fill=BOTH)
|
||||
|
||||
def show_popup(self):
|
||||
global app
|
||||
menu = Menu()
|
||||
menu.add_command(label="Copy selected items", command=lambda : self.copy_items())
|
||||
app.config(menu=menu)
|
||||
|
||||
def copy_items(self):
|
||||
try:
|
||||
Item = self.login_list.item(self.login_list.focus())
|
||||
FunctionValue = Item.values()
|
||||
Array = list(FunctionValue)
|
||||
Login = Array[2]
|
||||
self.login_amount.insert(END, "Username : {} \nPassword : {} \n".format(Login[0], Login[1]))
|
||||
time.sleep(1)
|
||||
|
||||
except:
|
||||
Msg = Toplevel(root)
|
||||
l1 = Label(Msg, text="You have to take something")
|
||||
l1.pack(fill=BOTH, expand=True)
|
||||
|
||||
def submit_request(self):
|
||||
amount = self.login_amount.get("1.0", END)
|
||||
complex_bool = self.complex.get()
|
||||
try:
|
||||
amount = int(amount)
|
||||
except:
|
||||
amount = 1
|
||||
self.controller.get_new_logins(amount, complex_bool)
|
||||
|
||||
def update_logins(self, login):
|
||||
username, password = login
|
||||
self.login_list.insert('', END, values=[username, password])
|
||||
|
||||
class Controller(object):
|
||||
def __init__(self):
|
||||
global app
|
||||
app.title("Login Generator")
|
||||
app.geometry("450x300")
|
||||
self.view = View(self)
|
||||
self.model = Model(self)
|
||||
|
||||
def get_new_logins(self, amount, complex_bool):
|
||||
self.model.generate_login(amount, complex_bool)
|
||||
|
||||
def set_new_logins(self, logins):
|
||||
self.view.update_logins(logins)
|
||||
|
||||
|
||||
class Model(threading.Thread):
|
||||
def __init__(self, controller):
|
||||
threading.Thread.__init__(self, target=self.main)
|
||||
self.controller = controller
|
||||
self.queue = queue.Queue()
|
||||
self.start()
|
||||
|
||||
def main(self):
|
||||
while True:
|
||||
amount, complex_bool = self.queue.get()
|
||||
username = login_generator.generate_username()
|
||||
password = login_generator.generate_password(12, complex_bool)
|
||||
logins = (username, password)
|
||||
self.controller.set_new_logins(logins)
|
||||
time.sleep(1)
|
||||
|
||||
def generate_login(self, amount, complex_bool):
|
||||
for i in range(0, amount):
|
||||
self.queue.put((amount, complex_bool))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = Tk()
|
||||
mvc = Controller()
|
||||
app.mainloop()
|
||||
|
||||
```
|
||||
|
||||
If you want to add a scrollbar in the project.
|
||||
|
||||
````python
|
||||
from tkinter import *
|
||||
from tkinter import ttk
|
||||
import login_generator
|
||||
import time
|
||||
import threading
|
||||
import queue
|
||||
|
||||
|
||||
class View(Frame):
|
||||
def __init__(self, controller):
|
||||
Frame.__init__(self)
|
||||
self.controller = controller
|
||||
self.full_window = PanedWindow(self, orient=VERTICAL)
|
||||
self.full_window.pack(fill=BOTH, expand=True)
|
||||
self.create_top_panel()
|
||||
self.create_bottom_panel()
|
||||
self.pack(fill=BOTH, expand=True)
|
||||
|
||||
def create_bottom_panel(self):
|
||||
bottom_panel = PanedWindow(self.full_window, bg="green")
|
||||
bottom_panel.pack(fill=BOTH, side=BOTTOM)
|
||||
##Scrollbar
|
||||
self.Scrollbar = Scrollbar(bottom_panel )
|
||||
self.Scrollbar.pack(side=RIGHT, fill= Y)
|
||||
# List
|
||||
self.login_list = ttk.Treeview(bottom_panel, columns=["username", "password"], show="headings", height=6, yscrollcommand= self.Scrollbar.set)
|
||||
self.login_list.pack()
|
||||
self.login_list.heading("username", text="Username")
|
||||
self.login_list.heading("password", text="Password")
|
||||
self.login_list.bind("<ButtonRelease-1>", lambda e: self.show_popup())
|
||||
self.Scrollbar.config(command= self.login_list.yview)
|
||||
|
||||
|
||||
def create_top_panel(self):
|
||||
top_panel = PanedWindow(self.full_window, bg="red")
|
||||
self.login_amount = Text(top_panel, height=5, width=52)
|
||||
self.login_amount.place(x=10, y=10)
|
||||
self.complex = BooleanVar()
|
||||
self.complex.set(False)
|
||||
self.login_complex = Checkbutton(top_panel, text="complex", var=self.complex)
|
||||
self.login_complex.place(x=10, y=100)
|
||||
self.login_create = Button(top_panel, text="Create", command=lambda: self.submit_request())
|
||||
self.login_create.place(x=100, y=100)
|
||||
top_panel.pack(expand=True, fill=BOTH)
|
||||
|
||||
def show_popup(self):
|
||||
global app
|
||||
menu = Menu()
|
||||
menu.add_command(label="Copy selected items", command=lambda : self.copy_items())
|
||||
app.config(menu=menu)
|
||||
|
||||
def copy_items(self):
|
||||
try:
|
||||
Item = self.login_list.item(self.login_list.focus())
|
||||
FunctionValue = Item.values()
|
||||
Array = list(FunctionValue)
|
||||
Login = Array[2]
|
||||
self.login_amount.insert(END, "Username : {} \nPassword : {} \n".format(Login[0], Login[1]))
|
||||
time.sleep(1)
|
||||
|
||||
except:
|
||||
Msg = Toplevel(root)
|
||||
l1 = Label(Msg, text="You have to take something")
|
||||
l1.pack(fill=BOTH, expand=True)
|
||||
|
||||
def submit_request(self):
|
||||
amount = self.login_amount.get("1.0", END)
|
||||
complex_bool = self.complex.get()
|
||||
try:
|
||||
amount = int(amount)
|
||||
except:
|
||||
amount = 1
|
||||
self.controller.get_new_logins(amount, complex_bool)
|
||||
|
||||
def update_logins(self, login):
|
||||
username, password = login
|
||||
self.login_list.insert('', END, values=[username, password])
|
||||
|
||||
class Controller(object):
|
||||
def __init__(self):
|
||||
global app
|
||||
app.title("Login Generator")
|
||||
app.geometry("450x300")
|
||||
self.view = View(self)
|
||||
self.model = Model(self)
|
||||
|
||||
def get_new_logins(self, amount, complex_bool):
|
||||
self.model.generate_login(amount, complex_bool)
|
||||
|
||||
def set_new_logins(self, logins):
|
||||
self.view.update_logins(logins)
|
||||
|
||||
|
||||
class Model(threading.Thread):
|
||||
def __init__(self, controller):
|
||||
threading.Thread.__init__(self, target=self.main)
|
||||
self.controller = controller
|
||||
self.queue = queue.Queue()
|
||||
self.start()
|
||||
|
||||
def main(self):
|
||||
while True:
|
||||
amount, complex_bool = self.queue.get()
|
||||
username = login_generator.generate_username()
|
||||
password = login_generator.generate_password(12, complex_bool)
|
||||
logins = (username, password)
|
||||
self.controller.set_new_logins(logins)
|
||||
time.sleep(1)
|
||||
|
||||
def generate_login(self, amount, complex_bool):
|
||||
for i in range(0, amount):
|
||||
self.queue.put((amount, complex_bool))
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = Tk()
|
||||
mvc = Controller()
|
||||
app.mainloop()
|
||||
````
|
||||
|
||||
|
||||
|
||||
|
||||
# Coding challenge - Login generator with GUI
|
||||
|
||||
# Coding challenge - Trivial pursuit with GUI
|
||||
|
|
|
@ -0,0 +1,975 @@
|
|||
# WXpython
|
||||
|
||||
|
||||
# tkinter
|
||||
|
||||
## Tkinter helloworld
|
||||
|
||||
The absolute most basic way to have a *hello world* GUI program up and running with Tkinter is the following.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
|
||||
root = Tk()
|
||||
|
||||
MyLabel = Label(root,text="hello world")
|
||||
MyLabel.pack()
|
||||
|
||||
root.mainloop()
|
||||
```
|
||||
Tkinter have two popular architectures, the Tcl and Tk. This two architectures are different, they have their own functionality and their own official documentation.
|
||||
We are gonna use the Tk architecture.
|
||||
|
||||
The Tk architecture is used to create a GUI widget. He adds a lot of custom commands, so the widget is very customizable.
|
||||
|
||||
In the previous code,the `mainloop()` instruction allows us to open the main window and to not close immediately the window.
|
||||
And then, the `Label()` method creates label in a layout. This method has many parameters to customize a label. The instruction `pack()` will be always call , this instruction can
|
||||
add and adjust a widget.
|
||||
|
||||
While it works we know better by now.
|
||||
We should include the `if __name__ == "__main__"` statement.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
|
||||
MyLabel = Label(root,text="hello world")
|
||||
MyLabel.pack()
|
||||
|
||||
root.mainloop()
|
||||
```
|
||||
|
||||
The instance of `Tk()` is what will actually process our event loop and the `root` is the content of our window. We can customize `root` with instruction like geometry, title,etc.
|
||||
In the latter we will create our button and labels so should create our own class and inherit from it.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Label.__init__(self, text="hello world")
|
||||
self.pack()
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("title of the window")
|
||||
root.geometry("500x300")
|
||||
MainWindow()
|
||||
root.mainloop()
|
||||
```
|
||||
|
||||
We can add content to the *frame*, such as labels, input boxes and buttons as follows.
|
||||
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Label.__init__(self, text="hello world")
|
||||
#Label
|
||||
MyLabel = Label(self, text="This is a label")
|
||||
MyLabel.pack()
|
||||
|
||||
self.config(bg="yellow")
|
||||
self.pack()
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("title of the window")
|
||||
root.geometry("500x300")
|
||||
MainWindow()
|
||||
root.mainloop()
|
||||
```
|
||||
|
||||
Let's try to put multiple visual object into the same frame.
|
||||
|
||||
```python3
|
||||
from tkinter import *
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Label.__init__(self, text="hello world")
|
||||
#Label
|
||||
MyLabel = Label(self, text="This is a label")
|
||||
MyLabel.pack()
|
||||
#Button
|
||||
MyButton = Button(self, text="I'm clickable!")
|
||||
MyButton.pack()
|
||||
|
||||
self.config(bg="yellow")
|
||||
self.pack()
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("title of the window")
|
||||
root.geometry("500x300")
|
||||
MainWindow()
|
||||
root.mainloop()
|
||||
```
|
||||
|
||||
You see how they are *stacked* one on top of the other?
|
||||
We can overcome this problem with parameters of [pack()](https://wxpython.org/Phoenix/docs/html/sizers_overview.html).
|
||||
We can also use other geometry managers like [grid()](https://www.pythontutorial.net/tkinter/tkinter-grid/) and [place()](https://www.pythontutorial.net/tkinter/tkinter-place/).
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Frame.__init__(self, master=None)
|
||||
MyPanel = PanedWindow.__init__(self, bg="Blue")
|
||||
#Label
|
||||
MyLabel = Label(MyPanel, text="this is a label", bg= "yellow")
|
||||
MyLabel.pack(fill="x")
|
||||
#Bouton
|
||||
MyButton = Button(MyPanel, text="I'm clickable!")
|
||||
MyButton.place(x=10, y=50)
|
||||
self.pack(fill=BOTH,expand=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("this is the title of the window")
|
||||
root.geometry("500x300")
|
||||
win = MainWindow()
|
||||
root.mainloop()
|
||||
```
|
||||
|
||||
This is looking better!
|
||||
But it requires some explanation though.
|
||||
Let's break it down.
|
||||
The `Frame.__init__` is your **window** in which you create a `PanedWindow.__init__` which is used to draw thing to.
|
||||
To this panel you're adding two different *objects* (`Label()` and `Button()`) each with or without their own settings (such as a label or not).
|
||||
It probably looks a bit convoluted but this is how most GUI libraries work internally.
|
||||
|
||||
1. You create a frame
|
||||
2. Within this frame you create a *drawing area* and set some form of automatic layout to it.
|
||||
3. You create the visual elements you want and add them one by one to the drawing area.
|
||||
4. Success
|
||||
|
||||
Now how do we link **user input** to **code actions**?
|
||||
This is a complicated way of saying *actually do something when I click the damn button*!
|
||||
For this we'll need to create a function, or better yet a method to the `Frame.__init__` objects.
|
||||
Each time we click the button, that method will be called.
|
||||
Because it is a *method* it has access to *self* so it can modify *anything* within the scope of the instance.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Frame.__init__(self, master=None)
|
||||
MyPanel = PanedWindow.__init__(self, bg="Blue")
|
||||
#Label
|
||||
MyLabel = Label(MyPanel, text="this is a label", bg= "yellow")
|
||||
MyLabel.pack(fill="x")
|
||||
#Bouton
|
||||
MyButton = Button(MyPanel, text="I'm clickable!", command=lambda : self.ButtonEnable(MyLabel))
|
||||
MyButton.place(x=10, y=50)
|
||||
self.pack(fill=BOTH,expand=True)
|
||||
|
||||
def ButtonEnable(self, Label):
|
||||
global number
|
||||
number += 1
|
||||
counter = "You have press the button {} time".format(number)
|
||||
Label.config(text=counter)
|
||||
|
||||
|
||||
number=0
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("this is the title of the window")
|
||||
root.geometry("500x300")
|
||||
win = MainWindow()
|
||||
root.mainloop()
|
||||
|
||||
|
||||
```
|
||||
|
||||
We can use the same *idea* to grab input from the textbox.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Frame.__init__(self, master=None)
|
||||
MyPanel = PanedWindow.__init__(self, bg="Blue")
|
||||
#Label
|
||||
MyLabel = Label(MyPanel, text="this is a label", bg= "yellow")
|
||||
MyLabel.pack(fill="x")
|
||||
#TextBox
|
||||
MyEntry = Entry(MyPanel)
|
||||
MyEntry.place(x=200,y=50)
|
||||
#Bouton
|
||||
MyButton = Button(MyPanel, text="I'm clickable!", command=lambda : self.ButtonEnable(MyLabel,MyEntry))
|
||||
MyButton.place(x=10, y=50)
|
||||
self.pack(fill=BOTH,expand=True)
|
||||
|
||||
|
||||
def ButtonEnable(self, MyLabel, MyEntry):
|
||||
MyText = MyEntry.get()
|
||||
MyLabel.config(text=MyText)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("this is the title of the window")
|
||||
root.geometry("500x300")
|
||||
win = MainWindow()
|
||||
root.mainloop()
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Tkinter guess the number
|
||||
|
||||
```python
|
||||
import time
|
||||
from tkinter import *
|
||||
import random
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Frame.__init__(self, master=None, bg="white")
|
||||
MyPanel = PanedWindow.__init__(self)
|
||||
|
||||
MyNumber = random.randint(0, 100)
|
||||
|
||||
#Label
|
||||
self.MyLabel = Label(MyPanel, text="I have a number in mind...", bg= "blue")
|
||||
self.MyLabel.pack(fill="x", ipadx=25, ipady=20)
|
||||
#TextBox
|
||||
MyEntry = Entry(MyPanel)
|
||||
MyEntry.place(x=200,y=90)
|
||||
#Bouton
|
||||
MyButton = Button(MyPanel, text="I'm clickable!", command=lambda : self.ButtonEnable(MyEntry, MyNumber))
|
||||
MyButton.place(x=10, y=90)
|
||||
|
||||
self.pack(fill=BOTH,expand=True)
|
||||
|
||||
|
||||
def ButtonEnable(self, MyEntry, MyNumber):
|
||||
if self.IsCorrect(MyEntry.get()):
|
||||
number = int(MyEntry.get())
|
||||
if number != MyNumber:
|
||||
self.GameOver(number, MyNumber)
|
||||
else:
|
||||
self.Win()
|
||||
else:
|
||||
self.MyLabel.config(text="I need numbers!")
|
||||
|
||||
def GameOver(self, number, MyNumber):
|
||||
if number > MyNumber:
|
||||
self.MyLabel.config(text="My number is smaller")
|
||||
else:
|
||||
self.MyLabel.config(text="My number is bigger")
|
||||
|
||||
def Win(self):
|
||||
self.MyLabel.config(text="You WIN!")
|
||||
|
||||
def IsCorrect(self, MyEntry):
|
||||
x = str(MyEntry)
|
||||
if x.isdigit() == True:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("Guess the number")
|
||||
root.geometry("500x300")
|
||||
win = MainWindow()
|
||||
root.mainloop()
|
||||
|
||||
|
||||
```
|
||||
|
||||
## MVC design pattern
|
||||
|
||||
A simple console only MVC.
|
||||
We'll add the GUI view in a bit.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
|
||||
class ConsoleView(object):
|
||||
"""A view for console."""
|
||||
|
||||
def select_task(self):
|
||||
"""Asks which index to look up."""
|
||||
idx = input("which task do you want to see? ")
|
||||
return idx
|
||||
|
||||
def show(self, task):
|
||||
"""Displays the task to the console. This method is called from the
|
||||
controller."""
|
||||
print("your task: {}".format(task))
|
||||
|
||||
def error(self, msg):
|
||||
"""Prints error messages coming from the controller."""
|
||||
print("error: {}".format(msg))
|
||||
|
||||
class Model(object):
|
||||
"""The model houses add data and should implement all methods related to
|
||||
adding, modifying and deleting tasks."""
|
||||
|
||||
db = ["go to the shops", "dryhop beer", "drop of motorbike"]
|
||||
|
||||
def get_task(self, idx):
|
||||
"""Performs a task lookun into the database and returns it when found.
|
||||
If no task is found, it returns an error message that will be displayed
|
||||
in the view (via the controller)."""
|
||||
try:
|
||||
task = Model.db[idx]
|
||||
except IndexError:
|
||||
task = "task with {} not found!".format(idx)
|
||||
return task
|
||||
|
||||
|
||||
class Controller(object):
|
||||
"""Binds the model and the view together."""
|
||||
|
||||
def __init__(self, view):
|
||||
self.model = Model()
|
||||
self.view = view
|
||||
|
||||
def run(self):
|
||||
"""The controller's main function. Depending on what type of view is
|
||||
selected, a different event loop is setup. Do note that the ConsoleView
|
||||
is not a real event loop, just a basic flow of action."""
|
||||
if self.view is ConsoleView:
|
||||
self.view = self.view()
|
||||
self._run_console_view()
|
||||
elif self.view is TkinterView:
|
||||
root = Tk()
|
||||
root.title("Task Manager")
|
||||
root.geometry("500x300")
|
||||
self.view = self.view()
|
||||
self.view._set_controller(self)
|
||||
root.mainloop()
|
||||
|
||||
def get_task_from_model(self, idx):
|
||||
"""Needed for the TkinterView to communicate with the controller."""
|
||||
task = self.model.get_task(idx)
|
||||
self.view.show_task(task)
|
||||
|
||||
def _run_console_view(self):
|
||||
"""Super simple event loop."""
|
||||
while True:
|
||||
try:
|
||||
idx = self.view.select_task()
|
||||
idx = int(idx)
|
||||
except Exception as e:
|
||||
self.view.error(e)
|
||||
continue
|
||||
task = self.model.get_task(idx)
|
||||
self.view.show(task)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
view = ConsoleView
|
||||
app = Controller(view)
|
||||
app.run()
|
||||
```
|
||||
|
||||
And now with the implemented `TkinterView` class.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
|
||||
class ConsoleView(object):
|
||||
"""A view for console."""
|
||||
|
||||
def select_task(self):
|
||||
"""Asks which index to look up."""
|
||||
idx = input("which task do you want to see? ")
|
||||
return idx
|
||||
|
||||
def show(self, task):
|
||||
"""Displays the task to the console. This method is called from the
|
||||
controller."""
|
||||
print("your task: {}".format(task))
|
||||
|
||||
def error(self, msg):
|
||||
"""Prints error messages coming from the controller."""
|
||||
print("error: {}".format(msg))
|
||||
|
||||
|
||||
class TkinterView(Frame):
|
||||
"""A view using a wx.Dialog window"""
|
||||
|
||||
def __init__(self):
|
||||
Frame.__init__(self, master=None)
|
||||
#Panel
|
||||
self.panel = PanedWindow(self, bg="green")
|
||||
self.panel.pack(fill=BOTH, expand=True)
|
||||
|
||||
#Task Label
|
||||
self.task = Label(self.panel, text="your task")
|
||||
self.task.pack(expand=True)
|
||||
|
||||
#SpinBox
|
||||
self.idx = Spinbox(self.panel, from_=0, to=2, wrap=True )
|
||||
self.idx.pack(side= TOP)
|
||||
|
||||
#Button
|
||||
self.button = Button(self.panel, text="submit", command=lambda : self.select_task())
|
||||
self.button.pack(ipadx=60, ipady=30)
|
||||
|
||||
self.pack(fill=BOTH, expand=True)
|
||||
|
||||
def _set_controller(self, controller):
|
||||
"""Set the controller so the view can communicate it's requests to it
|
||||
and update it's values too."""
|
||||
self.controller = controller
|
||||
|
||||
def select_task(self):
|
||||
"""Gets the index to look up in the model and submits the request to
|
||||
the controller."""
|
||||
idx = self.idx.get()
|
||||
self.controller.get_task_from_model(idx)
|
||||
|
||||
def show_task(self, task):
|
||||
"""Updates the visual label in the view with the task. This method is
|
||||
called from the controller."""
|
||||
self.task.config(text=task)
|
||||
|
||||
|
||||
class Model(object):
|
||||
"""The model houses add data and should implement all methods related to
|
||||
adding, modifying and deleting tasks."""
|
||||
|
||||
db = ["go to the shops", "dryhop beer", "drop of motorbike"]
|
||||
|
||||
def get_task(self, idx):
|
||||
"""Performs a task lookun into the database and returns it when found.
|
||||
If no task is found, it returns an error message that will be displayed
|
||||
in the view (via the controller)."""
|
||||
try:
|
||||
task = Model.db[int(idx)]
|
||||
except IndexError:
|
||||
task = "task with {} not found!".format(idx)
|
||||
return task
|
||||
|
||||
|
||||
class Controller(object):
|
||||
"""Binds the model and the view together."""
|
||||
|
||||
def __init__(self, view):
|
||||
self.model = Model()
|
||||
self.view = view
|
||||
|
||||
def run(self):
|
||||
"""The controller's main function. Depending on what type of view is
|
||||
selected, a different event loop is setup. Do note that the ConsoleView
|
||||
is not a real event loop, just a basic flow of action."""
|
||||
if self.view is ConsoleView:
|
||||
self.view = self.view()
|
||||
self._run_console_view()
|
||||
elif self.view is TkinterView:
|
||||
root = Tk()
|
||||
root.title("Task Manager")
|
||||
root.geometry("500x300")
|
||||
self.view = self.view()
|
||||
self.view._set_controller(self)
|
||||
root.mainloop()
|
||||
|
||||
def get_task_from_model(self, idx):
|
||||
"""Needed for the TkinterView to communicate with the controller."""
|
||||
task = self.model.get_task(idx)
|
||||
self.view.show_task(task)
|
||||
|
||||
def _run_console_view(self):
|
||||
"""Super simple event loop."""
|
||||
while True:
|
||||
try:
|
||||
idx = self.view.select_task()
|
||||
idx = int(idx)
|
||||
except Exception as e:
|
||||
self.view.error(e)
|
||||
continue
|
||||
task = self.model.get_task(idx)
|
||||
self.view.show(task)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
view = TkinterView
|
||||
app = Controller(view)
|
||||
app.run()
|
||||
|
||||
```
|
||||
|
||||
For a GUI only login generator an implementation without MVC could look a bit like this.
|
||||
Note that the actual calculation is done inside the window itself.
|
||||
This is not a good idea because we should separate responsibilities into classes!
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
from tkinter import ttk
|
||||
import login_generator
|
||||
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Frame.__init__(self)
|
||||
self.full_window = PanedWindow(self,orient=VERTICAL)
|
||||
self.full_window.pack(fill=BOTH,expand=True)
|
||||
self.create_top_panel()
|
||||
self.create_bottom_panel()
|
||||
self.pack(fill=BOTH, expand=True)
|
||||
|
||||
def create_bottom_panel(self):
|
||||
bottom_panel = PanedWindow(self.full_window, bg="green")
|
||||
bottom_panel.pack(fill=BOTH, side=BOTTOM)
|
||||
#List
|
||||
self.login_list = ttk.Treeview(bottom_panel, columns=["username", "password"], show="headings", height=6)
|
||||
self.login_list.pack()
|
||||
self.login_list.heading("username", text="Username")
|
||||
self.login_list.heading("password", text="Password")
|
||||
self.login_list.bind("<ButtonRelease-1>", lambda e: self.show_popup())
|
||||
|
||||
def create_top_panel(self):
|
||||
|
||||
top_panel = PanedWindow(self.full_window, bg="red")
|
||||
self.login_amount = Text(top_panel, height=5, width=52)
|
||||
self.login_amount.place(x=10,y=10)
|
||||
self.complex = BooleanVar()
|
||||
self.complex.set(False)
|
||||
self.login_complex = Checkbutton(top_panel, text="complex",var=self.complex)
|
||||
self.login_complex.place(x=10,y=100)
|
||||
self.login_create = Button(top_panel, text="Create", command=lambda: self.add_login())
|
||||
self.login_create.place(x=100,y=100)
|
||||
top_panel.pack(expand=True, fill=BOTH)
|
||||
|
||||
def show_popup(self):
|
||||
global root
|
||||
menu = Menu()
|
||||
menu.add_command(label="Copy selected items", command=lambda : self.copy_items())
|
||||
root.config(menu=menu)
|
||||
|
||||
def copy_items(self):
|
||||
global root
|
||||
try:
|
||||
Item = self.login_list.item(self.login_list.focus())
|
||||
FunctionValue = Item.values()
|
||||
Array = list(FunctionValue)
|
||||
Login = Array[2]
|
||||
self.login_amount.insert(END, "Username : {} \nPassword : {} \n".format(Login[0],Login[1]))
|
||||
|
||||
except:
|
||||
Msg = Toplevel(root)
|
||||
l1 = Label(Msg, text="You have to take something")
|
||||
l1.pack(fill=BOTH, expand=True)
|
||||
|
||||
def add_login(self):
|
||||
self.username = login_generator.generate_username()
|
||||
self.password = login_generator.generate_password(12,self.complex.get())
|
||||
self.login_list.insert('', END,values=[self.username, self.password])
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("Login Generator")
|
||||
root.geometry("500x300")
|
||||
win = MainWindow()
|
||||
root.mainloop()
|
||||
|
||||
```
|
||||
|
||||
Now let's assume the generate username and password function take some calculation time.
|
||||
We'll add in a fake `time.sleep` to simulate.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
from tkinter import ttk
|
||||
import login_generator
|
||||
import time
|
||||
|
||||
|
||||
class MainWindow(Frame):
|
||||
def __init__(self):
|
||||
Frame.__init__(self)
|
||||
self.full_window = PanedWindow(self,orient=VERTICAL)
|
||||
self.full_window.pack(fill=BOTH,expand=True)
|
||||
self.create_top_panel()
|
||||
self.create_bottom_panel()
|
||||
self.pack(fill=BOTH, expand=True)
|
||||
|
||||
def create_bottom_panel(self):
|
||||
bottom_panel = PanedWindow(self.full_window, bg="green")
|
||||
bottom_panel.pack(fill=BOTH, side=BOTTOM)
|
||||
#List
|
||||
self.login_list = ttk.Treeview(bottom_panel, columns=["username", "password"], show="headings", height=6)
|
||||
self.login_list.pack()
|
||||
self.login_list.heading("username", text="Username")
|
||||
self.login_list.heading("password", text="Password")
|
||||
self.login_list.bind("<ButtonRelease-1>", lambda e: self.show_popup())
|
||||
|
||||
def create_top_panel(self):
|
||||
top_panel = PanedWindow(self.full_window, bg="red")
|
||||
self.login_amount = Text(top_panel, height=5, width=52)
|
||||
self.login_amount.place(x=10,y=10)
|
||||
self.complex = BooleanVar()
|
||||
self.complex.set(False)
|
||||
self.login_complex = Checkbutton(top_panel, text="complex",var=self.complex)
|
||||
self.login_complex.place(x=10,y=100)
|
||||
self.login_create = Button(top_panel, text="Create", command=lambda: self.add_login())
|
||||
self.login_create.place(x=100,y=100)
|
||||
top_panel.pack(expand=True, fill=BOTH)
|
||||
|
||||
def show_popup(self):
|
||||
global root
|
||||
menu = Menu()
|
||||
menu.add_command(label="Copy selected items", command=lambda : self.copy_items())
|
||||
root.config(menu=menu)
|
||||
|
||||
def copy_items(self):
|
||||
global root
|
||||
try:
|
||||
Item = self.login_list.item(self.login_list.focus())
|
||||
FunctionValue = Item.values()
|
||||
Array = list(FunctionValue)
|
||||
Login = Array[2]
|
||||
self.login_amount.insert(END, "Username : {} \nPassword : {} \n".format(Login[0],Login[1]))
|
||||
time.sleep(1)
|
||||
|
||||
except:
|
||||
Msg = Toplevel(root)
|
||||
l1 = Label(Msg, text="You have to take something")
|
||||
l1.pack(fill=BOTH, expand=True)
|
||||
|
||||
def add_login(self):
|
||||
self.username = login_generator.generate_username()
|
||||
self.password = login_generator.generate_password(12,self.complex.get())
|
||||
self.login_list.insert('', END,values=[self.username, self.password])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.title("Login Generator")
|
||||
root.geometry("500x300")
|
||||
win = MainWindow()
|
||||
root.mainloop()
|
||||
|
||||
```
|
||||
|
||||
A clear separation of responsabilities can be acchieved via an MVC pattern and a login *library*.
|
||||
The library code is quite straightforward and goes as follows.
|
||||
It's basically the same code we did before but with added `try except` blocks.
|
||||
Can you tell me why I added those?
|
||||
|
||||
```python
|
||||
import random
|
||||
import string
|
||||
|
||||
|
||||
def load_file(filename):
|
||||
"""
|
||||
We load a file and make a list out of it. Note that the same function is
|
||||
used for both files (both adjectives and subjects). Functions should be
|
||||
made as generic as possible.
|
||||
|
||||
There IS a problem you can fix, some logins will have spaces in them. Try
|
||||
to remove them in this function!
|
||||
"""
|
||||
words = []
|
||||
with open(filename, "r") as fp:
|
||||
lines = fp.readlines()
|
||||
for line in lines:
|
||||
words.append(line.strip()) # what does strip() do, what does append() do? remember CTRL+Q!
|
||||
return words
|
||||
|
||||
|
||||
def generate_username():
|
||||
"""
|
||||
We'll generate a random pair of adjectives and subjects from two wordlists.
|
||||
You NEED to have both files in you python project for this to work! Note
|
||||
the capitalize method call to make it all prettier...
|
||||
"""
|
||||
try:
|
||||
adjectives = load_file("./adjectives.txt")
|
||||
except:
|
||||
adjectives = ["big", "funny", "normal", "red"]
|
||||
try:
|
||||
subjects = load_file("./subjects.txt")
|
||||
except:
|
||||
subjects = ["giraffe", "elephant", "cougar", "tiger"]
|
||||
adjective = random.choice(adjectives)
|
||||
subject = random.choice(subjects)
|
||||
username = adjective.capitalize() + subject.capitalize()
|
||||
return username
|
||||
|
||||
|
||||
def generate_password(length=10, complictated=True):
|
||||
"""
|
||||
We generate a password with default settings. You can overide these by
|
||||
changing the arguments in the function call.
|
||||
"""
|
||||
password = ""
|
||||
if complictated:
|
||||
chars = string.ascii_letters + string.digits + string.punctuation
|
||||
else:
|
||||
chars = string.ascii_letters
|
||||
for i in range(0, length):
|
||||
password += random.choice(chars)
|
||||
return password
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# let's do some testing!
|
||||
username_test = generate_username()
|
||||
print(username_test)
|
||||
password_test = generate_password()
|
||||
print(password_test)
|
||||
```
|
||||
|
||||
And now the GUI code nicely split up in a **model**, **controller** and a **view**.
|
||||
The overhead is quite large but it makes the code a lot more scalable.
|
||||
|
||||
```python
|
||||
from tkinter import *
|
||||
from tkinter import ttk
|
||||
import login_generator
|
||||
import time
|
||||
import threading
|
||||
import queue
|
||||
|
||||
|
||||
class View(Frame):
|
||||
def __init__(self, controller):
|
||||
Frame.__init__(self)
|
||||
self.controller = controller
|
||||
self.full_window = PanedWindow(self, orient=VERTICAL)
|
||||
self.full_window.pack(fill=BOTH, expand=True)
|
||||
self.create_top_panel()
|
||||
self.create_bottom_panel()
|
||||
self.pack(fill=BOTH, expand=True)
|
||||
|
||||
def create_bottom_panel(self):
|
||||
bottom_panel = PanedWindow(self.full_window, bg="green")
|
||||
bottom_panel.pack(fill=BOTH, side=BOTTOM)
|
||||
# List
|
||||
self.login_list = ttk.Treeview(bottom_panel, columns=["username", "password"], show="headings", height=6)
|
||||
self.login_list.pack()
|
||||
self.login_list.heading("username", text="Username")
|
||||
self.login_list.heading("password", text="Password")
|
||||
self.login_list.bind("<ButtonRelease-1>", lambda e: self.show_popup())
|
||||
|
||||
def create_top_panel(self):
|
||||
top_panel = PanedWindow(self.full_window, bg="red")
|
||||
self.login_amount = Text(top_panel, height=5, width=52)
|
||||
self.login_amount.place(x=10, y=10)
|
||||
self.complex = BooleanVar()
|
||||
self.complex.set(False)
|
||||
self.login_complex = Checkbutton(top_panel, text="complex", var=self.complex)
|
||||
self.login_complex.place(x=10, y=100)
|
||||
self.login_create = Button(top_panel, text="Create", command=lambda: self.submit_request())
|
||||
self.login_create.place(x=100, y=100)
|
||||
top_panel.pack(expand=True, fill=BOTH)
|
||||
|
||||
def show_popup(self):
|
||||
global app
|
||||
menu = Menu()
|
||||
menu.add_command(label="Copy selected items", command=lambda : self.copy_items())
|
||||
app.config(menu=menu)
|
||||
|
||||
def copy_items(self):
|
||||
try:
|
||||
Item = self.login_list.item(self.login_list.focus())
|
||||
FunctionValue = Item.values()
|
||||
Array = list(FunctionValue)
|
||||
Login = Array[2]
|
||||
self.login_amount.insert(END, "Username : {} \nPassword : {} \n".format(Login[0], Login[1]))
|
||||
time.sleep(1)
|
||||
|
||||
except:
|
||||
Msg = Toplevel(root)
|
||||
l1 = Label(Msg, text="You have to take something")
|
||||
l1.pack(fill=BOTH, expand=True)
|
||||
|
||||
def submit_request(self):
|
||||
amount = self.login_amount.get("1.0", END)
|
||||
complex_bool = self.complex.get()
|
||||
try:
|
||||
amount = int(amount)
|
||||
except:
|
||||
amount = 1
|
||||
self.controller.get_new_logins(amount, complex_bool)
|
||||
|
||||
def update_logins(self, login):
|
||||
username, password = login
|
||||
self.login_list.insert('', END, values=[username, password])
|
||||
|
||||
class Controller(object):
|
||||
def __init__(self):
|
||||
global app
|
||||
app.title("Login Generator")
|
||||
app.geometry("450x300")
|
||||
self.view = View(self)
|
||||
self.model = Model(self)
|
||||
|
||||
def get_new_logins(self, amount, complex_bool):
|
||||
self.model.generate_login(amount, complex_bool)
|
||||
|
||||
def set_new_logins(self, logins):
|
||||
self.view.update_logins(logins)
|
||||
|
||||
|
||||
class Model(threading.Thread):
|
||||
def __init__(self, controller):
|
||||
threading.Thread.__init__(self, target=self.main)
|
||||
self.controller = controller
|
||||
self.queue = queue.Queue()
|
||||
self.start()
|
||||
|
||||
def main(self):
|
||||
while True:
|
||||
amount, complex_bool = self.queue.get()
|
||||
username = login_generator.generate_username()
|
||||
password = login_generator.generate_password(12, complex_bool)
|
||||
logins = (username, password)
|
||||
self.controller.set_new_logins(logins)
|
||||
time.sleep(1)
|
||||
|
||||
def generate_login(self, amount, complex_bool):
|
||||
for i in range(0, amount):
|
||||
self.queue.put((amount, complex_bool))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = Tk()
|
||||
mvc = Controller()
|
||||
app.mainloop()
|
||||
|
||||
```
|
||||
|
||||
If you want to add a scrollbar in the project.
|
||||
|
||||
````python
|
||||
from tkinter import *
|
||||
from tkinter import ttk
|
||||
import login_generator
|
||||
import time
|
||||
import threading
|
||||
import queue
|
||||
|
||||
|
||||
class View(Frame):
|
||||
def __init__(self, controller):
|
||||
Frame.__init__(self)
|
||||
self.controller = controller
|
||||
self.full_window = PanedWindow(self, orient=VERTICAL)
|
||||
self.full_window.pack(fill=BOTH, expand=True)
|
||||
self.create_top_panel()
|
||||
self.create_bottom_panel()
|
||||
self.pack(fill=BOTH, expand=True)
|
||||
|
||||
def create_bottom_panel(self):
|
||||
bottom_panel = PanedWindow(self.full_window, bg="green")
|
||||
bottom_panel.pack(fill=BOTH, side=BOTTOM)
|
||||
##Scrollbar
|
||||
self.Scrollbar = Scrollbar(bottom_panel )
|
||||
self.Scrollbar.pack(side=RIGHT, fill= Y)
|
||||
# List
|
||||
self.login_list = ttk.Treeview(bottom_panel, columns=["username", "password"], show="headings", height=6, yscrollcommand= self.Scrollbar.set)
|
||||
self.login_list.pack()
|
||||
self.login_list.heading("username", text="Username")
|
||||
self.login_list.heading("password", text="Password")
|
||||
self.login_list.bind("<ButtonRelease-1>", lambda e: self.show_popup())
|
||||
self.Scrollbar.config(command= self.login_list.yview)
|
||||
|
||||
|
||||
def create_top_panel(self):
|
||||
top_panel = PanedWindow(self.full_window, bg="red")
|
||||
self.login_amount = Text(top_panel, height=5, width=52)
|
||||
self.login_amount.place(x=10, y=10)
|
||||
self.complex = BooleanVar()
|
||||
self.complex.set(False)
|
||||
self.login_complex = Checkbutton(top_panel, text="complex", var=self.complex)
|
||||
self.login_complex.place(x=10, y=100)
|
||||
self.login_create = Button(top_panel, text="Create", command=lambda: self.submit_request())
|
||||
self.login_create.place(x=100, y=100)
|
||||
top_panel.pack(expand=True, fill=BOTH)
|
||||
|
||||
def show_popup(self):
|
||||
global app
|
||||
menu = Menu()
|
||||
menu.add_command(label="Copy selected items", command=lambda : self.copy_items())
|
||||
app.config(menu=menu)
|
||||
|
||||
def copy_items(self):
|
||||
try:
|
||||
Item = self.login_list.item(self.login_list.focus())
|
||||
FunctionValue = Item.values()
|
||||
Array = list(FunctionValue)
|
||||
Login = Array[2]
|
||||
self.login_amount.insert(END, "Username : {} \nPassword : {} \n".format(Login[0], Login[1]))
|
||||
time.sleep(1)
|
||||
|
||||
except:
|
||||
Msg = Toplevel(root)
|
||||
l1 = Label(Msg, text="You have to take something")
|
||||
l1.pack(fill=BOTH, expand=True)
|
||||
|
||||
def submit_request(self):
|
||||
amount = self.login_amount.get("1.0", END)
|
||||
complex_bool = self.complex.get()
|
||||
try:
|
||||
amount = int(amount)
|
||||
except:
|
||||
amount = 1
|
||||
self.controller.get_new_logins(amount, complex_bool)
|
||||
|
||||
def update_logins(self, login):
|
||||
username, password = login
|
||||
self.login_list.insert('', END, values=[username, password])
|
||||
|
||||
class Controller(object):
|
||||
def __init__(self):
|
||||
global app
|
||||
app.title("Login Generator")
|
||||
app.geometry("450x300")
|
||||
self.view = View(self)
|
||||
self.model = Model(self)
|
||||
|
||||
def get_new_logins(self, amount, complex_bool):
|
||||
self.model.generate_login(amount, complex_bool)
|
||||
|
||||
def set_new_logins(self, logins):
|
||||
self.view.update_logins(logins)
|
||||
|
||||
|
||||
class Model(threading.Thread):
|
||||
def __init__(self, controller):
|
||||
threading.Thread.__init__(self, target=self.main)
|
||||
self.controller = controller
|
||||
self.queue = queue.Queue()
|
||||
self.start()
|
||||
|
||||
def main(self):
|
||||
while True:
|
||||
amount, complex_bool = self.queue.get()
|
||||
username = login_generator.generate_username()
|
||||
password = login_generator.generate_password(12, complex_bool)
|
||||
logins = (username, password)
|
||||
self.controller.set_new_logins(logins)
|
||||
time.sleep(1)
|
||||
|
||||
def generate_login(self, amount, complex_bool):
|
||||
for i in range(0, amount):
|
||||
self.queue.put((amount, complex_bool))
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = Tk()
|
||||
mvc = Controller()
|
||||
app.mainloop()
|
||||
````
|
||||
|
||||
|
||||
|
||||
|
||||
# Coding challenge - Login generator with GUI
|
||||
|
||||
# Coding challenge - Trivial pursuit with GUI
|
Loading…
Reference in New Issue