# Tkinter Module/Library

## Standard Imports

```py
from tkinter import *  # import Python Tk Binding
from tkinter import ttk  # import Themed Widgets
```

## GEOMETRY MANAGEMENT

Putting  widgets on screen  
master widget --> top-level window, frame  
slave widget --> widgets contained in master widget  
geometry managers determine size and oder widget drawing properties

## EVENT HANDLING

event loop receives events from the OS  
customizable events provide a callback as a widget configuration

```py
widget.bind('event', function)  # method to capture any event and than execute an arbitrary piece of code (generally a function or lambda)
```

VIRTUAL EVENT --> hig level event generated by widget (listed in widget docs)

## WIDGETS

Widgets are objects and all things on screen. All widgets are children of a window.

```py
widget_name = tk_object(parent_window)  # widget is inserted into widget hierarchy
```

## FRAME WIDGET

Displays a single rectangle, used as container for other widgets

```py
frame = ttk.Frame(parent, width=None, height=None, borderwidth=num:int)
# BORDERWIDTH: sets frame border width (default: 0)
# width, height MUST be specified if frame is empty, otherwise determined by parent geometry manager
```

### FRAME PADDING

Extra space inside widget (margin).

```py
frame['padding'] = num  # same padding for every border
frame['padding'] = (horizontal, vertical)  # set horizontal THEN vertical padding
frame['padding'] = (left, top, right, bottom)  # set left, top, right, bottom padding

# RELIEF: set border style, [flat (default), raised, sunken, solid, ridge, groove]
frame['relief'] = border_style
```

## LABEL WIDGET

Display text or image without interactivity.

```py
label = ttk.Label(parent, text='label text')
```

### DEFINING UPDATING LABEL

```py
var = StringVar()  # variable containing text, watches for changes. Use get, set methods to interact with the value
label['textvariable'] = var  # attach var to label (only of type StringVar)
var.set("new text label")  #  change label text
```

### DISPLAY IMAGES (2 steps)

```py
image = PhotoImage(file='filename')  # create image object
label['image'] = image  # use image config
```

### DISPLAY IMAGE AND-OR TEXT

```py
label['compound'] = value
```

Compound value:

- none ../img if present, text otherwise)
- text (text only)
- image (image only)
- center (text in center of image)
- top (image above text), left, bottom, right

## LAYOUT

Specifies edge or corner that the label is attached.

```py
label['anchor'] = compass_direction  #compass_direction: n, ne, e, se, s, sw, w, nw, center
```

### MULTI-LINE TEXT WRAP

```py
# use \n for multi line text
label['wraplength'] = size  # max line length
```

### CONTROL TEXT JUSTIFICATION

```py
label['justify'] = value  #value: left, center, right

label['relief'] = label_style
label['foreground'] = color  # color passed with name or HEX RGB codes
label['background'] = color  # color passed with name or HEX RGB codes
```

### FONT STYLE (use with caution)

```py
# used outside style option
label['font'] = font
```

Fonts:

- TkDefaultFont    -- default for all GUI items
- TkTextFont    -- used for entry widgets, listboxes, etc
- TkFixedFont    -- standard fixed-width font
- TkMenuFont    -- used for menu items
- TkHeadingFont -- for column headings in lists and tables
- TkCaptionFont    -- for window and dialog caption bars
- TkSmallCaptionFont -- smaller caption for subwindows or tool dialogs
- TkIconFont    -- for icon caption
- TkTooltipFont    -- for tooltips

## BUTTON

Press to perform some action

```py
button = ttk.Button(parent, text='button_text', command=action_performed)
```

### TEXT or IMAGE

```py
button['text/textvariable'], button['image'], button['compound']
```

### BUTTON INVOCATION

```py
button.invoke()  # button activation in the program
```

### BUTTON STATE

Activate or deactivate the widget.

```py
button.state(['disabled'])            # set the disabled flag, disabling the button
button.state(['!disabled'])           # clear the disabled flag
button.instate(['disabled'])          # return true if the button is disabled, else false
button.instate(['!disabled'])         # return true if the button is not disabled, else false
button.instate(['!disabled'], cmd)  # execute 'cmd' if the button is not disabled
# WIDGET STATE FLAGS: active, disabled, focus, pressed, selected, background, readonly, alternate, invalid
```

## CHECKBUTTON

Button with binary value of some kind (e.g a toggle) and also invokes a command callback

```py
checkbutton_var = TkVarType
check = ttk.Checkbutton(parent, text='button text', command=action_performed, variable=checkbutton_var, onvalue=value_on, offvalue=value_off)
```

### WIDGET VALUE

