PyQt5 connect Enter key to Widget - python

Currently I am working with my own custom widget that consists of a QLineEdit and a QPushButton. A user is limited to entering an integer between 0 and 1000 in the text field. Upon clicking the button, the widget's custom clicked signal emits the value in the field.
Here is the code:
class ResizeWidget(QWidget):
clicked = pyqtSignal(int)
def __init__(self):
super().__init__()
#NumField and Button
self.field = QLineEdit()
self.field.setPlaceholderText("Resize Percentage [0,1000]")
self.resizeButton = QPushButton("Resize")
#Layout
self.lay = QHBoxLayout()
self.setLayout(self.lay)
#Add to Widget
self.lay.addWidget(self.field)
self.lay.addWidget(self.resizeButton)
#Field limits
self.field.setMaxLength(4)
self.field.setValidator(QIntValidator(0,1000))
#Connection
self.resizeButton.clicked.connect(self.onClick)
#pyqtSlot()
def onClick(self):
val = int(self.field.text())
self.clicked.emit(val)
Now what I'd like to add to the class is some way of allowing the user to press enter when the blinking cursor | sometimes called a 'caret' is in the text field.
I am able to find documentation on the mouse in general, mouseEvent and mousePressEvent as a method within QWidgets. But I can't find any documentation that refers to the blinking cursor within the text field.
I would like to add some sort of pseudocode like this within init():
if(cursor == inQLineEdit and pressedEnter):
self.onClick()
I know QLineEdit::returnPressed plays a major role in creating the correct function but I only want the enter key to be valid if the user is using the ResizeWidget. Not some other part of my GUI. I would think the enter key isn't binded to only 1 widget in my entire application but I'd be interested to find out.

It was as simple as adding the following line:
self.field.returnPressed.connect(self.onClick)
As long as the caret (blinking cursor) isn't in the text field, pressing the Enter key doesn't cause any reaction from my custom widget.

Related

setChecked radiobutton of another group pyqt

I have 2 radiobuttons created (inside a QMainWindow class) like:
def dtype_radiobuttons(self):
layout = QHBoxLayout()
rb1 = QRadioButton("complex")
rb1.toggled.connect(lambda: self.update_image("dtype", rb1.text()))
self.real_dtype_rb = QRadioButton("real", self)
self.real_dtype_rb.toggled.connect(lambda: self.update_image("dtype", self.real_dtype_rb.text()))
self.btngroup.append(QButtonGroup())
self.btngroup[-1].addButton(self.real_dtype_rb)
self.btngroup[-1].addButton(rb1)
rb1.setChecked(True)
layout.addWidget(rb1)
layout.addWidget(self.real_dtype_rb)
layout.addStretch()
return layout
def library_radiobutton(self):
layout = QHBoxLayout()
self.cvnn_library_rb = QRadioButton("cvnn", self)
self.cvnn_library_rb.toggled.connect(lambda: self.update_image("library", self.cvnn_library_rb.text()))
rb2 = QRadioButton("tensorflow", self)
rb2.toggled.connect(lambda: self.update_image("library", rb2.text()))
self.btngroup.append(QButtonGroup())
self.btngroup[-1].addButton(rb2)
self.btngroup[-1].addButton(self.cvnn_library_rb)
self.cvnn_library_rb.setChecked(True)
layout.addWidget(self.cvnn_library_rb)
layout.addWidget(rb2)
layout.addStretch()
return layout
I want to make it impossible to select the complex option of the dtype radiobuttons group and tensorflow radiobutton of the library radiobuttons. Leaving 3 out of the 4 possible combinations. So if I select complex and library was tensorflow, I want to automatically change the library to cvnn. I tried to implement it like this:
def update_image(self, key, value):
if value == "complex":
if hasattr(self, 'cvnn_library_rb'): # It wont exists if I still didnt create the radiobutton.
self.cvnn_library_rb.setChecked(True) # Set library cvnn
elif value == "tensorflow":
if hasattr(self, 'real_dtype_rb'):
self.real_dtype_rb.setChecked(True) # Set real dtype
... Do the other stuff I need to do.
The weird thing is that it actually works in the sense that, for example, if I am on complex activated and select tensorflow, the radiobutton changes to real (what I want!) but tensorflow does not get selected! I need to select it again as if making self.real_dtype_rb.setChecked(True) cancels the selection of the radiobutton I clicked on. (Very weird if you ask me).
The hasattr is used because depending on the order I call the
functions, there are some radiobuttons that will be created before
the other, so it might not exist.
This
is an option I am considering but it's disabling the radiobutton
group instead of changing their state (not what I prefer).
The signal toggled is triggered whenever you change the state of your radio buttons. So, it will be triggered when you call setChecked (once for the radio button you toggle and once for the other you untoggle) and update_image is called is the wrong case.
You have to check the state of the radio button and call update_image only if the radio button is toggled:
rb2.toggled.connect(lambda state: state and self.update_image("library", rb2.text(), state))

