first gui doc
This commit is contained in:
		
							parent
							
								
									f3441e1b94
								
							
						
					
					
						commit
						196ad4abeb
					
				| 
						 | 
				
			
			@ -2491,10 +2491,257 @@ TODO add a countdown timer to the multiple choice game
 | 
			
		|||
 | 
			
		||||
# GUI programming
 | 
			
		||||
 | 
			
		||||
TODO event loop introduction
 | 
			
		||||
 | 
			
		||||
## wxpython helloworld
 | 
			
		||||
 | 
			
		||||
The absolute most basic way to have a *hello world* GUI program up and running with wxpython is the following.
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
import wx
 | 
			
		||||
 | 
			
		||||
app = wx.App()
 | 
			
		||||
 | 
			
		||||
frame = wx.Frame(None, title="hello world")
 | 
			
		||||
 | 
			
		||||
frame.Show()
 | 
			
		||||
 | 
			
		||||
app.MainLoop()
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
While it works we know better by now.
 | 
			
		||||
We should include the `if __name__ == "__main__"` statement.
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
import wx
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    app = wx.App()
 | 
			
		||||
    frame = wx.Frame(None, title="hello world")
 | 
			
		||||
    frame.Show()
 | 
			
		||||
    app.MainLoop()
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The instance of `wx.App` is what will actually process our event loop and the `wx.Frame` is the content of our window.
 | 
			
		||||
In the latter we will create our button and labels so should create our own class and inherit from it.
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
import wx
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MainWindow(wx.Frame):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        wx.Frame.__init__(self, None, title="hello world")
 | 
			
		||||
        self.Show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    app = wx.App()
 | 
			
		||||
    win = MainWindow()
 | 
			
		||||
    app.MainLoop()
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
We can add content to the *frame*, such as labels, input boxes and buttons as follows.
 | 
			
		||||
Note the first argument to the `wx.StaticText` creation.
 | 
			
		||||
This is *where* we put the label *into*.
 | 
			
		||||
It kind of works but we'll encounter a **problem** when we pack more visual **objects** into the same frame.
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
import wx
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MainWindow(wx.Frame):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        wx.Frame.__init__(self, None, title="hello world")
 | 
			
		||||
        self.label = wx.StaticText(self, label="this is a label")
 | 
			
		||||
        self.Show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    app = wx.App()
 | 
			
		||||
    win = MainWindow()
 | 
			
		||||
    app.MainLoop()
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Let's try to put multiple visual object into the same frame.
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
import wx
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MainWindow(wx.Frame):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        wx.Frame.__init__(self, None, title="hello world")
 | 
			
		||||
        self.label = wx.StaticText(self, label="this is a label")
 | 
			
		||||
        self.input = wx.TextCtrl(self)
 | 
			
		||||
        self.button = wx.Button(self)
 | 
			
		||||
        self.Show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    app = wx.App()
 | 
			
		||||
    win = MainWindow()
 | 
			
		||||
    app.MainLoop()
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You see how they are *stacked* one on top of the other?
 | 
			
		||||
