How to delete QPushButtons from inside QtWidgets? - python

In PyQt5 I am dynamically adding QPushButtons, is there a way to delete them based on some label value.
I am dynamically adding buttons in the following manner:
for i in range(0, len(self.all_saved)):
self.button = QPushButton("X", self)
self.button.setStyleSheet("background-color: red")
self.button.resize(20, 20)
self.button.clicked.connect(lambda ch, i=i: self.future(i))
self.button.move(self.all_rect[i][0], self.all_rect[i][1])
self.button.show()
Once the user clicks the button 'X' it should delete itself, thats basically all I am trying to do here, as to why I cant use QVBoxLayout is because all the buttons would be placed on different x,y co ordinates please let me know if you have any suggestions?
I know we can do this easily with QVBoxLayout or QHBoxLayout but is there a way to do it directly on QtWidgets.QWidget

You just have to invoke the deleteLater() method that will delete the object and notify(using destroyed signal) the layout that the widget was deleted. Note: Don't use self.button as it is useless.
for i in range(0, len(self.all_saved)):
button = QPushButton("X", self)
button.setStyleSheet("background-color: red")
button.resize(20, 20)
button.clicked.connect(button.deleteLater)
button.move(self.all_rect[i][0], self.all_rect[i][1])
button.show()
If you want to delete certain buttons based on some condition then you must use the sender () method to get the button and call deleteLater:
button.clicked.connect(self.handle_clicked)
def handle_clicked(self):
button = self.sender()
if some_condition:
button.deleteLater()

Related

ttk.Combobox triggers <<ListboxSelect>> event in different tk.Listbox

What i am trying to do: I am trying to build a GUI using tkinter in Python (i am using version 3.7.7 on Windows) with a main window that has a Listbox and a second window that contains a Combobox.
In the Listbox there are different files that can be opened. When clicking on each entry some additional information are displayed in the main window (That part is not in my shorter example code below). So what i did was binding the Listbox to a function that loads and displays the information (function_1 in the code below) with the <<ListboxSelect>> event.
The second window can be opened with a button and should edit the selected file. In the Combobox in the second window are different options that change other elements of the second window depending on whats selected. Thats why i bind the <<ComboboxSelected>> event to a different function (function_2 in the code below).
My problem with this is: When i select a value in the Combobox of the second window, the first function that is binded to the Listbox of the first window is executed just after the correct function for the Combobox. This only happens the first time a value for the Combobox is selected, for each time the second window is created. Also when looking at the selected value of the Listbox it returns the selected value of the Combobox.
Edit: I just noticed this problem only happens when i previously selected an item in the Listbox. So like i said below it might have something to do that the Listbox is still active or something like that.
My code and an example:
import tkinter as tk
from tkinter import ttk
class MainGUI:
def __init__(self):
self.root = tk.Tk()
items = ['Test 1', 'Test 2', 'Test 3']
self.item_box = tk.Listbox(self.root)
self.item_box.insert('end', *items)
self.item_box.bind('<<ListboxSelect>>', self.function_1)
self.item_box.pack()
tk.Button(self.root, text='Open second window',
command=self.open_window).pack()
self.root.mainloop()
def function_1(self, event):
print('MainGUI function:', event)
print('Text in Listbox:', self.item_box.selection_get())
def open_window(self):
SecondGUI()
class SecondGUI:
def __init__(self):
self.window = tk.Toplevel()
self.window.grab_set()
items = ['A', 'B', 'C']
self.dropdown = ttk.Combobox(self.window, values=items)
self.dropdown.current(0)
self.dropdown.pack()
self.dropdown.bind('<<ComboboxSelected>>', self.function_2)
def function_2(self, event):
print('SecondGUI function:', event)
MainGUI()
Image of the GUI before and after the option B is clicked
The output after selecting Test 1, opening the second window with the button and selecting B looks like this:
MainGUI function: <VirtualEvent event x=0 y=0>
Text in Listbox: Test 1
SecondGUI function: <VirtualEvent event x=0 y=0>
MainGUI function: <VirtualEvent event x=0 y=0>
Text in Listbox: B
My guess: If i had to guess what the problem is, i would say the Combobox maybe had some sort of Listbox in it that sends the event, but as i wasn't able to find more about when and how these events are sent, i couldn't really find anything about it (Edit: Less likely). When looking at the picture, you can see that the entry in the first window is still selected until the entry in the second window is clicked, so my second guess would be that the Listbox is still active and takes the new selected value when it is clicked or something like that (Edit: more likely).
My question: Why does the first function execute when i use a different widget in a different window, how can i fix this, or are there any better way to do this in the first place?
TL;DR: A Listbox recieves an event when a Combobox is selected in a different window. Why?
Thanks in advance!
It may be due to that exportselection=False is not set for both the Listbox and Combobox. So when an item in the Combobox is selected, it will clear the selection of Listbox which triggers the <<ListboxSelect>> event.
Also you have used wrong function, self.item_box.selection_get(), to get the current selected item of Listbox. self.item_box.get(self.item_box.curselection()) should be used instead.
Below changes should fix the issue:
class MainGUI:
def __init__(self):
...
self.item_box = tk.Listbox(self.root, exportselection=False)
...
def function_1(self, event):
print('MainGUI function:', event)
print('Text in Listbox:', self.item_box.get(self.item_box.curselection()))
...
class SecondGUI:
def __init__(self):
...
self.dropdown = ttk.Combobox(self.window, values=items, exportselection=False)
...
...

