Wxpython doubling up underscore characters in Menu event.GetLabel() - python

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()

Related

I can't scroll with the keyboard in the text of my Text tag -Tkinter

I basically got the unwanted characters removed from the Text widget, but when I hit a line break and try to scroll the text with the keyboard or mouse, I just can't (it always stays in the same place).
this is done in the "validate text" method
class NewNewsFrame(Frame):
def __init__(self, parent):
self.Parent = parent
self.initializecomponents()
pass
def validate_text(self, widger):
value = widger.get("1.0", "end-1c")
print value.fon
if not value.isalnum():
var = str()
for i in value:
print(f"({i})")
var += i if(i.isalpha() or i.isdigit() or i == "(" or i == ")" or i == " " or i == "," or i == "." or i == "\n")else ""
widger.delete("1.0", "end-1c")
widger.insert("end-1c", var)
pass
def initializecomponents(self):
Frame.__init__(self, self.Parent)
self.DescriptionLabel = Label(self)
self.DescriptionBox = Text(self)
# DescriptionLabel
self.DescriptionLabel.config(text="Description of the news:",bg=self["bg"], fg="#FFFFFF", font="none 15",anchor="nw")
self.DescriptionLabel.place(relwidth=1, relheight=0.1, relx=0, rely=0.11, anchor="nw")
# DescriptionBox
self.DescriptionBox.bind("<KeyPress>", lambda event: self.validate_text(self.DescriptionBox))
self.DescriptionBox.bind("<FocusOut>", lambda event: self.validate_text(self.DescriptionBox))
self.DescriptionBox.place(relheight=0.4, relwidth=1, relx=0, rely=0.16, anchor="nw")
pass
I tried to find how keyboard scrolling works, but I still don't know how to do it
The problem is that you're deleting and restoring all of the text with every keypress. This causes the cursor position to change in unexpected ways that breaks the default bindings.
If you're wanting to prevent certain characters from being entered, there's a better way. If your validation function returns the string "break", that prevents the character from being inserted. You don't have to re-scan the entire contents or delete and restore the text, because the bad characters never get entered in the first place.
Your validation function might look something like this:
def validate_text(self, event):
if event.char.isalpha() or event.char.isdigit() or event.char in "() ,.\n":
pass
else:
return "break"
Next, simplify the binding to look like the following. Tkinter will automatically pass the event parameter to the function.
self.DescriptionBox.bind("<KeyPress>", self.validate_text)
This will break your <FocusOut> binding, but I'm not sure it's needed.
For more information about how events are processed and why returning "break" does what it does, see this answer to the question Basic query regarding bindtags in tkinter. That question is about an Entry widget but the concept is the same for all widgets.

How can I add spacing each time I insert text into a textbox in tkinter?

I have been fiddling with adding spacing between text each time I call an insert to the text box. What I noticed is that when I call it multiple times the spacing is already added but when I have 2 or more lines of .insert all being called at once when I click a button the text is all mushed together with no spacing in between. Attached below are the pictures of the issue and here is my code.
taskList.insert("1.end", siteTaskID)
taskList.insert("1.end",siteTaskTXT)
siteTaskID and siteTaskTXT are 2 variables holding strings in each. The strings are being decided based off of user input. For instance these variable could be
siteTaskID = "HELLO"
siteTaskTXT = "Store"
This is really all the necessary code i have written to execute this. I left out the string conversion and also left out the button which calls this function.
This is when I call both with the code above
This is when I remove the second .insert function and click the button multiple times (this is what I want to happen when I call both at the same time.)
Just use an fstring and put as many spaces as you want between the 2 parts.
taskList.insert("1.end", f'{siteTaskID} {siteTaskTXT}')
edit:
You refuse to post an MRE for some reason so, perceive this shot-in-the-dark
from tkinter import Tk, Text, Button
class App(Tk):
WIDTH = 800
HEIGHT = 600
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
self.textbox = Text(self)
self.textbox.grid(row=0, column=0, sticky='nswe')
self.index = 0
#this is the current line you want to affect
self.cline = 1
#this represents 5 different times you have gathered line data into a list
#...via a much more dynamic method
self.vals = [
["id1", "text1", "usertext1"],
["id2", "text2", "usertext2"],
["id3", "text3", "usertext3"],
["id4", "text4", "usertext4"],
["id5", "text5", "usertext5"],
]
#current line data
self.data = self.vals[self.index]
Button(self, text="click", command=lambda: self.process(self.data)).grid(row=1, column=0)
#this represents processing a line of gathered data
def process(self, data):
if data:
self.textbox.insert(f'{self.cline}.end', f'{" ".join(data)}\n')
#below is only necessary for this example
#the equivalents should be done somewhere else
self.cline += 1
self.index += 1
self.data = self.vals[self.index] if self.index < len(self.vals) else None
if __name__ == '__main__':
app = App()
app.title("My Application")
app.geometry(f'{App.WIDTH}x{App.HEIGHT}')
app.mainloop()
aside
Judging from your comment it would seem you are trying to cheat a quality method, like a table full of Label or Entry, for a inferior method of space formatting a Text widget into a table-like display. If that is the case, you may want to consider that your current method is a complete waste of time and will give you more headaches than results.

How to give the user pre-filled input to edit and then approve? Python

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()

wxPython - Prevent the same warning dialog from appearing twice

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.

capturing tkinter checkbox input

I am running a script with tkinter that captures user input and then opens a second and possibly a third window based on the input. The issue I am having is capturing user input from the third and final window. Each window is divided up into it's own python class on execution.
Here is the code that calls the third window, which executes properly:
test_assign = TestAssign(mylist)
Here is the third window code:
class TestAssign:
def __init__(self, mylist):
self.mylist = mylist
self.selected_values = []
self.master = Tk()
for i in range(len(mylist)):
setattr(self, 'item'+mylist[i], IntVar())
ch = Checkbutton(master, text='item'+mylist[i], variable=getattr(self, 'item'+mylist[i])
ch.pack()
b = Button(master, text='Next', command=self.get_selected_values)
b.pack()
mainloop()
def get_selected_values(self):
for i in range(len(self.mylist)):
if getattr(self, 'item'+self.mylist[i]) == 1:
self.selected_values.append(self.mylist[i])
self.master.destroy()
Control then returns to the call point (at least I believe it does). Where I attempt to print the selected values:
test_assign = TestAssign(mylist)
while not test_assign.selected_values:
pass
print test_assign.selected_values
Everytime execution gets to the print statement it prints an empty list whether there are boxes checked or not. If I call dir(test_assign) for testing purposes, the checkbox attrs are there. Not sure why I am not able to capture it like this.
Can anyone see the flaw in my code?
Two things:
1)
ch = Checkbutton(master, text='item'+mylist[i], variable=getattr(self, 'item'+mylist[i])
and
b = Button(master, text='Next', command=self.get_selected_values)
I think master should be self.master (but honestly, that almost certainly just a copy/pasting error.)
2) The important one:
if getattr(self, 'item'+self.mylist[i]) == 1:
should be
if getattr(self, 'item'+self.mylist[i]).get() == 1:
(you need to call get on your IntVars to read the value.)

Categories

Resources