Highlight a ListItem in a wx.ListCtrl on MouseOver - python

I have a wxListCtrl that display a table with information (with rows and column fields). Typically the row will be highlighted only when you click it with the mouse button. But I would like to have the highlight without clicking it. i.e. When I move the mouse to different row, the row will be highlighted without clicking the mouse. Is this possible?
########################################################################
import wx
import sys, glob
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "List Control Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
self.index = 0
self.list_ctrl = wx.ListCtrl(panel, size=(-1,100),
style=wx.LC_REPORT
|wx.BORDER_SUNKEN
)
self.list_ctrl.InsertColumn(0, 'Subject')
self.list_ctrl.InsertColumn(1, 'Due')
self.list_ctrl.InsertColumn(2, 'Location', width=125)
self.list_ctrl.Bind(wx.EVT_ENTER_WINDOW, self.onMouseOver)
self.list_ctrl.Bind(wx.EVT_LEAVE_WINDOW, self.onMouseLeave)
btn = wx.Button(panel, label="Add Line")
btn.Bind(wx.EVT_BUTTON, self.add_line)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_ctrl, 0, wx.ALL|wx.EXPAND, 5)
sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
bmp = wx.Image("icon.bmp", wx.BITMAP_TYPE_BMP).ConvertToBitmap()
il = wx.ImageList(16,16)
il.Add(bmp)
self.list_ctrl.AssignImageList(il,wx.IMAGE_LIST_SMALL)
line = "Line %s" % self.index
self.list_ctrl.InsertStringItem(self.index, line,-1)
self.list_ctrl.SetStringItem(self.index, 1, "01/19/2010")
self.list_ctrl.SetStringItem(self.index, 2, "USA")
self.index += 1
self.list_ctrl.InsertStringItem(self.index, line,-1)
self.list_ctrl.SetStringItem(self.index, 1, "01/19/2010")
self.list_ctrl.SetStringItem(self.index, 2, "USA")
#self.list_ctrl.SetItemBackgroundColour(self.index,wx.LIGHT_GREY)
self.index += 1
self.list_ctrl.InsertStringItem(self.index, line,-1)
self.list_ctrl.SetStringItem(self.index, 1, "01/19/2010")
self.list_ctrl.SetStringItem(self.index, 2, "USA")
self.index += 1
#----------------------------------------------------------------------
def add_line(self, event):
if self.index > 0:
image = 1
else:
image = -1
line = "Line %s" % self.index
self.list_ctrl.InsertStringItem(self.index, line,image)
self.list_ctrl.SetStringItem(self.index, 1, "01/19/2010")
self.list_ctrl.SetStringItem(self.index, 2, "USA")
self.index += 1
def onMouseOver(self, event):
print "mouse over"
for item in range(self.list_ctrl.GetItemCount()):
self.list_ctrl.SetItemBackgroundColour(item,wx.NullColor)
x = event.GetX()
y = event.GetY()
item, flags = self.list_ctrl.HitTest((x, y))
self.list_ctrl.SetItemBackgroundColour(item,wx.RED)
#self.list_ctrl.RefreshItems(0,2)
event.Skip()
def onMouseLeave(self, event):
print "mouse leave"
for item in range(self.list_ctrl.GetItemCount()):
self.list_ctrl.SetItemBackgroundColour(item,wx.NullColor)
#self.list_ctrl.RefreshItems(0,2)
event.Skip()
'''
def onMouseOver(self, event): #USED to display tooltip on items that cannot be selected
x = event.GetX()
y = event.GetY()
item, flags = self.list_ctrl.HitTest((x, y))
color = self.list_ctrl.GetItemBackgroundColour(item)
if color == wx.NullColor:
self.list_ctrl.SetItemBackgroundColour(item,wx.RED)
elif color == wx.RED:
item = item - 1
color = self.list_ctrl.GetItemBackgroundColour(item)
self.list_ctrl.SetItemBackgroundColour(item,wx.Nu)
'''
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()

I would try grabbing the background color of one of the ListItems when you create the ListCtrl and putting it in a variable:
self.defaultItemColor = someListItem.GetBackgroundColour()
Then use that to change the color back. After you call the item's setter, you sometimes need to call the ListCtrl's SetItem(listItem) method as well. For some reason, setting the background to NullColour doesn't work with the ListCtrl. I discovered that back when I was creating a dark mode for one of my applications. I actually wrote about it here: http://www.blog.pythonlibrary.org/2011/11/05/wxpython-creating-a-dark-mode/

I know that this is a late response but the following works for me using:
self.listCtrl.Bind(wx.EVT_MOTION, self.onMouseOver)
and setting self.previous_item initially to -1
I am using it to highlight the row with the mouse over it and alter the tooltip at the same time.
def onMouseOver(self, event):
x = event.GetX()
y = event.GetY()
self.item, flags = self.listCtrl.HitTest((x, y))
if self.item < 0:
self.listCtrl.SetToolTipString("Colour codes Red - Loaded, Yellow - In Progress, Green - Finished, Blue - Invoiced, White - User defined")
return
if self.item != self.previous_item:
self.old_item = self.previous_item
self.previous_item = self.item
else:
return
bg_colour = self.listCtrl.GetItemBackgroundColour(self.item)
if bg_colour == wx.BLACK or bg_colour == wx.NullColour:
self.listCtrl.SetItemBackgroundColour(self.item,"#3246A8")
self.listCtrl.SetItemBackgroundColour(self.old_item,wx.BLACK)
elif bg_colour == "#3246A8":
self.listCtrl.SetItemBackgroundColour(self.item,wx.BLACK)
self.currentItem = self.item
rowid = self.listCtrl.GetItem(self.currentItem,13)
stat_test = rowid.GetText()
rowid = self.listCtrl.GetItem(self.currentItem,1)
file_test = rowid.GetText()
rowid = self.listCtrl.GetItem(self.currentItem,4)
running_test = rowid.GetText()
if stat_test == "0":
self.listCtrl.SetToolTipString("File currently playing\nRunning time "+running_test)
elif stat_test == "1":
self.listCtrl.SetToolTipString("In Progress\nRunning time "+running_test)
elif stat_test == "2":
self.listCtrl.SetToolTipString("Finished\nRunning time "+running_test)
elif stat_test == "3":
self.listCtrl.SetToolTipString("Invoiced\nRunning time "+running_test)
if file_test == self.file_playing and stat_test == "1":
self.listCtrl.SetToolTipString("File currently playing & In Progress\nRunning time "+running_test)
if file_test == self.file_playing and stat_test == "2":
self.listCtrl.SetToolTipString("File currently playing but Finished\nRunning time "+running_test)
if file_test == self.file_playing and stat_test == "3":
self.listCtrl.SetToolTipString("File currently playing but Invoiced\nRunning time "+running_test)
I Hope it helps someone

This is not "easily" done out of the box, but you should be able to do it with a bit of tinkering.
You'll have to add a mouseover and mouseout event listener to your wxListCtrl object, and then test which item is being hit each time you get a mouseover event. You may be able to cache the coordinates of the list items, but scrolling lists and window resizes could pose a problem if you go this route.
Some code to get you started (not tested, probably won't work and you'd need to add a MyListCtrl to a suitable wx.Frame):
import wx
class MyListCtrl(wx.ListCtrl):
def __init__(self, parent, id):
wx.ListCtrl.__init__(self, parent, id)
self.Bind(wx.EVT_ENTER_WINDOW, self.onMouseOver)
self.Bind(wx.EVT_LEAVE_WINDOW, self.onMouseLeave)
def onMouseOver(self, event):
#Loop through all items and set bgcolor to default, then:
item = self.HitTest(event.GetPosition())
self.SetItemBackgroundColour(item, 'Green')
self.RefreshItems()
event.Skip()
def onMouseLeave(self, event):
#Loop through all items and set bgcolor to default, then:
self.RefreshItems()
event.Skip()

Related

Use rich module in curses panel

It's my first time with curses module and I want make menu with this.
I have found example on internet for make this.
import curses
from curses import panel
class Menu(object):
def __init__(self, items, stdscreen):
self.window = stdscreen.subwin(0, 0)
self.window.keypad(1)
self.panel = panel.new_panel(self.window)
self.panel.hide()
panel.update_panels()
self.position = 0
self.items = items
self.items.append(("exit", "exit"))
def navigate(self, n):
self.position += n
if self.position < 0:
self.position = 0
elif self.position >= len(self.items):
self.position = len(self.items) - 1
def display(self):
self.panel.top()
self.panel.show()
self.window.clear()
while True:
self.window.refresh()
curses.doupdate()
for index, item in enumerate(self.items):
if index == self.position:
mode = curses.A_REVERSE
else:
mode = curses.A_NORMAL
msg = "%d. %s" % (index, item[0])
self.window.addstr(10 + index, 1, msg, mode)
key = self.window.getch()
if key in [curses.KEY_ENTER, ord("\n")]:
if self.position == len(self.items) - 1:
break
else:
self.items[self.position][1]()
elif key == curses.KEY_UP:
self.navigate(-1)
elif key == curses.KEY_DOWN:
self.navigate(1)
self.window.clear()
self.panel.hide()
panel.update_panels()
curses.doupdate()
class MyApp(object):
def __init__(self, stdscreen):
self.screen = stdscreen
curses.curs_set(0)
submenu_items = [("beep", curses.beep), ("flash", curses.flash)]
submenu = Menu(submenu_items, self.screen)
main_menu_items = [
("beep", curses.beep),
("flash", curses.flash),
("submenu", submenu.display),
]
main_menu = Menu(main_menu_items, self.screen)
main_menu.display()
if __name__ == "__main__":
curses.wrapper(MyApp)
It's work pretty good, but I want show a Banner for my title. For this I use rich module and pyfiglet like this :
from rich import print
from rich.text import Text
from rich.panel import Panel
banner = Text(pyfiglet.figlet_format("Title Test", font="bulbhead"), style="bold magenta")
bannerPanel = Panel(Text(pyfiglet.figlet_format("Title Test", font="bulbhead"), style="bold magenta").plain)
I can't add Text object or Panel object with self.window.addstr() because he want a string object.
Is it possible to show bannerPanel or banner above my menu?
Thanks in advance
The only thing I managed to do is add Text().plain.

Resizing custom widget by dragging the edges in pyqt5

I have made a custom widget similar to QPushbutton or label. I would like to let the user resize the widget when the mouse is over the edge of the widget. How can I do this?
(Note: I am not looking for Splitter window)
An image editing software, you have a dedicated "space" for the image, and the user is free to do anything she/he wants within the boundaries of that space. When a widget is placed within a layout-managed container (as it normally should) that can represent multiple issues. Not only you've to implement the whole mouse interaction to resize the widget, but you also need to notify the possible parent widget(s) about the resizing.
That said, what you're trying to achieve can be done, with some caveats.
The following is a very basic implementation of a standard QWidget that is able to resize itself, while notifying its parent widget(s) about the size hint modifications. Note that this is not complete, and its behavior doesn't correctly respond to mouse movements whenever they happen on the top or left edges of the widget. Moreover, while it (could) correctly resize the parent widget(s) while increasing its size, the resize doesn't happen when shrinking. This can theoretically be achieved by setting a minimumSize() and manually calling adjustSize() but, in order to correctly provide all the possible features required by a similar concept, you'll need to do the whole implementation by yourself.
from PyQt5 import QtCore, QtGui, QtWidgets
Left, Right = 1, 2
Top, Bottom = 4, 8
TopLeft = Top|Left
TopRight = Top|Right
BottomRight = Bottom|Right
BottomLeft = Bottom|Left
class ResizableLabel(QtWidgets.QWidget):
resizeMargin = 4
# note that the Left, Top, Right, Bottom constants cannot be used as class
# attributes if you want to use list comprehension for better performance,
# and that's due to the variable scope behavior on Python 3
sections = [x|y for x in (Left, Right) for y in (Top, Bottom)]
cursors = {
Left: QtCore.Qt.SizeHorCursor,
Top|Left: QtCore.Qt.SizeFDiagCursor,
Top: QtCore.Qt.SizeVerCursor,
Top|Right: QtCore.Qt.SizeBDiagCursor,
Right: QtCore.Qt.SizeHorCursor,
Bottom|Right: QtCore.Qt.SizeFDiagCursor,
Bottom: QtCore.Qt.SizeVerCursor,
Bottom|Left: QtCore.Qt.SizeBDiagCursor,
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.startPos = self.section = None
self.rects = {section:QtCore.QRect() for section in self.sections}
# mandatory for cursor updates
self.setMouseTracking(True)
# just for demonstration purposes
background = QtGui.QPixmap(3, 3)
background.fill(QtCore.Qt.transparent)
qp = QtGui.QPainter(background)
pen = QtGui.QPen(QtCore.Qt.darkGray, .5)
qp.setPen(pen)
qp.drawLine(0, 2, 2, 0)
qp.end()
self.background = QtGui.QBrush(background)
def updateCursor(self, pos):
for section, rect in self.rects.items():
if pos in rect:
self.setCursor(self.cursors[section])
self.section = section
return section
self.unsetCursor()
def adjustSize(self):
del self._sizeHint
super().adjustSize()
def minimumSizeHint(self):
try:
return self._sizeHint
except:
return super().minimumSizeHint()
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
if self.updateCursor(event.pos()):
self.startPos = event.pos()
return
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.startPos is not None:
delta = event.pos() - self.startPos
if self.section & Left:
delta.setX(-delta.x())
elif not self.section & (Left|Right):
delta.setX(0)
if self.section & Top:
delta.setY(-delta.y())
elif not self.section & (Top|Bottom):
delta.setY(0)
newSize = QtCore.QSize(self.width() + delta.x(), self.height() + delta.y())
self._sizeHint = newSize
self.startPos = event.pos()
self.updateGeometry()
elif not event.buttons():
self.updateCursor(event.pos())
super().mouseMoveEvent(event)
self.update()
def mouseReleaseEvent(self, event):
super().mouseReleaseEvent(event)
self.updateCursor(event.pos())
self.startPos = self.section = None
self.setMinimumSize(0, 0)
def resizeEvent(self, event):
super().resizeEvent(event)
outRect = self.rect()
inRect = self.rect().adjusted(self.resizeMargin, self.resizeMargin, -self.resizeMargin, -self.resizeMargin)
self.rects[Left] = QtCore.QRect(outRect.left(), inRect.top(), self.resizeMargin, inRect.height())
self.rects[TopLeft] = QtCore.QRect(outRect.topLeft(), inRect.topLeft())
self.rects[Top] = QtCore.QRect(inRect.left(), outRect.top(), inRect.width(), self.resizeMargin)
self.rects[TopRight] = QtCore.QRect(inRect.right(), outRect.top(), self.resizeMargin, self.resizeMargin)
self.rects[Right] = QtCore.QRect(inRect.right(), self.resizeMargin, self.resizeMargin, inRect.height())
self.rects[BottomRight] = QtCore.QRect(inRect.bottomRight(), outRect.bottomRight())
self.rects[Bottom] = QtCore.QRect(inRect.left(), inRect.bottom(), inRect.width(), self.resizeMargin)
self.rects[BottomLeft] = QtCore.QRect(outRect.bottomLeft(), inRect.bottomLeft()).normalized()
# ---- optional, mostly for demonstration purposes ----
def paintEvent(self, event):
super().paintEvent(event)
qp = QtGui.QPainter(self)
if self.underMouse() and self.section:
qp.save()
qp.setPen(QtCore.Qt.lightGray)
qp.setBrush(self.background)
qp.drawRect(self.rect().adjusted(0, 0, -1, -1))
qp.restore()
qp.drawText(self.rect(), QtCore.Qt.AlignCenter, '{}x{}'.format(self.width(), self.height()))
def enterEvent(self, event):
self.update()
def leaveEvent(self, event):
self.update()
class Test(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout(self)
for row in range(3):
for column in range(3):
if (row, column) == (1, 1):
continue
layout.addWidget(QtWidgets.QPushButton(), row, column)
label = ResizableLabel()
layout.addWidget(label, 1, 1)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Test()
w.show()
sys.exit(app.exec_())

wxPython Program Scan

I am trying to get a better understanding of how wxPython 'scans'.
Please see my code below:
import os
import wx
from time import sleep
NoFilesToCombine = 0
class PDFFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title, size=(400,400))
panel = wx.Panel(self)
self.Show()
try: #Set values of PDFNoConfirmed to zero on 1st initialisation
if PDFNoConfimed != 0:
None
except UnboundLocalError:
PDFNoConfimed = 0
try: #Set values of PDFNoConfirmed to zero on 1st initialisation
if PDFNoConfirmedInitially != 0:
None
except UnboundLocalError:
PDFNoConfirmedInitially = 0
while ((PDFNoConfimed == 0) and (PDFNoConfirmedInitially == 0)):
while PDFNoConfirmedInitially == 0:
BoxInputNo = wx.NumberEntryDialog(panel, "So You Want To Combine PDF Files?", "How Many?", "Please Enter", 0, 2, 20)
if BoxInputNo.ShowModal() == wx.ID_OK: #NumberEntryDialog Pressed OK
NoFilesToCombine = BoxInputNo.GetValue()
PDFNoConfirmedInitially = 1
elif BoxInputNo.ShowModal() == wx.ID_CANCEL:
exit()
print(NoFilesToCombine)
ConfirmationLabel = wx.StaticText(panel, -1, label="You Have Selected " + str(NoFilesToCombine) + " Files To Combine, Is This Right?", pos=(20, 100))
ConfirmationBoxConfirm = wx.ToggleButton(panel, label="Confirm", pos=(20, 200))
ConfirmationBoxCancel = wx.ToggleButton(panel, label="Cancel", pos=(180, 200))
#if ConfirmationBoxConfirm.GetValue() == 1:
# exit()
if ConfirmationBoxCancel.GetValue() == 1:
PDFNoConfirmedInitially = 0
app = wx.App()
frame = PDFFrame(None, title="Robs PDF Combiner Application")
app.MainLoop()
Now this is a work in progress so it obviously isn't complete. However what I'm trying to accomplish with the above is:
Display a number entry popup. If user presses 'cancel' exit the application (this works but needs 2 presses for some reason?). If press OK, then:
Display the number entered in step 1, with 2 additional buttons. The 'confirm' doesn't do anything as yet, but the 'cancel' should take you back to step 1. (by resetting the PDFNoConfirmedInitially flag).
Now step 2 doesn't work. When I debug it almost appears as though the PDFFrameonly gets scanned once. My presumably false assumption being that this would be continually scanned due to app.MainLoop() continually scanning wx.App() which in turn would call the child frame?
Help/ pointers/ deeper understanding always appreciated,
Thanks
Rob
1) ShowModal() shows dialog window and you use it two times
if BoxInputNo.ShowModal() == wx.ID_OK:
and
elif BoxInputNo.ShowModal() == wx.ID_CANCEL:
so it shows your window two times.
And only at second time you check wx.ID_CANCEL.
You should run it only once and check its result
result = BoxInputNo.ShowModal()
if result == wx.ID_OK:
pass
elif result == wx.ID_CANCEL:
self.Close()
return
2) You have to assign function to button and this function should reset variable and show dialog window again. But I think wx.Button could be better then wx.ToggleButton
ConfirmationBoxCancel = wx.Button(panel, label="Cancel", pos=(180, 200))
ConfirmationBoxCancel.Bind(wx.EVT_BUTTON, self.on_button_cancel)
def on_button_cancel(self, event):
#print('event:', event)
pass
# reset variable and show dialog window
Frankly I don't understand some of your variables. Maybe if you use True/False instead of 0/1 then they will be more readable. But main problem for me are while loops. GUI frameworks (wxPython, tkinter, PyQt, etc) should run only one loop - Mainloop(). Any other loop may block Mainloop() and GUI will freeze.
I created own version without any while loop but I don't know if it resolve all problems
import wx
class PDFFrame(wx.Frame):
def __init__(self, parent, title):
super().__init__(parent, -1, title, size=(400,400))
self.panel = wx.Panel(self)
self.Show()
# show dialog at start
if self.show_dialog(self.panel):
# create StaticLabel and buttons
self.show_confirmation(self.panel)
else:
# close main window and program
self.Close()
def show_dialog(self, panel):
"""show dialog window"""
global no_files_to_combine
box_input_no = wx.NumberEntryDialog(panel, "So You Want To Combine PDF Files?", "How Many?", "Please Enter", 0, 2, 20)
result = box_input_no.ShowModal()
if result == wx.ID_OK: #NumberEntryDialog Pressed OK
no_files_to_combine = box_input_no.GetValue()
return True
elif result == wx.ID_CANCEL:
print('exit')
return False
def show_confirmation(self, panel):
"""create StaticLabel and buttons"""
self.confirmation_label = wx.StaticText(self.panel, -1, label="You Have Selected {} Files To Combine, Is This Right?".format(no_files_to_combine), pos=(20, 100))
self.confirmation_box_confirm = wx.Button(self.panel, label="Confirm", pos=(20, 200))
self.confirmation_box_cancel = wx.Button(self.panel, label="Cancel", pos=(180, 200))
# assign function
self.confirmation_box_confirm.Bind(wx.EVT_BUTTON, self.on_button_confirm)
self.confirmation_box_cancel.Bind(wx.EVT_BUTTON, self.on_button_cancel)
def update_confirmation(self):
"""update existing StaticLabel"""
self.confirmation_label.SetLabel("You Have Selected {} Files To Combine, Is This Right?".format(no_files_to_combine))
def on_button_cancel(self, event):
"""run when pressed `Cancel` button"""
#print('event:', event)
# without `GetValue()`
if self.show_dialog(self.panel):
# update existing StaticLabel
self.update_confirmation()
else:
# close main window and program
self.Close()
def on_button_confirm(self, event):
"""run when pressed `Confirn` button"""
#print('event:', event)
# close main window and program
self.Close()
# --- main ---
no_files_to_combine = 0
app = wx.App()
frame = PDFFrame(None, title="Robs PDF Combiner Application")
app.MainLoop()

wxpython ClientDC on loop

I have a requirement to display sampled video frames using wx.ClientDC whenever a button is pressed. The code I wrote detects the button press and goes into a loop, reading the frames but does not display it. I am reading frames using OpenCV. I am unable to identify where I am going wrong.
class VideoFrame(gui.wxVideoFrame):
def __init__(self, parent):
self.parent = parent
gui.wxVideoFrame.__init__(self, parent)
self.webcam = WebcamFeed() #opencv Webcam class to feed frames.
if not self.webcam.has_webcam():
print ('Webcam has not been detected.')
self.Close()
self.STATE_RUNNING = 1
self.STATE_CLOSING = 2
self.state = self.STATE_RUNNING
self.SetSize(wx.Size(1280, 720))
self.dc = wx.ClientDC(self.panel)
self.verify.Bind(wx.EVT_BUTTON, self.onVerify) #the button
self.Bind(wx.EVT_CLOSE, self.onClose)
def onClose(self, event):
if not self.state == self.STATE_CLOSING:
self.state = self.STATE_CLOSING
self.Destroy()
def onVerify(self, event):
#self.verify.Enable(True)
#i = 0
while i<= 100:
frame = self.webcam.get_image()#reads image successfully
image = wx.Bitmap.FromBuffer(640, 480, frame)
self.dc.Clear()
self.dc.DrawBitmap(image, 0, 0)
#i += 1
#print(i)
def onEraseBackground(self, event):
return

