wxPython, Set value of StaticText() - python

I am making a little GUI frontend for a app at the moment using wxPython.
I am using wx.StaticText() to create a place to hold some text, code below:
content = wx.StaticText(panel, -1, "Text Here", style=wx.ALIGN_CENTRE)
I have a button when clicked retrieves data from MySQL, I am wanting to change the value of the StaticText() to the MySQL data or what else could I use the hold the data.
I have tried using the below method:
contents = wx.TextCtrl(bkg, style=wx.TE_MULTILINE | wx.HSCROLL)
content.SetValue("New Text")
This displays the data fine but after the data is loaded you can edit the data and I do not want this.
Hope you guys understand what I am trying to do, I am new to Python :)
Cheers

If you are using a wx.StaticText() you can just:
def __init__(self, parent, *args, **kwargs): #frame constructor, etc.
self.some_text = wx.StaticText(panel, wx.ID_ANY, label="Awaiting MySQL Data", style=wx.ALIGN_CENTER)
def someFunction(self):
mysql_data = databasemodel.returnData() #query your database to return a string
self.some_text.SetLabel(mysql_data)
As litb mentioned, the wxWidgets docs are often much easier to use than the wxPython docs. In order to see that the SetLabel() function can be applied to a wx.StaticText instance, you have to travel up the namespace hierarchy in the wxPython docs to the wxWindow superclass, from which wx.StaticText is subclassed. There are a few things different in wxPython from wxWidgets, and it can be challenging to find out what they are. Fortunately, a lot of the time, the differences are convenience functions that have been added to wxPython and are not found in wxWidgets.

wx.TextCtrl has a style called wx.TE_READONLY . Use that to make it read-only.
As a sidenode, you can use the C++ wxWidgets Manual for wxPython aswell. Where special handling for wxPython or other ports is required, the manual often points out the difference.

Related

How to set an attribute for a whole frame in Python Tkinter?

In this frame I have 5 buttons. Each button has nearly all the same settings. My code then looks quite crowded and it takes longer to reproduce.
homeButton = tk.Button(navFrame,command=lambda:self.switch(homeButton,"homeFrame"),image=home,
bg=colour["red"],activebackground=colour["black"],bd=0, cursor="hand2",width=100,height=100)
homeButton.place(x=50,y=150,anchor="center")
taskButton = tk.Button(navFrame,command=lambda:self.switch(taskButton,"taskFrame"),image=tasks,
bg=colour["red"],activebackground=colour["black"],bd=0, cursor="hand2",width=100,height=100)
taskButton.place(x=50,y=250,anchor="center")
monitorButton = tk.Button(navFrame,command=lambda:self.switch(monitorButton,"monitorFrame"),image=proxy,
bg=colour["red"],activebackground=colour["black"],bd=0, cursor="hand2",width=100,height=100)
monitorButton.place(x=50,y=350,anchor="center")
controllerButton = tk.Button(navFrame, command=lambda:self.switch(controllerButton,"controllerFrame"),image=options,
bg=colour["red"], activebackground=colour["black"],bd=0, cursor="hand2",width=100,height=100)
controllerButton.place(x=50,y=450,anchor="center")
settingsButton = tk.Button(navFrame,command=lambda:self.switch(settingsButton,"settingsFrame"),image=settings,
bg=colour["red"],activebackground=colour["black"],bd=0, cursor="hand2",width=100,height=100)
settingsButton.place(x=50,y=550,anchor="center")
Is there a way, like in css/html to set these settings for all buttons in this frame (navFrame).
I've had a look and not much is said on the topic.
Also Tkinter sucks, if anyone can recommend any better Python GUI tools as it's very limiting not being able to create rounded buttons.
Using the option database
At its core, tkinter uses something called an option database for managing default options to widgets. For an excellent description of how the option database works from a tcl/tk perspective see Options and Tk - A Beginner's Guide. You'll have to do some simple mental translations to convert the code examples to python/tkinter, but it's not that difficult to do.
Setting options happens with the option_add method, available on any widget. You need to give it an option pattern that describes the option being set along with widget names or classes that will get the option value.
If you want to set the value before creating the frame, you need to specify a widget name. Otherwise, you can wait until the widget has been created and then use the name that was auto-generated.
For your case, I recommend using the name "navFrame" for your widget name. You can then define the options prior to creating the widget. For example:
root.option_add("*navFrame.Label.background", "red")
root.option_add("*navFrame.Label.cursor", "hand2")
root.option_add("*navFrame.Label.width", 100)
root.option_add("*navFrame.Label.height", 100)
You need to make sure your frame has the name navFrame, which you can do when you create the frame:
navFrame = tk.Frame(root, name="navFrame")
Although I've shown how to set the values in the code, you can also put all of the options in a file and load them all up at once. This is a quick and easy way to support themes, giving each theme its own file of options.
There are many questions and answers on this site that deal with the option database.
Using a custom class
A more pythonic solution is to create a custom class, where you can hard-code any defaults that you want. For example, you might want to create a NavButton class that looks something like this:
class NavButton(tk.Button):
def __init__(self, parent, **kwargs):
kwargs.setdefault("bg", "red")
kwargs.setdefault("cursor", "hand2")
...
super().__init__(parent, **kwargs)
You would then use NavButton like a normal button:
homeButton = NavButton(navFrame, text="Home", command=...)
taskButton = NavButton(navFrame, text="Task", command=...)
...
Most of time, I will try to use kwargs to define same attributes, function or class to define same actions. Like
def navButton(self, key, image, option, index):
button = tk.Button(navFrame, image=image, **option)
button.configure(command=lambda:self.switch(button, key))
x, y = 50, 50 + 100*index
button.place(x=x, y=y, anchor='center')
return button
option1 = {
'bg': colour['red'],
'activebackground': colour['black'],
'bd': 0,
'cursor': 'hand2',
'width': 100,
'height': 100,
}
homeButton = self.navButton("homeFrame", home, option1, 1)
taskButton = self.navButton("taskFrame", tasks, option1, 2)
monitorButton = self.navButton("monitorFrame", proxy, option1, 3)
controllerButton = self.navButton("controllerFrame", options, option1, 4)
settingsButton = self.navButton("settingsFrame", settings, option1, 5)
I just create a function that sets up my buttons for me:
def set_up_buttons(button, x_placement, y_placement, navFrame, image):
button.configure(navFrame, image=image,bg=colour["red"], activebackground=colour["black"], bd=0, cursor="hand2",width=100,height=100)
button.place(x = x_placement, y = y_placement, anchor = "center")
homeButton = tk.Button(command=lambda:self.switch(homeButton,"homeFrame"))
set_up_buttons(homeButton, 50, 150, navFrame, home)
# Do the above two lines for every button that you have.
I am sure that there are many more efficient solutions, but this is what I use.
For your other question, I personally use Tkinter but have heard that PyQt is a good choice, but advanced. PySimpleGUI is easy to implement and recommended.
For Tkinter, you could just subclass the widgets you want and add extra configuration in your subclass, using whatever you like as your defaults. you could even load those settings from an external JSON or TOML if you like. Not as sophisticated as CSS but maybe good enough.