We can overcome this problem with [sizers](https://wxpython.org/Phoenix/docs/html/sizers_overview.html).
 | 
			
		||||
There are multiple ones we can use but let's dive into a first one called a **box sizer**.
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
import wx
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MainWindow(wx.Frame):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        wx.Frame.__init__(self, None, title="hello world")
 | 
			
		||||
        self.panel = wx.Panel(self)
 | 
			
		||||
        self.box = wx.BoxSizer()
 | 
			
		||||
        self.label = wx.StaticText(self.panel, label="this is a label")
 | 
			
		||||
        self.input = wx.TextCtrl(self.panel)
 | 
			
		||||
        self.button = wx.Button(self.panel, label="I'm clickable!")
 | 
			
		||||
        self.box.Add(self.label)
 | 
			
		||||
        self.box.Add(self.input)
 | 
			
		||||
        self.box.Add(self.button)
 | 
			
		||||
        self.panel.SetSizer(self.box)
 | 
			
		||||
        self.Show()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    app = wx.App()
 | 
			
		||||
    win = MainWindow()
 | 
			
		||||
    app.MainLoop()
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This is looking better!
 | 
			
		||||
But it requires some explanation though.
 | 
			
		||||
Let's break it down.
 | 
			
		||||
The `wx.Frame` is your **window** in which you create a `wx.Panel` which is used to draw thing to.
 | 
			
		||||
To this panel you're adding three different *objects* (`wx.StaticText`, `wx.TextCtrl` and `wx.Button`) each with or without their own settings (such as a label or not).
 | 
			
		||||
Next you add these three objects to the `wx.BoxSizer` and you tell the panel to use this box as sizer.
 | 
			
		||||
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 (the `wx.BoxSizer`).
 | 
			
		||||
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 `wx.Frame` 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
 | 
			
		||||
import wx
 | 
			
		||||
import random 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MainWindow(wx.Frame):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        wx.Frame.__init__(self, None, title="hello world")
 | 
			
		||||
        self.panel = wx.Panel(self)
 | 
			
		||||
        self.box = wx.BoxSizer()
 | 
			
		||||
        self.label = wx.StaticText(self.panel, label="this is a label")
 | 
			
		||||
        self.input = wx.TextCtrl(self.panel)
 | 
			
		||||
        self.button = wx.Button(self.panel, label="I'm clickable!")
 | 
			
		||||
        self.button.Bind(wx.EVT_BUTTON, self.set_label_value)
 | 
			
		||||
        self.box.Add(self.label)
 | 
			
		||||
        self.box.Add(self.input)
 | 
			
		||||
        self.box.Add(self.button)
 | 
			
		||||
        self.panel.SetSizer(self.box)
 | 
			
		||||
        self.Show()
 | 
			
		||||
 | 
			
		||||
    def set_label_value(self, event):
 | 
			
		||||
        number = random.randint(0, 100)
 | 
			
		||||
        self.label.SetLabel("{}".format(number))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    app = wx.App()
 | 
			
		||||
    win = MainWindow()
 | 
			
		||||
    app.MainLoop()
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
We can use the same *idea* to grab input from the textbox.
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
import wx
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MainWindow(wx.Frame):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        wx.Frame.__init__(self, None, title="hello world")
 | 
			
		||||
        self.panel = wx.Panel(self)
 | 
			
		||||
        self.box = wx.BoxSizer()
 | 
			
		||||
        self.label = wx.StaticText(self.panel, label="this is a label")
 | 
			
		||||
        self.input = wx.TextCtrl(self.panel)
 | 
			
		||||
        self.button = wx.Button(self.panel, label="I'm clickable!")
 | 
			
		||||
        self.button.Bind(wx.EVT_BUTTON, self.set_label_value)
 | 
			
		||||
        self.box.Add(self.label)
 | 
			
		||||
        self.box.Add(self.input)
 | 
			
		||||
        self.box.Add(self.button)
 | 
			
		||||
        self.panel.SetSizer(self.box)
 | 
			
		||||
        self.Show()
 | 
			
		||||
 | 
			
		||||
    def set_label_value(self, event):
 | 
			
		||||
        msg = self.input.GetValue()
 | 
			
		||||
        self.label.SetLabel("{}".format(msg))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    app = wx.App()
 | 
			
		||||
    win = MainWindow()
 | 
			
		||||
    app.MainLoop()
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## wxpython guess the number
 | 
			
		||||
 | 
			
		||||
```python
 | 
			
		||||
import wx
 | 
			
		||||
import random
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MainWindow(wx.Frame):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        wx.Frame.__init__(self, None, title="Guess the number")
 | 
			
		||||
        self.number = random.randint(0, 100)
 | 
			
		||||
        self.panel = wx.Panel(self)
 | 
			
		||||
        self.box = wx.BoxSizer(wx.VERTICAL)
 | 
			
		||||
        self.label = wx.StaticText(self.panel, label="I have a number in mind...")
 | 
			
		||||
        self.input = wx.TextCtrl(self.panel)
 | 
			
		||||
        self.button = wx.Button(self.panel, label="I'm clickable!")
 | 
			
		||||
        self.button.Bind(wx.EVT_BUTTON, self.set_label_value)
 | 
			
		||||
        self.box.Add(self.label)
 | 
			
		||||
        self.box.Add(self.input)
 | 
			
		||||
        self.box.Add(self.button)
 | 
			
		||||
        self.panel.SetSizer(self.box)
 | 
			
		||||
        self.Show()
 | 
			
		||||
 | 
			
		||||
    def set_label_value(self, event):
 | 
			
		||||
        result = self.input.GetValue()
 | 
			
		||||
        if result.isdigit():
 | 
			
		||||
            status, context = self.evaluate_user_number(int(result))
 | 
			
		||||
            self.label.SetLabel(context)
 | 
			
		||||
        else:
 | 
			
		||||
            self.label.SetLabel("I need numbers!")
 | 
			
		||||
 | 
			
		||||
    def evaluate_user_number(self, number):
 | 
			
		||||
        if number > self.number:
 | 
			
		||||
            return False, "my number is smaller"
 | 
			
		||||
        elif number < self.number:
 | 
			
		||||
            return False, "my number is bigger"
 | 
			
		||||
        elif number == self.number:
 | 
			
		||||
            return True, "You win!"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    app = wx.App()
 | 
			
		||||
    win = MainWindow()
 | 
			
		||||
    app.MainLoop()
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## MVC design pattern
 | 
			
		||||
 | 
			
		||||
# Coding challenge - Login generator with GUI
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue