tkinter review
This commit is contained in:
parent
382d0db2b9
commit
dabb20053a
|
@ -3,134 +3,134 @@
|
||||||
## Tkinter helloworld
|
## Tkinter helloworld
|
||||||
|
|
||||||
The absolute most basic way to have a *hello world* GUI program up and running with Tkinter is the following.
|
The absolute most basic way to have a *hello world* GUI program up and running with Tkinter is the following.
|
||||||
|
It creates an *application*, sets the *title* as `hello world` and enters the **main eventloop**.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from tkinter import *
|
import tkinter as tk
|
||||||
|
|
||||||
root = Tk()
|
root = tk.Tk()
|
||||||
|
root.title("hello world")
|
||||||
MyLabel = Label(root,text="hello world")
|
|
||||||
MyLabel.pack()
|
|
||||||
|
|
||||||
root.mainloop()
|
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.
|
Tkinter has two popular architectures, Tcl and Tk.
|
||||||
|
These two architectures are quite different as they each have their own functionality and their own official documentation.
|
||||||
|
We are going use the Tk architecture.
|
||||||
|
|
||||||
In the previous code,the `mainloop()` instruction allows us to open the main window and to not close immediately the window.
|
The Tk architecture is used to create a GUI widget which has a lot of **methods** and **attributes** so it's quite customizable.
|
||||||
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.
|
In the previous code,the `mainloop()` method allows us to open the main window and to not close the window immediately .
|
||||||
|
This **event loop** will process all mouse event, button clicks, and changes.
|
||||||
|
|
||||||
While it works we know better by now.
|
While it works we know better by now.
|
||||||
We should include the `if __name__ == "__main__"` statement.
|
We should include the `if __name__ == "__main__"` statement so let's do this!
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from tkinter import *
|
import tkinter as tk
|
||||||
if __name__ == "__main__":
|
|
||||||
root = Tk()
|
|
||||||
|
|
||||||
MyLabel = Label(root,text="hello world")
|
if __name__ == "__main__":
|
||||||
MyLabel.pack()
|
root = tk.Tk()
|
||||||
|
root.title("hello world")
|
||||||
|
|
||||||
root.mainloop()
|
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.
|
The instance of `tk.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.
|
To this instance we can attach other widgets and display them.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from tkinter import *
|
import tkinter as tk
|
||||||
|
|
||||||
class MainWindow(Frame):
|
|
||||||
|
class Application(tk.Tk):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Label.__init__(self, text="hello world")
|
tk.Tk.__init__(self)
|
||||||
self.pack()
|
self.title("hello world")
|
||||||
|
self.geometry("500x400")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
root = Tk()
|
app = Application()
|
||||||
root.title("title of the window")
|
app.mainloop()
|
||||||
root.geometry("500x300")
|
|
||||||
MainWindow()
|
|
||||||
root.mainloop()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
We can add content to the *frame*, such as labels, input boxes and buttons as follows.
|
We can add content to this window, such as labels, input boxes and buttons as follows.
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from tkinter import *
|
import tkinter as tk
|
||||||
|
|
||||||
class MainWindow(Frame):
|
|
||||||
|
class Application(tk.Tk):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Label.__init__(self, text="hello world")
|
tk.Tk.__init__(self)
|
||||||
#Label
|
self.title("hello world")
|
||||||
MyLabel = Label(self, text="This is a label")
|
self.geometry("500x400")
|
||||||
MyLabel.pack()
|
self.resizable(0, 0)
|
||||||
|
self.label = tk.Label(self, text="This is a label", bg="yellow")
|
||||||
|
self.label.pack()
|
||||||
|
|
||||||
self.config(bg="yellow")
|
|
||||||
self.pack()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
root = Tk()
|
app = Application()
|
||||||
root.title("title of the window")
|
app.mainloop()
|
||||||
root.geometry("500x300")
|
|
||||||
MainWindow()
|
|
||||||
root.mainloop()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's try to put multiple visual object into the same frame.
|
Let's try to put multiple visual object into the same window.
|
||||||
|
|
||||||
```python3
|
```python3
|
||||||
from tkinter import *
|
import tkinter as tk
|
||||||
|
|
||||||
class MainWindow(Frame):
|
|
||||||
|
class Application(tk.Tk):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Label.__init__(self, text="hello world")
|
tk.Tk.__init__(self)
|
||||||
#Label
|
self.title("hello world")
|
||||||
MyLabel = Label(self, text="This is a label")
|
self.geometry("500x400")
|
||||||
MyLabel.pack()
|
self.label = tk.Label(text="This is a label")
|
||||||
#Button
|
self.label.pack()
|
||||||
MyButton = Button(self, text="I'm clickable!")
|
|
||||||
MyButton.pack()
|
self.button = tk.Button(text="I'm clickable!")
|
||||||
|
self.button.pack()
|
||||||
|
|
||||||
|
self.output = tk.Label(text="I'm a second label")
|
||||||
|
self.output.pack()
|
||||||
|
|
||||||
self.config(bg="yellow")
|
|
||||||
self.pack()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
root = Tk()
|
app = Application()
|
||||||
root.title("title of the window")
|
app.mainloop()
|
||||||
root.geometry("500x300")
|
|
||||||
MainWindow()
|
|
||||||
root.mainloop()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You see how they are *stacked* one on top of the other?
|
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 overcome this problem with parameters of [pack()](https://wxpython.org/Phoenix/docs/html/sizers_overview.html) or we can use other geometry managers like [grid()](https://www.pythontutorial.net/tkinter/tkinter-grid/) and [place()](https://www.pythontutorial.net/tkinter/tkinter-place/).
|
||||||
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
|
```python
|
||||||
from tkinter import *
|
import tkinter as tk
|
||||||
|
|
||||||
class MainWindow(Frame):
|
|
||||||
|
class Application(tk.Tk):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Frame.__init__(self, master=None)
|
tk.Tk.__init__(self)
|
||||||
MyPanel = PanedWindow.__init__(self, bg="Blue")
|
self.title("hello world")
|
||||||
#Label
|
self.geometry("500x400")
|
||||||
MyLabel = Label(MyPanel, text="this is a label", bg= "yellow")
|
self.resizable(0, 0)
|
||||||
MyLabel.pack(fill="x")
|
self.label = tk.Label(self, text="This is a label", bg="yellow")
|
||||||
#Bouton
|
self.label.grid(column=0, row=0, sticky=tk.W, padx=5, pady=5)
|
||||||
MyButton = Button(MyPanel, text="I'm clickable!")
|
|
||||||
MyButton.place(x=10, y=50)
|
self.button = tk.Button(self, text="I'm clickable!")
|
||||||
self.pack(fill=BOTH,expand=True)
|
self.button.grid(column=1, row=0, sticky=tk.E, padx=5, pady=5)
|
||||||
|
|
||||||
|
self.output = tk.Label(self, text="I'm a second label")
|
||||||
|
self.output.grid(column=1, row=1, sticky=tk.E, padx=5, pady=5)
|
||||||
|
|
||||||
|
self.config(bg="yellow")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
root = Tk()
|
app = Application()
|
||||||
root.title("this is the title of the window")
|
app.mainloop()
|
||||||
root.geometry("500x300")
|
|
||||||
win = MainWindow()
|
|
||||||
root.mainloop()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This is looking better!
|
This is looking better!
|
||||||
|
@ -152,137 +152,179 @@ 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.
|
Because it is a *method* it has access to *self* so it can modify *anything* within the scope of the instance.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from tkinter import *
|
import tkinter as tk
|
||||||
class MainWindow(Frame):
|
|
||||||
|
|
||||||
|
class Application(tk.Tk):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Frame.__init__(self, master=None)
|
tk.Tk.__init__(self)
|
||||||
MyPanel = PanedWindow.__init__(self, bg="Blue")
|
self.title("hello world")
|
||||||
#Label
|
self.geometry("500x400")
|
||||||
MyLabel = Label(MyPanel, text="this is a label", bg= "yellow")
|
self.resizable(0, 0)
|
||||||
MyLabel.pack(fill="x")
|
self.label = tk.Label(self, text="This is a label", bg="yellow")
|
||||||
#Bouton
|
self.label.pack()
|
||||||
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):
|
self.button = tk.Button(self, text="I'm clickable!", command=self.clicked)
|
||||||
global number
|
self.button.pack()
|
||||||
number += 1
|
|
||||||
counter = "You have press the button {} time".format(number)
|
|
||||||
Label.config(text=counter)
|
|
||||||
|
|
||||||
|
self.output = tk.Label(self, text="I'm a second label")
|
||||||
|
self.output.pack()
|
||||||
|
|
||||||
number=0
|
self.config(bg="yellow")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
def clicked(self):
|
||||||
root = Tk()
|
print("hello world!")
|
||||||
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__":
|
if __name__ == "__main__":
|
||||||
root = Tk()
|
app = Application()
|
||||||
root.title("this is the title of the window")
|
app.mainloop()
|
||||||
root.geometry("500x300")
|
|
||||||
win = MainWindow()
|
|
||||||
root.mainloop()
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tkinter guess the number
|
We can see *hello world!* printed to the console with each click!
|
||||||
|
As we're using a `class` we have *access* to the instance of the application so we can use the `self.output` label to show our messages.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import time
|
import tkinter as tk
|
||||||
from tkinter import *
|
|
||||||
|
|
||||||
|
class Application(tk.Tk):
|
||||||
|
def __init__(self):
|
||||||
|
tk.Tk.__init__(self)
|
||||||
|
self.title("hello world")
|
||||||
|
self.geometry("500x400")
|
||||||
|
self.resizable(0, 0)
|
||||||
|
self.label = tk.Label(self, text="This is a label", bg="yellow")
|
||||||
|
self.label.pack()
|
||||||
|
|
||||||
|
self.button = tk.Button(self, text="I'm clickable!", command=self.clicked)
|
||||||
|
self.button.pack()
|
||||||
|
|
||||||
|
self.output = tk.Label(self, text="I'm a second label")
|
||||||
|
self.output.pack()
|
||||||
|
|
||||||
|
self.config(bg="yellow")
|
||||||
|
self.counter = 0
|
||||||
|
|
||||||
|
def clicked(self):
|
||||||
|
self.counter += 1
|
||||||
|
self.output.config(text="butter has been clicked {} times".format(self.counter))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = Application()
|
||||||
|
app.mainloop()
|
||||||
|
```
|
||||||
|
|
||||||
|
🏃 Try it
|
||||||
|
---
|
||||||
|
|
||||||
|
We can use the same *idea* to grab input from the input boxes called `tk.Entry`.
|
||||||
|
The code below *adds* two numbers but it's not working properly.
|
||||||
|
Can you fix it for me please?
|
||||||
|
|
||||||
|
```python
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
|
||||||
|
class Application(tk.Tk):
|
||||||
|
def __init__(self):
|
||||||
|
tk.Tk.__init__(self)
|
||||||
|
self.title("hello world")
|
||||||
|
self.geometry("500x400")
|
||||||
|
self.resizable(0, 0)
|
||||||
|
self.label_one = tk.Label(self, text="Number 1:", bg="yellow")
|
||||||
|
self.label_one.grid(column=0, row=0, sticky=tk.W, padx=5, pady=5)
|
||||||
|
|
||||||
|
self.label_two = tk.Label(self, text="Number 2:", bg="yellow")
|
||||||
|
self.label_two.grid(column=0, row=1, sticky=tk.W, padx=5, pady=5)
|
||||||
|
|
||||||
|
self.input_one = tk.Entry(self)
|
||||||
|
self.input_one.grid(column=1, row=0, sticky=tk.W, padx=5, pady=5)
|
||||||
|
self.input_two = tk.Entry(self)
|
||||||
|
self.input_two.grid(column=1, row=1, sticky=tk.W, padx=5, pady=5)
|
||||||
|
|
||||||
|
self.button = tk.Button(self, text="add two numbers", command=self.clicked)
|
||||||
|
self.button.grid(column=0, row=3, sticky=tk.W, padx=5, pady=5)
|
||||||
|
|
||||||
|
self.output = tk.Label(self, text="...")
|
||||||
|
self.output.grid(column=1, row=3, sticky=tk.W, padx=5, pady=5)
|
||||||
|
|
||||||
|
self.config(bg="yellow")
|
||||||
|
|
||||||
|
def clicked(self):
|
||||||
|
number_one = self.input_one.get()
|
||||||
|
number_two = self.input_two.get()
|
||||||
|
total = number_one + number_two
|
||||||
|
self.output.config(text="sum: {}".format(total))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = Application()
|
||||||
|
app.mainloop()
|
||||||
|
```
|
||||||
|
|
||||||
|
# Coding challenge - Guess the number
|
||||||
|
|
||||||
|
Can you code me a *guess the number* game but using `tkinter`?
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Spoiler warning</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
import tkinter as tk
|
||||||
import random
|
import random
|
||||||
|
|
||||||
class MainWindow(Frame):
|
|
||||||
|
class Application(tk.Tk):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Frame.__init__(self, master=None, bg="white")
|
tk.Tk.__init__(self)
|
||||||
MyPanel = PanedWindow.__init__(self)
|
self.title("hello world")
|
||||||
|
self.geometry("500x400")
|
||||||
|
self.resizable(0, 0)
|
||||||
|
|
||||||
MyNumber = random.randint(0, 100)
|
self.number = random.randint(0, 10)
|
||||||
|
|
||||||
#Label
|
self.header = tk.Label(text="I have a number in mind...")
|
||||||
self.MyLabel = Label(MyPanel, text="I have a number in mind...", bg= "blue")
|
self.header.grid(column=0, row=0, sticky=tk.W)
|
||||||
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)
|
self.question = tk.Label(self, text="What's your guess?")
|
||||||
|
self.question.grid(column=0, row=1, sticky=tk.W)
|
||||||
|
|
||||||
|
self.input = tk.Entry(self)
|
||||||
|
self.input.grid(column=1, row=1, sticky=tk.W)
|
||||||
|
|
||||||
def ButtonEnable(self, MyEntry, MyNumber):
|
self.button = tk.Button(self, text="check", command=self.clicked)
|
||||||
if self.IsCorrect(MyEntry.get()):
|
self.button.grid(column=0, row=2, sticky=tk.W)
|
||||||
number = int(MyEntry.get())
|
|
||||||
if number != MyNumber:
|
self.output = tk.Label(self, text="...")
|
||||||
self.GameOver(number, MyNumber)
|
self.output.grid(column=1, row=2, sticky=tk.W)
|
||||||
|
|
||||||
|
def _is_entry_digit(self):
|
||||||
|
number = self.input.get()
|
||||||
|
if number.isdigit():
|
||||||
|
number = int(number)
|
||||||
|
return number
|
||||||
|
|
||||||
|
def clicked(self):
|
||||||
|
number = self._is_entry_digit()
|
||||||
|
if not number:
|
||||||
|
msg = "numbers please..."
|
||||||
else:
|
else:
|
||||||
self.Win()
|
if number < self.number:
|
||||||
else:
|
msg = "my number is bigger"
|
||||||
self.MyLabel.config(text="I need numbers!")
|
elif number > self.number:
|
||||||
|
msg = "my number is smaller"
|
||||||
def GameOver(self, number, MyNumber):
|
elif number == self.number:
|
||||||
if number > MyNumber:
|
msg = "bingo!"
|
||||||
self.MyLabel.config(text="My number is smaller")
|
self.output.config(text=msg)
|
||||||
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__":
|
if __name__ == "__main__":
|
||||||
root = Tk()
|
app = Application()
|
||||||
root.title("Guess the number")
|
app.mainloop()
|
||||||
root.geometry("500x300")
|
|
||||||
win = MainWindow()
|
|
||||||
root.mainloop()
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
## MVC design pattern
|
## MVC design pattern
|
||||||
|
|
||||||
|
@ -290,215 +332,152 @@ A simple console only MVC.
|
||||||
We'll add the GUI view in a bit.
|
We'll add the GUI view in a bit.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from tkinter import *
|
import tkinter as tk
|
||||||
|
|
||||||
|
|
||||||
class ConsoleView(object):
|
class ConsoleView(object):
|
||||||
"""A view for console."""
|
"""A view for console."""
|
||||||
|
|
||||||
def select_task(self):
|
def __init__(self, controller):
|
||||||
"""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
|
self.controller = controller
|
||||||
|
|
||||||
def select_task(self):
|
def select_task(self):
|
||||||
"""Gets the index to look up in the model and submits the request to
|
"""Asks which index to look up."""
|
||||||
the controller."""
|
idx = input("which task do you want to see? ")
|
||||||
idx = self.idx.get()
|
return idx
|
||||||
self.controller.get_task_from_model(idx)
|
|
||||||
|
|
||||||
def show_task(self, task):
|
def show_message(self, msg):
|
||||||
"""Updates the visual label in the view with the task. This method is
|
print("MSG: {}".format(msg))
|
||||||
called from the controller."""
|
|
||||||
self.task.config(text=task)
|
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))
|
||||||
|
|
||||||
|
def mainloop(self):
|
||||||
|
"""Super simple event loop."""
|
||||||
|
while True:
|
||||||
|
self.controller.request_number_of_tasks_from_model()
|
||||||
|
try:
|
||||||
|
idx = self.select_task()
|
||||||
|
idx = int(idx)
|
||||||
|
except Exception as e:
|
||||||
|
self.error(e)
|
||||||
|
continue
|
||||||
|
self.controller.request_task_from_model(idx)
|
||||||
|
|
||||||
|
|
||||||
class Model(object):
|
class Model(object):
|
||||||
"""The model houses add data and should implement all methods related to
|
"""The model houses add data and should implement all methods related to
|
||||||
adding, modifying and deleting tasks."""
|
adding, modifying and deleting tasks."""
|
||||||
|
|
||||||
db = ["go to the shops", "dryhop beer", "drop of motorbike"]
|
def __init__(self):
|
||||||
|
self.db = ["go to the shops", "dryhop beer", "drop of motorbike"]
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.db)
|
||||||
|
|
||||||
def get_task(self, idx):
|
def get_task(self, idx):
|
||||||
"""Performs a task lookun into the database and returns it when found.
|
"""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
|
If no task is found, it returns an error message that will be displayed
|
||||||
in the view (via the controller)."""
|
in the view (via the controller)."""
|
||||||
try:
|
try:
|
||||||
task = Model.db[int(idx)]
|
task = self.db[idx]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
task = "task with {} not found!".format(idx)
|
task = None
|
||||||
return task
|
return task
|
||||||
|
|
||||||
|
|
||||||
class Controller(object):
|
class Controller(object):
|
||||||
"""Binds the model and the view together."""
|
"""Binds the model and the view together."""
|
||||||
|
|
||||||
def __init__(self, view):
|
def __init__(self, model):
|
||||||
self.model = Model()
|
self.model = model
|
||||||
|
|
||||||
|
def set_view(self, view):
|
||||||
self.view = view
|
self.view = view
|
||||||
|
if isinstance(view, TkinterView):
|
||||||
|
self.request_number_of_tasks_from_model()
|
||||||
|
|
||||||
def run(self):
|
def request_number_of_tasks_from_model(self):
|
||||||
"""The controller's main function. Depending on what type of view is
|
number = len(self.model)
|
||||||
selected, a different event loop is setup. Do note that the ConsoleView
|
self.view.show_message("you have {} tasks".format(number))
|
||||||
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):
|
def request_task_from_model(self, idx):
|
||||||
"""Needed for the TkinterView to communicate with the controller."""
|
"""Needed for the ConsoleView and 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)
|
task = self.model.get_task(idx)
|
||||||
|
if task is None:
|
||||||
|
self.view.error("task not found!")
|
||||||
|
else:
|
||||||
self.view.show(task)
|
self.view.show(task)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
view = TkinterView
|
model = Model()
|
||||||
app = Controller(view)
|
controller = Controller(model)
|
||||||
app.run()
|
view = ConsoleView(controller)
|
||||||
|
controller.set_view(view)
|
||||||
|
view.mainloop()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Below you can see a `tkinter` class that acts as a drop in replacement for the `ConsoleView` class.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class TkinterView(tk.Tk):
|
||||||
|
def __init__(self, controller):
|
||||||
|
tk.Tk.__init__(self)
|
||||||
|
self.controller = controller
|
||||||
|
self.title("hello world")
|
||||||
|
self.geometry("500x400")
|
||||||
|
self.resizable(0, 0)
|
||||||
|
|
||||||
|
self.header = tk.Label()
|
||||||
|
self.header.grid(column=0, row=0, sticky=tk.W)
|
||||||
|
|
||||||
|
self.question = tk.Label(self, text="Which task do you want to see?")
|
||||||
|
self.question.grid(column=0, row=1, sticky=tk.W)
|
||||||
|
|
||||||
|
self.input = tk.Entry(self)
|
||||||
|
self.input.grid(column=1, row=1, sticky=tk.W)
|
||||||
|
|
||||||
|
self.button = tk.Button(self, text="lookup", command=self.clicked)
|
||||||
|
self.button.grid(column=0, row=2, sticky=tk.W)
|
||||||
|
|
||||||
|
self.output = tk.Label(self, text="...")
|
||||||
|
self.output.grid(column=1, row=2, sticky=tk.W)
|
||||||
|
|
||||||
|
def _is_entry_digit(self):
|
||||||
|
number = self.input.get()
|
||||||
|
if number.isdigit():
|
||||||
|
number = int(number)
|
||||||
|
return number
|
||||||
|
|
||||||
|
def clicked(self):
|
||||||
|
number = self._is_entry_digit()
|
||||||
|
if not number:
|
||||||
|
msg = "numbers please..."
|
||||||
|
else:
|
||||||
|
msg = self.controller.request_task_from_model(number)
|
||||||
|
|
||||||
|
def show_message(self, msg):
|
||||||
|
self.header.config(text=msg)
|
||||||
|
|
||||||
|
def show(self, task):
|
||||||
|
"""Displays the task to the console. This method is called from the
|
||||||
|
controller."""
|
||||||
|
self.output.config(text=task)
|
||||||
|
|
||||||
|
def error(self, msg):
|
||||||
|
"""Prints error messages coming from the controller."""
|
||||||
|
self.output.config(text=msg)
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
For a GUI only login generator an implementation without MVC could look a bit like this.
|
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.
|
Note that the actual calculation is done inside the window itself.
|
||||||
This is not a good idea because we should separate responsibilities into classes!
|
This is not a good idea because we should separate responsibilities into classes!
|
||||||
|
@ -964,10 +943,16 @@ if __name__ == "__main__":
|
||||||
app.mainloop()
|
app.mainloop()
|
||||||
````
|
````
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
## Coding challenge - Login generator with GUI
|
## Coding challenge - Login generator with GUI
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
## Coding challenge - Trivial pursuit with GUI
|
## Coding challenge - Trivial pursuit with GUI
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
# WXpython
|
# WXpython
|
||||||
|
|
||||||
## wxpython helloworld
|
## wxpython helloworld
|
||||||
|
|
Loading…
Reference in New Issue