Variable option holds value of button, updated by widget toggle.  
DEFAULT: 1 (while checked), 0 (while unchecked)  
`onvalue`, `offvalue` are used to personalize checked and unchecked values  
if linked variable is empty or different from on-off value the state flag is set to alternate  
checkbutton won't set the linked variable (MUST be done in the program)

### CONFIG OPTIONS

```py
check['text/textvariable']
check['image']
check['compound']
check.state(['flag'])
check.instate(['flag'])
```

## RADIOBUTTON

Multiple-choice selection (good if options are few).

```py
#RADIOBUTTON CREATION (usually as a set)
radio_var = TkVarType
radio_1 = ttk.Radiobutton(parent, text='button text', variable=radio_var, value=button_1_value)
radio_2 = ttk.Radiobutton(parent, text='button text', variable=radio_var, value=button_2_value)
radio_3 = ttk.Radiobutton(parent, text='button text', variable=radio_var, value=button_3_value)
# if linked value does not exist flag state is alternate

# CONFIG OPTIONS
radio['text/textvariable']
radio['image']
radio['compound']
radio.state(['flag'])
radio.instate(['flag'])
```

## ENTRY

Single line text field accepting a string.

```py
entry_var = StringVar()
entry = ttk.Entry(parent, textvariable=entry_var, width=char_num, show=symbol)
# SHOW: replaces the entry test with symbol, used for password
# entries don't have an associated label, needs a separate widget
```

### CHANGE ENTRY VALUE

```py
entry.get()  # returns entry value
entry.delete(start, 'end')  # delete between two indices, 0-based
entry.insert(index, 'text value')  # insert new text at a given index
```

### ENTRY CONFIG OPTIONS

```py
radio.state(['flag'])
radio.instate(['flag'])
```

## COMBOBOX

Drop-down list of available options.

```py
combobox_var = StringVar()
combo = ttk.Combobox(parent, textvariable=combobox_var)
combobox.get()  # return combobox current value
combobox.set(value)  # set combobox new value
combobox.current()  # returns which item in the predefined values list is selected (0-based index of the provided list, -1 if not in the list)
#combobox will generate a bind-able <ComboboxSelected> virtual event whenever the value changes
combobox.bind('<<ComboboxSelected>>', function)
```

### PREDEFINED VALUES

```py
combobox['values'] = (value_1, value_2, ...)  # provides a list of choose-able values
combobox.state(['readonly'])  # restricts choose-able values to those provided with 'values' config option
# SUGGESTION: call selection clear method on value change (on ComboboxSelected event) to avoid visual oddities
```

## LISTBOX (Tk Classic)

Display list of single-line items, allows browsing and multiple selection (part og Tk classic, missing in themed Tk widgets).

```py
lstbx = Listbox(parent, height=num, listvariable=item_list:list)
# listvariable links a variable (MUST BE a list) to the listbox, each element is a item of the listbox
# manipulation of the list changes the listbox
```

### SELECTING ITEMS

```py
lstbx['selectmode'] = mode  # MODE: browse (single selection), extended (multiple selection)
lstbx.curselection()  # returns list of indices of selected items
# on selection change: generate event <ListboxSelect>
# often each string in the program is associated with some other data item
# keep a second list, parallel to the list of strings displayed in the listbox, which will hold the associated objects
# (association by index with .curselection() or with a dict).
```

## SCROLLBAR

```py
scroll = ttk.Scrollbar(parent, orient=direction, command=widget.view)
# ORIENT: VERTICAL, HORIZONTAL
# WIDGET.VIEW: .xview, .yview
# NEEDS ASSOCIATED WIDGET SCROLL CONFIG
widget.configure(xscrollcommand=scroll.set)
widget.configure(yscrollcommand=scroll.set)
```

## SIZEGRIP

Box in right bottom of widget, allows resize.

```py
ttk.Sizegrip(parent).grid(column=999, row=999, sticky=(S, E))
```

## TEXT (Tk Classic)

Area accepting multiple line of text.

```py
txt = Text(parent, width=num:int, height=num:int, wrap=flag)  # width is character num, height is row num
# FLAG: none (no wrapping), char (wrap at every character), word (wrap at word boundaries)
txt['state'] = flag  # FLAG: disabled, normal
# accepts commands xscrollcommand and yscrollcommandand and yview, xview methods
txt.see(line_num.char_num)  # ensure that given line is visible (line is 1-based, char is 0-based)
txt.get( index, string)   # insert string in pos index (index = line.char), 'end' is shortcut for end of text
txt.delete(start, end)  # delete range of text
```

## PROGRESSBAR

Feedback about progress of lenghty operation.

```py
progbar = ttk.Progressbar(parent, orient=direction, length=num:int, value=num, maximum=num:float mode=mode)
# DIRECTION: VERTICAL, HORIZONTAL
# MODE: determinate (relative progress of completion), indeterminate (no estimate of completion)
# LENGTH: dimension in pixel
# VALUE: sets the progress, updates the bar as it changes
# MAXIMUM: total number of steps (DEFAULT: 100)
```