How to remove icursor from Tkinter canvas text item?

I'm following along with Effbot's Tkinter page here:
http://effbot.org/zone/editing-canvas-text-items.htm
The trouble I'm having is that after inserting the icursor, I can't seem to get it to go away!
How do I stop editing altogether?
As for examples, the one from the linked page will work:
# File: canvas-editing-example-1.py
#
# editing canvas items
#
# fredrik lundh, december 1998
#
# fredrik#pythonware.com
# http://www.pythonware.com
#
from tkinter import *
#Change to Tkinter to use python 2.x series
class MyCanvas(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.canvas = Canvas(self)
self.canvas.pack(fill=BOTH, expand=1)
# standard bindings
self.canvas.bind("<Double-Button-1>", self.set_focus)
self.canvas.bind("<Button-1>", self.set_cursor)
self.canvas.bind("<Key>", self.handle_key)
# add a few items to the canvas
self.canvas.create_text(50, 50, text="hello")
self.canvas.create_text(50, 100, text="world")
def highlight(self, item):
# mark focused item. note that this code recreates the
# rectangle for each update, but that's fast enough for
# this case.
bbox = self.canvas.bbox(item)
self.canvas.delete("highlight")
if bbox:
i = self.canvas.create_rectangle(
bbox, fill="white",
tag="highlight"
)
self.canvas.lower(i, item)
def has_focus(self):
return self.canvas.focus()
def has_selection(self):
# hack to work around bug in Tkinter 1.101 (Python 1.5.1)
return self.canvas.tk.call(self.canvas._w, 'select', 'item')
def set_focus(self, event):
if self.canvas.type(CURRENT) != "text":
return
self.highlight(CURRENT)
# move focus to item
self.canvas.focus_set() # move focus to canvas
self.canvas.focus(CURRENT) # set focus to text item
self.canvas.select_from(CURRENT, 0)
self.canvas.select_to(CURRENT, END)
def set_cursor(self, event):
# move insertion cursor
item = self.has_focus()
if not item:
return # or do something else
# translate to the canvas coordinate system
x = self.canvas.canvasx(event.x)
y = self.canvas.canvasy(event.y)
self.canvas.icursor(item, "#%d,%d" % (x, y))
self.canvas.select_clear()
def handle_key(self, event):
# widget-wide key dispatcher
item = self.has_focus()
if not item:
return
insert = self.canvas.index(item, INSERT)
if event.char >= " ":
# printable character
if self.has_selection():
self.canvas.dchars(item, SEL_FIRST, SEL_LAST)
self.canvas.select_clear()
self.canvas.insert(item, "insert", event.char)
self.highlight(item)
elif event.keysym == "BackSpace":
if self.has_selection():
self.canvas.dchars(item, SEL_FIRST, SEL_LAST)
self.canvas.select_clear()
else:
if insert > 0:
self.canvas.dchars(item, insert-1, insert)
self.highlight(item)
# navigation
elif event.keysym == "Home":
self.canvas.icursor(item, 0)
self.canvas.select_clear()
elif event.keysym == "End":
self.canvas.icursor(item, END)
self.canvas.select_clear()
elif event.keysym == "Right":
self.canvas.icursor(item, insert+1)
self.canvas.select_clear()
elif event.keysym == "Left":
self.canvas.icursor(item, insert-1)
self.canvas.select_clear()
else:
pass # print event.keysym
# try it out (double-click on a text to enable editing)
c = MyCanvas(Tk())
c.pack()
mainloop()
After you double click on one of the items to be edited, I cannot make the cursor go away; I've tried moving focus and setting the index to -1, but neither seems to work.
self.canvas.focus("")
To remove focus from the item, call this method with an empty string. Reference
you can add the following
self.canvas.focus_set() # move focus to canvas window
self.canvas.focus("") # remove focus from the current item that has it
To
def set_focus(self, event):
if self.canvas.type(CURRENT) != "text":
#Here
return
So when the user double-clicks on any part or item of the canvas that isn't created by canvas.create_text the focus is removed from the current "text" item, hence stopping the edit.
Plus you can add
self.canvas.delete("highlight")
to remove the white rectangle around the text, when focus is removed.

Categories

Resources