binding to cursor movement doesnt change INSERT mark - python

I need to perform a quick check everytime the user changes the insertion point, by arrows, mouseclick, etc... so I bound it thus:
text.bind("<Button-1>", insertchanged)
def insertchanged(event):
pos=text.index(INSERT)
n=text.tag_names(INSERT)
...
but I found out that pos is still the position before the user changed it! How do I find the new position (a general solution, if possible: I have to bind it to home,end, pgup, pgdown,...)
thank you!

There are several problems with your approach. For one, you'll need to bind to just about everything in order to track the insertion point (remember: it changes every time you insert or delete anything).
Second, changes happen to the widget based on class bindings and, by default, any bindings you create will fire before the class bindings. You can work around these issues, but it's tricky. For example, to work around the event handling order, search around this site and others for "bind tags" or "bindtags".
There is, however, an almost foolproof solution. The downside is that it requires some serious Tcl voodoo: you have to replace the internal widget with a proxy that calls a callback whenever the insertion point changes. I've included a complete working example, below.
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.text = CustomText(self, wrap="word")
self.text.pack(side="top", fill="both", expand=True)
self.label = tk.Label(self, anchor="w")
self.label.pack(side="bottom", fill="x")
# this is where we tell the custom widget what to call
self.text.set_callback(self.callback)
def callback(self, result, *args):
'''Updates the label with the current cursor position'''
index = self.text.index("insert")
self.label.configure(text="index: %s" % index)
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
tk.Text.__init__(self, *args, **kwargs)
# Danger Will Robinson!
# Heavy voodoo here. All widget changes happen via
# an internal Tcl command with the same name as the
# widget: all inserts, deletes, cursor changes, etc
#
# The beauty of Tcl is that we can replace that command
# with our own command. The following code does just
# that: replace the code with a proxy that calls the
# original command and then calls a callback. We
# can then do whatever we want in the callback.
private_callback = self.register(self._callback)
self.tk.eval('''
proc widget_proxy {actual_widget callback args} {
# this prevents recursion if the widget is called
# during the callback
set flag ::dont_recurse(actual_widget)
# call the real tk widget with the real args
set result [uplevel [linsert $args 0 $actual_widget]]
# call the callback and ignore errors, but only
# do so on inserts, deletes, and changes in the
# mark. Otherwise we'll call the callback way too
# often.
if {! [info exists $flag]} {
if {([lindex $args 0] in {insert replace delete}) ||
([lrange $args 0 2] == {mark set insert})} {
# the flag makes sure that whatever happens in the
# callback doesn't cause the callbacks to be called again.
set $flag 1
catch {$callback $result {*}$args } callback_result
unset -nocomplain $flag
}
}
# return the result from the real widget command
return $result
}
''')
self.tk.eval('''
rename {widget} _{widget}
interp alias {{}} ::{widget} {{}} widget_proxy _{widget} {callback}
'''.format(widget=str(self), callback=private_callback))
def _callback(self, result, *args):
self.callback(result, *args)
def set_callback(self, callable):
self.callback = callable
if __name__ == "__main__":
root = tk.Tk()
frame = Example(root)
frame.pack(side="top", fill="both", expand=True)
root.mainloop()

You can use WidgetRedirector from standard library's idlelib (https://github.com/python/cpython/blob/master/Lib/idlelib/redirector.py), which seems to do what Bryan proposed:
import tkinter as tk
from idlelib.WidgetRedirector import WidgetRedirector
root = tk.Tk()
text = tk.Text(root)
text.grid()
def on_mark(*args):
print("mark", args)
return original_mark(*args)
def on_insert(*args):
print("insert", args)
return original_insert(*args)
def on_delete(*args):
print("delete", args)
return original_delete(*args)
redirector = WidgetRedirector(text)
original_mark = redirector.register("mark", on_mark)
original_insert = redirector.register("insert", on_insert)
original_delete = redirector.register("delete", on_delete)
root.mainloop()

Related