Checkbutton responds to clicks on text

By default, tkinter's Checkbutton widget responds to clicks anywhere in the widget, rather than just in the check box field.
For example, consider the following (Python 2) code:
import Tkinter as tk
main = tk.Tk()
check = tk.Checkbutton(main, text="Click").pack()
main.mainloop()
Running this code results in a small window, with a single check box, accompanied by the word "Click". If you click on the check box, it is toggled.
However, this also happens if you click on the text of the widget, rather than the check box.
Is this preventable? I could make a separate widget to hold the text, but that still results in a small area around the check box that responds to clicks.
Two solutions come to mind:
Do as you suggest and create a separate widget for the checkbutton and for the label.
replace the bindings on the checkbutton with your own, and examine the x/y coordinates of the click and only accept the click if it happens in a small region of the widget.
This program creates a checkbutton and overrides the default event on it by binding a method to the checkbutton. When the button is clicked, the method checks a defined limit to allow the normal operation or to override. OP wanted to make sure that when text of the checkbutton is clicked, no default action is taken. That is essentially what this does.
import tkinter as tk
import tkinter.ttk as ttk
class App(tk.Frame):
def __init__(self, parent, *args, **kwargs):
self.checkvar = IntVar()
check = tk.Checkbutton(parent, text='Click', variable=self.checkvar)
check.bind('<Button-1>', self.checkCheck)
check.pack()
print(dir(check))
def checkCheck(self, event):
# Set limit based on your font and preference
limit = 18
print(event.x, event.y, self.checkvar.get())
if event.x > limit or event.y > limit:
self.checkvar.set(not self.checkvar.get())
else:
print("Normal behavior")
if __name__ == "__main__":
window = tk.Tk()
app = App(window)
window.mainloop()

QMainWindow not shown if functools.partial not used

I show Second QMainWindow after click on button in parent QMainWindow
def on_click(self):
window = second_window.MainWindow()
window.show()
Second window not shown (Without any error). But if in Second window I add line:
self.func = functools.partial(self.some_func)
All work correct.
Why it's happens?
I think the problem here is that you are creating the window as a local variable inside the on_click scope. As soon as on_click finishes the window attribute will be destroyed.
Try storing the window in an instance variable:
def on_click(self):
self._window = second_window.MainWindow()
self._window.show()
The functools.partial approach is probably working just because you are already storing it at the instance.

PyQt QHBoxLayout inside QPushButton text is being truncated