In-game save feature (in tkinter game)

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.

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()

How to tweak my tooltips in wxpython?

I was trying to add a tooltip to show the full content of a truncated ObjectListView, until it turned out it had such a feature built-in:
I tried making my own tool tips using wx.TipWindow, wx.PopupWindow and SuperToolTip, but none of them looked as 'native' as this one.
While I'm aware of this wiki article that supposedly enables the tooltip for truncated wx.Listrctrls, I didn't really understand how to get it working. I also expect that it only works when something is truncated, whereas I'd like to be able to use it to display some more information.
I guess the SuperToolTip comes close, but when you remove the 'header' it leaves it with empty space at the top, rather than centering the text in the middle of the tooltip and making it fit.
I tried looking through the source code of ObjectListView, SuperToolTip and wxpython to try and find how tooltips are being created, but I can't really find the low level parts that make it happen.
So how can I tweak tooltips so they look more like native tooltips?
The code to generate my current popups was:
text = "I'm a popup"
class PopUp(wx.TipWindow):
def __init__(self, parent, text):
wx.TipWindow.__init__(self, parent, text)
class PopUp2(wx.PopupWindow):
def __init__(self, parent, text):
wx.PopupWindow.__init__(self, parent)
st = wx.StaticText(self, parent, text)
# Import `from agw import supertooltip as STT`
popup3 = STT.SuperToolTip(text)
I'm not sure if we have a way to create a native Win7 tooltip yet, as you've seen wx.TipWindow looks like the tooltips from older versions of Windows, so there are probably some newer APIs that we should be using instead. Please create a ticket at trac.wxwidgets.org to find out for sure or to request the change if it's not possible some other way that I'm not thinking of at the moment.
Even if you can't create and pop up a native tooltip from scratch, you can still assign the entire ListCtrl a tooltip when you create it, and then change the text to whatever you want based on the item under the mouse pointer. It doesn't position the tooltip neatly over the list item like ObjectListView does, but I think it still accomplishes what you're asking.
self.lc = wx.ListCtrl(self, style=wx.LC_REPORT)
# ...
self.lc.Bind(wx.EVT_MOTION, self.OnMouseMotion)
def OnMouseMotion(self, evt):
pos = self.lc.ScreenToClient(wx.GetMousePosition())
item_index, flag = self.lc.HitTest(pos)
tip = self.lc.GetToolTip()
if flag == wx.LIST_HITTEST_ONITEMLABEL:
tip.SetTip('Some information about ' + self.lc.GetItemText(item_index))
else:
tip.SetTip('')
evt.Skip()

Alternatives to a wizard

