In-game save feature (in tkinter game) - python

I'm working on a text-based game in Python using Tkinter. All the time the window contains a Label and a few Buttons (mostly 3). If it didn't have GUI, I could just use Pickle or even Python i/o (.txt file) to save data and later retrieve it. But what is the best way to tell the program to load the exact widgets without losing bindings, buttons' commands, classes etc.? P.S.: Buttons lead to cleaning the frame of widgets and summoning new widgets. I'm thinking of assigning a lambda (button's command) to a variable and then saving it (Pickle?) to be able to load it in the future and get the right point in the plot. Should I go for it or is there a better, alternative way to accomplish the thing? (If using lambda may work, I'd still be grateful to see your way of doing that.)

You need to save stuff in some kind of config file. In generel I'd recommend JSON and YAML as file formats also ini for ease of parsing.
Also, do not forget about the windows registry (portability lost then though).

My understanding was that you need a widget manager, to put them where you want and it is easy to pick up values.
Create a new class called Manager, make two functions, _setNewWidget, _deleteWidget, like this:
class Manager():
def __init__(self, *args, **kwargs):
objects = {}
def _createButton(self, frame, id, function, etc):
# object[id] = frame.Button(function, etc, ...) i dnt' know sintaxes, but this is the way
def _deleteWidget(self, id):
# object[id] = None or del(object[id]) same here
To get, just:
manager = Manager()
manager._createWidget("button_fase_one", frameTk, etc, etc)
manager.objects["button_fase_one"].changeFrame() # example
print(manager.objects["button_fase_one"].text)
In this way u can create objects and blit where u want.
To save data just make another function and save as json.

Related

How does one update a field from outside the __init__() function with pyqt5