I'm trying to place a checkbox and some text inside a button, but I'm having trouble getting the button to expand wide enough to see the full text.
self.check = QtGui.QCheckBox("long text", self)
self.checkLayout = QtGui.QHBoxLayout()
self.checkLayout.addWidget(self.check)
self.checkButton = QtGui.QPushButton(None, self)
self.checkButton.setLayout(self.checkLayout)
I've tried various combinations of adding stretches, setting the size policy, setting margins and styles etc. but haven't had any luck so far.
Thanks
Add a layout to the button. Place the checkbox inside the layout. Looks like this is the only way for QWidget to automatically track a size of its subwidgets.
But QWidget doesn't automatically resize itself to fit its content. You must invoke adjustSize function to do it.
class TestWidget(QtGui.QWidget):
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
self.check = QtGui.QCheckBox("very very long text")
self.checkLayout = QtGui.QHBoxLayout()
self.checkLayout.addWidget(self.check)
self.checkButton = QtGui.QPushButton()
self.checkButton.setLayout(self.checkLayout)
self.checkButton.adjustSize()
self.checkButton.setParent(self)
UPD: There is a problem with adjustSize. This function doesn't work for me if I call it after adding checkButton to the screen:
# works
self.checkButton.adjustSize()
self.checkButton.setParent(self)
# doesn't work
self.checkButton.setParent(self)
self.checkButton.adjustSize()
My solution is manual resizing instead of calling adjustSize:
self.checkButton.setFixedSize(self.checkLayout.sizeHint())

How to disable selecting text in a Python/Tk program?

How would that be done?
I have been unable to find it on here or with Google.
#Refrences
from tkinter import *
class Interface:
def __init__(self,stage):
topLeft = Frame(stage,bg='black')
test = Entry(topLeft,bg='#2a2a2a',fg='white',insertontime=0)
test.config(insertbackground='white', exportselection=0)
test.grid()
topLeft.grid(row=0,column=0)
def launch():
window = Tk()
lobby = Interface(window)
window.mainloop()
launch()
I assume you wish for users to not be able to edit the Entry box? If so, you must use the config parameter of (state="disabled"), eg.
test.config(insertbackground='white', exportselection=0, state="disabled")
be wary though, I could not find a way to keep the background of your entry box black. Hope this helps
You can set the text highlight color to match the background of entry widget. Note that the text in the widget can still be selected but the user will not see it visually which will give the illusion that selection is disabled.
test = Entry(topLeft,bg='#2a2a2a',fg='white',insertontime=0)
test.configure(selectbackground=test.cget('bg'), inactiveselectbackground=test.cget('bg'))
When we select a text we tkinter trigger(fire) 3 events, which are ButtonPress, Motion and ButtonRelease all these 3 events call a event handler fucntion.
The function run select_range(start, end) method which highlight selected text.
To disable this we have to hanlde ButtonPress and Motion events together and call select_clear method on the widget.
If you handle the events and call select_clear method it work but not properly, the text will be highlighted and when another event occured highligh color will be cleared.
This happend because of execution order of events. That's mean you have to tell to tk handle your event after default event handler. We can change order of events execution with bindtags and bin_class methods
example:
from tkinter import *
from tkinter import ttk
def on_select(event):
event.widget.select_clear() # clear selected text.
root = Tk()
name_entry = ttk.Entry(root)
name_entry.pack()
# with PostEvent tag, on_select the event hanlde after default event handler
name_entry.bindtags((str(name_entry), "TEntry", "PostEvent", ".", "all"))
name_entry.bind_class("PostEvent", "<ButtonPress-1><Motion>", on_select)
root.mainloop()
A useful solution to Tkinter not having tons of built-in widgets (like, say, JavaFX does), is that you can easily make your own if you don't mind them being not-quite what you wanted :) Here's a rough-around-the-edges example of using a canvas to emulate an entry field that can't be selected. All I've given is the insert functionality (and delete too, I suppose, if you were clever about it), but you may want more. (Another plus is that, because it's a canvas item, you can do nifty formatting with it).
Am I right that by a not-selectable entry widget, you mean a canvas with text written on it? If you want to disable text-highlighting in ALL widgets, including the top-level frame, you could highjack the Button-1 event, deselect everything, and then consume the event whenever text is selected.
from tkinter import *
class NewEntry(Canvas):
def __init__( self, master, width, height ):
Canvas.__init__( self, master, width=width,height=height )
self.width = width
self.height = height
self.text = ''
def insert( self, index, text ):
self.text =''.join([self.text[i] for i in range(index)] + [text] + [self.text[i] for i in range(index,len(self.text))])
self.delete(ALL)
self.create_text(self.width//2,self.height//2,text=self.text)
root = Tk()
entry = NewEntry(root,100,100)
entry.insert(0,'hello world')
entry.insert(5,'world')
entry.pack()
root.mainloop()

Categories

Resources