I have a textctrl accepting user input. I want to check the text after the user enters it to see if it is also in a predefined list of words. I can do this check when the textctrl loses focus. I can also set it to check when the enter key is pressed. However, if I do both, the input is checked twice (not a huge deal, but not necessary). If the input is incorrect (the word is not in the list) then 2 error dialogs pop up. This is not ideal. What is the best way around this?
Edit: In case I wasn't clear, 2 warnings popup if the input is incorrect and Enter is hit. This causes one dialog to appear, which steals focus, causing the second to appear.
This demo code meets your criteria.
You should be able to test run it in its entirety in a separate file.
import sys; print sys.version
import wx; print wx.version()
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "hello frame")
self.inspected = True
self.txt = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER)
self.txt.SetLabel("this box must contain the word 'hello' ")
self.txt.Bind(wx.EVT_TEXT_ENTER, self.onEnter)
self.txt.Bind(wx.EVT_KILL_FOCUS, self.onLostFocus)
self.txt.Bind(wx.EVT_TEXT, self.onText)
def onEnter(self, e):
self.inspectText()
def onLostFocus(self, e):
self.inspectText()
def onText(self, e):
self.inspected = False
def inspectText(self):
if not self.inspected:
self.inspected = not self.inspected
if 'hello' not in self.txt.GetValue():
self.failedInspection()
else:
print "no need to inspect or warn user again"
def failedInspection(self):
dlg = wx.MessageDialog(self,
"The word hello is required before hitting enter or changing focus",
"Where's the hello?!",
wx.OK | wx.CANCEL)
result = dlg.ShowModal()
dlg.Destroy()
if result == wx.ID_OK:
pass
if result == wx.ID_CANCEL:
self.txt.SetLabel("don't forget the 'hello' !")
mySandbox = wx.App()
myFrame = TestFrame()
myFrame.Show()
mySandbox.MainLoop()
exit()
The strategy used was to add a flag to the instance to indicate if it has already been inspected and to overwrite the flag if the text changes.
Related
I'm using a QLineEdit widget to enter email addresses and I set up a QRegExpValidor to validate the entry.
The validator is working as far as preventing the input of characters not allowed in the QRegExp, but funny enough, it allows to enter intermediate input, either by pushing the enter key or by firing the "editingfinished" signal.
I verified the return of the validator which is correct (Intermediate or Acceptable).
Checking the PyQt5 documentation it seams that an intermediate state of the validator does not prevent the focus to change to another widget. Furthermore, it stops the firing of the editingFinished and returnedPressed signals so the user may enter wrong address as far as it marches partially the RegExp.
Ref: https://doc.qt.io/qt-5/qlineedit.html#acceptableInput-prop
I was able to solve my needs by removing the validator from the QLineEdit widget, and placing a method "checkValidator" linked to the cursorPositionChanged of the QLineEdit widget discarting the last character entered when nonacceptable, and setting the focus back to the QLineEdit when it validats intermmediate. It works just fine but when the the focus was reset, the others signals on other widgets were fired one at a time. Momentarily, I handle this by checking if the sender has focus at the start of the methods (see lookForFile)
Even though I could handle the issue, I appreciate very much anybody explaining me the proper way to use the RegExpValidator and or why resetting the focus fires the other signals out of the blue.
def setUI(self):
self.setWindowTitle("EMail Settings")
self.setModal(True)
rx = QRegExp(r"[a-z0-9_%]+#[a-z0-9%_]+\.[a-z0-9%_]{3,3}")
lblAddress = QLabel("EMail Address")
self.lineAddress = QLineEdit(self)
self.mailValidator = QRegExpValidator(rx, self.lineAddress)
#self.lineAddress.setValidator(self.mailValidator)
self.lineAddress.cursorPositionChanged.connect(self.checkValidator)
self.lineAddress.returnPressed.connect(self.checkValidator)
lblPassword = QLabel("Password")
self.linePwd = QLineEdit()
self.linePwd.setEchoMode(QLineEdit.PasswordEchoOnEdit)
lblOauth2 = QLabel("Oauth2 Token")
self.lineOauth = QLineEdit()
pushOauth = QPushButton("...")
pushOauth.setObjectName("token")
pushOauth.clicked.connect(self.lookForFile)
pushOauth.setFixedWidth(30)
#pyqtSlot()
def checkValidator(self):
self.lineAddress.blockSignals(True)
v = self.mailValidator.validate(self.lineAddress.text(), len(self.lineAddress.text()))
if v[0] == 0:
self.lineAddress.setText(self.lineAddress.text()[:-1])
elif v[0] == 1:
self.lineAddress.setFocus()
elif v[0] == 2:
pass
print("validates", v)
self.lineAddress.blockSignals(False)
#pyqtSlot()
def lookForFile(self):
try:
if not self.sender().hasFocus():
return
baseDir = "C"
obj = self.sender()
if obj.objectName() == "Draft":
capt = "Email Draft"
baseDir = os.getcwd() + "\\draft"
fileType = "Polo Management Email (*.pad)"
dialog = QFileDialog(self, directory=os.getcwd())
dialog.setFileMode(QFileDialog.Directory)
res = dialog.getExistingDirectory()
elif obj.objectName() == "token":
capt = "Gmail Outh2 token File"
fileType = "Gmail token Files (*.json)"
baseDir = self.lineOauth.text()
res = QFileDialog.getOpenFileName(self, caption=capt, directory=baseDir, filter=fileType)[0]
fileName = res
if obj.objectName() == "Draft":
self.lineDraft.setText(fileName)
elif obj.objectName() == "tokenFile":
self.lineOauth.setText(fileName)
except Exception as err:
print("settings: lookForFile", err.args)
Hope to answer #eyllanesc and Qmusicmante request with this minimal reproducible example. I change the regex for a simple one allowing for a string of lower case a-z follow by a dot and three more lowercase characters.
What I intend is the validator not allowing the user to enter a wrong input. The example allows for "xxxzb.ods" but also allows "xxxzb" or "xxxzb.o" for instance.
In short, not allowing the user to enter a wrong input.
This is my minimal reproducible example:
class CheckValidator(QDialog):
def __init__(self, parent=None):
super().__init__()
self.parent = parent
self.setUI()
def setUI(self):
self.setWindowTitle("EMail Settings")
self.setModal(True)
rx = QRegExp(r"[a-z]+\.[a-z]{3}")
lblAddress = QLabel("Check Line")
self.lineAddress = QLineEdit()
self.mailValidator = QRegExpValidator(rx, self.lineAddress)
self.lineAddress.setValidator(self.mailValidator)
self.lineAddress.cursorPositionChanged[int, int].connect(lambda
oldPos, newPos: self.printValidator(newPos))
lblCheck = QLabel("Check")
lineCheck = QLineEdit()
formLayout = QFormLayout()
formLayout.addRow(lblAddress, self.lineAddress)
formLayout.addRow(lblCheck, lineCheck)
self.setLayout(formLayout)
#pyqtSlot(int)
def printValidator(self, pos):
print(self.mailValidator.validate(self.lineAddress.text(), pos))
if __name__ == '__main__':
app = QApplication(sys.argv)
tst = CheckValidator()
tst.show()
app.exec()
I found a solution and I post it here for such a case it may help somebody else.
First I remove the QRegExpValidator from the QLineEdit widget. The reason being is that QLineEdit only fires editingFinished (and we'll need it ) only when QRegExpValidator returns QValidator.Acceptable while the validator is present.
Then we set a method fired by the 'cursorPositionchanged' signal of QlineEdit widget. On this method, using the QRegExpValidator we determine if the last character entered is a valid one. If not we remove it.
Finally, I set a method fired by the 'editingFinished' signal using the RegEx exactMatch function to determine if the entry is valid. If it is not, we give the user the option to clear the entry or to return to the widget to continue entering data. The regex used is for testing purposes only, for further information about email validation check #Musicamante comments.
This is the code involved:
def setUI(self):
................
................
rx = QRegExp(r"[a-z0-9_%]+#[a-z0-9%_]+\.[a-z0-9%_]{3,3}")
lblAddress = QLabel("EMail Address")
self.lineAddress = QLineEdit(self)
self.mailValidator = QRegExpValidator(rx, self.lineAddress)
self.lineAddress.cursorPositionChanged[int, int].connect(lambda oldPos,
newPos: self.checkValidator(newPos))
self.lineAddress.editingFinished.connect(lambda : self.checkRegExp(rx))
#pyqtSlot(int)
def checkValidator(self, pos):
v = self.mailValidator.validate(self.lineAddress.text(), pos ))
if v[0] == 0:
self.lineAddress.setText(self.lineAddress.text()[:-1])
#pyqtSlot(QRegExp)
def checkRegExp(self, rx):
if not rx.exactMatch(self.lineAddress.text()) and self.lineAddress.text():
if QMessageBox.question(self, "Leave the Address field",
"The string entered is not a valid email address! \n"
"Do you want to clear the field?", QMessageBox.Yes|QMessageBox.No) ==
QMessageBox.Yes:
self.lineAddress.clear()
else:
self.lineAddress.setFocus()
In my script, I want to ask the user for input on the correct or incorrect spelling of a sentence (i) and allow the user to make corrections if necessary. I am running this simply in Jupyter Notebook on a Mac. (We do not know in advance which sentence contains errors and which do not.) This is quite straightforward to do. However, I want to give the user the sentence i as an editable input at the prompt. I have tried to achieve this by adding a variable to the 'input' line in the script, but that does not work. I cannot find a positive answer to this question. Perhaps somebody knows if it is possible or impossible?
Here is the script.
i = "Today, John toak the bus to school."
print(i)
print(f"Is this sentence spelled correctly? No = 0, Yes = 1")
choice = input("> ")
if choice == "1":
return i
else choice == "0":
spelling = input("> ", i) # this does not work. Is there a way?
return spelling
Suppose the script gives the user the following line:
John wend to school by bus today.
Is this sentence spelled correctly? No = 0, Yes = 1
If the user selects 0 (=No), I want the sentence to already appear at the prompt so that the user can edit it (just change 'wend' to 'went' and hit 'enter') rather than having to type the entire sentence again (with the risk of new mistakes):
|-----------------------------------------|
| > John wend to school by bus today. |
|-----------------------------------------|
Does anyone know if this is possible and, if so, how?
We can do this in a Tkinter window. Which may not be what you are looking for, but is a solution using the standard library. Here is some example code that creates a window with a default string. You can edit the string. When the return key is pressed, the string in the window is read.
from tkinter import Tk, LEFT, BOTH, StringVar
from tkinter.ttk import Entry, Frame
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Entry")
self.pack(fill=BOTH, expand=1)
self.contents = StringVar()
# give the StringVar a default value
self.contents.set('test')
self.entry = Entry(self)
self.entry.pack(side=LEFT, padx=15)
self.entry["textvariable"] = self.contents
self.entry.bind('<Key-Return>', self.on_changed)
def on_changed(self, event):
print('contents: {}'.format(self.contents.get()))
return True
def main():
root = Tk()
ex = Example(root)
root.geometry("250x100+300+300")
root.mainloop()
if __name__ == '__main__':
main()
I load a menu with filenames from which the user will be able to choose from.
If the file name contains an underscore character (_), the
event.GetEventObject().GetLabel(event.GetId())
returns a value where any underscore character (_) is doubled up.
So a file name of a_file.txt becomes a__file.txt
I can get around the problem by using
event.GetEventObject().MenuItems[event.GetId()].GetLabel()
but not only do I not know if there are any repercussions of using this but
I don't particularly want to trawl through 1000's of lines of code hunting
for instances of this strange issue.
Does anyone have an explanation for this behaviour and how to avoid it?
The demonstration code below illustrates the problem and the work around.
The tests are for a normal file name, a file name with spaces and a file name
with an underscore.
import wx
class MenuProblem(wx.Frame):
def __init__(self, *args, **kwds):
self.frame=wx.Frame.__init__(self, *args, **kwds)
self.menubar = wx.MenuBar()
# self.statusbar = wx.StatusBar(self-1)
self.CreateStatusBar()
self.SetStatusText("Demonstration of wxPython")
menu1 = wx.Menu()
menu_item_1 = menu1.Append(wx.ID_OPEN, "&File")
menu_item_2 = menu1.Append(wx.ID_EXIT, "&Exit...")
#Build a list of things via another function or just a declaration
self.list_of_things = ["afilename.txt", "another filename.txt", "problem_filename.txt"]
list_used = wx.Menu()
thing_count = 0
for thing in self.list_of_things:
t1 = wx.MenuItem(list_used, thing_count, thing)
list_used.AppendItem(t1)
thing_count +=1
thing_end = wx.MenuItem(list_used,199,'End of List')
list_used.AppendItem(thing_end)
menu1.AppendMenu(wx.ID_FILE,'&Problem Demo',list_used)
menu1.SetHelpString(wx.ID_FILE, 'Click problem_filename.txt to see the doubling of underscore')
self.menubar.Append(menu1, "&File")
self.SetMenuBar(self.menubar)
# Create bindings for the Thing list
i_count = 0
for i in self.list_of_things:
self.Bind(wx.EVT_MENU, self.OnThingOpen, id=i_count)
i_count = i_count + 1
self.Bind(wx.EVT_MENU, self.OnThingEnd, id=199)
self.Bind(wx.EVT_MENU, self.OnClose, id=wx.ID_EXIT)
self.Show(True)
def OnThingOpen(self, event):
id_selected = event.GetId()
obj = event.GetEventObject()
print "Option :", id_selected
print "Label returned:", obj.GetLabel(id_selected)
print "Now get the label in another way"
print "Label returned:", obj.MenuItems[id_selected].GetLabel()
print "From the range:"
for i in range(obj.MenuItemCount):
print "\t\t", obj.MenuItems[i].GetLabel()
print "."*50
def OnThingEnd(self, event):
id_selected = event.GetId()
obj = event.GetEventObject()
print "Option :", id_selected
print "Label returned :",obj.GetLabel(id_selected)
print "Enabled", obj.IsEnabled(id_selected)
print obj.MenuItemCount
for i in range(obj.MenuItemCount):
print obj.MenuItems[i].GetLabel()
def OnClose(self, event):
self.Close()
if __name__ == '__main__':
app = wx.App()
MC=MenuProblem(parent=None, id=-1)
app.MainLoop()
EDIT:
It would seem that this is a bug in wxpython 2.8 (on Linux, perhaps other platforms) as the problem does not manifest itself using wxpython 3.0 on Windows. Information courtesy of #pss who tested the code for me.
The result being that I have indeed trawled through my code and used the work-around as detailed above.
Further to my original edit to the question, the fault seems to be a very old one as discussed on trac.wxwidgets.org ticket numbers #338, #9062, and #9055 and is related to the underscore (_) character being used as the escape character for GTK+. This form of error might exist on Windows as well but the errant character on Windows would not be the underscore but an ampersand (&) i.e. under Windows a file name such as a&filename.txt could be returned as afilename.txt as it would drop the ampersand completely (I'm not 100% sure of this see the code)
http://trac.wxwidgets.org/changeset/9055/svn-wx
So as #pss tested on the Windows platform, the issue may not be a simple bug that has been fixed but the result of a issue between wxpython/wxwidgets and the graphical platform that is being used.
One work-around should you come across this problem was detailed in the original question and works:
event.GetEventObject().MenuItems[event.GetId()].GetLabel()
or as stated in the code block more long-windedly:
id_selected = event.GetId()
obj = event.GetEventObject()
print "Label returned:", obj.MenuItems[id_selected].GetLabel()
In Python with gobject, I am having immense issues getting input from the user.
Here is my code:
def get_network_pw(self, e):
def okClicked(self):
print(pwd.get_text())
return pwd.get_text()
pwDialog.destroy()
def cancelClicked(self):
print("nope!")
pwDialog.hide()
return None
#Getting the about dialog from UI.glade
pwDialog = self.builder.get_object("passwordDialog")
okBtn = self.builder.get_object("pwdOkBtn")
cancelBtn = self.builder.get_object("pwdCancelBtn")
pwd = self.builder.get_object("userEntry")
# Opening the about dialog.
#okBtn.connect("clicked", okClicked)
#cancelBtn.connect("clicked", cancelClicked)
pwDialog.run()
I am not sure where I am going wrong? It refuses to return the userEntry text. I also tried the code from Python/Gtk3 : How to add a Gtk.Entry to a Gtk.MessageDialog? and Simple, versatile and re-usable entry dialog (sometimes referred to as input dialog) in PyGTK to no avail.
EDIT
I have a dialog I made in glade. It contains a GtkTextBox (userEntry), an Ok button (pwdOkBtn) and a cancel button (pwdCancelBtn). When the user clicks OK, it theoretically should return what they entered in the text box (say, 1234). When they click cancel, it should return None. However, when they click Ok, it returns "", and when they click cancel, it returns "". I'm not sure where I am going wrong here.
Extra code tries:
I tried the following code as well:
def get_network_pw(self, e):
d = GetInputDialog(None, "Enter Password")
dialog = d.run()
if dialog is 1:
print("OK")
else:
print("Nope!")
d.hide()
GetInputDialog:
class GetInputDialog(Gtk.Dialog):
def __init__(self, parent, title):
Gtk.Dialog._init(self, title, parent)
self.response = "Cancel"
self.setupHeader()
self.setupUI()
def setupUI(self):
wdg = self.get_content_area() #explained bellow
self.txtSource = Gtk.Entry() #create a text entry
wdg.add(self.txtSource)
self.show_all() #show the dialog and all children
def setupHeader(self, title="Get User Input"):
hb = Gtk.HeaderBar()
hb.props.show_close_button = True
hb.props.title = title
self.set_titlebar(hb)
btnOk = Gtk.Button("OK")
btnOk.connect("clicked", self.btnOkClicked)
hb.pack_start(btnOk)
btnCancel = Gtk.Button("Cancel")
btnCancel.connect("clicked", self.btnCancelClicked)
hb.pack_start(btnCancel)
def btnOkClicked(self, e):
self.response = "Ok" #set the response var
dst = self.txtSource #get the entry with the url
txt = dst.get_text()
return 1
def btnCancelClicked(self, e):
self.response = "Cancel"
return -1
I think you're overcomplicating it. The run method returns the id of the button pressed in a dialog:
Don't use the .hide() and .destroy() methods in that way, those are for different situations. .destroy() destroys the widget, so you should not call it unless you know what you're doing.
Place the .hide() after the .run().
Capture the return value of the run(), and setup the buttons in the dialog to a different Response ID in Glade.
The relevant part of the code is:
def _btn_cb(self, widget, data=None):
"""
Button callback
"""
ret = self.dialog.run()
self.dialog.hide()
if ret == 0:
self.label.set_text(
self.entry.get_text()
)
The full code for this example is here:
https://gist.github.com/carlos-jenkins/c27bf6d5d76723a4b415
Extra: If you want to check a condition to accept the Ok button (don't know, for example that the entry is valid) execute the run() in a while loop, check if button is Cancel then break, else check the validity of the input, if valid do something and break, else continue:
def _btn_cb(self, widget, data=None):
"""
Button callback
"""
while True:
ret = self.dialog.run()
if ret == -1: # Cancel
break
try:
entry = int(self.entry.get_text())
self.label.set_text(str(entry))
break
except ValueError:
# Show in an error dialog or whatever
print('Input is not an integer!')
self.dialog.hide()
I've built a wx.Dialog, with some wx.Radiobox need to validate. If user doesn't make a choice, the validator wouldn't return True and popup a wx.MessageBox tells user that you should choose something. Meanwhile, the background color should have changed to pink.
The problem here is when using RadioBox.SetBackgroundColour("pink"), the Background color of RadioBox's label text changed to pink, which is perfectly normal, but the background color for RadioButton's label text won't change until mouse pass by.
I'm wondering why this and how to fix it. I've looked up in official docs and googled, but find nothing. Anyone have idea?
The platform is win8.1x64 and python 2.7.3 and wxpython 2.8.12.1. For using Psychopy standalone package, the version of both python and wxpython are quite old.
The Dialog as follow, a quite simple one :
class Dlg(wx.Dialog):
"""A simple dialogue box."""
def __init__(self):
global app
app = wx.PySimpleApp()
wx.Dialog.__init__(self, None,-1)
self.sizer = wx.FlexGridSizer(cols=1)
def show(self, cancelBTN=False):
"""Show a dialog with 2 RadioBox"""
# add two RadioBox
RadioBox1 = wx.RadioBox(self, -1, label="Wierd color", choices=["", "choice A", "choice B"], validator=RadioObjectValidator())
RadioBox2 = wx.RadioBox(self, -1, label="Wierd color", choices=["", "choice A", "choice B"], validator=RadioObjectValidator())
RadioBox1.ShowItem(0, show=False) # Hide the first choice
RadioBox2.ShowItem(0, show=False) # Hide the first choice
RadioBox1.GetValue = RadioBox1.GetStringSelection
RadioBox2.GetValue = RadioBox2.GetStringSelection
self.sizer.Add(RadioBox1, 1, wx.ALIGN_LEFT)
self.sizer.Add(RadioBox2, 1, wx.ALIGN_LEFT)
# add buttons for OK
self.sizer.Add(wx.Button(self, wx.ID_OK), 1, flag=wx.ALIGN_RIGHT)
self.SetSizerAndFit(self.sizer)
if self.ShowModal() == wx.ID_OK:
pass
# do something
self.Destroy()
And Validator as this:
class RadioObjectValidator(wx.PyValidator):
""" This validator is used to ensure that the user has entered something
into the text object editor dialog's text field.
"""
def __init__(self):
""" Standard constructor.
"""
wx.PyValidator.__init__(self)
def Clone(self):
""" Standard cloner.
Note that every validator must implement the Clone() method.
"""
return RadioObjectValidator()
def Validate(self, win):
""" Validate the contents of the given RadioBox control.
"""
RadioBox = self.GetWindow()
choice = RadioBox.GetValue()
if not choice:
wx.MessageBox(u'Choose something please', u'info', wx.OK | wx.ICON_EXCLAMATION)
RadioBox.SetBackgroundColour("pink")
RadioBox.SetFocus()
RadioBox.Refresh()
return False
else:
RadioBox.SetBackgroundColour(
wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
RadioBox.Refresh()
return True
def TransferToWindow(self):
""" Transfer data from validator to window.
The default implementation returns False, indicating that an error
occurred. We simply return True, as we don't do any data transfer.
"""
return True # Prevent wxDialog from complaining.
def TransferFromWindow(self):
""" Transfer data from window to validator.
The default implementation returns False, indicating that an error
occurred. We simply return True, as we don't do any data transfer.
"""
return True # Prevent wxDialog from complaining.
And simply run like this:
if __name__ == "__main__":
test = Dlg()
test.show()
Well, it's a bug in wxpython2.8.12.1, and has fixed in wxpython3.0.0.0, Thanks #GreenAsJade for reminding.