I try to set Item Boundary Line in QListView.
When the mouse over the item, the line shows up and when the mouse leave the item, the line back to normal. That's the what I want.
So, I use QStyledItemDelegate and it seems do that, it is not proper.
class PixmapItemDelegate(QtGui.QStyledItemDelegate):
def paint(self, painter, option, index):
painter.save()
if (option.state & QtGui.QStyle.State_MouseOver):
pen = QtGui.QPen(QtCore.Qt.yellow)
else:
pen = QtGui.QPen(QtCore.Qt.transparent)
pen.setWidth(2)
painter.setPen(pen)
painter.setBrush(QtGui.QBrush(QtCore.Qt.transparent))
painter.drawRect(option.rect)
painter.restore()
super(PixmapItemDelegate, self).paint(painter, option, index)
The code is above.
If I select the item, it screwed up.
Selected item has a boundary and doesn't disappear.
How can I fix it?
Try making sure that the item state does not have selected as well:
if ( option.state & QtGui.QStyle.State_MouseOver and \
not option.state & QtGui.QStyle.State_Selected ):
Related
I am working on an editable tkinter.ttk.Treeview subclass. For editing I need to place the edit widget on top of a choosen "cell" (list row/column). To get the proper coordinates, there is the Treeview.bbox() method.
If the row to be edited is not in view (collapsed or scrolled away), I cannot get its bbox obviously. Per the docs, the see() method is meant to bring an item into view in such a case.
Example Code:
from tkinter import Tk, Button
from tkinter.ttk import Treeview
root = Tk()
tv = Treeview(root)
tv.pack()
iids = [tv.insert("", "end", text=f"item {n}") for n in range(20)]
# can only get bbox once everything is on screen.
n = [0]
def show_bbox():
n[0] += 1
iid = iids[n[0]]
b = tv.bbox(iid)
if not b:
# If not visible, scroll into view and try again
tv.see(iid)
# ... but this still doesn't return a valid bbox!?
b = tv.bbox(iid)
print(f"bbox of item {n}", b)
btn = Button(root, text="bbox", command=show_bbox)
btn.pack(side="bottom")
root.mainloop()
(start, then click the button until you reach an invisible item)
The second tv.bbox() call ought to return a valid bbox, but still returns empty string. Apparently see doesnt work immediately, but enqeues the viewport change into the event queue somehow. So my code cannot just proceed synchronously as it seems.
How to solve this? Can see() be made to work immediately? If not, is there another workaround?
The problem is that even after calling see, the item isn't visible (and thus, doesn't have a bounding box) until it is literally drawn on the screen.
A simple solution is to call tv.update_idletasks() immediately after calling tv.see(), which should cause the display to refresh.
Another solution is to use tv.after to schedule the display of the box (or the overlaying of an entry widget) to happen after mainloop has a chance to refresh the window.
def print_bbox(iid):
bbox = tv.bbox(iid)
print(f"bbox of item {iid}", bbox)
def show_bbox():
n[0] += 1
iid = iids[n[0]]
tv.see(iid)
tv.after_idle(print_bbox, iid)
I'm looking for a way to add a shortcut chooser widget on a dialog with Python and GTK+3.
I tried to search through all available widgets and don't seem to find any out-of-the-box solution. What would be my best call in that respect? Should I use a GtkEntry and intercept a key press?
Even though it seems like a pretty common use case, I failed to find any working example of that.
There is no out-of-the-box solution, but you can probably find an example to adapt in the Keyboard panel of GNOME Control Center.
I have implemented this myself using a separate dialog. There's a regular button displaying the current assignment, which, when clicked, opens a KeyboardShortcutDialog implemented as follows:
class KeyboardShortcutDialog(Gtk.Dialog):
"""Dialog that allows to grab a keyboard shortcut."""
def __init__(self, parent):
Gtk.Dialog.__init__(self, _("Keyboard shortcut"), parent, 0)
self.shortcut = None
self.set_border_width(32)
# Add a label
label = Gtk.Label(xalign=0.5, yalign=0.5)
label.set_markup(
_('Press the desired key combination, <b>Backspace</b> to remove any shortcut, or <b>Esc</b> to cancel.'))
self.get_content_area().pack_start(label, True, True, 0)
self.connect('key-press-event', self.on_key_press)
self.show_all()
def on_key_press(self, widget, event: Gdk.EventKey):
"""Signal handler: key pressed."""
keyval = event.get_keyval()[1]
name = Gdk.keyval_name(keyval)
# For some reason event.is_modifier == 0 here, even for modifier keys, so we need to resort to checking by name
if name not in [
'Shift_L', 'Shift_R', 'Control_L', 'Control_R', 'Meta_L', 'Meta_R', 'Alt_L', 'Alt_R', 'Super_L',
'Super_R', 'Hyper_L', 'Hyper_R']:
logging.debug('Key pressed: state=%s, keyval=%d', event.state, keyval)
self.shortcut = (
keyval,
event.state &
(Gdk.ModifierType.META_MASK | Gdk.ModifierType.SUPER_MASK | Gdk.ModifierType.HYPER_MASK |
Gdk.ModifierType.SHIFT_MASK | Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK))
self.response(Gtk.ResponseType.ACCEPT)
return True
def run(self):
"""Show the dialog and block until it's closed.
:return: tuple (keyval, state) of the key captured or None if the dialog has been closed."""
super().run()
return self.shortcut
The dialog's run() method returns a tuple specifying the pressed key combination.
I have a listctrl,
self.list = wx.ListCtrl(panel, -1, style=wx.LC_REPORT | wx.LC_NO_HEADER)
self.list.InsertColumn(col=0, heading='', format=wx.LIST_FORMAT_CENTER, width=150)
self.list.InsertColumn(col=1, heading='', format=wx.LIST_FORMAT_CENTER, width=450)
for person in people:
#this is the issue right here
index = self.list.InsertItem(0, person.age) #one line to insert an item and get the index, and the next line to update the item at that index... two lines to actually put a full entry in.
self.list.SetItem(index=index, column=1, label=person.name)
This works fine setting up the listctrl initially in the constructor, but what if I want to dynamically add/remove items in the listctrl at runtime?
I've come across wx.CallAfter, wx.CallLater, startWorker (from wx.lib.delayedresult), and wx.Timer
If you look at the example above the issue is that I've got one line that inserts the item, and another line that updates the item to have the correct name on the item that was just inserted. So if I've got threads that are taking turns removing and adding items to the listctrl, if I insert an item and another thread inserts an item at the same time, the index that I just got won't be relevant for updating. i.e. I need an atomic operation for inserting an item that includes inserting both the person's age and the person's name. So my first question is, is there a way to insert all information of a list item in one line?
If I cannot do that, then my next question is how could I accomplish the prescribed behavior? For example, suppose there are threads randomly coloring the top row, adding, and deleting:
self.color_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.item_colorizer, self.color_timer)
self.red_shown = True
self.color_timer.Start(500)
self.delete_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.item_deleter, self.delete_timer)
self.delete_timer.Start(500)
self.adder_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.item_queuer, self.adder_timer)
self.adder_timer.Start(400)
Then here are my methods that are used to add people, delete people, and color the top row:
def item_queuer(self, event):
startWorker(consumer=self.item_adder,
workerFn=self.person_generator)
def person_generator(self):
return {'person':random.choice(people)}
def item_adder(self, result):
res = result.get()
person = res['person']
wx.CallAfter(self.list.InsertItem, 0, person.name) # this is the problem right here. If another thread does a color swap in between this line and the next, then this won't work.
wx.CallAfter(self.list.SetItem, index=0, column=1, label=person.name)
def item_deleter(self, event):
wx.CallAfter(self.list.DeleteItem, 0)
def item_colorizer(self, event):
if self.color_timer.IsRunning():
if not self.red_shown:
wx.CallAfter(self.list.SetItemBackgroundColour, 0, wx.RED)
wx.CallAfter(self.list.SetItemTextColour, 0, wx.WHITE)
self.red_shown = True
else:
wx.CallAfter(self.list.SetItemBackgroundColour, 0, wx.BLUE)
wx.CallAfter(self.list.SetItemTextColour, 0, wx.BLACK)
self.red_shown = False
What actually happens when I run this is that I end up having rows where the person is partially inserted (just the age), and the color changes before the name is inserted. I've noticed that the InsertItem method on the listctrl is overloaded and offers one signature where I can insert a ListItem, but I cannot get that to work either.
item1 = wx.ListItem()
item1.SetBackgroundColour(wx.GREEN)
item1.SetColumn(0)
item1.SetText(32)
item1.SetColumn(1)
item1.SetText('John')
self.list.InsertItem(item1)
wx._core.wxAssertionError: C++ assertion "rv != -1" failed at ....\src\msw\listctrl.cpp(1730) in wxListCtrl::InsertItem(): failed to insert an item in wxListCtrl
I am working on a text editor using tkinter in python 3. I am having an issue with the custom text field class that I am using to attatch line numbers. It is from the answer from Brian Oakley to this question. Here is the code for the class:
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
tk.Text.__init__(self, *args, **kwargs)
# create a proxy for the underlying widget
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self._proxy)
def _proxy(self, *args):
# let the actual widget perform the requested action
cmd = (self._orig,) + args
result = self.tk.call(cmd)
# generate an event if something was added or deleted,
# or the cursor position changed
if (args[0] in ("insert", "replace", "delete") or
args[0:3] == ("mark", "set", "insert") or
args[0:2] == ("xview", "moveto") or
args[0:2] == ("xview", "scroll") or
args[0:2] == ("yview", "moveto") or
args[0:2] == ("yview", "scroll")
):
self.event_generate("<<Change>>", when = "tail")
# return what the actual widget returned
return result
Here is the code for the cut function:
def cutSelected(event=None):
textField.event_generate("<<Cut>>")
...
# Add cut to the edit menu in the menu bar
editMenu.add_command(label = "Cut", command = cutSelected, accelerator = "Ctrl+X")
...
# setting up the keyboard shortcut for the cut function
textField.bind("<Control-x>", cutSelected)
textField.bind("<Control-X>", cutSelected)
Here is the code for the paste function:
def paste(event=None):
textField.event_generate("<<Paste>>")
...
# Add paste to the edit menu in the menu bar
editMenu.add_command(label = "Paste", command = paste, accelerator = "Ctrl+V")
...
# setting up the keyboard shortcut for the paste function
textField.bind("<Control-v>", paste)
textField.bind("<Control-V>", paste)
Here is the error traceback (except for the credentials from the paths):
Traceback (most recent call last):
File "C:\Users\me\mydocuments\Programming\myeditor\main.py", line 457, in <module>
root.mainloop()
File "C:\Users\me\AppData\Local\Programs\Python\Python37-
32\lib\tkinter\__init__.py", line 1283, in mainloop
self.tk.mainloop(n)
File "C:\Users\me\mydocuments\Programming\myeditor\main.py", line 58, in _proxy
result = self.tk.call(cmd)
_tkinter.TclError: text doesn't contain any characters tagged with "sel"
When I try to paste the text via the keyboard shortcut, it doesn't give me an error, it just pastes my text twice on the same line. (ex: test gets pasted as testtest) but when I do it via the edit menu in the menu bar, I get the error shown above.
Cut works just fine when I do it from the edit menu but when I try to cut text via the keyboard shortcut, I get the error.
I have been trying to fix these issues for almost a week now and the closest that I have come to an answer was from this question on stack overrun. I tried that code and had the exact same problem. I am not sure what else to do so any help at all is greatly appreciated.
_tkinter.TclError: text doesn't contain any characters tagged with "sel"
I don't think I have a proper fix, but I have something that prevents your code from crashing.
Replace this:
result = self.tk.call(cmd)
... with this:
try:
result = self.tk.call(cmd)
except Exception:
return None
It may mask other things that should legitimately throw an error, but at the moment it's the best solution I have.
When I try to paste the text via the keyboard shortcut, it doesn't give me an error, it just pastes my text twice on the same line.
It's probably doing it twice because tkinter already has a default binding for cut and paste. What's probably happening is that your binding is firing and then the built-in binding is firing. If you want to prevent the built-in binding from taking effect you need to return "break" from paste. I'm only guessing at this point since you didn't provide a [mcve] for that.
{Edit: the answer by Bryan Oakley in the suggested duplicate question enter link description here a) fires a response on change to the array variable (arrayvar.trace mode="w"), and I need it triggered on FocusOut, as described in my original question; b) works for Python 2, but I'm having trouble converting it to work in Python 3.5. I'm currently using his and pyfunc's answers as leads and trying to figure out a similar solution using a FocusOut event.}
I am working on a tkinter GUI that lets a user select a particular type of calculation, using a pair of radio button lists. Based on the selections, a tool bar is populated with multiple modular entry widgets, one for each variable the calculation requires. The goal is to have the numerical entry values passed to the model, which will return data to be graphed on a canvas or matplotlib widget.
My question is: what typical strategy is used for gathering and continually refreshing values from multiple widgets, in order to update displays and to pass them on to the model? The trick here is that there will be a large number of possible calculation types, each with their own toolbar. I'd like the active toolbar to be "aware" of its contents, and ping the model on every change to a widget entry.
I think the widgets and the toolbar would have to be classes, where the toolbar can query each widget for a fresh copy of its entry values when a change is detected, and store them as some collection that is passed to the model. I'm not entirely sure how it can track changes to the widgets. Using a "validate='focusout' " validation on the entry widget (e.g. as in
this validation reference )
suggests itself, but I already use "validate='key' " to limit all entries to numbers. I don't want to use "validate=all" and piggyback onto it because I don't want to continually ask the model to do a lengthy calculation on every keypress.
I'm new to GUI programming, however, so I may be barking up the wrong tree. I'm sure there must be a standard design pattern to address this, but I haven't found it.
Below is a screenshot of a mockup to illustrate what I want the GUI to do. The Task radiobutton controls which secondary button menu appears below. The selection in the second menu populates the top toolbar with the necessary entry widgets.
The following code does (mostly) what I want. The ToolBar frame objects will store the values from its contained widgets, and call the appropriate model as needed. The VarBox objects are Entry widgets with extra functionality. Hitting Tab or Return refreshes the data stored in the ToolBar dictionary, tells the ToolBar to send data to the model, and shifts focus to the next VarBox widget.
from tkinter import *
# Actual model would be imported. "Dummy" model for testing below.
def dummy_model(dic):
"""
A "dummy" model for testing the ability for a toolbar to ping the model.
Argument:
-dic: a dictionary whose values are numbers.
Result:
-prints the sum of dic's values.
"""
total = 0
for value in dic.values():
total += value
print('The total of the entries is: ', total)
class ToolBar(Frame):
"""
A frame object that contains entry widgets, a dictionary of
their current contents, and a function to call the appropriate model.
"""
def __init__(self, parent=None, **options):
Frame.__init__(self, parent, **options)
self.vars = {}
def call_model(self):
print('Sending to dummy_model: ', self.vars)
dummy_model(self.vars)
class VarBox(Frame):
"""
A customized Frame containing a numerical entry box
Arguments:
-name: Name of the variable; appears above the entry box
-default: default value in entry
"""
def __init__(self, parent=None, name='', default=0.00, **options):
Frame.__init__(self, parent, relief=RIDGE, borderwidth=1, **options)
Label(self, text=name).pack(side=TOP)
self.widgetName = name # will be key in dictionary
# Entries will be limited to numerical
ent = Entry(self, validate='key') # check for number on keypress
ent.pack(side=TOP, fill=X)
self.value = StringVar()
ent.config(textvariable=self.value)
self.value.set(str(default))
ent.bind('<Return>', lambda event: self.to_dict(event))
ent.bind('<FocusOut>', lambda event: self.to_dict(event))
# check on each keypress if new result will be a number
ent['validatecommand'] = (self.register(self.is_number), '%P')
# sound 'bell' if bad keypress
ent['invalidcommand'] = 'bell'
#staticmethod
def is_number(entry):
"""
tests to see if entry is acceptable (either empty, or able to be
converted to a float.)
"""
if not entry:
return True # Empty string: OK if entire entry deleted
try:
float(entry)
return True
except ValueError:
return False
def to_dict(self, event):
"""
On event: Records widget's status to the container's dictionary of
values, fills the entry with 0.00 if it was empty, tells the container
to send data to the model, and shifts focus to the next entry box (after
Return or Tab).
"""
if not self.value.get(): # if entry left blank,
self.value.set(0.00) # fill it with zero
# Add the widget's status to the container's dictionary
self.master.vars[self.widgetName] = float(self.value.get())
self.master.call_model()
event.widget.tk_focusNext().focus()
root = Tk() # create app window
BarParentFrame = ToolBar(root) # holds individual toolbar frames
BarParentFrame.pack(side=TOP)
BarParentFrame.widgetName = 'BarParentFrame'
# Pad out rest of window for visual effect
SpaceFiller = Canvas(root, width=800, height=600, bg='beige')
SpaceFiller.pack(expand=YES, fill=BOTH)
Label(BarParentFrame, text='placeholder').pack(expand=NO, fill=X)
A = VarBox(BarParentFrame, name='A', default=5.00)
A.pack(side=LEFT)
B = VarBox(BarParentFrame, name='B', default=3.00)
B.pack(side=LEFT)
root.mainloop()