How to only call functions once if user presses it multiple times

I am currently working on a program which has a simply GUI and 3 buttons. One of them is start, which runs my code. I am trying to make it as user friendly as possible, so if they spam click it it only runs once. Currently, if the user presses the button multiple times, then the images update multiple times a second.
Any help would be appreciated.
One of the most common OOP practices is to view everything in your GUI as a component, a class, which handles its own variables, a component state, which you initialize when you first render the component.
For example, if we want to monitor when a button is clicked, we could disable the button afterwards by updating the component state.
class Button:
"""
Button that self monitors how many clicks have been performed
"""
def __init__(self,):
#Initialize state
self.disabled = False
self.button_clicked = 0
def click(self,):
if not self.disabled: #Only perform action if self.disabled == False
self.disabled = True
self.button_clicked += 1
#Do stuff on click
class ButtonContainer:
"""
Container for a group of associated Buttons
"""
def __init__(self,):
self.button1 = Button()
self.button2 = Button()
self.button3 = Button()
class App:
def __init__(self,):
#Add components to your app
self.button_container = ButtonContainer()
The reason we may want to use a container is because we can group buttons together and monitor then from our main App class.
For example, within the App class we can call
print(self.button_container.button1.disabled)
With this we can see component states outside of the actual component.
use an if-else statement
pressed = 0
Then pay attention to the times it was clicked and use
if pressed == 3:
# do this
else:
# do something different

When QComboBox is set editable

The code below creates QComboBox and QPushButton both assigned to the same layout. Combobox is set to be editable so the user is able to type a new combobox item's value.
If the user hits Tab keyboard key (instead of Enter) the New Value will not be added to the ComboBox.
Question: How to make sure the ComboBox's items are updated with the New Value even if the user leaves the ComboBox with Tab key?
from PyQt4 import QtGui
def comboActivated(arg=None):
print '\n ...comboActivated: %s'%arg
widget = QtGui.QWidget()
layout = QtGui.QVBoxLayout()
widget.setLayout(layout)
combo = QtGui.QComboBox()
combo.setEditable(True)
combo.addItems(['One','Two','Three'])
combo.activated.connect(comboActivated)
layout.addWidget(combo)
layout.addWidget(QtGui.QPushButton('Push'))
widget.show()
When a user edits the text in the box, the editTextChanged() signal is emitted with the edited text as its argument. In addition, when the widget itself loses focus, as when the user types Tab to move to the button, the widget emits the focusOutEvent() signal. The argument for this signal is a QFocusEvent, which you can query for the reason focus was lost. The reason() method of the event would return Qt.TabFocusReason, for example, if the user hit the Tab button to leave the widget.
You can connect a slot to either (or both) of these signals, so that when the user leaves the widget after editing text, you process it and add it to the box's list of values.
You may also want to look into the QValidator class and its subclasses, which you attach to widgets with editable text, and define the types of valid input for the widget (e.g., integers, text, etc.). This is the best and easiest way to verify a user's input for editable widgets.

How to set up and react to multiple display options?

I'm working on a GUI that will eventually run one of several data analyses depending on which the user selects. In this part of the GUI, I have four radio buttons with options, and then a display button. I would like one of four imported functions to run when the user hits display.
It boils down to something like this
import myFunction
class myUi(QtGui.QWidget):
def retranslateUi(self, myUi):
self.option1.clicked.connect(self.option1task)
self.option2.clicked.connect(self.option2task)
self.display.clicked.connect(self.displaytask)
def option1task(self):
#do something
def option2task(self):
#do something
def displaytask(self):
#if option 1 was clicked, run myFunction.option1()
#if option 2 was clicked, run myFunction.option2()
I'm just having trouble making it work. Is there some way to solve it just by passing variables or will I need to use the signal/slot method?
First of all you do not want to react immediately when a radio button is clicked, so you do not need to connect to their clicked signal.
Instead the radio buttons (which are automatically exclusive within the same parent) can be selected by the user and at the moment the display button is clicked, you just read out which of the radio buttons is selected (can be at most one) and do something according to which one is selected.
For four radio buttons you can do that by a if else clause on isChecked() of the buttons.
For larger numbers of button I recommend additionally using a QButtonGroup (documentation) which allowes to assign an integer to each button within the addButton() method and then easily retrieve the integer of the selected button with checkedId(). If no button is selected the return value is -1.
My example (using PySide which is very similar to PyQt):
from PySide import QtGui
def do_something():
id = g.checkedId()
if id == -1:
print('no option selected')
else:
print('selected option {}, read for display'.format(id))
app = QtGui.QApplication([])
w = QtGui.QWidget()
l = QtGui.QVBoxLayout(w)
# three radio buttons
b1 = QtGui.QRadioButton('Option 1')
l.addWidget(b1)
b2 = QtGui.QRadioButton('Option 2')
l.addWidget(b2)
b3 = QtGui.QRadioButton('Option 3')
l.addWidget(b3)
# a button group (mapping from buttons to integers)
g = QtGui.QButtonGroup(w)
g.addButton(b1, 1)
g.addButton(b2, 2)
g.addButton(b3, 3)
# display button
b = QtGui.QPushButton('Display')
b.clicked.connect(do_something)
l.addWidget(b)
w.show()
app.exec_()
And it looks like:

