Best way to replace Window in Toga (Beeware) - python

I'm currently trying to create a cross platform app with Beeware using Toga.
I konw how to update the content in a window (I just empty everything in the box and add new content to it).
But now I have the problem that I want to add entry fields which need to be assigned to a variable so I can get the value of them (And my class for the first window is already very big... so I don't want to add new properties and methods too it). So my intention was to create a new class besides my other class which displays my new window and closes the old one (or just replaces my old one)
E. g.:
import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW
class FirstWindow(toga.App):
def startup(self):
main_box = toga.Box(style=Pack(direction=COLUMN))
main_box.add(toga.Button('Open window 2', on_press=self.open_new_window))
self.main_window = toga.MainWindow(title=self.formal_name)
self.main_window.content = main_box
self.main_window.show()
def open_new_window(self, widget):
# This should close the current Window and display the other window
# return SecodnWindow() doesn't work
# close() and exit() doesn't work too, cause I can't execute code after this
# statements anymore
class SecondWindow(toga.App):
def startup(self):
main_box = toga.Box(style=Pack(direction=COLUMN))
#adding all the stuff to the window
self.main_window = toga.MainWindow(title=self.formal_name)
self.main_window.content = main_box
self.main_window.show()
# Other code
def main():
return FirstWindow()
Thanks ^^

I found a solution:
import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW
class MultiWindow(toga.App):
def startup(self):
main_box = toga.Box()
main_box.add(toga.Button('Open Window', on_press=self.show_second_window))
self.main_window = toga.Window(title=self.formal_name, closeable=True)
self.main_window.content = main_box
self.main_window.show()
def show_second_window(self, widget):
outer_box = toga.Box()
self.second_window = toga.Window(title='Second window')
self.windows.add(self.second_window)
self.second_window.content = outer_box
self.second_window.show()
self.main_window.close()
def main():
return MultiWindow()
Sadly it's all in one class but I will try to improve it
Note: In this solution it is not possible to have a main window. As soon as you close the main window the app will close. When you just want to have a second window without closing the main window you can still use a main window.
If I find better solutions I will update this gist:
https://gist.github.com/yelluw/0acee8e651a898f5eb46d8d2a577578c

Related

Tkinter: How do I return the text within an Entry widget to a variable upon a Button press

This is what I tried
GUI.py
from tkinter import *
class Search:
def __init__(self, root):
self.query = None
self.root = root
# Create and draw container
self.frame = Frame(self.root)
self.frame.pack()
# Create widgets
self.search_bar = Entry(self.frame)
self.search_button = Button(self.frame, command = self.get_query, text = 'Search')
# Draw widgets
self.search_bar.pack()
self.search_button.pack()
def get_query(self):
self.query = self.search_bar.get()
def create_gui_root(): # Should I be doing this in a function in GUI.py?
root = Tk()
root.geometry('300x300')
return root
main.py
from GUI import *
def use_query_to_do_stuff(query):
print(query)
def main():
# Create GUI root instance
root = create_gui_root()
# Initialize search page
search_page = Search(root)
# I know this part is wrong, just not sure where to go from here
# It runs the search_submit method on start instead of waiting for the button to be pressed
query = search_page.get_query()
use_query_to_do_stuff(query)
root.mainloop() # Also is this in the right place?
main()
I want the program to wait until I press the button, then assign the text inside the Entry widget to a variable.
I started off with a fully working text-based program in main.py and I'm trying to add a GUI.
I'm trying to learn how to split the logic and GUI into separate Python files so I import the Search class into the main.py file and I need GUI.py to return the search query back to main.py when the button is pressed.
I'm also unsure if I'm going about using classes and functions and separating logic from GUI the right way. Should I be using a bunch of functions instead of a class? Or am I using the class wrong?
Are the root = Tk() and root.mainloop() lines even in the right place?
I thought of importing main.py into GUI.py, creating a method in Search() to call the function use_query_to_do_stuff() from main.py while passing self.search_bar.get() as a parameter directly, then binding that method to the self.search_button command. However, I feel like cross-importing your main.py file into your GUI.py is counter-intuitive, I kind of feel like all roads should 'lead back' to the main.py file, if that makes sense. Then again, maybe I'm completely lost.
I made main.py first and it ran as intended, but now that I'm trying to implement a GUI I feel like I'm going about everything slightly incorrectly.
Add Label in GUI.
Add label.configure in get_query(self) function
.
Snippet code:
# Create widgets
self.search_bar = Entry(self.frame)
self.search_button = Button(self.frame, command = self.get_query, text = 'Search')
self.label = Label(self.frame)
# Draw widgets
self.search_bar.pack()
self.search_button.pack()
self.label.pack(padx=15, pady=35)
def get_query(self):
self.query = self.search_bar.get()
self.label.configure(text=self.query)
self.search_bar.delete(0, END)
print(self.query)
Screenshot before:
Screenshot after:

Is there a way to reopen a window after closing it using destroy() in tkinter?

window6.after(1,lambda:window6.destroy())
is what I've been using to close my windows, is there any way to get them back after doing this?
basically, is there something that is the opposite of this?
ps. these are the libraries that I've imported, if it helps in any way
import tkinter as tk
from tkinter import *
import time
from tkinter import ttk
Is there a way to reopen a window after closing it using destroy() in tkinter?
The short answer is "no". Once it has been destroyed, it is impossible to get back. You should either create the window via a frame or class so that it's easy to recreate, or hide the window by calling .withdraw() rather than .destroy().
If you put the window code into a class or a function then after destroying it you can create a new instance of it by
1: creating a new instance of the class with the window code in the init function
2: call the function the has the code for the window
By doing this you are essentially creating a new instance of the program, but without initiating the script.
from tkinter impot *
from tkinter import ttk
#creating window function, not class
def main_window():
#window code here
root = Tk()
Label(root, text = "Hello World").pack()
#destroying main window
root.destroy()
root.mainloop()
main_window()
Of course, there are a few hurdles such as the window shutting down as soon as it opens, but this is to show that you can create a new instance of a window from your program.
You can wait for user input to see whether or not the window will open or close.
If you took an OOP approach, you can pass a reference to the Parent Widget as argument to the New_Window and store it in a class attribute.
You´ll have a two way reference: Parent knows child and child knows parent.
Then you can set the Parent Reference to the New_Window to None, from within the child Widget self.parent.new_window = None in a close_me() method right after you call self.destroy() on the New_Window:
1st Bonus: this code prevents the opening of more than 1 instance of a Window at a time. You won´t get more than 1 New_Window on the screen. I don´t think having two loggin windows opened or two equal options window makes sense.
2nd Bonus: It is possible to close the window from other parts of the code, as in a MVC patter, the Controller can close the window after doing some processing.
Here´s a working example:
import tkinter as tk
class Toolbar(tk.Frame):
'''Toolbar '''
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
# to store the New Window reference
self.new_window = None
self.button_new_window = tk.Button(self, text = 'New Window', command = lambda : self.get_window(self))
self.configure_grid()
def configure_grid(self):
'''Configures the Grid layout'''
self.grid(row=1, column=0, columnspan=3, sticky=(tk.N,tk.S,tk.E,tk.W))
self.button_new_window.grid(row = 2, column = 2, padx=5, pady=5)
def get_window(self, parent):
''' If window exists, return it, else, create it'''
self.new_window = self.new_window if self.new_window else Window(parent)
return self.new_window
class Window(tk.Toplevel):
'''Opens a new Window.
#param parent -- tk.Widget that opens/reference this window
'''
def __init__ (self, parent : tk.Widget):
# Stores reference to the Parent Widget, so you can set parent.new_window = None
self.parent = parent
super().__init__(master = parent.master)
self.title('New Window')
self.button_dummy = tk.Button(self, text = 'Do the thing', width = 25, command = lambda : print("Button pressed on window!"))
self.button_close = tk.Button(self, text = 'Close', width = 25, command = self.close_me)
self.configure_grid()
def configure_grid(self):
'''Grid'''
self.button_dummy.grid(row = 1, column = 0)
self.button_close.grid(row = 2, column = 0)
def close_me(self):
'''Tkinter widgets are made of two parts. 1. The python Object and 2. The GUI Widget.
The destroy() method gets rid of the widget part, but leaves the object in memory.
To also destroy the object, you need to set all of its references count to ZERO on
the Parent Widget that created the new Window, so the Garbage Collector can collect it.
'''
# Destroys the Widget
self.destroy()
# Decreasses the reference count on the Parent Widget so the Garbage Collector can destroy the python object
self.parent.new_window = None
if __name__ == '__main__':
root = tk.Tk()
toolbar = Toolbar(root)
root.mainloop()
I don´t know if this: .destroy() and re-instantiate approach is more efficient than the .withdraw() and .deiconify(). Maybe if you have a program that runs for long periods of time and opens a lot of windows it can be handy to avoid stackoverflow or heapoverflow.
It sure frees up the object reference from memory, but it has the additional cost of the re-instantiation, and that is processing time.
But as David J. Malan would say on CS50, “There´s always a tradeoff”.

Clearing QTableView in PyQt5

I am working with PyQt5 and I am trying to clear a QTreeView by pressing a button. The program is supposed to take a path from the user with a QLineEdit and then display it on the TreeView. So far it's working great. The thing is that I can't figure out how to clear the view once I'm finished or maybe if I typed in the wrong path. I know that I could just use the clear() function but it works only with a Widget, not with a View. If I use the reset() function it just displays the "This PC" folder without completey clearing the Tree.
I am going to include the code just in case.
from PyQt5 import QtWidgets as qtw
import sys
class MainWindow(qtw.QWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Init UI
self.tree = qtw.QTreeView()
self.model = qtw.QFileSystemModel()
# Items
self.path_input = qtw.QLineEdit()
path_label = qtw.QLabel("Enter a path to begin: ")
check_btn = qtw.QPushButton("Check") # To display the items
clear_btn = qtw.QPushButton("Clear") # To clear the TreeView
start_btn = qtw.QPushButton("Start") # Not implemented yet
# Layouts
top_h_layout = qtw.QHBoxLayout()
top_h_layout.addWidget(path_label)
top_h_layout.addWidget(self.path_input)
top_h_layout.addWidget(check_btn)
bot_h_layout = qtw.QHBoxLayout()
bot_h_layout.addWidget(clear_btn)
bot_h_layout.addWidget(start_btn)
main_v_layout = qtw.QVBoxLayout()
main_v_layout.addLayout(top_h_layout)
main_v_layout.addWidget(self.tree)
main_v_layout.addLayout(bot_h_layout)
self.setLayout(main_v_layout)
check_btn.clicked.connect(self.init_model)
clear_btn.clicked.connect(self.clear_model)
self.show()
def init_model(self):
self.model.setRootPath(self.path_input.text())
self.tree.setModel(self.model)
self.tree.setRootIndex(self.model.index(self.path_input.text()))
def clear_model(self):
self.tree.reset() # This is where I get the problem
if __name__ == "__main__":
app = qtw.QApplication(sys.argv)
w = MainWindow()
sys.exit(app.exec_())
You can't "clear a view", as the view only shows the model's content, it cannot directly modify (or "clear") it. Also, reset() does not do what you're trying to achieve:
void QAbstractItemView::reset()
Reset the internal state of the view.
The state, not the model. As you can see, in your case it only resets the root node in its collapsed state.
What you need to clear is the model, but since you clearly cannot clear a filesystem model, the solution is to set the view's state as it was before starting the search: by removing the model from the view.
def clear_model(self):
self.tree.setModel(None)

Tkinter - My custom icon "disturbs" the Top Level window when it opens

when I opened my Top Level window, I always saw an annoying and weird behaviour.. at the end I realized that it was because of my custom icon.
below an exaple code:
from tkinter import *
from tkinter import ttk
class MainWindow:
def __init__(self):
self.parent=Tk()
self.parent.geometry("494x410+370+100")
self.parent.title("My Software - WITH ICON")
self.parent.iconbitmap("icon.ico")
Button = ttk.Button(self.parent, text="open a new widnow", command=self.OpenNewWindow)
Button.place(x=16, y=16)
def OpenNewWindow(self):
self.obj = NewWindow(self)
class NewWindow:
def __init__(self, mw):
self.window, self.mw = Toplevel(mw.parent), mw
self.window.geometry("200x150+360+200")
self.window.title("New Window")
self.window.iconbitmap("icon.ico") # it creates the issue..
self.window.protocol("WM_DELETE_WINDOW", self.on_close)
self.window.focus()
self.mw.parent.attributes('-disabled', 1)
self.window.transient(mw.parent)
self.window.grab_set()
self.mw.parent.wait_window(self.window)
def on_close(self):
self.mw.parent.attributes('-disabled', 0)
self.window.destroy()
def main():
app=MainWindow()
app.parent.mainloop()
if __name__=="__main__":
main()
to make it clear where is the issue, I create a GIF:
here we have two softwares, "without icon.py" and "with icon.py". they are the same, but the first one doesn't use my custom icon for his second window.
as you can see, if I run "with icon.py" the second window will be always affected by something when I will open it, but for "without icon.py" this "something" doesn't exist.
what is the issue? the software opens his second window, focus the root one (it's the issue), and then focus the second window again. you can see it clearly from the GIF.
how can I solve the issue? and why with the default icon this weird behaviour doesn't happen?

Qt - keep reference of widget in Python

In reference to a previous question, I need some help with keeping references in my application.
First a snippet from my code.
from PyQt4 import QtGui
import os, os.path
import sys
class mainWindowHandler():
equationEditor = []
_listview = None
_window = None
def __init__(self):
return
def showAddEquation(self):
"""Creates a new instance of the dynamic editor for adding an equation"""
#create a horizontal split layout
window = QtGui.QWidget()
layout = QtGui.QHBoxLayout()
current = len(self.equationEditor) - 1
de = QtGui.QPushButton(str(current))
self.equationEditor.append(de)
de.clicked.connect(self.clicked)
#fill list view with items from equation collection
listview = QtGui.QListWidget()
for equation in self.equationEditor:
item = QtGui.QListWidgetItem()
item.setText(equation.text())
listview.addItem(item)
layout.addWidget(listview)
layout.addWidget(de)
window.setWindowTitle("Equation {}".format(str(current))
window.setLayout(layout)
self._window = window
self._listview = listview
window.show()
return window
def clicked(self):
"""Method for handling the button events in the solver settings\n
signal = the button hit\n"""
return self.showAddEquation()
if __name__ == "__main__":
path = os.path.dirname(os.path.abspath(__file__))
app = QtGui.QApplication(sys.argv)
ewh = mainWindowHandler()
window = ewh.showAddEquation()
sys.exit(app.exec_())
The application will (later) create a window that allows the manipulation of certain settings - in my code example represented by the QPushButton. These settings are later written to a txt-file, but until then I save them in form of there widget. I simply add the widget to a collection and recall them from there. That works well on the Python-level.
Now, I have a button that creates a new instance of the window from inside of the window itself. That works too. But only until the third instance. At that point I loose the reference to my QPushButton on the Qt-level. I get the
wrapped C/C++ object of type `QPushButton` has been deleted
error when trying to retrieve the buttons from my collection (equationEditor). In Python they are still there, but obviously the corresponding Qt-objects where destroyed because I somewhere mishandled the references.
Can someone point out a better solution or how I can keep the references?
Thanks...
Edit:
As there seem to be some confusions I will try to explain the functionality a bit more in detail.
The program starts and creates a window "Equation 1" with a QListViewand a QPushButton "1". In the list view all available QPushButton are listed (at start only 1 item). In my actual program the QPushButton is QWidget with some text fields and the QPushButton.
If the user clicks "1" then the button "1" should disappear and a new instance of QPushButton named "2" should appear at the position of "1". Additionally, the listview should now hold two items "1" and "2" and the window should have the title "Equation 2". If it is a new window or the same as before with new contents is not relevant. Both variants would be okay. The former is the way it is implemented at the moment. Visible should by only one window at a time.
All instances of QPushButton should by collected in a small list (called equationEditor) to keep them in the memory. In my actual program this is used for saving all changes made in the widgets without writing the changes to a temp file.
Later, if the user selects item "1" in the QListView then the current visible QPushButton should be replaced by the QPushButton "1" (from the collection equationEditor) or if he selects the second item the QPushButton "2" should be shown.
Why?
The widget that will be used later contains a lot of editable data. As the user could edit that at any time it is easier to keep the widgets in memory without showing them instead of repopulating all the data again. As soon as the user selects one in the QListView the corresponding widget should be shown in the window so that he can edited the data in the widget again.
It's quite hard to understand what exactly you're trying to do. Looking at your code I wonder why it is even working twice before failing.
Btw. I just saw, that there is a quite accurate description of why it's failing in the previous post, given by Schollii
Anyways, I think you should make a new class for the equation window. The main class can then keep track of all opened windows in the equationEditor list. It can also add the values of the other opened windows to a new one, once created.
Here is how it would look like
from PyQt4 import QtGui
import os, os.path
import sys
class ShowAddEquation(QtGui.QWidget):
"""Creates a new instance of the dynamic editor for adding an equation"""
def __init__(self,parent=None):
super(ShowAddEquation, self).__init__(parent=parent)
#create a horizontal split layout
layout = QtGui.QHBoxLayout()
self.current = 0
self.de = QtGui.QPushButton(str(self.current))
self.listview = QtGui.QListWidget()
layout.addWidget(self.listview)
layout.addWidget(self.de)
self.setWindowTitle("Equation Editor")
self.setLayout(layout)
self.show()
def setCurrent(self, current):
self.current=current
self.de.setText(str(self.current))
class mainWindowHandler():
equationEditor = []
def __init__(self):
return
def clicked(self):
se = ShowAddEquation()
self.equationEditor.append(se)
se.de.clicked.connect(self.clicked)
current = len(self.equationEditor) - 1
se.setCurrent(current)
for equation in self.equationEditor:
item = QtGui.QListWidgetItem()
item.setText(str(equation.current))
se.listview.addItem(item)
if __name__ == "__main__":
path = os.path.dirname(os.path.abspath(__file__))
app = QtGui.QApplication(sys.argv)
ewh = mainWindowHandler()
ewh.clicked()
sys.exit(app.exec_())
So, after understanding the approach given in the first answer I have solved my problem. Here is the working code
# -*- coding: utf-8 -*-
"""
Created on Sat Sep 3 14:31:15 2016
"""
from PyQt4 import QtGui
from PyQt4 import QtCore
import os, os.path
import sys
class mainWindowHandler():
equationEditor = []
_listview = None
_window = None
def __init__(self):
return
def showAddEquation(self):
"""Creates a new instance of the dynamic editor for adding an equation"""
#create a horizontal split layout
self._window = QtGui.QWidget()
layout = QtGui.QHBoxLayout()
self._listview = QtGui.QListWidget()
layout.addWidget(self._listview)
self._listview.clicked[QtCore.QModelIndex].connect(self.changed)
self._window.setLayout(layout)
#populate the right side of the layout with a button
self.clicked()
self._window.show()
return self._window
def clicked(self):
"""Make a new button instance and add it to the window and the collection"""
window = self._window
layout = window.layout()
current = len(self.equationEditor) - 1
de = QtGui.QPushButton(str(current))
self.equationEditor.append(de)
de.clicked.connect(self.clicked)
#close the currently shown button
item = layout.takeAt(1)
if item is not None:
item.widget().close()
layout.addWidget(de)
#fill list view with items from material collection
item = QtGui.QListWidgetItem()
item.setText(de.text())
self._listview.addItem(item)
self._window.setWindowTitle("Equation Editor {}".format(str(current)))
def changed(self, index):
"""hide the object on the right side of the layout and show the button at position index in the collection"""
layout = self._window.layout()
item = layout.takeAt(1)
item.widget().close()
# insert the selected button from the collection
de = self.equationEditor[index.row()]
layout.insertWidget(1, de)
self._window.setWindowTitle("Equation Editor {}".format(str(index.row() - 1)))
de.show()
if __name__ == "__main__":
path = os.path.dirname(os.path.abspath(__file__))
app = QtGui.QApplication(sys.argv)
ewh = mainWindowHandler()
window = ewh.showAddEquation()
sys.exit(app.exec_())

Categories