Bokeh: Determine which model called an on_change event handler

I have the following scenario where I want to execute a function with on_change:
def update_func:
calling_widget = < I need the name of the widget here: "SelectorWidget" >
do_something
SelectorWidget = MultiSelect(title="A widget", value = "default", options = option_list)
SelectorWidget.on_change('value', update_func)
The same update_func will be used on different widgets, and I'd like to be able to get the name of the Widget which triggered the function each time.
Any ideas?
Try:
from functools import partial
# add a widget arg to standard callback signature
def update_func(attr, old, new, widget):
# do something with widget
SelectorWidget = MultiSelect(title="A widget",
value="default",
options=option_list)
# use partial to make a callback callable with widget arg bound
SelectorWidget.on_change('value',
partial(update_func, widget=SelectorWidget))

Create a Log Box with tkinter text Widget

I want to create a box were the user is informed of what the application is actually doing.
I created a Text Widget were to show the print statements that I wrote in key points of the applications, so that it could serve as a log box.
To do this, I redirected the stdout to a subclass of the widget itself "upgraded" with a write method as I saw here in another post.
This does indeed work, but I noticed a problem that makes the box almost useless.
If you run the code, you can see that the sentences appear all at once. More puzzling for me is that not only the sentences of the "wait2" functions
appear togheter, but even the print statement of the calling function, "wait1", is shown at the end of the process.
Why this behaviour? what can I do to see the statement shown in the box as they are executed?
from Tkinter import *
import sys
import time
root = Tk()
class addwritemethod(object):
def __init__(self, widget):
self.widget = widget
def write(self, string):
self.widget.configure(state="normal")
self.widget.insert("end", string)
self.widget.see("end")
self.widget.configure(state="disabled")
def wait1():
print "Can you see me?"
wait2()
def wait2():
for i in range(10):
time.sleep(5)
print "Long time no see!"
t_go = Button(root, text= "Start!", width =12, command = wait1)
t_go.pack(side = LEFT, padx = 20, pady = 20)
tlog = Text(root,wrap = "word")
tlog.pack(side="top", fill="both", expand=True)
tlog.configure(state="disabled")
sys.stdout = addwritemethod(tlog)
mainloop()
EDIT: I want to say thanks to the people who answered me and an apology: I did not give all the required information.
I put time.sleep() in the test code only to show you the behaviour. In the real application, I trasfer a file via ssh with Paramiko and I don't use sleep().
Maybe I choose the wrong example, but the result is the same, all the print stament are shown at the same moment.
You could also use the built-in logging module to achieve your goal of
creating a box where the user is informed of what the application is actually doing ... a log box.
I had this same need and converged on the recommendations provided here and here.
I have an example below that I created to illustrate the concept of logging to a GUI control using Tkinter. The example below logs to a text control as you ask, but you can send log messages to other GUI components by replacing/copying the class MyHandlerText with other handler classes like MyHandlerLabel, MyHandlerListbox, etc. (choose your own names for the handler classes). Then you'd have a handler for a variety of GUI controls of interest. The big "a-ha" moment for me was the module-level getLogger concept encouraged by python.org.
import Tkinter
import logging
import datetime
# this item "module_logger" is visible only in this module,
# (but you can create references to the same logger object from other modules
# by calling getLogger with an argument equal to the name of this module)
# this way, you can share or isolate loggers as desired across modules and across threads
# ...so it is module-level logging and it takes the name of this module (by using __name__)
# recommended per https://docs.python.org/2/library/logging.html
module_logger = logging.getLogger(__name__)
class simpleapp_tk(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.grid()
self.mybutton = Tkinter.Button(self, text="ClickMe")
self.mybutton.grid(column=0,row=0,sticky='EW')
self.mybutton.bind("<ButtonRelease-1>", self.button_callback)
self.mytext = Tkinter.Text(self, state="disabled")
self.mytext.grid(column=0, row=1)
def button_callback(self, event):
now = datetime.datetime.now()
module_logger.info(now)
class MyHandlerText(logging.StreamHandler):
def __init__(self, textctrl):
logging.StreamHandler.__init__(self) # initialize parent
self.textctrl = textctrl
def emit(self, record):
msg = self.format(record)
self.textctrl.config(state="normal")
self.textctrl.insert("end", msg + "\n")
self.flush()
self.textctrl.config(state="disabled")
if __name__ == "__main__":
# create Tk object instance
app = simpleapp_tk(None)
app.title('my application')
# setup logging handlers using the Tk instance created above
# the pattern below can be used in other threads...
# ...to allow other thread to send msgs to the gui
# in this example, we set up two handlers just for demonstration (you could add a fileHandler, etc)
stderrHandler = logging.StreamHandler() # no arguments => stderr
module_logger.addHandler(stderrHandler)
guiHandler = MyHandlerText(app.mytext)
module_logger.addHandler(guiHandler)
module_logger.setLevel(logging.INFO)
module_logger.info("from main")
# start Tk
app.mainloop()
When you call sleep, the application does exactly that: it sleeps. When it's sleeping it can't update the display. As a general rule you should never call sleep in a GUI.
That being said, a quick fix is to make sure you call update after printing something to the log, so that Tkinter has a chance to update the screen. Add self.widget.update_idletasks() at the end of write (redrawing the screen is considered an "idle task").
This isn't a proper fix but it's good enough to illustrate why the data isn't appearing. A proper fix involves not calling sleep. There are many examples on stackoverflow related to this, and almost all of them involve using the after method.

How can I embed a python interpreter frame in python using tkinter?

I want to add a control terminal widget to my pure python+tkinter application similar to the python interpreter provided in Blender. It should be running within the same context (process) so the user can add features and control the application that is currently running from the control widget. Ideally I'd like it to also "hijack" stdout and stderr of the current application so it will report any problems or debugging information within the running application.
This is what I have come up with so far. The only problems are that it isn't responding to commands, and the thread doesn't stop when the user closes the window.
import Tkinter as tk
import sys
import code
from threading import *
class Console(tk.Frame):
def __init__(self,parent=None):
tk.Frame.__init__(self, parent)
self.parent = parent
sys.stdout = self
sys.stderr = self
self.createWidgets()
self.consoleThread = ConsoleThread()
self.after(100,self.consoleThread.start)
def write(self,string):
self.ttyText.insert('end', string)
self.ttyText.see('end')
def createWidgets(self):
self.ttyText = tk.Text(self.parent, wrap='word')
self.ttyText.grid(row=0,column=0,sticky=tk.N+tk.S+tk.E+tk.W)
class ConsoleThread(Thread):
def __init__(self):
Thread.__init__(self)
def run(self):
vars = globals().copy()
vars.update(locals())
shell = code.InteractiveConsole(vars)
shell.interact()
if __name__ == '__main__':
root = tk.Tk()
root.config(background="red")
main_window = Console(root)
main_window.mainloop()
try:
if root.winfo_exists():
root.destroy()
except:
pass
I have the answer in case anyone still cares! (I have also changed to python 3, hence the import tkinter rather than import Tkinter)
I have changed the approach slightly from the original by using a separate file to run the InteractiveConsole, and then making the main file open this other file (which I have called console.py and is in the same directory) in a subprocess, linking the stdout, stderr, and stdin of this subprocess to the tkinter Text widget programatically.
Here is the code in the for the console file (if this is run normally, it acts like a normal console):
# console.py
import code
if __name__ == '__main__':
vars = globals().copy()
vars.update(locals())
shell = code.InteractiveConsole(vars)
shell.interact()
And here is the code for the python interpreter, that runs the console inside the Text widget:
# main.py
import tkinter as tk
import subprocess
import queue
import os
from threading import Thread
class Console(tk.Frame):
def __init__(self,parent=None):
tk.Frame.__init__(self, parent)
self.parent = parent
self.createWidgets()
# get the path to the console.py file assuming it is in the same folder
consolePath = os.path.join(os.path.dirname(__file__),"console.py")
# open the console.py file (replace the path to python with the correct one for your system)
# e.g. it might be "C:\\Python35\\python"
self.p = subprocess.Popen(["python3",consolePath],
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
# make queues for keeping stdout and stderr whilst it is transferred between threads
self.outQueue = queue.Queue()
self.errQueue = queue.Queue()
# keep track of where any line that is submitted starts
self.line_start = 0
# make the enter key call the self.enter function
self.ttyText.bind("<Return>",self.enter)
# a daemon to keep track of the threads so they can stop running
self.alive = True
# start the functions that get stdout and stderr in separate threads
Thread(target=self.readFromProccessOut).start()
Thread(target=self.readFromProccessErr).start()
# start the write loop in the main thread
self.writeLoop()
def destroy(self):
"This is the function that is automatically called when the widget is destroyed."
self.alive=False
# write exit() to the console in order to stop it running
self.p.stdin.write("exit()\n".encode())
self.p.stdin.flush()
# call the destroy methods to properly destroy widgets
self.ttyText.destroy()
tk.Frame.destroy(self)
def enter(self,e):
"The <Return> key press handler"
string = self.ttyText.get(1.0, tk.END)[self.line_start:]
self.line_start+=len(string)
self.p.stdin.write(string.encode())
self.p.stdin.flush()
def readFromProccessOut(self):
"To be executed in a separate thread to make read non-blocking"
while self.alive:
data = self.p.stdout.raw.read(1024).decode()
self.outQueue.put(data)
def readFromProccessErr(self):
"To be executed in a separate thread to make read non-blocking"
while self.alive:
data = self.p.stderr.raw.read(1024).decode()
self.errQueue.put(data)
def writeLoop(self):
"Used to write data from stdout and stderr to the Text widget"
# if there is anything to write from stdout or stderr, then write it
if not self.errQueue.empty():
self.write(self.errQueue.get())
if not self.outQueue.empty():
self.write(self.outQueue.get())
# run this method again after 10ms
if self.alive:
self.after(10,self.writeLoop)
def write(self,string):
self.ttyText.insert(tk.END, string)
self.ttyText.see(tk.END)
self.line_start+=len(string)
def createWidgets(self):
self.ttyText = tk.Text(self, wrap=tk.WORD)
self.ttyText.pack(fill=tk.BOTH,expand=True)
if __name__ == '__main__':
root = tk.Tk()
root.config(background="red")
main_window = Console(root)
main_window.pack(fill=tk.BOTH,expand=True)
root.mainloop()
The reason that reading from stdout and stderr is in separate threads is because the read method is blocking, which causes the program to freeze until the console.py subprocess gives more output, unless these are in separate threads. The writeLoop method and the queues are needed to write to the Text widget since tkinter is not thread safe.
This certainly still has problems to be ironed out, such as the fact that any code on the Text widget is editable even once already submitted, but hopefully it answers your question.
EDIT: I've also neatened some of the tkinter such that the Console will behave more like a standard widget.
it isn't responding to commands
The reason it isn't responding to commands is because you haven't linked the Text widget (self.ttyText) into stdin. Currently when you type it adds text into the widget and nothing else. This linking can be done similarly to what you've already done with stdout and stderr.
When implementing this, you need to keep track of which part of the text in the widget is the text being entered by the user - this can be done using marks (as described here).
the thread doesn't stop when the user closes the window.
I don't think there is a "clean" way to solve this issue without a major code re-write, however a solution that seems to work well enough is it simply detect when the widget is destroyed and write the string "\n\nexit()" to the interpreter. This calls the exit function inside the interpreter, which causes the call to shell.interact to finish, which makes the thread finish.
So without further ado, here is the modified code:
import tkinter as tk
import sys
import code
from threading import Thread
import queue
class Console(tk.Frame):
def __init__(self, parent, _locals, exit_callback):
tk.Frame.__init__(self, parent)
self.parent = parent
self.exit_callback = exit_callback
self.destroyed = False
self.real_std_in_out = (sys.stdin, sys.stdout, sys.stderr)
sys.stdout = self
sys.stderr = self
sys.stdin = self
self.stdin_buffer = queue.Queue()
self.createWidgets()
self.consoleThread = Thread(target=lambda: self.run_interactive_console(_locals))
self.consoleThread.start()
def run_interactive_console(self, _locals):
try:
code.interact(local=_locals)
except SystemExit:
if not self.destroyed:
self.after(0, self.exit_callback)
def destroy(self):
self.stdin_buffer.put("\n\nexit()\n")
self.destroyed = True
sys.stdin, sys.stdout, sys.stderr = self.real_std_in_out
super().destroy()
def enter(self, event):
input_line = self.ttyText.get("input_start", "end")
self.ttyText.mark_set("input_start", "end-1c")
self.ttyText.mark_gravity("input_start", "left")
self.stdin_buffer.put(input_line)
def write(self, string):
self.ttyText.insert('end', string)
self.ttyText.mark_set("input_start", "end-1c")
self.ttyText.see('end')
def createWidgets(self):
self.ttyText = tk.Text(self.parent, wrap='word')
self.ttyText.grid(row=0, column=0, sticky=tk.N + tk.S + tk.E + tk.W)
self.ttyText.bind("<Return>", self.enter)
self.ttyText.mark_set("input_start", "end-1c")
self.ttyText.mark_gravity("input_start", "left")
def flush(self):
pass
def readline(self):
line = self.stdin_buffer.get()
return line
if __name__ == '__main__':
root = tk.Tk()
root.config(background="red")
main_window = Console(root, locals(), root.destroy)
main_window.mainloop()
This code has few changes other than those that solve the problems stated in the question.
The advantage of this code over my previous answer is that it works inside a single process, so can be created at any point in the application, giving the programmer more control.
I have also written a more complete version of this which also prevents the user from editing text which shouldn't be editable (e.g. the output of a print statement) and has some basic coloring: https://gist.github.com/olisolomons/e90d53191d162d48ac534bf7c02a50cd

Ending the GTK+ main loop in an Python MDI application

I am trying to code an application that consists of various windows (e.g., generic message dialog, login dialog, main interface, etc.) and am having trouble getting the gtk.main_quit function to be called: either I get a complaint about the call being outside the main loop, or the function doesn't get called at all.
I am a newbie to both Python and GTK+, but my best guess as to how to get this to work is to have a "root" window, which is just a placeholder that is never seen, but controls the application's GTK+ loop. My code, so far, is as follows:
import pygtk
pygtk.require("2.0")
import gtk
class App(gtk.Window):
_exitStatus = 0
# Generic message box
def msg(self, title, text, type = gtk.MESSAGE_INFO, buttons = gtk.BUTTONS_OK):
# Must always have a button
if buttons == gtk.BUTTONS_NONE:
buttons = gtk.BUTTONS_OK
dialog = gtk.MessageDialog(None, 0, type, buttons, title)
dialog.set_title(title)
dialog.set_geometry_hints(min_width = 300)
dialog.set_resizable(False)
dialog.set_deletable(False)
dialog.set_position(gtk.WIN_POS_CENTER)
dialog.set_modal(True)
dialog.format_secondary_text(text)
response = dialog.run()
dialog.destroy()
return response
def nuke(self, widget, data):
gtk.main_quit()
exit(self._exitStatus)
def __init__(self):
super(App, self).__init__()
self.connect('destroy', self.nuke)
try:
raise Exception()
except:
self.msg('OMFG!', 'WTF just happened!?', gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE)
self._exitStatus = 1
self.destroy()
if self.msg('OK', 'Everything worked fine') == gtk.RESPONSE_OK:
self.destroy()
# Let's go!
App()
gtk.main()
The nuke function never gets called, despite the explicit calls to destroy.
DIFF On #DonQuestion's advice:
- self.destroy()
+ self.emit('destroy')
- App()
+ app = App()
This didn't solve the problem...
UPDATE Accepted #jku's answer, but also see my own answer for extra information...
First, there is a bit of a test problem with the code: You call Gtk.main_quit() from the App initialization: this happens before main loop is even running so signals probably won't work.
Second, you'll probably get a warning on destroy(): 'destroy' handler only takes two arguments (self plus one) but yours has three...
Also with regards to your comment about control flow: You don't need a Window to get signals as they're a GObject feature. And for your testing needs you could write a App.test_except() function and use glib.idle_add (self.test_except) in the object initialization -- this way test_except() is called when main loop is running.
I think #jku's answer identifies my key error, so I have marked it accepted, but while playing around, I found that the MessageDialog does not need to run within the GTK+ loop. I don't know if this is as designed, but it works! So, I broke my generic message dialog out into its own function and then kept the main app altogether in a class of its own, which respects the main loop as I was expecting:
import pygtk
pygtk.require("2.0")
import gtk
def msg(title, text, type = gtk.MESSAGE_INFO, buttons = gtk.BUTTONS_OK):
# Only allowed OK, Close, Cancel, Yes/No and OK/Cancel buttons
# Otherwise, default to just OK
if buttons not in [gtk.BUTTONS_OK, gtk.BUTTONS_CLOSE, gtk.BUTTONS_CANCEL, gtk.BUTTONS_YES_NO, gtk.BUTTONS_OK_CANCEL]:
buttons = gtk.BUTTONS_OK
dialog = gtk.MessageDialog(None, 0, type, buttons, title)
dialog.set_title(title)
dialog.set_geometry_hints(min_width = 300)
dialog.set_resizable(False)
dialog.set_deletable(False)
dialog.set_position(gtk.WIN_POS_CENTER)
dialog.set_modal(True)
dialog.format_secondary_text(text)
response = dialog.run()
dialog.destroy()
return response
class App:
def __init__(self):
# Build UI
# Connect signals
# Show whatever
def appQuit(self, widget):
gtk.main_quit()
def signalHandler(self, widget, data = None):
# Handle signal
# We can call msg here, when the main loop is running
# Load some resource
# We can call msg here, despite not having invoked the main loop
try:
# Load resource
except:
msg('OMFG!', 'WTF just happened!?', gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE)
exit(1)
# n.b., Calls to msg work even without the following code
App()
gtk.main()
exit(0)

Tkinter entry widget get is undefined, not updating to my main function,

i'm following a few different guides to re-learn Tkinter by writing a little application that grabs stock prices. My issue that I am up a wall against is calling the .get() method from my entry widget variable. I've seen a couple other suggestions to put all the widget creating inside a function and then call the function in the init method, however I'm getting the same error, even when using the self argument in front of my entry variables. I know it's an issue with the way i'm passing data from function to function, but I can't wrap my head around it. Here's the code, sorry for the wall of text:
class MyApp:
def __init__(self, parent):
self.myParent = parent
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
self.createWidgets()
button1 = Button(self.myContainer1, command = self.button1Click)
button1.configure(text = "get quote")
button1.pack()
def createWidgets(self):
root.title("Stock App")
self.symbol = Entry(self.myContainer1)
self.symbol.pack()
self.symbol.focus_set()
def button1Click(self):
stock = symbol.get()
print stock
I've taken it down to simplest form even and just had the button1Click call a callback function-
def button1Click(self):
print callback()
def callback():
print symbol.get()
This returns the exact same error:
NameError: global name 'symbol' is not defined
Is it getting destroyed too early? how do I fix this?
I've referenced multiple documents for tkinter and have seen some great fixes but none are extensible, or um unable to see how they relate to me using it inside of an object.
Thanks in advance for the help.
As far as I can tell inside of your button1Click method you need to add self as in:
def callback():
print self.symbol.get()
You're missing self. to make the callback:
def callback():
print self.symbol.get()
instead.

Categories

Resources