Readonly tkinter text widget

I want to use tkinter text widget as a readonly widget. It should act as a transcript area. My idea is to keep this transcript in a file and whenever the user writes anything, just remove all the contents of the widget, and rewrite it again.
The code will look like:
transcript_entry = SimpleEditor() # SimpleEditor is inherited from ScrolledText
transcript_entry.text.delete("1.0", END)
# this is just a test string, it should be the contents of the transcript file
transcript_entry.text.insert("1.0", "This is test transcript")
transcript_entry.text.bind("<KeyPress>", transcript_entry.readonly)
And readonly function will look like:
def readonly(self, event):
self.text.delete("1.0", END)
# this is just a test string, it should be the contents of the transcript file
self.text.insert("1.0", "This is test transcript")
The bug here is that the last character entered by the user is added to the transcript. I suspect the reason is that the readonly function is called, then the user input is wrote to the widget. How to reverse this order & let the readonly function be called after the user input is wrote to the widget?
Any hints?
The reason that the last character is inserted is because the default bindings (which causes the insert) happens after custom bindings you put on the widget. So your bindings fire first and then the default binding inserts the characters. There are other questions and answers here that discuss this in more depth. For example, see https://stackoverflow.com/a/11542200/
However, there is a better way to accomplish what you are trying to do. If you want to create a readonly text widget, you can set the state attribute to "disabled". This will prevent all inserts and deletes (and means you need to revert the state whenever you want to programmatically enter data).
On some platforms it will seem like you can't highlight and copy text, but that is only because the widget won't by default get focus on a mouse click. By adding a binding to set the focus, the user can highlight and copy text but they won't be able to cut or insert.
Here's an example using python 2.x; for 3.x you just have to change the imports:
import Tkinter as tk
from ScrolledText import ScrolledText
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
t = ScrolledText(self, wrap="word")
t.insert("end", "Hello\nworld")
t.configure(state="disabled")
t.pack(side="top", fill="both", expand=True)
# make sure the widget gets focus when clicked
# on, to enable highlighting and copying to the
# clipboard.
t.bind("<1>", lambda event: t.focus_set())
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Please do not delete and reinsert your text :
It is huge performance issue.
It will remove any tags and marks set on the text
This will be visible to the user, and users don't like flickering interfaces
This is not necessary, Tkinter is customizable enough to just not allow the user change the content.
The best way I found to create a read only Text is to disable all the bindings leading to a text change.
My solution is to create a new Widget binding map containing only "read only commands". Then, just reconfigure your widget to use the new RO binding map instead of the default one :
from Tkinter import *
# This is the list of all default command in the "Text" tag that modify the text
commandsToRemove = (
"<Control-Key-h>",
"<Meta-Key-Delete>",
"<Meta-Key-BackSpace>",
"<Meta-Key-d>",
"<Meta-Key-b>",
"<<Redo>>",
"<<Undo>>",
"<Control-Key-t>",
"<Control-Key-o>",
"<Control-Key-k>",
"<Control-Key-d>",
"<Key>",
"<Key-Insert>",
"<<PasteSelection>>",
"<<Clear>>",
"<<Paste>>",
"<<Cut>>",
"<Key-BackSpace>",
"<Key-Delete>",
"<Key-Return>",
"<Control-Key-i>",
"<Key-Tab>",
"<Shift-Key-Tab>"
)
class ROText(Text):
tagInit = False
def init_tag(self):
"""
Just go through all binding for the Text widget.
If the command is allowed, recopy it in the ROText binding table.
"""
for key in self.bind_class("Text"):
if key not in commandsToRemove:
command = self.bind_class("Text", key)
self.bind_class("ROText", key, command)
ROText.tagInit = True
def __init__(self, *args, **kwords):
Text.__init__(self, *args, **kwords)
if not ROText.tagInit:
self.init_tag()
# Create a new binding table list, replace the default Text binding table by the ROText one
bindTags = tuple(tag if tag!="Text" else "ROText" for tag in self.bindtags())
self.bindtags(bindTags)
text = ROText()
text.insert("1.0", """A long text with several
lines
in it""")
text.pack()
text.mainloop()
Note that just the bindings are changed. All the Text command (as insert, delete, ...) are still usable.
I recently worked a different, slightly simpler solution. Rather than changing all the bindings, one can add a function to delete all input characters as soon as they are written:
def read_only(self, event):
if event.char is not '': # delete only if the key pressed
# corresponds to an actual character
self.text.delete('insert-1c')
and just bind it to any event:
root.bind('<Key>', self.read_only)

Categories