### DETERMINATE PROGRESS

```py
progbar.step(amount)  # increment value of given amount (DEFAULT: 1.0)
```

### INDETERMINATE PROGRESS

```py
progbar.start()  # starts progressbar
progbar.stop()  #stoops progressbar
```

## SCALE

Provide a numeric value through direct manipulation.

```py
scale = ttk.Scale(parent, orient=DIR, length=num:int, from_=num:float, to=num:float, command=cmd)
# COMMAND: calls cmd at every scale change, appends current value to func call
scale['value']  # set or read current value
scale.set(value)  # set current value
scale.get()  # get current value
```

## SPINBOX

Choose numbers. The spinbox choses item from a list, arrows permit cycling lits items.

```py
spinval = StringVar()
spin = Spinbox(parent, from_=num, to=num, textvariable=spinval, increment=num, value=lst, wrap=boolean)
# INCREMENT specifies increment\decrement by arrow button
# VALUE: list of items associated with the spinbox
# WRAP: boolean value determining if value should wrap around if beyond start-end value
```

## GRID GEOMETRY MANAGER

Widgets are assigned a "column" number and a "row" number, which indicates their relative position to each other.  
Column and row numbers must be integers, with the first column and row starting at 0.  
Gaps in column and row numbers are handy to add more widgets in the middle of the user interface at a later time.  
The width of each column (or height of each row) depends on the width or height of the widgets contained within the column or row.

Widgets can take up more than a single cell in the grid ("columnspan" and "rowspan" options).

### LAYOUT WITHIN CELL

By default, if a cell is larger than the widget contained in it, the widget will be centered within it,  
both horizontally and vertically, with the master's background showing in the empty space around it.  
The "sticky" option can be used to change this default behavior.  
The value of the "sticky" option is a string of 0 or more of the compass directions "nsew", specifying which edges of the cell the widget should be "stuck" to.  
Specifying two opposite edges means that the widget will be stretched so it is stuck to both.  
Specifying "nsew" it will stick to every side.

### HANDLING RESIZE

Every column and row has a "weight" grid option associated with it, which tells it how much it should grow if there is extra room in the master to fill.  
By default, the weight of each column or row is 0, meaning don't expand to fill space.  
This is done using the "columnconfigure" and "rowconfigure" methods of grid.  
Both "columnconfigure" and "rowconfigure" also take a "minsize" grid option, which specifies a minimum size.

### PADDING

Normally, each column or row will be directly adjacent to the next, so that widgets will be right next to each other.  
"padx" puts a bit of extra space to the left and right of the widget, while "pady" adds extra space top and bottom.  
A single value for the option puts the same padding on both left and right (or top and bottom),  
while a two-value list lets you put different amounts on left and right (or top and bottom).  
To add padding around an entire row or column, the "columnconfigure" and "rowconfigure" methods accept a "pad" option.  

```py
widget.grid(column=num, row=num, columnspan=num, rowspan=num, sticky=(), padx=num, pady=num)  # sticky: N, S, E, W
widget.columnconfigure(pad=num, weight=num)
widget.rowconfigure(pad=num, weight=num)

widget.grid_slaves()  # returns map, list of widgets inside a master
widget.grid_info()  # returns list of grid options
widget.grid_configure()  # change one or more option

widget.grid_forget(slaves)  # takes a list of slaves, removes slaves from grid (forgets slaves options)
widget.grid_remove(slaves)  # takes a list of slaves, removes slaves from grid (remembers slaves options)
```

## WINDOWS AND DIALOGS

### CREATING TOPLEVEL WINDOW

```py
tlw = Toplevel(parent)  # parent of root window, no need to grid it

window.destroy()
# can destroy every widget
# destroying parent also destroys it's children
```

### CHANGING BEHAVIOR AND STYLE

```py
# WINDOW TILE
window.title()  # returns title of the window
window.title('new title')  # sets title

# SIZE AND LOCATION
window.geometry(geo_specs)
'''full geometry specification: width * height +- x +- y (actual coordinates of screen)
+x --> x pixels from left edge
-x --> x pixels from right edge
+y --> y pixels from top edge
-y --> y pixels from bottom edge'''

# STACKING ORDER
# current stacking order (list from lowest to highest) --- NOT CLEANLY EXPOSED THROUGH TK API
root.tk.eval('wm stackorder ' + str(window))
# check if window is above or below
if (root.tk.eval('wm stackorder '+str(window)+' isabove '+str(otherwindow))=='1')
if (root.tk.eval('wm stackorder '+str(window)+' isbelow '+str(otherwindow))=='1')
# raise or lower windows
window.lift()  # absolute position
window.lift(otherwin)  # relative to other window
window.lower()  # absolute position
window.lower(otherwin)   # relative to other window

# RESIZE BEHAVIOR
window.resizable(boolean, boolean)  # sets if resizable in width (1st param) and width (2nd param)
window.minsize(num, num)  # sets min width and height
window.maxsize(num, num)  # sets max width and height

# ICONIFYING AND WITHDRAWING
# WINDOW STATE: normal. iconic (iconified window), withdrawn, icon, zoomed
window.state()  # returns current window state
window.state('state')  # sets window state
window.iconify()  # iconifies window
window.deiconify()  # deiconifies window
```

