passing data to tkinter dnd (dragndrop) vs event binding problem - python

tkinterDnD 2.8 - I managed to get it to detect a drop onto a tkinter Entry widget and event.data does give up the dropped text, but I have an array of entry widgets. I used Page gui builder to get the boilerplate code. Page generated code for a LabelFrame that contains the Entry and other widgets and I just looped the code to get a bunch of them.
My question is how to go about passing the widget list index to the tkinterDnD drop handler. I get all kinds of weird errors or just no joy when I try using lambda function tricks and maybe the only answer is to use the good old event binds for this. You'll see what I mean below (second code box) when I show my output.
Also note that a couple code comments have questions about things that didn't work and maybe some implied comments so I'd appreciate comments on those, too.
class Toplevel1:
def __init__(self, top=None):
self.section_Labelframe1s = []
self.section_Entry1s = []
#self.sz_text = [] # ugly hack attempt - no joy
for i in range(0, 8):
self.section_Labelframe1s.append(None)
self.section_Labelframe1s[i] = tk.LabelFrame(top)
# blah set attributes for LabelFrame[i]
self.section_Entry1s.append(None)
self.section_Labelframe1s[i] = tk.LabelFrame(top)
# self.sz_text.append # and what ever i did with it - perhaps setting it to my index i so I could do an
# add_trace which python3 doesn't seem to recognize
# blah
# Entry widgets don't have a command attribute (oh joy, so I gave it a name)
self.section_Entry1s[i] = tk.Entry(self.section_Labelframe1s[i], name='dnd_demo_entry_' + str(i))
# naming it didn't help or maybe it caused the prob to which I hinted at at the end
# which I can't duplicate now, sigh.
# previous attempts:
# self.section_Entry1s[i].configure(command=lambda i=i: dnd_demo_support.drop(i))
# RESULT: no attribute "command" in tk.Entry -- BUGGERS!
# self.section_Entry1s[i].configure(textvariable = self.sz_text[i])
# IndexError: list index out of range.
# Index error? What? WHY?
# more widgets
# and I get a nice window with 8 of the above and I want to do something when I drag some text into the Entry widget, so:
self.section_Entry1s[i].drop_target_register(DND_TEXT)
self.section_Entry1s[i].dnd_bind('<<Drop>>', lambda i=i : dnd_demo_support.drop(i))
So at this point, dropping text onto the Entry widget works. I handle the drop event in the dnd_demo_support.py file that Page generated where I also call a function print_event_info() which does:
print('Widget:', event.widget, '(type: %s)' % type(event.widget))
# which prints
Widget: .!labelframe.dnd_demo_entry_0 (type: <class 'tkinter.Entry'>)
All that would be good except rather than looking for the "0" at the end of the above, I'd like to just pass the index of the widget and now it gets nearly hair-pulling ugly for me.
The drop handler is like:
def drop(event):
global w, top_level, root # w is key. it's from Page and it comes from the whole w = tk.Toplevel (root) shebang (no. not "#!" lol)
if event.data:
print('Dropped data:\n', event.data)
print('event.widget: ', event.widget)
if event.widget == w.section_Entry1s[0]: # i'm still forcing a 0 index to the Event widget while degubbing this mess
I can't find my other problem, but something I tried caused the script to print a line for every i in the for loop. Dang I wish I could reproduce that. For now, I'd like to find a way to get my i index in my drop(event) callback without having to pluck it from event.widget which as I said previously, gives me:
.!labelframe.dnd_demo_entry_0
and once I unforce the index, I end up with
self.section_Entry1s[i].dnd_bind('<<Drop>>', lambda i=i : dnd_demo_support.drop(i))
# in the GUI creation (?? no sleep) __init__ part
# and the drop handler - something like:
def drop(event, i):
#blah
TypeError: drop() missing 1 required positional argument: 'i'
ARRGGGHHHHH!!!! see what i mean 8) I messed with that positional arg thing forfreakin' too long. No fix using lambda self, i : func nor lambda event, i, : func because obviously, event isn't defined in the first statement. Maybe using self.something is the ticket and maybe that's how I got the aforementioned effect that I couldn't reproduce which gave me 8 print outputs. I'll hve to try that after no sleep/work/etc
Thanks again for your time and any help ^3

Not sure why this didn't work the first time I tried it:
Change to:
lambda i=i: dnd_demo_support.drop(i) => lambda event, i=i: dnd_demo_support.drop(event, i)

Related

getting event late in tkinter listbox [duplicate]

