i wanna know how to override the default keysearch in a treectrl.
When i bind a method to the EVT_TREE_KEY_DOWN event and call the selectItem method of the treectrl, it doesn't have any effect.
This is my Tree:
Test <--root
-Aero orea(EI)
-Blub(BL)
-Test(AX)
-123(45)
-Blib (LOL)
My intention:
With the keydown event i am concatenating a searchstring. when iterating over the treeitems, i split the names to get the content of the brackets(e.g.:"EI", "BL"...).
Then i check if the content of the brackets starts with my searchstring. if it is true the selectItem(TreeItemId) is called. But this won't work. It seems that the default search ist still working and is causing problems in my keysearch.
class MeinTreeCtrl(wx.TreeCtrl):
def __init__(self, parent):
wx.TreeCtrl.__init__(self, parent, -1)
root = self.AddRoot("test")
self.AppendItem(root, "Aero orea(EI)")
self.AppendItem(root, "Blub(BL)")
self.AppendItem(root, "Test(AX)")
self.AppendItem(root, "123(45)")
self.AppendItem(root, "Blib(LOL)")
self.searchString = ""
self.lastKeyDown = time.time()
parent.Bind(wx.EVT_TREE_KEY_DOWN, self.OnTreeKeySearch, self)
def GetItem(self, match, root):
item = self.GetFirstChild(root)
while item.IsOk():
tmp = self.GetItemText(item)
tmp = tmp.split(")")
tmp = tmp[len(tmp) - 2]
tmp = tmp.split("(")
tmp = tmp[len(tmp) - 1]
if tmp.startswith(match):
self.SelectItem(item)
break
item = self.GetNextChild(root, item)
return False
def OnTreeKeySearch(self, event):
now = time.time()
if self.searchString == "":
self.searchString = chr(event.GetKeyCode())
if (now - self.lastKeyDown) < 3:
self.searchString += str(chr(event.GetKeyCode()))
else:
self.searchString = str(chr(event.GetKeyCode()))
self.lastKeyDown = now
self.GetItem(self.searchString, self.GetRootItem())
Do you have any clue?
Thank you and best regards
Thomas
After a few days of search i found my mistake.
This line was the problem:
parent.Bind(wx.EVT_TREE_KEY_DOWN, self.OnTreeKeySearch, self)
First I need just the EVT_KEY_DOWN not the tree event for key down.
Second I binded the method to my parent not to self (That is because i did copy & paste :( )
This statement was really useful to me:
A typical example of an event not propagated is the wx.EVT_KEY_DOWN.
It is send only to the control having the focus, and will not
propagate to its parent.
-- EventPropagation
Related
I have carefully reviewed answers to Interactively validating Entry widget content in tkinter, but my script fails to restore previous value if the validate command returns False. I captured %P and %s and print them out...They both show the same value.
import tkinter as tk
class Controller :
def __init__(self) :
i=10
j=20
# list comprehension
self.entry_widgets = [[None for col in range(j)] for row in range(i)]
#print(self.entry_widgets)
self.values = [["string"+str(row) + str(col) for col in range(10)] for row in range(20)]
#print(self.values)
class EnterBox(tk.Entry):
def __init__(self,*args,**kwargs):
#print (args)
self._modified = False
self._save = 0
self._raise = 1
self._lower = 2
frame, i,j, *newargs = args
self._content = tk.StringVar()
# tk.Entry.__init__(self,frame,*newargs,
# validate = 'focusout',
# validatecommand = vcmd,
# **kwargs)
tk.Entry.__init__(self,frame,*newargs,**kwargs)
vcmd = (self.register(self._revert), '%P', '%s')
ct.entry_widgets[i][j] = self
self.config(textvariable=self._content)
self.config(validate = "focusout")
self.config(validatecommand = vcmd )
x=(ct.values[i][j])
self.insert(0,x)
#self._content.set(x)
self.bind("<Return>",lambda event, x=self._save : self._action(event,x) )
self.bind("<Button-2>",lambda event, x=self._save : self._action(event,x) )
self.bind("<FocusIn>", lambda event, x=self._raise : self._action(event,x))
self.bind("<FocusOut>", lambda event, x=self._lower : self._action(event,x))
self.bind('<Button-3>', lambda event, x=self._lower : self._action(event,x))
self.grid(column=i+1,row=j+2)
def _revert(self,P,s):
print ("Hi There")
print(P)
print(s)
return False
def _action(self,event,action):
print(str(action)+' ' + str(event))
if action == self._save :
ct.values[i][j] = self._content.get()
self.config(bg='lightskyblue2')
self._modified = True
elif action == self._raise :
self.config(bg = 'light pink')
elif action == self._lower :
self.config(bg = 'gray80')
self._modified = False
else :
print('action value is bad action =>' + str(action))
if "__main__" == __name__ :
root = tk.Tk()
frame = tk.Frame()
i=j=0
ct = Controller()
root.grid()
frame.grid()
check = EnterBox(frame,i,j,width = 24)
check2 = EnterBox(frame,i+1,j,width = 24)
root.mainloop()
I have tried removing all other bindings, to no avail.
Interestingly, but a separate issue, If I use StringVar. set instead of self.insert, (see commented out line) the validate command runs once, and never again despite several focus changes. Using Python 3.8
The validation isn't designed to restore anything if the validation happens on focusout. The validation can only prevent characters from being added at the time they are added. You will have to add code to restore the previous value.
I am not sure why validation stops working after I end up replacing spaces in a string.
The validation works fine for most things I need. It allows for only numbers and up to 10 of them while also allowing for back spaces and highlighting all and back spacing. It also works once when pasting in values that contain spaces.
For example if I try to paste 12 34 into the entry field it will correctly change the value to 1234 but after that validation just stops working.
Update:
It appears that validation stops working after entry.delete() though I still am not sure how to correct this. I have tried to redefine the validation but that did not work.
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.vcmd = (self.register(self.validate), '%d', '%P', '%s')
self.entry = tk.Entry(self, validate='key', validatecommand=self.vcmd)
self.entry.pack()
def validate(self, *a):
b = a[1].replace(' ', '')
if b.isdigit() and len(b) <= 10 or ((b == '' or b < a[2]) and a[0] == '0'):
if ' ' in a[1]:
x = a[1]
x = x.replace(' ', '')
self.entry.delete(0, 'end')
self.entry.insert(0, x)
return True
else:
return False
App().mainloop()
Update:
I managed to get it working by deleting the entry field and redefining it and its validation after inserting the new string. But this seams like the wrong way to do this.
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.vcmd = (self.register(self.validate), '%d', '%P', '%s')
self.entry = tk.Entry(self, validate='key', validatecommand=self.vcmd)
self.entry.pack()
def validate(self, *a):
b = a[1].replace(' ', '')
if b.isdigit() and len(b) <= 10 or ((b == '' or b < a[2]) and a[0] == '0'):
if ' ' in a[1]:
x = a[1]
x = x.replace(' ', '')
self.entry.destroy()
self.entry = tk.Entry(self)
self.entry.insert(0, x)
self.entry.config(validate='key', validatecommand=self.vcmd)
self.entry.pack()
return True
else:
return False
App().mainloop()
I am not sure why validation stops working after I end up replacing spaces in a string. The validation works fine for most things I need.
It stops working because that's how it is designed to work. If you try to modify the data from within the validation function, the validate option is automatically reset to "none".
This is what the official tcl/tk documentation says:
The validate option will also set itself to none when you edit the entry widget from within either the validateCommand or the invalidCommand.
You will need to reset the validate option in the case where you modify the widget from within the validation function.
I have written this function:
def duplicate_sheet1(wb, title=None):
if title is None:
title = wb.sheet1.title + ' DUPLICATE'
wb._sheet_list = [wb.sheet1]
wb.add_worksheet(title, wb.sheet1.row_count, wb.sheet1.col_count)
wb._sheet_list = wb._sheet_list[::-1]
wb._sheet_list[0].update_cells(wb._sheet_list[1]._fetch_cells())
...everything works as expected upon inspection with a debugger except update_cells, when I _fetch_cells for worksheet 0 after running the code, the sheet is empty.
Apparently the list returned by _fetch_cells is not the same as what is expected by update_cells. This may be because _fetch_cells does not include empty cells in the returned list, update_cells may only work with a 1 or 2-D grid--I am unsure.
Here is the work-around I found, apologies as the code could could probably be improved:
def duplicate_sheet1(wb, title=None):
if title is None:
title = wb.sheet1.title + ' DUPLICATE'
wb._sheet_list = [wb.sheet1]
wb.add_worksheet(title, wb.sheet1.row_count, wb.sheet1.col_count)
wb._sheet_list = wb._sheet_list[::-1]
cell_list = build_cell_list(wb._sheet_list[0], wb._sheet_list[1])
wb._sheet_list[0].update_cells(cell_list)
def build_cell_list(new_worksheet, old_worksheet):
fetched = old_worksheet._fetch_cells()
max_row = fetched[-1].row
max_col = max([cell.col for cell in fetched])
cell_list = new_worksheet.range('A1:' + chr(max_col + 64) + str(max_row))
for cell in cell_list:
cell.value = next(
(
f.value for f in fetched
if f.col == cell.col and f.row == cell.row
),
'',
)
return cell_list
I have a wx.Dialog that contains a button and 2 ListBoxes, the button findAppBtn searches through a list of directories and then displays the result in actListBox. Selecting the directory of your choice from actListBox should then fire the event EVT_LISTBOX which calls actListBoxList. This function does an ls on the directory and should list the files it finds in the lower list box binListBox using Append. Upon selecting an item from the lower ListBox, the window closes.
The problem is that the self.Bind(EVT_LISTBOX, self.actListBoxList) does not seem to be firing when an item is selected.
(also please excuse the bad coding, I am trying to get it working before minifying)
self.findAppBtn = wx.Button(panel, -1, "Find app")
self.findAppBtn.SetDefault()
self.Bind(wx.EVT_BUTTON, self.startConf, self.findAppBtn)
hBox2.Add(self.findAppBtn, 0, flag=wx.LEFT, border=5)
vBox.Add(hBox2, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP|wx.BOTTOM, border=3)
self.actListBox = wx.ListBox(panel, choices=[])
self.Bind(wx.EVT_LISTBOX, self.actListBoxList)
vBox.Add(self.actListBox, 2, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP|wx.BOTTOM, border=3)
self.binListBox = wx.ListBox(panel, choices=[])
self.Bind(wx.EVT_LISTBOX, self.binListBoxList)
vBox.Add(self.binListBox, 2, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP|wx.BOTTOM, border=3)
self.closeBtn = wx.Button(panel, wx.ID_OK)
hBox4.Add(self.closeBtn, 0, flag=wx.LEFT, border=5)
vBox.Add(hBox4, 0, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.BOTTOM, border=5)
panel.SetSizer(vBox)
def startConf(self,e):
val = self.cmdTxt.GetValue().replace(" ","\ ")
path = "/private/"
aCmd = "find " + path + " -iname '*"+val+"*.app'"
try:
s = pxssh.pxssh()
s.login(sshIP, "root", sshPort, sshPass)
s.sendline(aCmd)
s.prompt()
AP = s.before
for m in AP.split('\n'):
if path in m:
self.actListBox.Append(m.replace(path,"").strip())
s.logout()
return path
except pxssh.ExceptionPxssh as e:
self.parent.progressBox.AppendText(str(e))
def actListBoxList(self,e):
#get string from top box selection e.g xxxx-xxxx-xxxx-/myapp.app
selName = self.actListBox.GetStringSelection()
path = "/private/"
#list all the files in the dir from top box selection
aCmd = "ls " + path + selName
try:
s = pxssh.pxssh()
s.login(sshIP, "root", sshPort, sshPass)
s.sendline(aCmd)
s.prompt()
ls = s.before
for file in ls.split('\n'):
if not file.endswith("/"):
reg = r"\..*"
matchObj = re.search(reg, file)
if not matchObj:
self.binListBox.Append(file)
s.logout()
except pxssh.ExceptionPxssh as e:
self.parent.progressBox.AppendText(str(e))
def binListBoxList(self,e):
binaryName = self.binListBox.GetStringSelection()
self.Close()
EDIT: self.actListBox.Bind(wx.EVT_LISTBOX, self.actListBoxList) fixed the issue.
calling self.Bind(... binds the event to the parent window which is why you're not seeing the event being called. Bind to the listbox instead:
self.actListBox.Bind(wx.EVT_LISTBOX, self.actListBoxList)
I'm looking for a way for a user to edit data in bulk in a wxPython grid, a little like in Excel when you select a range, type data and press shift-Enter. This is a simplified version of my grid:
class MyGrid(gridlib.Grid):
def __init__(self, panel):
gridlib.Grid.__init__(self, panel)
self.Bind(gridlib.EVT_GRID_CELL_CHANGE, self.onEditCell)
self.Bind(gridlib.EVT_GRID_RANGE_SELECT, self.onSelection)
def onSelection(self, event):
if self.GetSelectionBlockTopLeft() == []:
self.selected_row_number = 0
self.selected_col_number = 0
else:
self.selected_row_number = self.GetSelectionBlockBottomRight()[0][0] - self.GetSelectionBlockTopLeft()[0][0] + 1
self.selected_col_number = self.GetSelectionBlockBottomRight()[0][1] - self.GetSelectionBlockTopLeft()[0][1] + 1
print self.selected_row_number, self.selected_col_number
def onEditCell(self,event):
print self.selected_row_number, self.selected_col_number
The issue seems to be that the onEditCell event overwrites the previous selection. So I can select e.g. a four by four block in the grid, and onSelection will print 4 4. But when I start typing and press Enter, onEditCell will print 0,0 as if only the cell I'm editing was selected. How can I keep a "memory" of how many cells are selected? Thank you,
Answering my own question: I can get it to work with an ugly hack that doesn't seem like the right way to do things:
def onSelection(self, event):
self.previous_selected_row_number = self.selected_row_number
self.previous_selected_col_number = self.selected_col_number
if self.GetSelectionBlockTopLeft() == []:
self.selected_row_number = 0
self.selected_col_number = 0
else:
self.selected_row_number = self.GetSelectionBlockBottomRight()[0][0] - self.GetSelectionBlockTopLeft()[0][0] + 1
self.selected_col_number = self.GetSelectionBlockBottomRight()[0][1] - self.GetSelectionBlockTopLeft()[0][1] + 1
print self.selected_row_number, self.selected_col_number
print self.previous_selected_row_number, self.previous_selected_col_number
def onEditCell(self,event):
print self.previous_selected_row_number, self.previous_selected_col_number
If anyone can think of a better way...