I'm making a program that fits the wizard concept ideally; the user is walked through the steps to create a character for a game.
However, I'm realizing that the limitations of the wizard are making it difficult to design "elegant" logic flow. For example, because all pages of the wizard are initalized at the same time, I can't have the values entered in one page available to the next one. I have to put a button on each page to get the values from a previous page rather than simply having fields auto-populated.
I've thought about alternatives to using the wizard. I think the best idea is to have some buttons on one panel that change the information on another panel, e.g. a splitter window.
However, I can't find any documentation in wxPython on how to dynamically change the panel. Everything I've found so far is really pretty static, hence the use of the wizard. Even the "wxPython in Action" book doesn't mention it.
Are there any tutorials for making "dynamic panels" or better management of a wizard?
Here is a simple example. This way you can make your "wizard" work like a finite state machine where states are different pages that are initialized on demand. Also, the data is shared between pages.
import wx
import wx.lib.newevent
(PageChangeEvent, EVT_PAGE_CHANGE) = wx.lib.newevent.NewEvent()
class Data:
foo = None
bar = None
class Page1(wx.Panel):
def __init__(self, parent, data):
wx.Panel.__init__(self, parent)
self.parent = parent
self.data = data
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(sizer)
label = wx.StaticText(self, label="Page 1 - foo")
self.foo = wx.TextCtrl(self)
goto_page2 = wx.Button(self, label="Go to page 2")
for c in (label, self.foo, goto_page2):
sizer.Add(c, 0, wx.TOP, 5)
goto_page2.Bind(wx.EVT_BUTTON, self.OnPage2)
def OnPage2(self, event):
self.data.foo = self.foo.Value
wx.PostEvent(self.parent, PageChangeEvent(page=Page2))
class Page2(wx.Panel):
def __init__(self, parent, data):
wx.Panel.__init__(self, parent)
self.parent = parent
self.data = data
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(sizer)
label = wx.StaticText(self, label="Page 2 - bar")
self.bar = wx.TextCtrl(self)
goto_finish = wx.Button(self, label="Finish")
for c in (label, self.bar, goto_finish):
sizer.Add(c, 0, wx.TOP, 5)
goto_finish.Bind(wx.EVT_BUTTON, self.OnFinish)
def OnFinish(self, event):
self.data.bar = self.bar.Value
wx.PostEvent(self.parent, PageChangeEvent(page=finish))
def finish(parent, data):
wx.MessageBox("foo = %s\nbar = %s" % (data.foo, data.bar))
wx.GetApp().ExitMainLoop()
class Test(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.data = Data()
self.current_page = None
self.Bind(EVT_PAGE_CHANGE, self.OnPageChange)
wx.PostEvent(self, PageChangeEvent(page=Page1))
def OnPageChange(self, event):
page = event.page(self, self.data)
if page == None:
return
if self.current_page:
self.current_page.Destroy()
self.current_page = page
page.Layout()
page.Fit()
page.Refresh()
app = wx.PySimpleApp()
app.TopWindow = Test()
app.TopWindow.Show()
app.MainLoop()
The wxPython demo has an example of a "dynamic" wizard. Pages override GetNext() and GetPrev() to show pages dynamically. This shows the basic technique; you can extend it to add and remove pages, change pages on the fly, and rearrange pages dynamically.
The wizard class is just a convenience, though. You can modify it, or create your own implementation. A style that seems popular nowadays is to use an HTML-based presentation; you can emulate this with the wxHtml control, or the IEHtmlWindow control if your app is Windows only.
You could try using a workflow engine like WFTK. In this particular case author has done some work on wx-based apps using WFTK and can probably direct you to examples.
I'd get rid of wizard in whole. They are the most unpleasant things I've ever used.
The problem that requires a wizard-application where you click 'next' is perhaps a problem where you could apply a better user interface in a bit different manner. Instead of bringing up a dialog with annoying 'next' -button. Do this:
Bring up a page. When the user inserts the information to the page, extend or shorten it according to the input. If your application needs to do some processing to continue, and it's impossible to revert after that, write a new page or disable the earlier section of the current page. When you don't need any input from the user anymore or the app is finished, you can show a button or enable an existing such.
I don't mean you should implement it all in browser. Make simply a scrolling container that can contain buttons and labels in a flat list.
Benefit: The user can just click a tab, and you are encouraged to put all the processing into the end of filling the page.
It should be noted that a Wizard should be the interface for mutli-step, infrequently-performed tasks. The wizard is used to guide the user through something they don't really understand, because they almost never do it.
And if some users might do the task frequently, you want to give those power users a lightweight interface to do the same thing - even if it less self explanatory.
See: Windows Vista User Experience Guidelines - Top Violations
Wizards
Consider lightweight alternatives first, such as dialog boxes, task
panes, or single pages. Wizards are
a heavy UI, best used for multi-step,
infrequently performed task. You don't
have to use wizards—you can provide
helpful information and assistance in
any UI.

Categories