This question already has an answer here:
Python tkinter listbox bind on <Button-1> only works on second click
(1 answer)
Closed 1 year ago.
I was creating simple listbox containing numbers from 0 to 9. I wanted to print number when it get clicked so i bind list box with Button-1. Im facing problem that is when ever i select any number and try to get its location using list_box.curselection() it does not print any thing(return empty tuple), if i click on on any other number then it print previous selected number. I want to get current selected number.
from tkinter import *
root = Tk()
root.title("test listbox")
list_box = Listbox(root)
list_box.pack()
for i in range(0,10):
list_box.insert("end",i)
def def_fun(event):
print(list_box.curselection())
list_box.bind("<Button-1>",def_fun)
root.mainloop()
You don't have to bind to <Button-1> or anything, there is a virtual event with Listbox that you can use here:
def def_fun(event):
print(event.widget.curselection()) # The widget that triggers the event is event.widget
list_box.bind("<<ListboxSelect>>",def_fun) # Gets triggered each time something is selected
Just in case you are wondering why the Button-1 did not work, it is because there is a delay, the delay might be due to binding order, you can read more about it here but here is a gist:
In the default case, your binding on <Key> happens before the class binding, and it is the class binding where the text is actually inserted into the widget. That is why your binding always seems to be one character behind.
Change the binding to releasing mouse button, this will also be more user friendly (for example if they accidentally clicked on a selection they didn't want to select, they can move their mouse to a one they want and only releasing will call the function):
from tkinter import Tk, Listbox
def def_fun(event=None):
print(list_box.curselection())
root = Tk()
root.title("test listbox")
list_box = Listbox(root)
list_box.pack()
for i in range(0, 10):
list_box.insert("end", i)
list_box.bind("<ButtonRelease-1>", def_fun)
root.mainloop()
Another option if you want to call the function on select is either use #CoolCloud answer or you can also set a delay like this (although it will most certainly work in 99.9% of cases, there might be a case where it doesn't):
list_box.bind("<Button-1>", lambda e: root.after(10, def_fun))
The reason is that .curselection() gets the current selection but Button-1 is triggered before anything gets selected so it will print the previous selection because that is what was selected before and where the current selection is now, and then immediately after this, it will move the current selection to the item you clicked.
Important (because it may cause hard-to-debug issues):
I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.
Also:
I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case, class names in CapitalCase. Don't have space around = if it is used as a part of keyword argument (func(arg='value')) but use if it is used for assigning a value (variable = 'some value'). Have two blank lines around function and class declarations.

Getting, Storing, Setting and Modifying Transform Attributes through PyMel

I'm working on something that gets and stores the transforms of an object moved by the user and then allows the user to click a button to return to the values set by the user.
So far, I have figured out how to get the attribute, and set it. However, I can only get and set once. Is there a way to do this multiple times within the script running once? Or do I have to keep rerunning the script? This is a vital question for me get crystal clear.
basically:
btn1 = button(label="Get x Shape", parent = layout, command ='GetPressed()')
btn2 = button(label="Set x Shape", parent = layout, command ='SetPressed()')
def GetPressed():
print gx #to see value
gx = PyNode( 'object').tx.get() #to get the attr
def SetPressed():
PyNode('object').tx.set(gx) #set the attr???
I'm not 100% on how to do this correctly, or if I'm going the right way?
Thanks
You aren't passing the variable gx so SetPressed() will fail if you run it as written(it might work sporadically if you tried executing the gx = ... line directly in the listener before running the whole thing -- but it's going to be erratic). You'll need to provide a value in your SetPressed() function so the set operation has something to work with.
As an aside, using string names to invoke your button functions isn't a good way to go -- you code will work when executed from the listener but will not work if bundled into a function: when you use a string name for the functions Maya will only find them if they live the the global namespace -- that's where your listener commands go but it's hard to reach from other functions.
Here's a minimal example of how to do this by keeping all of the functions and variables inside another function:
import maya.cmds as cmds
import pymel.core as pm
def example_window():
# make the UI
with pm.window(title = 'example') as w:
with pm.rowLayout(nc =3 ) as cs:
field = pm.floatFieldGrp(label = 'value', nf=3)
get_button = pm.button('get')
set_button = pm.button('set')
# define these after the UI is made, so they inherit the names
# of the UI elements
def get_command(_):
sel = pm.ls(sl=True)
if not sel:
cmds.warning("nothing selected")
return
value = sel[0].t.get() + [0]
pm.floatFieldGrp(field, e=True, v1= value[0], v2 = value[1], v3 = value[2])
def set_command(_):
sel = pm.ls(sl=True)
if not sel:
cmds.warning("nothing selected")
return
value = pm.floatFieldGrp(field, q=True, v=True)
sel[0].t.set(value[:3])
# edit the existing UI to attech the commands. They'll remember the UI pieces they
# are connected to
pm.button(get_button, e=True, command = get_command)
pm.button(set_button, e=True, command = set_command)
w.show()
#open the window
example_window()
In general, it's this kind of thing that is the trickiest bit in doing Maya GUI -- you need to make sure that all the functions and handlers etc see each other and can share information. In this example the function shares the info by defining the handlers after the UI exists, so they can inherit the names of the UI pieces and know what to work on. There are other ways to do this (Classes are the most sophisticated and complex) but this is the minimalist way to do it. There's a deeper dive on how to do this here

