Is there a way, from a button to reinitialize a specific panel? I have a section of an app that looks for specific files in the OS and then creates some checkboxes and textctrls and then adds them to the sizer dynamically:
for db in numDB:
if xIndex >= 12:
pass
xIndex += 1
else:
check = wx.CheckBox(self, -1, db)
sizer.Add(check, pos=(xIndex,0), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=10)
label = wx.StaticText(panel, label="")
sizer.Add(label, pos=(xIndex,1), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=10)
name = wx.TextCtrl(panel)
#Set Temp Name
if db.endswith('.db'):
name.Value = db[:-3]
sizer.Add(name, pos=(xIndex,2), span=(1,3),flag=wx.EXPAND|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, border=10)
xIndex +=1
I have a refresh button, in the event the user adds a new db, or removes one. The simplest concept for me, at least, is to try to find a way to rerun the init.
I'm trying to reference the init by:
def onRefreshClick(self, event):
self.__init__
That def happens within the Class itself, but it doesn't seem to do anything. And I'm super new to the whole wxPython and Python in general, so it's probably something simple that I cannot quite figure out.
Edited to try and incorporate super():
class LaunchPanel(wx.Panel):
"""
Launch Tab for finding and launching .db files
"""
#----------------------------------------------------------------------
def __init__(self, parent):
""""""
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
super (LaunchPanel)
self.initialize()
def initialize(self):
panel = self
sizer = wx.GridBagSizer(11, 3)
<snip...>
This results in my panel being empty. When I hit refresh, it adds (I think) all my elements to the upper left portion of the panel...? I'm sure it's easy, whatever I am doing wrong...?
You're missing parentheses:
self.__init__() # now, this is a method call
A simple solution is to have your __init__ do nothing but call another method, then you can call that method whenever you want:
def __init__(self, ...):
super(...)
self.initialize()
def initialize(self):
<actual intialization code here>
I think that's a lot cleaner than directly calling __init__. Plus, this lets you separate stuff that must truly be done only at object instantiation (such as calling the init of the superclass) from the code you want to call multiple times.
Related
i have a main GUI-Window from which i open a new Window (FCT-popup) with a buttonclick:
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow() # sets ui = to the main window from the ui-file
self.ui.setupUi(self)
[...]
def enter_fct_results(self):
self.FCTpopup = FCT_Window()
self.FCTpopup.show()
In the Window i have a QTable to fill and a button to submit the data and close the window:
class FCT_Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_FCT_Window()
self.ui.setupUi(self)
[...]
self.ui.pushButton_submitFCT.clicked.connect(lambda: MainWindow.store_fct_data(MainWindow, self.on_submit()[0]))
def on_submit(self): # event when user clicks
fct_nparray = np.zeros((self.ui.tableFCTinputs.rowCount(), self.ui.tableFCTinputs.columnCount()))
for j in range(self.ui.tableFCTinputs.columnCount()):
for i in range(self.ui.tableFCTinputs.rowCount()):
fct_nparray[i, j] = float(self.ui.tableFCTinputs.item(i, j).text())
return fct_nparray, lambda: self.close()
self.ui.pushButton_submitFCT.clicked.connect(lambda: MainWindow.store_fct_data(MainWindow, self.on_submit()[0]))
The receiving function iin the main window looks like ths:
def store_fct_data(self, data):
self.fct_data = data
Now i just want to understand how i can make either the mainwindow or the pushbutton which opens the 2nd window disabled. Disabling inside enter_fct_results() works, but if i want to enable it again with either store_fct_data or on_submit provides errors like this:
self.ui.pushButton_FCTresults.setEnabled(1)
self.ui.pushButton_submitFCT.clicked.connect(lambda: MainWindow.store_fct_data(MainWindow, self.on_submit()[0]))
AttributeError: type object 'MainWindow' has no attribute 'ui'
I dont think i have understood it here how to deal with multiple windows and stuff. For example how would i change a the color of a button in the main window by using a button in window2. How do i access the widgets? if i am inside the same Window i do that easily by
self.ui.pushbutton.setText("New Text")
I dont get how to access items and attributes across Windows :( Can you help me?
Access to attributes of another instance
There is a fundamental difference between disabling the button of the second window in enter_fct_results and what you tried in the lambda: in the first case, you're accessing an instance attribute (for instance, self.FCTpopup.ui.pushButton), while in the second you're trying to access a class attribute.
The self.ui object is created in the __init__ (when the class instance is created), so the instance will have an ui attribute, not the class:
class Test:
def __init__(self):
self.value = True
test = Test()
print(test.value)
>>> True
print(Test.value)
>>> AttributeError: type object 'Test' has no attribute 'value'
Provide a reference
The simple solution is to create a reference of the instance of the first window for the second:
def enter_fct_results(self):
self.FCTpopup = FCT_Window(self)
self.FCTpopup.show()
class FCT_Window(QMainWindow):
def __init__(self, mainWindow):
QMainWindow.__init__(self)
self.mainWindow = mainWindow
self.ui.pushButton_submitFCT.clicked.connect(self.doSomething)
def doSomething(self):
# ...
self.mainWindow.ui.pushButton.setEnabled(True)
Using modal windows (aka, dialogs)
Whenever a window is required for some temporary interaction (data input, document preview, etc), a dialog is preferred: the main benefit of using dialogs is that they are modal to the parent, preventing interaction on that parent until the dialog is closed; another benefit is that (at least on Qt) they also have a blocking event loop within their exec() function, which will only return as soon as the dialog is closed. Both of these aspects also make unnecessary disabling any button in the parent window. Another important reason is that QMainWindow is not intended for this kind of operation, also because it has features that generally unnecessary for that (toolbars, statusbars, menus, etc).
def enter_fct_results(self):
self.FCTpopup = FCT_Window(self)
self.FCTpopup.exec_()
class FCT_Window(QDialog):
def __init__(self, parent):
QMainWindow.__init__(self, parent)
self.ui.pushButton_submitFCT.clicked.connect(self.doSomething)
def doSomething(self):
# ...
self.accept()
The above makes mandatory to recreate the ui in designer using a QDialog (and not a QMainWindow) instead. You can just create a new one and drag&drop widgets from the original one.
i finally found my mistake: It was the place of the signal connection. It has to be right before the 2nd window is opened:
def enter_fct_results(self):
self.FCTpopup = Dialog(self.fct_data)
self.FCTpopup.submitted.connect(self.store_fct_data)
self.FCTpopup.exec_()
With this now i can send the stored data from the mainwindow to the opened window, import into the table, edit it and send it back to the main window on submit.
The most obvious approach for me is to declare each window (window, dialog or widget) in the constructor and call the show() method when needed. Something like this:
class MultiWindowApp():
def __init__(self):
self.window_1 = self.init_window_1()
self.window_2 = self.init_window_2()
def init_window_1(self):
gui = uic.loadUi(...)
# other settings
return gui
def init_window_2(self):
gui = uic.loadUi(...)
# other settings
return gui
def show_window_1(self):
self.window_1.show()
def show_window_2(self):
self.window_2.show()
Nevertheless, it does not seem to be memory efficient, because I store the windows in the memory all the time, even when I am not showing it.
Alternative solution that comes to my mind is to create a separate class for each window (or other widget) and have one placeholder for all in the main class. Assign an instance of the respective class and delete on closing the window. A minimal example below:
class Window_1(QWidget):
def __init__(self):
QWidget.__init__(self)
uic.loadUi(...)
# other settings
self.show()
class Window_2(QWidget):
def __init__(self):
QWidget.__init__(self)
uic.loadUi(...)
# other settings
self.show()
class MultiWindowApp():
def __init__(self):
self.widget_placeholder = None
def show_window_1(self):
self.widget_placeholder = Window_1()
def show_window_2(self):
self.widget_placeholder = Window_1()
This would be a bit slower, but I would avoid keeping in memory unnecessary stuff. Nevertheless, I still have a feeling that there is a better way. What is the proper way of designing such an application?
I didn't run the examples above, so there can be some errors, but I think that the concepts behind them are clear.
def home(self):
btn = QtGui.QPushButton("Log in", self)
self.show()
if btn.clicked:
btn.clicked.connect(btn.deleteLater)
self.Page()
else:
pass
def Page(self):
btn2 = QtGui.QPushButton("Exit", self)
self.show()
Sorry if the indenting isn't correct here, but it is in my python file:
So the btn does delete when it is pressed, but Page function isn't correctly being run because the btn2 doesn't appear.
This is only the relevant code snippet pasted.
TIA for help as to why the Page function isn't being run. I am using python 2,7 and pyqt4
It looks like btn variable is local inside home method. This means it is only visible within this method (unless it's defined outside in a higher-level scope.)
If you want to share a variable in multiple methods of the class, you should store it as an object property - that's why you'll need some OOP. For example (assuming the made-up rest of your class definition):
class YourApp(object):
def __init__(self):
# All the preparations should go here.
# If self.btn is created later dynamically,
# it's still recommended to declare it here
# and assign `None` to it
self.btn = QtGui.QPushButton("Log in", self)
def home(self):
# Do stuff with self.btn
self.btn.spam()
pass
def page(self):
# Do other stuff with self.btn
self.btn.eggs()
Goal of the script:
(3) different windows, each in its own class, with its own widgets and layout, are created via Toplevel and callbacks.
When a new (Toplevel) window is created, the previous one is destroyed. Thus, only one window is visible and active at a time.
Problem?
Basically, I've tried many things and failed, so I must understand too little of ["parent", "master", "root", "app", "..."] :(
Note on raising windows:
I have implemented a successful example of loading all frames on top of each other, and controlling their visibility via the .raise method.
For this problem, however, I don't want to load all the frames at once.
This is an abstracted version of a quiz program that will require quite a lot of frames with images, which makes me reluctant to load everything at once.
Script (not working; bugged):
#!/usr/bin/env python
from Tkinter import *
import tkMessageBox, tkFont, random, ttk
class First_Window(Frame):
"""The option menu which is shown at startup"""
def __init__(self, master):
Frame.__init__(self, master)
self.gotosecond = Button(text = "Start", command = self.goto_Second)
self.gotosecond.grid(row = 2, column = 3, sticky = W+E)
def goto_Second(self):
self.master.withdraw()
self.master.update_idletasks()
Second_Window = Toplevel(self)
class Second_Window(Toplevel):
"""The gamewindow with questions, timer and entrywidget"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.gotothird = Button(text = "gameover", command = self.goto_Third)
self.gotothird.grid(row = 2, column = 3, sticky = W+E)
def goto_Third(self):
Third_Window = Toplevel(self)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.master = First_Window
self.gotofirst = Button(text = "startover", command = self.goto_First)
self.gotofirst.grid(row = 2, column = 3, sticky = W+E)
def goto_First(self):
self.master.update()
self.master.deiconify()
self.destroy()
def main():
root = Tk()
root.title("Algebra game by PJK")
app = First_Window(root)
root.resizable(FALSE,FALSE)
app.mainloop()
main()
The problem is not really a Tkinter problem, but a basic problem with classes vs. instances. Actually, two similar but separate problems. You probably need to read through a tutorial on classes, like the one in the official Python tutorial.
First:
self.master = First_Window
First_Window is a class. You have an instance of that class (in the global variable named app), which represents the first window on the screen. You can call update and deiconify and so forth on that instance, because it represents that window. But First_Window itself isn't representing any particular window, it's just a class, a factory for creating instances that represent particular windows. So you can't call update or deiconify on the class.
What you probably want to do is pass the first window down through the chain of windows. (You could, alternatively, access the global, or do various other things, but this seems cleanest.) You're already trying to pass it to Second_Window, you just need to stash it and pass it again in the Second_Window (instead of passing self instance, which is useless—it's just a destroyed window object), and then stash it and use it in the Third_Window.
Second:
Second_Window = Toplevel(self)
Instead of creating an instance of the Second_Window class, you're just creating an instance of the generic Toplevel class, and giving it the local name Second_Window (which temporarily hides the class name… but since you never use that class, that doesn't really matter).
And you have the same problem when you try to create the third window.
So:
class First_Window(Frame):
# ...
def goto_Second(self):
# ...
second = Second_Window(self)
class Second_Window(Toplevel):
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_Third(self):
third = Third_Window(self.first)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_First(self):
self.first.update()
self.first.deiconify()
self.destroy()
This is one of those questions that is really frustrating for me to ask because I'm sure the answer is out there, I just haven't been able to word the searches correctly. Basically I am new to GUI programming (done a decent amount of embedded C/C++) and learning wxPython to start out.
I'm making an app to read and write to a config file. So I have a StaticText to display the name of the parameter to be read/written, a TextCtrl to display the value and allow user input, and then a Get button and a Set button. I'll call all of these together a "group" of widgets. For this app obviously this group will be repeated several times. Rather than write and maintain all that code by hand, I thought it would be easier to simply have a list of the config parameters I want to be able to edit, and then iterate through the list and generate an instance of this "group" of widgets for each item in the list. I made it work except for one thing: I had to bind all of the Get buttons to the same function. Same thing with the Set buttons. Is there any way from within those functions to know which Get or Set button was pushed, and thus which parameter to find and edit in the config file? I'm sure there's a way to do this with parent or IDs or something but I'm just too new to OOP.
I assume that the get button reads the parameter value from the config file and displays the value.
Why do you need one get button for each parameter? I would have just one get button for tham all. When the user clicks the get button, every parameter is read from the file and all the displays are updated.
A similar approach for the set button. One set button for them all - when the button is pressed every parameter that has a new value entered is updated in the config file. Parameters where the user has not entered a new value remain with their previous values.
This scheme is easier to code, and easier for the user also.
However, I really suggest you look at the wxPropertyGrid widget. This has the potential to make your life a lot easier! Here's a screenshot showing one in action
In your button's event handler, you can do something like this:
btn = event.GetEventObject()
btn.GetId()
btn.GetName()
Then you just use an If statement to decide what to do based on whatever info you want to use. Note: you can set the button's name when you create the button like this:
setBtn = wx.Button(self, label="Set", name="SetX")
You might find this article on wxPython and ConfigObj helpful too: http://www.blog.pythonlibrary.org/2010/01/17/configobj-wxpython-geek-happiness/
You can derive your own class from the wx.Button and add one or more attributes to it to make the button remember any information you want.
You can use this stored information during a callback function call. Something like:
import wx
L = [("1", "One"), ("2", "Two"), ("3", "Three")]
# =====================================================================
class MemoryButton(wx.Button):
def __init__(self, memory, *args, **kwargs):
wx.Button.__init__(self, *args, **kwargs)
self.memory = memory
# =====================================================================
class MainWindow(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.panel = wx.Panel(self)
self.buttons = []
for description in L:
button = MemoryButton(memory=description[1], parent=self.panel,
label=description[0])
button.Bind(wx.EVT_BUTTON, self.OnMemoryButton)
self.buttons.append(button)
self.sizer = wx.BoxSizer()
for button in self.buttons:
self.sizer.Add(button)
self.panel.SetSizerAndFit(self.sizer)
self.Show()
# -----------------------------------------------------------------
def OnMemoryButton(self, e):
print("Clicked '%s'" % e.GetEventObject().memory)
# =====================================================================
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()
or alternatively:
import wx
L = [("1", "One"), ("2", "Two"), ("3", "Three")]
# =====================================================================
class MemoryButton(wx.Button):
def __init__(self, memory, *args, **kwargs):
wx.Button.__init__(self, *args, **kwargs)
self.memory = memory
def OnButton(self, e):
print("Clicked '%s'" % self.memory)
# =====================================================================
class MainWindow(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.panel = wx.Panel(self)
self.buttons = []
for description in L:
button = MemoryButton(memory=description[1], parent=self.panel,
label=description[0])
button.Bind(wx.EVT_BUTTON, button.OnButton)
self.buttons.append(button)
self.sizer = wx.BoxSizer()
for button in self.buttons:
self.sizer.Add(button)
self.panel.SetSizerAndFit(self.sizer)
self.Show()
# =====================================================================
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()