I am reading a sensor and want to display its output as a decimal number in a GUI using PyQt5. I have found a number of tutorials that point out the label.setText('myStr') function. This does not work for my setup, however, because I need to update the field based on the input from another function. I'm not very familiar with PyQt5 yet, and I would appreciate any insight into how this problem ought to be approached.
Note: (I am using LCM to acquire data from a Raspberry Pi. I'm not sure that that is relevant to the problem, but it helps explain my code below.)
Here is what I am trying to do:
class Home_Win(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
loadUi("sensor_interface.ui", self)
self.label_temp.setText('temperature') #Just to verify that I can change it from here
def acquire_sensors(self):
temp = 0 #Make variable available outside nested function
def listen(channel, data):
msg=sensor_data.decode(data)
temp = msg.temperature
lc = lcm.LCM()
subscription = lc.subscribe("sensor_data_0", listen)
while True:
lc.handle()
self.label_temp.setText(str(temp))
Any thoughts on how I can update the GUI to display the readings I am getting from my sensors?
Thanks!
You're almost there. All you need to do is to save the ui in an instance variable in __init__:
self.ui = loadUi("sensor_interface.ui", self)
Then, assuming label_temp is the name of your QLabel widget, just do:
self.ui.label_temp.setText(str(temp))
It turned out that I needed to add repaint(). I also switched to a QLineEdit as this seemed to work better for me. So inside the while loop I now have:
self.ui.lineEdit_temp.setText(str(temp))
self.ui.lineEdit_temp.repaint()
This now outputs live updates to the GUI while reading the data stream.

Init screen in kv on button release using ScreenManager

I'm a beginner Kivy developer and I need some advice from you guys.
I'm using ScreenManager to jump between screens and as far as I noticed, all the screens are initialized just after the application starts, and I need them to be initialized with certain attributes from previous screens, like, selecting the category or stuff. Is there any way to do that?
I have two buttons in CategorySelectScreen both representing certain category, I want them to send a string attribute to DictScreen, where it will be used as an argument in CategorySelect method, which filters the items list, but the thing is, the application need that argument on start and without it the interpreter would just throw errors.
Also, I think I'm using kivy in a very bad way, could you please look into my code and give me some pro tips? Thanks in advance, cheers :)
kv file: http://pastebin.com/UdvGS7Wv
py files: http://pastebin.com/gJn9Mrip
When declaring your screens decide what object would be it's input. Then make this object a property. After that, setup on_... callback where you build your screen with widgets with values based on this input object. For example:
class DictScreen(Screen):
category_selected = ObjectProperty(None)
def on_category_selected(self, instance, value):
category_selected = value
self.clear_widgets()
self.add_widget(Button(text=category_selected.name))
And in previous screen, before you switch to DictScreen get its instance from app.root.ids, then assign category_selected to it and then set new current screen with ScreenManager. This way your DictScreen will be immediately build with choosen category right before you switch to it.
"before you switch to DictScreen get its instance" how this can be done? It's well explained here:
https://kivy.org/docs/api-kivy.uix.widget.html?highlight=widget#kivy.uix.widget.Widget.ids

tkinter and GUI programming methods

Hopefully this doesn't fall under "general discussion topic", since I'd like it to be more about resolving these issues in an efficient manner than a giant debate about which general approach to GUI programming is the absolute best.
So I've started some GUI programming with tkinter and long story short my code is getting pretty ugly pretty quickly. I'm trying to create a tile-based map editor for a video game. My main issues seem to be:
the inability of callbacks to return values.
the inability to transfer data between windows easily.
I assume that the reason I see these as issues is because I'm using functions a lot more than I'm using classes. For instance, my "load tileset" window is handled entirely functionally: Clicking the menu option in the main window calls the function that loads the new window. From within that window, I create an open file dialog when looking for the image, and modify the canvas displaying the image when I press the enter key (so that it draws the appropriate grid over the image). function function function.
What looks like really bad practice to me is the inclusion of extra arguments to compensate. For example, when I create a tileset, the instance of the TileSet class created should be sent back to the main window where the appropriate information can be displayed. I have a list of loaded tilesets as a global variable (even more bad practice: Everything dealing with my root window is in the global scope! yay!), and because callback functions don't return values, I pass that list as an argument to my "load tileset window" function, which then passes the argument to the create tileset function (called when you click the appropriate button in the window), where it's actually needed so that I can add my newly created tileset to the list. Passing arguments through a function 'hierarchy' like that seems like a horrible idea. It gets confusing, it's horrible for writing modular code, and just generally seems unnecessary.
My attempt at fixing the problem would be to write a class representing the whole GUI, and custom made window classes (that the GUI class can create and reference) that can actually store relevant data. That should take care of issues with transferring data between windows. Hopefully it would cut down on my gratuitous use of lambda functions in callbacks as well.
But I'm wondering: is this the best way? Or at least close? I'd rather not start rewriting and then end up with another system that's just sloppy and confusing in a different way. I know my methods are bad, but I don't really know what the best approach would be. I'm getting a lot of advice on how to do specific things, but none on how to structure the program as a whole. Any help would be greatly appreciated.
It sounds like you're trying to create a GUI that acts procedurally, which won't work. GUIs aren't procedural, their code doesn't run linearly where functions call callbacks which return values. What you're asking isn't unique to tkinter. This is the nature of event based GUI programming -- callbacks can't return anything because the caller is an event rather than a function.
Roughly speaking, you must use a global object of some sort to store your data. Typically this is called the "Model". It can be a global variable, or it might be a database, or it can be an object of some sort. In any case, it must exist "globally"; that is, it must be accessible to the whole GUI.
Often, this access is provided by a third component called a "Controller". It is the interface between the GUI (the "View") and the data (the "Model"). These three components make up what is called the model-view-controller pattern, or MVC.
The model, view and controller don't have to be three different objects. Often, the GUI and the controller are the same object. For small programs this works quite well -- the GUI components talk directly to your data model.
For example, you could have a class that represents a window which inherits from Tkinter.Toplevel. It can have an attribute that represents the data being edited. When the user selects "New" from a main window, it does something like self.tileset = TileSet(filename). That is, it sets the attribute named tileset of the GUI object named self to be an instance of the TileSet class specific to the given filename. Later functions that manipulate the data use self.tileset to access the object. For functions that live outside the main window object (for example, a "save all" function from the main window) you can either pass this object as an argument, or use the window object as the controller, asking it to do something to its tileset.
Here's a brief example:
import Tkinter as tk
import tkFileDialog
import datetime
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.windows = []
menubar = tk.Menu(self)
self.configure(menu=menubar)
fileMenu = tk.Menu(self)
fileMenu.add_command(label="New...", command=self.new_window)
fileMenu.add_command(label="Save All", command=self.save_all)
menubar.add_cascade(label="Window", menu=fileMenu)
label = tk.Label(self, text="Select 'New' from the window menu")
label.pack(padx=20, pady=40)
def save_all(self):
# ask each window object, which is acting both as
# the view and controller, to save it's data
for window in self.windows:
window.save()
def new_window(self):
filename = tkFileDialog.askopenfilename()
if filename is not None:
self.windows.append(TileWindow(self, filename))
class TileWindow(tk.Toplevel):
def __init__(self, master, filename):
tk.Toplevel.__init__(self, master)
self.title("%s - Tile Editor" % filename)
self.filename = filename
# create an instance of a TileSet; all other
# methods in this class can reference this
# tile set
self.tileset = TileSet(filename)
label = tk.Label(self, text="My filename is %s" % filename)
label.pack(padx=20, pady=40)
self.status = tk.Label(self, text="", anchor="w")
self.status.pack(side="bottom", fill="x")
def save(self):
# this method acts as a controller for the data,
# allowing other objects to request that the
# data be saved
now = datetime.datetime.now()
self.status.configure(text="saved %s" % str(now))
class TileSet(object):
def __init__(self, filename):
self.data = "..."
if __name__ == "__main__":
app = SampleApp()
app.mainloop()

Getting visible text from a QTextEdit in PyQt

This is related to another question I found here that seems to be inactive for a few months, so I think it's worth asking again.
I have created a simple QDialog that has a QTextEdit and a QPushButton. This pops up in my application when a user right-clicks and selects the option to "add comments". I want them to be able to write free-form text and I'll just save whatever they write as a long string with no concern for new lines, etc.
When the user clicks the button, it executes code like this:
self.connect(accept_button,QtCore.SIGNAL('clicked()'),lambda arg=str(view_textedit.toPlainText()): self.updateGroupComments(arg))
def updateGroupComments(self,new_comment_str):
print "Updating user comment to have new string: " + new_comment_str
self.group_entry.list_of_user_comments[self.currentFrameCounter] = new_comment_str
This is not detecting the TextEdit text that is visible (it only detects whatever the text edit text is set to when it is created). How do I make a simple command that returns the currently visible text from a QTextEdit. Again, the function
toPlainText()
is not working correctly... it doesn't find the currently visible text, only whatever text was on screen before changes or additions started being made by the user.
If this can't be done without subclassing and appealing to cursor positions, it makes the whole thing seem worthless... so please keep suggestions only to those implemented without subclassing or manipulating cursors. It should be really simple and straightforward to just return all currently visible text... what am I missing?
Objects that are being bound to default arguments are evaluated at the definition time. The function is working correctly, it returns whatever was in the text field when it was executed. Your code simply calls it at the wrong moment. If you want to use lambda, then do:
self.connect(
accept_button, QtCore.SIGNAL('clicked()'),
lambda: self.updateGroupComments(str(view_textedit.toPlainText()))
)
Or make view_textedit an instance attribute instead, and do simply
self.connect(
accept_button, QtCore.SIGNAL('clicked()'), self.updateGroupComments
)
And change updateGroupComments to call self.view_textedit.toPlainText instead of taking an argument.
BTW, this is not PyQt specific, this is how Python works in general.
To illustrate my last comment, that lambda can very well be replaced with:
def slot():
self.updateGroupComments(str(view_textedit.toPlainText()))
self.connect(accept_button, QtCore.SIGNAL('clicked()'), slot)

Creating a pygtk text field that only accepts number

Does anybody know how to create a text field using PyGTK that only accepts number. I am using Glade to build my UI.
Cheers,
I wouldn't know about a way to do something like this by simple switching a settings, I guess you will need to handle this via signals, one way would be to connect to the changed signal and then filter out anything that's not a number.
Simple approach(untested but should work):
class NumberEntry(gtk.Entry):
def __init__(self):
gtk.Entry.__init__(self)
self.connect('changed', self.on_changed)
def on_changed(self, *args):
text = self.get_text().strip()
self.set_text(''.join([i for i in text if i in '0123456789']))
If you want formatted Numbers you could of course go more fancy with a regex or something else, to determine which characters should stay inside the entry.
EDIT
Since you may not want to create your Entry in Python I'm going to show you a simple way to "numbify" an existing one.
def numbify(widget):
def filter_numbers(entry, *args):
text = entry.get_text().strip()
entry.set_text(''.join([i for i in text if i in '0123456789']))
widget.connect('changed', filter_numbers)
# Use gtk.Builder rather than glade, you'll need to change the format of your .glade file in Glade accordingly
builder = gtk.Builder()
builder.add_from_file('yourprogram.glade')
entry = builder.get_object('yourentry')
numbify(entry)
If you don't want to sanitize user input, avoid allowing text input entirely. If you're trying to collect hours and minutes, how about spin buttons or other widgets where you can limit the user's choice.
Check out the spinbutton example:
http://www.pygtk.org/pygtk2tutorial/examples/spinbutton.py
Convert your text to a number and in case it doesn't handle the error and set the text to an empty string.
You can generalize this to match a regular expression the way you want
try:
val = float(entry.get_text())
entry.set_text(str(val))
except ValueError:
entry.set_text('')

Categories