TKinter- is StringVar.trace called when the StringVar is added to a widget?

I have a Tkinter-using program which uses a number of StringVars to track data over multiple windows. I had been using
myStringVar.trace('w', lambda *args: update())
to detect when the user changed one of these variables, either by interacting with Entries or by choosing options from an OptionMenu, where 'update' was a function that changed other parts of the GUI as appropriate and 'root' was . However, I found out that update() was called by myStringVar.trace() at a particular line of my code:
name = OptionMenu(self, self.source, self.source.get(), *root.namelist)
where self.source is the StringVariable in question and root.namelist is an ordinary List. I found this out by sandwiching the above line between two trace statments and adding a trace statement to my update function. I also confirmed that replacing the above line with
name = Entry(self, textvariable=self.source)
would not have the same result.
Trying to copy the form of my original code as much as I could, I wrote the following test code:
from tkinter import *
root = Tk()
root.myStringVar = StringVar(root)
root.myStringVar.set('hello')
class myFrame(Frame):
def __init__(self, root):
Frame.__init__(self, root)
self.source = root.myStringVar
self.update()
self.pack()
def update(self):
for widget in self.winfo_children(): widget.destroy()
name = OptionMenu(self, self.source, self.source.get(), '1','2','3')
name.pack()
root.myframe = myFrame(root)
root.myStringVar.get()
def speak(root):
print(root.myStringVar.get())
root.myframe.update()
root.myStringVar.trace('w', lambda *args: speak(root))
mainloop()
However, when I ran this code it did not print anything unless I changed the option in the OptionMenu, as you would expect.
The only difference between the two cases I can think of is that in my test code the frame in which the OptionMenu was placed was static while in my actual code it is dynamically generated, but I don't see how this could affect the way in which the creation of an OptionMenu is handled.
To answer your specific question "TKinter- is StringVar.trace called when the StringVar is added to a widget?", the answer is "no". The trace is not called when you add a stringvar to a widget. The trace -- assuming it is a write trace -- is only called when the value of the variable changes.
The problem is likely due to the fact you're creating a method named update. This is a method that already exists on widgets, and is possibly called internally by various tkinter functions. Try renaming your function to something else (eg: update_widget).
maybe i didn't catch you point. It works to me, maybe you forget a print:
[...]
root.myframe = myFrame(root)
#this print myStringVar content even when the value doesn't change (each time you call it):
print("main:", root.myStringVar.get()) #<-- this is the missing print, i guess
def foo(*args): #custom useless function
"""Prints "hello" each time OptionMenu selection change"""
print("foo: hello")
def showargs(*args): #show params, useless too
"""Prints args content each time OptionMenu change"""
print("showargs:", args)
def showcontent(*args): #finally this print myStringVar content
"""Prints myStringVar content each time OptionMenu change"""
print("showcontent: ",root.myStringVar.get())
root.myStringVar.trace('w', foo)
root.myStringVar.trace('w', showargs)
root.myStringVar.trace('w', showcontent)
#output:
#main: hello
#after user select "1" from OptionMenu:
#showcontent: 1
#showargs: ('PY_VAR0', '', 'w')
#foo: hello
I added three debug methods to test myStringVar content:
foo is useless, but is fired each time user change OptionMenu value,
showargs shows the *args param content,
showcontent shows myStringVar content.
While if you're stuck because about at line 19 you'd aspect a result, you forget to print it to console or show somehow to gui(I added a print) and it works.
So you can pass myStringVar and get its contents and/or show any changes to it.

How do you modify the current selection length in a Tkinter Text widget?