### STANDARD DIALOGS

```py
# SLEETING FILE AND DIRECTORIES
# on Windows and Mac invokes underlying OS dialogs directly
from tkinter import filedialog
filename = filedialog.askopenfilename()
filename = filedialog.asksaveasfilename()
dirname = filedialog.askdirectory()
'''All of these commands produce modal dialogs, which means that the commands (and hence the program) will not continue running until the user submits the dialog.
The commands return the full pathname of the file or directory the user has chosen, or return an empty string if the user cancels out of the dialog.'''

# SELECTING COLORS
from tkinter import colorchooser
 # returns HEX color code, INITIALCOLOR: exiting color, presumably to replace
colorchooser.askcolor(initialcolor=hex_color_code)

# ALERT AND COMFIRMATION DIALOGS
from tkinter import messagebox
messagebox.showinfo(title="title", message='text')  # simple box with message and OK button
messagebox.showerror(title="title", message='text')
messagebox.showwarning(title="title", message='text')
messagebox.askyesno(title="title", message='text', detail='secondary text' icon='icon')
messagebor.askokcancel(message='text', icon='icon', title='title', detail='secondary text', default=button)  # DEFAULT: default button, ok or cancel
messagebox.akdquestion(title="title", message='text', detail='secondary text', icon='icon')
messagebox.askretrycancel(title="title", message='text', detail='secondary text', icon='icon')
messagebox.askyesnocancel(title="title", message='text', detail='secondary text', icon='icon')
# ICON: info (default), error, question, warning
```

POSSIBLE ALERT/CONFIRMATION RETURN VALUES:

- `ok (default)` -- "ok"
- `okcancel` -- "ok" or "cancel"
- `yesno` -- "yes" or "no"
- `yesnocancel` -- "yes", "no" or "cancel"
- `retrycancel` -- "retry" or "cancel"

## SEPARATOR

```py
# horizontal or vertical line between groups of widgets
separator = ttk.Separator(parent, orient=direction)
# DIRECTION: horizontal, vertical
'''LABEL FRAME'''
# labelled frame, used to group widgets
lf = ttk.LabelFrame(parent, text='label')
'''PANED WINDOWS'''
# stack multimple resizable widgets
# panes ara adjustable (drag sash between panes)
pw = ttk.PanedWindow(parent, orient=direction)
# DIRECTION: horizontal, vertical
lf1 = ttk.LabelFrame(...)
lf2 = ttk.LabelFrame(...)
pw.add(lf1)  # add widget to paned window
pw.add(lf2)
pw.insert(position, subwindow)  # insert widget at given position in list of panes (0, ..., n-1)
pw.forget(subwindow)  # remove widget from pane
pw.forget(position)  # remove widget from pane
```

### NOTEBOOK

Allows switching between multiple pages

```py
nb = ttk.Notebook(parent)
f1 = ttk.Frame(parent, ...)  # child of notebook
f2 = ttk.Frame(parent, ...)
nb.add(subwindow, text='page title', state=flag)
# TEXT: name of page, STATE: normal, dusabled (not selectable), hidden

nb.insert(position, subwindow, option=value)
nb.forget(subwindow)
nb.forget(position)
nb.tabs()  # retrieve all tabs
nb.select()  # return current tab
nb.select(position/subwindow)  # change current tab
nb.tab(tabid, option)  # retrieve tab (TABID: position or subwindow) option
nb.tab(tabid, option=value)   # change tab option
```

#### FONTS, COLORS, IMAGES

#### NAMED FONTS

Creation of personalized fonts

```py
from tkinter import font
font_name = font.Font(family='font_family', size=num, weight='bold/normal', slant='roman/italic', underline=boolean, overstrike=boolean)
# FAMILY: Courier, Times, Helvetica (support guaranteed)
font.families()  # all avaiable font families
```

#### COLORS

Specified w/ HEX RGB codes.

#### IMAGES

imgobj = PhotoImage(file='filename')
label['image'] = imgobj

#### IMAGES W/ Pillow

```py
from PIL import ImageTk, Image
myimg = ImageTk.PhotoImage(Image.open('filename'))
```
