I'm trying to build a Python application that has a Tkinter GUI.
I got stuck there that I don’t understand how to literate through the read yaml string by keys. I want the values of field_names to be the names of the Entries in the code and the values of labels are the properties of labels text.
cfg.yml looks like this:
- field_name: neptun_code
label: Neptun code
- field_name: result
label: Result [%]
- field_name: mark
label: Mark [1-5]
config.py looks like this:
from pathlib import Path
class Config(object):
def __init__(self):
self.document_yaml = None
def function(self):
self.from_yaml()
def from_yaml(self):
self.document_yaml = Path("./cfg.yaml").read_text()
return self.document_yaml
Main.py The problem here is that I don’t know how to use the yaml list and how to iterate through it in def labels(self) and def entries(self)
import tkinter as tk
from tkinter import messagebox
class Window(object):
def __init__(self):
self.name = None
self.window = None
self.lbl = None
self.txt = None
def show(self):
self.create_window()
self.labels()
self.entries()
self.window.mainloop()
def create_window(self):
self.window = tk.Tk()
self.window.title("MainWindow")
self.window.geometry('350x200')
def labels(self):
# label
row = 0
for key, value in yaml_str: #?????
self.lbl = tk.Label(self.window, text=value)
self.lbl.grid(column=0, row=row)
row += 1
def entries(self):
# entry
row = 0
for key, value in yaml_str: # ??????
self.txt = tk.Entry(self.window, textvariable=self.name, width=20)
self.txt.grid(column=1, row=row)
row += 1
my_window = Window()
my_window.show()
Install the pyyaml library:
pip install pyyaml
Now convert your yaml to a python list and process it:
import yaml
from pathlib import Path
document_yaml = Path("./cfg.yaml").read_text()
yaml_list = yaml.load(document_yaml)
print(yaml_list)
# [{'field_name': 'neptun_code', 'label': 'Neptun code'}, {'field_name': 'result', #'label': 'Result [%]'}, {'field_name': 'mark', 'label': 'Mark [1-5]'}]
# So labels, for instance, will be:
def labels(self):
# label
row = 0
for item in yaml_list:
self.lbl = tk.Label(self.window, text=item["label"])
self.lbl.grid(column=0, row=row)
row += 1
Full Example:
import yaml
from pathlib import Path
class Config(object):
def __init__(self):
self.document_yaml = Path("./cfg.yaml").read_text()
def load_yaml(self) -> list:
return yaml.load(self.document_yaml)
class Window(object):
def __init__(self):
self.name = None
self.window = None
self.lbl = None
self.txt = None
self.config = Config().load_yaml()
def show(self):
self.create_window()
self.labels()
self.window.mainloop()
def create_window(self):
self.window = tk.Tk()
self.window.title("MainWindow")
self.window.geometry('350x200')
def labels(self):
# label
row = 0
for item in self.config:
self.lbl = tk.Label(self.window, text=item["label"])
self.lbl.grid(column=0, row=row)
row += 1
Related
I am trying to create my own python code editor. For that I tried to use the tkcode module. Tkcode partially covers my needs (colors in the text for example), but it does not have the keyboard events that Idle has (automatic indentation when pressing enter, when pressing tab puts 4 spaces, etc). Some of them I can try to recreate, but automatic indentation is difficult. Is there any way to associate the Idle events to my code editor using the idlelib without creating another window (since I am making a notebook)? I went through the source code of Idle and couldn't find the way.
I know that this site is not to ask for recommendations, but if you recommend a better module to create a text widget that allows me to create this editor, it would be great too.
This is the code I have made:
from tkcode import CodeEditor
from tkinter import ttk
import tkinter as tk
import re
class CodeEditor(CodeEditor):
def __init__(Self, *args, **kargs):
super().__init__(*args, **kargs)
Self.bind("<Return>", Self.enter)
def get_current_line(Self):
return Self.get("insert linestart", "insert lineend")
def enter(Self, event):
for index, char in enumerate(Self.get_current_line()):
if(char != " "):
break
else:
index += 1
Self.insert("insert", "\n")
Self.insert("insert", " "*index)
return "break"
class FileHandler:
def __init__(Self, progs_path, filetabs):
Self.files = {}
Self.progs_path = progs_path
Self.filetabs = filetabs
def askopen(Self):
v = tk.Toplevel()
v.transient()
v.resizable(0, 0)
prog = ttk.Entry(v)
prog.pack(padx=10, pady=10)
prog.bind("<Return>", lambda Event:(Self.open(prog.get()), v.destroy()))
def open(Self, prog):
progfile = str(prog)[0]
progfile = f"prog{progfile}00A{progfile}99.py"
if(progfile in Self.files):
text = Self.files[progfile][prog]
else:
functions = {}
name = None
with open(f"{Self.progs_path}/{progfile}") as file:
for line in file:
match = re.match("(def|class) prog(\w+)", line)
if(match):
name = match[2]
functions[name] = line
if(line.startswith(" ") and name):
functions[name] += line
Self.files[progfile] = functions
text = functions[prog]
frame = ttk.Frame(Self.filetabs)
code_editor = CodeEditor(frame, language="python", highlighter="mariana", font="TkFixedFont", autofocus=True, padx=10, pady=10)
code_editor.pack(fill="both", expand=True)
code_editor.content = text
Self.filetabs.add(frame, text="prog"+prog)
v = tk.Tk()
filetabs = ttk.Notebook()
fh = FileHandler(".", filetabs)
menubar = tk.Menu(tearoff=0)
file = tk.Menu(tearoff=0)
menubar.add_cascade(label="Archivo", menu=file)
file.add_command(label="Abrir prog", command=fh.askopen)
v["menu"] = menubar
filetabs.pack(fill="both", expand=True)
fh.open("833")
In the end I was able to fix it. I was able to recreate almost all but one of the functions. For the last one, I made an impersonator class for idlelib.editor.EditorWindow to be able to do the automatic indentation, in addition to adding two new functions to the tkcode.CodeEditor.
However, I find this solution to be very unstable. Any better answer is still appreciated c:
This is the code:
from tkcode import CodeEditor
from tkinter import ttk
from idlelib.editor import EditorWindow
import tkinter as tk
import re
class FakeEditorWindow(EditorWindow):
def __init__(Self, text):
Self.text = text
Self.indentwidth = 4
Self.tabwidth = 4
Self.prompt_last_line = ''
Self.num_context_lines = 50, 500, 5000000
Self.usetabs = False
def is_char_in_string(Self, text_index):
return 1
class CodeEditor(CodeEditor):
def __init__(Self, *args, **kargs):
super().__init__(*args, **kargs)
Self.fake_editor_window = FakeEditorWindow(Self)
Self.bind("<Tab>", Self.tab)
Self.bind("<Control-Shift_L>", Self.dedent)
Self.bind("<BackSpace>", Self.backspace)
Self.bind("<Home>", Self.inicio)
Self.bind("<Return>", Self.enter)
def undo_block_start(Self):
pass
def undo_block_stop(Self):
pass
def get_current_line(Self):
return Self.get("insert linestart", "insert lineend")
def selection_get(Self):
if(Self.tag_ranges("sel")):
return Self.get("sel.first", "sel.last")
else:
return ""
def get_selection_zone(Self):
return (map(int, Self.index('sel.first').split(".", 1)),
map(int, Self.index('sel.last').split(".", 1)))
def tab(Self, event):
selection = Self.selection_get()
if(selection):
(startline, startcolumn), (endline, endcolumn) = Self.get_selection_zone()
if(startcolumn == 0):
for line in range(startline, endline+1):
Self.insert(f"{line}.0", " "*4)
Self.tag_add("sel", f"{startline}.0", "sel.last")
Self.mark_set("insert", f"{endline+1}.0")
else:
Self.insert("insert", " "*4)
return "break"
def dedent(Self, event):
if(Self.tag_ranges("sel")):
(startline, startcolumn), (endline, endcolumn) = Self.get_selection_zone()
if(startcolumn == 0):
for line in range(startline, endline+1):
if(Self.get(f"{line}.0", f"{line}.4") == " "*4):
Self.delete(f"{line}.0", f"{line}.4")
def backspace(Self, event):
if(not Self.tag_ranges("sel") and Self.get("insert linestart", "insert").isspace()):
cursor_line, cursor_col = map(int, Self.index("insert").split(".", 1))
Self.delete(f"{cursor_line}.{cursor_col-4}", "insert")
return "break"
def inicio(Self, event):
cursor_line, cursor_column = map(int, Self.index('insert').split(".", 1))
if(not Self.get("insert linestart", f"{cursor_line}.{cursor_column}").isspace()):
for i in range(cursor_column, -1, -1):
if(Self.get("insert linestart", f"{cursor_line}.{i}").isspace()):
Self.mark_set("insert", f"{cursor_line}.{i}")
return "break"
def enter(Self, event):
return EditorWindow.newline_and_indent_event(Self.fake_editor_window, event)
class FileHandler:
def __init__(Self, progs_path, filetabs):
Self.files = {}
Self.progs_path = progs_path
Self.filetabs = filetabs
def askopen(Self):
v = tk.Toplevel()
v.transient()
v.resizable(0, 0)
prog = ttk.Entry(v)
prog.pack(padx=10, pady=10)
prog.bind("<Return>", lambda Event:(Self.open(prog.get()), v.destroy()))
def open(Self, prog):
progfile = str(prog)[0]
progfile = f"prog{progfile}00A{progfile}99.py"
if(progfile in Self.files):
text = Self.files[progfile][prog]
else:
functions = {}
name = None
with open(f"{Self.progs_path}/{progfile}") as file:
for line in file:
match = re.match("(def|class) prog(\w+)", line)
if(match):
name = match[2]
functions[name] = line
if(line.startswith(" ") and name):
functions[name] += line
Self.files[progfile] = functions
text = functions[prog]
frame = ttk.Frame(Self.filetabs)
code_editor = CodeEditor(frame, language="python", highlighter="mariana", font="TkFixedFont", autofocus=True, padx=10, pady=10)
code_editor.pack(fill="both", expand=True)
code_editor.content = text
Self.filetabs.add(frame, text="prog"+prog)
v = tk.Tk()
filetabs = ttk.Notebook()
fh = FileHandler(".", filetabs)
menubar = tk.Menu(tearoff=0)
file = tk.Menu(tearoff=0)
menubar.add_cascade(label="Archivo", menu=file)
file.add_command(label="Abrir prog", command=fh.askopen)
v["menu"] = menubar
filetabs.pack(fill="both", expand=True)
fh.open("833")
I'm working with tkinter at the moment, ive needed to call a method onto an object that has a Tk() class. im trying to call the method not in the Tk() class, but from the class ive already created, which the tkinter object is in already.
Ive tried to call .Format() onto l_label, but i think it is trying to find an attribute or method within the Label class from tkinter, because it returns: AttributeError: 'Label' object has no attribute 'Format'
Any thoughts?
from tkinter import *
from tkinter import messagebox
from tkinter import filedialog
thing = Tk()
class App():
def __init__(self, master, _title, back='white'):
self.master = master
self._title = _title
self.back = back
self.master.title(self._title)
self.master.configure(background=back)
def label(self, _text, back='white', w=1, h=1):
self.back = back
self.w = w
self.h = h
self._text = _text
l_label = Label(self.master, text=self._text, bg=self.back, width=self.w, height=self.h)
return l_label
def Format(self, l_label, Row=1, Column=1):
self.Row = Row
self.Column = Column
l_label.grid(row=Row, column=Column)
app = App(thing, 'hello world')
label = app.label('this is a text boc', 'white').Format()
thing.mainloop()
What you are looking for is method chaining. In order for that to work your function needs to return self
class App(object):
def __init__(self, master, _title, back='white'):
self.master = master
self._title = _title
self.back = back
self.master.title(self._title)
self.master.configure(background=back)
self.w = None
self.h = None
self._text = None
self.Row = None
self.Column = None
self.l_label = None
def label(self, _text, back='white', w=1, h=1):
self.back = back
self.w = w
self.h = h
self._text = _text
self.l_label = Label(self.master, text=_text, bg=back, width=w, height=h)
return self
def Format(self, Row=1, Column=1):
self.Row = Row
self.Column = Column
self.l_label.grid(row=Row, column=Column)
return self
Notice how I removed the l_label from Format and assigned Label(self.master, text=_text, bg=back, width=w, height=h) to self.l_label
Then you would be able to do:
thing = Tk()
app = App(thing, 'hello world')
label = app.label('this is a text box', 'white', 15, 15).Format()
thing.mainloop()
I'm making a ide for brainf*ck in python using tkinter and I'm adding a recent projects section but when I'm placing the buttons they do not appear on the screen.
Here is the code for the Scene:
from tkinter import *
from tkinter import filedialog as File
import tkinter as tk
class HoverButton(tk.Button):
def __init__(self, master, **kw):
tk.Button.__init__(self, master=master, **kw)
self.defaultBackground = "#5d5d5d"
self['background'] = self.defaultBackground
self['activebackground'] = "#6d6d6d"
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
def on_enter(self, e):
self['background'] = "#6d6d6d"
def on_leave(self, e):
self['background'] = self.defaultBackground
class ProjectPage(Frame):
def __init__(self, master, projects=[]):
super().__init__(master)
self.projects = projects
self.buttons = []
self.mstr = self.master.master
self.title = "PesolIde: Projets"
self.width = 800
self.height = 500
self.color = "#4d4d4d"
self.projectFrame = tk.Frame(self.mstr,width=800,height=50,bg="#5d5d5d")
self.newProject = HoverButton(self.mstr,text="New Project", height=1, bg="#6d6d6d")
self.openProject = HoverButton(self.mstr,text="Open Project", height=1,bg="#6d6d6d", command=OpenAsk)
self.projectdisplay = tk.Frame(self.mstr, width=700, height=300, bg="#5d5d5d", highlightbackground="black", highlightthickness=1)
for i in range(len(self.projects)):
self.buttons.append(HoverButton(master, text=self.projects[i].split(':')[0], width=50, height=1))
if len(self.buttons)>=40:
break
self.loaded = False
def show(self):
self.projectFrame.place(x=0, y=0)
self.newProject.place(x=20, y=10)
self.openProject.place(x=120, y=10)
self.projectdisplay.place(x=50,y=100)
self.y = 100
print(len(self.buttons))
for i in range(len(self.buttons)):
print("placing " + str(self.buttons[i]))
self.buttons[i].place(x=50,y=100+(20*i))
self.master.set(title=self.title,width=self.width,height=self.height)
self.master.master['bg'] = self.color
def hide(self):
self.newProject.place_forget()
self.openProject.place_forget()
def load(self):
if not self.loaded:
self.newProject.place_forget()
self.openProject.place_forget()
self.loaded = True
def unload(self):
self.newProject.destroy()
self.openProject.destroy()
def OpenAsk():
name = File.askopenfilename()
and here is the code for main.py:
from tkinter import *
import src.framework.modules.Window.Window as windows
import src.framework.src.Scenes.all as Scenes
import tkinter as tk
root = tk.Tk()
window = windows.window(root, "", 800, 500)
window.place()
projects = open("projects.txt",'r').read().split("\n")
start = Scenes.ProjectPage.ProjectPage(window,projects)
start.show()
window.mainloop()
When I make a HoverButton outside the ProjectPage class in the ProjectPage file, it appears as expected but not when initialised from within the class of directly from the main file.
Here are some screenshots.
The output from running main.py:
The output from running from outside the ProjectPage class with the code on the left:
Try to insert values for relx, rely, relwidth, relheight as attributes in "place" or you can as well insert height, width as attributes for place.
Check out the documentation: https://www.tutorialspoint.com/python/tk_place.htm
I'm trying to make a text adventure with tkinter and I'm slowly getting something together. I'm trying to display commands as they come from room to room but even though the buttons appear, nothing happens when I press them.
game.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import world
from player import Player
from ui import *
def main():
gui = Window(root())
while True:
gui.mainloop()
else:
pass
if __name__ == '__main__':
main()
ui.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import tkinter as tk
from tkinter import ttk
import world, tiles, action
from player import Player
class Window(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master=master)
self.master = master
self.player = Player()
self.init_ui()
def init_ui(self):
self.master.title("****")
self.tabs = Tabs(self.master)
world.load_tiles()
self.world = world.tile_exists(self.player.location_x, self.player.location_y)
self.update_main()
def update_main(self):
self.world.scene_init()
self.world.modify_player(self.player)
self.tabs.update_tab(self.world, self.player)
def _label(self, master, text, side=None, anchor=None):
new_label = tk.Label(master, text=text)
new_label.pack(side=side, anchor=anchor)
def _button(self, master, text, command, side=None, anchor=None):
new_button = tk.Button(master, text=text, command=command)
new_button.pack(side=side, anchor=anchor)
class Tabs(Window):
def __init__(self, master):
self.master = master
self.nb = ttk.Notebook(self.master)
nb_1 = ttk.Frame(self.nb)
self.frame_1 = tk.Frame(nb_1, bg='red', bd=2, relief=tk.SUNKEN, padx=5, pady=5)
self.frame_1.pack(expand=1, fill='both', side=tk.LEFT)
self.nb.add(nb_1, text='Game')
self.nb.pack(expand=1, fill='both', side=tk.LEFT)
def update_tab(self, world, player):
avaliable_actions = world.avaliable_actions()
self._label(self.frame_1, world.display_text(), side=tk.LEFT, anchor=tk.N)
for action in avaliable_actions:
self._button(self.frame_1, text=action, command=player.do_action(action, **action.kwargs), side=tk.BOTTOM, anchor=tk.E)
def root():
root = tk.Tk()
root.geometry("600x350+200+200")
return root
world.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
_world = {}
def tile_exists(x, y):
"""Returns the tile at the given coordinates or None if there is no tile.
:param x: the x-coordinate in the worldspace
:param y: the y-coordinate in the worldspace
:return: the tile at the given coordinates or None if there is no tile
"""
return _world.get((x, y))
def load_tiles():
with open('scenes.txt', 'r') as f:
rows = f.readlines()
x_max = len(rows[0].split('\t'))
for y in range(len(rows)):
cols = rows[y].split('\t')
for x in range(x_max):
tile_name = cols[x].replace('\n', '')
_world[(x, y)] = None if tile_name == '' else getattr(__import__('tiles'),
tile_name)(x, y)
return _world
tiles.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import world, action
from player import Player
class MapTile():
def __init__(self, x, y):
self.x = x
self.y = y
def display_text(self):
pass
# raise NotImplementedError()
def modify_player(self, the_player):
raise NotImplementedError()
def adjacent_moves(self):
moves = []
if world.tile_exists(self.x + 1, self.y):
moves.append(action.MoveEast())
if world.tile_exists(self.x - 1, self.y):
moves.append(action.MoveWest())
if world.tile_exists(self.x, self.y - 1):
moves.append(action.MoveNorth())
if world.tile_exists(self.x, self.y + 1):
moves.append(action.MoveSouth())
return moves
def avaliable_actions(self):
'''Returns all of the default avaliable_actions in a room'''
moves = self.adjacent_moves()
# moves.append(action.ViewInventory())
return moves
class Scene_1(MapTile):
def scene_init(self):
self.location = 'Scene_1'
self.long_desc = 'Welcome to {}, the shittiest place on earth.'.format(self.location)
self.short_desc = 'Eh, I don\'t care.'
def display_text(self):
return self.long_desc
def modify_player(self, the_player):
self.first = True
return self.display_text()
class Scene_2(MapTile):
def scene_init(self):
self.location = 'Scene_2'
self.long_desc = 'This is {}, but noone gives a damn.'.format(self.location)
self.short_desc = 'Eh, I don\'t care, really.'
def display_text(self):
return self.long_desc
def modify_player(self, the_player):
self.first = True
return self.display_text()
player.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
class Player():
'''Base for player'''
def __init__(self):
self.inventory = []
self.hp = 100
self.location_x, self.location_y = 1, 1
self.victory = False
def is_alive(self):
return self.hp >= 0
def do_action(self, action, **kwargs):
action_method = getattr(self, action.method.__name__)
if action_method:
action_method(**kwargs)
def print_inventory(self):
for item in self.inventory:
print(item, 'n')
def move(self, dx, dy):
self.location_x += dx
self.location_y += dy
def move_north(self):
self.move(dx=0, dy=-1)
def move_south(self):
self.move(dx=0, dy=1)
def move_east(self):
self.move(dx=1, dy=0)
def move_west(self):
self.move(dx=-1, dy=0)
action.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
from player import Player
class Action():
def __init__(self, method, name, **kwargs):
"""Creates a new action
:param method: the function object to execute
:param name: the name of the action
:param ends_turn: True if the player is expected to move after this action else False
:param hotkey: The keyboard key the player should use to initiate this action
"""
self.method = method
self.name = name
self.kwargs = kwargs
def __str__(self):
return "{}".format(self.name)
class MoveNorth(Action):
def __init__(self):
super().__init__(method=Player.move_north, name='north')
class MoveSouth(Action):
def __init__(self):
super().__init__(method=Player.move_south, name='south')
class MoveEast(Action):
def __init__(self):
super().__init__(method=Player.move_east, name='east')
class MoveWest(Action):
def __init__(self):
super().__init__(method=Player.move_west, name='west')
class ViewInventory(Action):
"""Prints the player's inventory"""
def __init__(self):
super().__init__(method=Player.print_inventory, name='View inventory', hotkey='i')
class Attack(Action):
def __init__(self, enemy):
super().__init__(method=Player.attack, name="Attack", hotkey='a', enemy=enemy)
class Flee(Action):
def __init__(self, tile):
super().__init__(method=Player.flee, name="Flee", hotkey='f', tile=tile)
command expect function name without () and arguments.
Mistake:
command=player.do_action(action, **action.kwargs)
This way you assign to command value returned by player.do_action() but this functions returns None
You have to use lambda function
command=lambda:player.do_action(action, **action.kwargs)
but maybe you will need also arguments in lambda because you create this in for loop.
command=lambda act=action, kws=action.kwargs : player.do_action(act, **kws)
I have the following code:
class Test:
def __init__(self, master): # master is a Tk or Toplevel instance
self.master, self.typeFrame = master, tk.Frame(master)
self.typeVar = tk.StringVar(self.typeFrame)
for column, wordType in enumerate(['Noun', 'Verb', 'Adjective', 'Adverb'], 1):
typeRadioButton = tk.Radiobutton(self.typeFrame, text = wordType, textvariable = self.typeVar, value = wordType, command = self.createLambda(wordType))
typeRadioButton.grid(row = 1, column = column)
self.typeFrame.grid()
def createLambda(self, obj):
return lambda: self.changeInfo(obj)
def changeInfo(self, obj):
pass # this will do something later
However, when I run the code like this, the Radiobuttons have no text associated with them.
root = tk.Tk()
test_instance = Test(root)
test_instance.master.mainloop()
How can I fix this? Thanks in advance!
Change textvariable=... to variable=....
BTW, your example does not contain self.typeFrame.pack() or self.typeFrame.grid(..).