I would like to be able to double click on test,
in a Tkinter Text widget, and have it select test (and exclude the comma).
Here is what I've tried:
import Tkinter as tk
def selection_mod(event=None):
result = aText.selection_get().find(',')
if result > 0:
try:
aText.tag_add("sel", "sel.first", "sel.last-1c")
except tk.TclError:
pass
lord = tk.Tk()
aText = tk.Text(lord, font=("Georgia", "12"))
aText.grid()
aText.bind("<Double-Button-1>", selection_mod)
lord.mainloop()
The first issue is that <Double-Button-1> seems to trigger the handler before the selection is made, producing:
TclError: PRIMARY selection doesn't exist or form "STRING" not defined
The second issue is that even when using a binding that works,
my selection tag doesn't seem to do anything.
It doesn't even raise an error, and I've tried without the except tk.TclError:.
Your binding is happening before the default bindings occur. Thus, the selection doesn't yet exist when your binding fires. Because your binding tries to get the selection, it fails with the error that you see.
You will need to arrange for your binding to happen after the class bindings. A cheap hack is to use after to execute your code once the default bindings have a chance to work. Or, you can use the bindtag feature to make sure your binding fires after the default bindings.
The second problem is that you don't clear the old selection before setting the new. You'll want to do tag_remove to first remove the existing selection. Otherwise, the comma (if it was somehow selected) will remain selected since all you're doing is re-applying the tag to text that already has the tag.
However, double-click doesn't normally capture the comma so I don't quite understand then point of your code. At least, when I test it on OSX it doesn't include the comma.
Here is what I came up with thanks to Bryan's answer:
import Tkinter as tki # tkinter in Python 3
def selection_mod(event=None):
result = txt.selection_get().find(',')
if result > 0:
fir, sec = txt.tag_ranges("sel")
txt.tag_remove("sel", "sel.first", "sel.last")
txt.tag_add("sel", fir, str(sec)+"-1c")
root = tki.Tk()
txt = tki.Text(root, font=("Georgia", "12"))
txt.grid()
txt.bind("<Double-Button-1>", lambda x: root.after(20, selection_mod))
root.mainloop()
It's worth noting that I'm using Windows 7, and according to Bryan,
OSX doesn't include the comma when you double click a word.

Question about "Python GTK adding signal to a combo box" / answered Apr 24 '10 at 18:21

I'm very new to Python and pygtk , I want to use the changed value of the combo
in the main window , but I can't !
I need some help
Thanks in advance
Best regards
Without code, it is rather difficult to help for sure, but I'll take a stab at this...
From your question, I am guessing that you're wanting to retrieve the value of the combo box once the user changes it.
You're going to need to create a new event handler to retrieve the value upon the user changing. In order to ensure that the entire module can use this data, we'll start by creating a global variable at the TOP of your module. This must be above and outside of all classes and definitions!
combo_value = -1
Here, we've created a variable called "combo_value", and set it's value to -1. This is important because, first of all, it defines the variable as type 'integer', and second, '-1' is the value returned by the code below if nothing is selected in the combo box.
Now, in the class where you have your pygtk code, put this definition. I prefer putting all my event handlers inside the "__ init __" definition, as it makes them easier to access.
def combo_changed(event, data=None):
#This imports the combo_value variable declared above. Otherwise, the module
#would be creating a local variable instead, which would be of no use to the
#rest of the program.
global combo_value
#This retrieves the combo box's selected index and sets the combo_value
#variable to that index.
combo_value = combobox.get_active()
Now we need to connect our combo box to this event using the "changed" signal.
combobox.connect("changed", combo_changed)
And there you have it! You can then hook up all of your other processes by checking the value of the combo_value variable. Just remember - this code sets that variable to the INDEX of the selected item in the combo box, and not the text value! This is extremely important to remember, because if you try and check for a string in this variable, it'll get you nowhere.
The value will be "-1" if nothing is selected. Remember to count from zero for the index of all your items. It may be useful to write down the values of your combo box and their indexes, for reference. It might look something like this:
Combobox (choose color)
"Black" - 0
"White" - 1
"Red" - 2
"Green" - 3
"Blue" - 4
Then, in the code for working with the combo box value, you may have a little something like this:
if combo_value == -1:
pass
#In other words, do nothing at all.
elif combo_value == 0
#Set color to black
elif combo_value == 1
#Set color to white
And so on, and so forth.
So you can see everything in context, here is the entire code, minus my corny little example above...
combo_value = -1
class MyApplication:
def __init__(self):
#Your code here.
def combo_changed(event, data=None):
#This imports the combo_value variable declared above. Otherwise, the module
#would be creating a local variable instead, which would be of no use to the
#rest of the program.
global combo_value
#This retrieves the combo box's selected index and sets the combo_value
#variable to that index.
combo_value = combobox.get_active()
#Your GUI code is here.
#This is where your combobox is created from a model (we're assuming it is
#already declared before this point, and called "MyModel".
combobox = gtk.ComboBox(MyModel)
#Now we connect the "changed" signal of the combobox to the event we created.
combobox.connect("changed", combo_changed)
I hope this helps! Again, without code, it is very hard to give you specifics. I'm helping you out, since you're new here, but please be sure to post specific examples and code from your project on all future questions.
Cheers!

Categories