开发者

Why does this valid Tkinter code crash when mixed with a bit of PyWin32?

开发者 https://www.devze.com 2023-02-01 21:30 出处:网络
So I\'m making a very small program for personal use in tkinter, and I\'ve run into a really strange wall. I\'m mixing tkinter with the pywin32 bindings because I really hate everything to do with the

So I'm making a very small program for personal use in tkinter, and I've run into a really strange wall. I'm mixing tkinter with the pywin32 bindings because I really hate everything to do with the syntax and naming conventions of pywin32, and it feels like tkinter gets more done with far less code. The strangeness is happening in the transition between the pywin32 clipboard watching and my program's reaction to it in tkinter.

My window and all its controls are being handled in tkinter. The pywin32 bindings are doing clipboard watching and clipboard access when the clipboard changes. From what I've gathered about the way the clipboard watching pieces of pywin32 work, you can make it work with anything you want as long as you provide pywin32 with the hwnd value of your window. I'm doing that part, and it works when the program first starts. It just doesn't seem to work when the clipboard changes.

When the program launches, it grabs the clipboard and puts it into the search box and edit box just fine. When the clipboard is modified, the event I want to fire off is firing off...except that event that totally worked before when the program launched is now causing a weird hang instead of doing what it's supposed to do. I can print the clipboard contents to stdout all I want if the clipboard changes, but not put that same data into a tkinter widget. It only hangs like that if it starts to interact with any of my tkinter widgets after being fired off by a clipboard change notification.

It feels like there's some pywin32 etiquette I've missed in adapting the clipboard-watching sample code I was using over to my tkinter-using program. Tkinter apparently doesn't like to produce stack traces or error messages, and I can't really even begin to know what to look for trying to debug it with pdb.

Here's the code:

#coding: utf-8
#Clipboard watching cribbed from ## {{{ http://code.activestate.com/recipes/355593/ (r1)

import pdb
from Tkinter import *
import win32clipboard
import win32api
import win32gui
import win32con
import win32clipboard


def force_unicode(object, encoding="utf-8"):
    if isinstance(object, basestring) and not isinstance(object, unicode):
        object = unicode(object, encoding)
    return object

class Application(Frame):
    def __init__(self, master=None):
        self.master = master
        Frame.__init__(self, master)
        self.pack()
        self.createWidgets()

        self.hwnd = self.winfo_id()
        self.nextWnd = None
        self.first = True
        self.oldWndProc = win32gui.SetWindowLong(self.hwnd, win32con.GWL_WNDPROC, self.MyWndProc)
        try:
            self.nextWnd = win32clipboard.SetClipboardViewer(self.hwnd)
        except win32api.error:
            if win32api.GetLastError () == 0:
                # information that there is no other window in chain
                pass
            else:
                raise

        self.update_search_box()
        self.word_search()

    def word_search(self):
        #pdb.set_trace()
        term = self.searchbox.get()
        self.resultsbox.insert(END, term)

    def update_search_box(self):
        clipboardtext = ""
        if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_TEXT):
            win32clipboard.OpenClipboard()
            clipboardtext = win32clipboard.GetClipboardData()
            win32clipboard.CloseClipboard()

        if clipboardtext != "":
            self.searchbox.delete(0,END)
            clipboardtext = force_unicode(clipboardtext)
            self.searchbox.insert(0, clipboardtext)

    def createWidgets(self):
        self.button = Button(self)
        self.button["text"] = "Search"
        self.button["command"] = self.word_search

        self.searchbox = Entry(self)
        self.resultsbox = Text(self)

        #Pack everything down here for "easy" layout changes later
        self.searchbox.pack()
        self.button.pack()
        self.resultsbox.pack()

    def MyWndProc (self, hWnd, msg, wParam, lParam):
        if msg == win32con.WM_CHANGECBCHAIN:
            self.OnChangeCBChain(msg, wParam, lParam)
        elif msg == win32con.WM_DRAWCLIPBOARD:
            self.OnDrawClipboard(msg, wParam, lParam)

        # Restore the old WndProc. Notice the use of win32api
        # instead of win32gui here. This is to avoid an error due to
        # not passing a callable object.
        if msg == win32con.WM_DESTROY:
            if self.nextWnd:
               win32clipboard.Ch开发者_开发百科angeClipboardChain (self.hwnd, self.nextWnd)
            else:
               win32clipboard.ChangeClipboardChain (self.hwnd, 0)

            win32api.SetWindowLong(self.hwnd, win32con.GWL_WNDPROC, self.oldWndProc)

        # Pass all messages (in this case, yours may be different) on
        # to the original WndProc
        return win32gui.CallWindowProc(self.oldWndProc, hWnd, msg, wParam, lParam)

    def OnChangeCBChain (self, msg, wParam, lParam):
        if self.nextWnd == wParam:
           # repair the chain
           self.nextWnd = lParam
        if self.nextWnd:
           # pass the message to the next window in chain
           win32api.SendMessage (self.nextWnd, msg, wParam, lParam)

    def OnDrawClipboard (self, msg, wParam, lParam):
        if self.first:
           self.first = False
        else:
            #print "changed"
            self.word_search()
            #self.word_search()

        if self.nextWnd:
           # pass the message to the next window in chain
           win32api.SendMessage(self.nextWnd, msg, wParam, lParam)


if __name__ == "__main__":
    root = Tk()
    app = Application(master=root)
    app.mainloop()
    root.destroy()


Not sure if it could help, but i assume it breaks down as you invoke the update from inside an win32 event handler and tkinter might not like that.

Usual trick to work around it is to defer the update via an after_idle() callback.

So try to replace:

   def OnDrawClipboard (self, msg, wParam, lParam):
    if self.first:
       self.first = False
    else:
        #print "changed"
        self.word_search()
        #self.word_search()

    if self.nextWnd:
       # pass the message to the next window in chain
       win32api.SendMessage(self.nextWnd, msg, wParam, lParam)

with this:

   def OnDrawClipboard (self, msg, wParam, lParam):
    if self.first:
       self.first = False
    else:
        #print "changed"
        self.after_idle(self.word_search)
        #self.word_search()

    if self.nextWnd:
       # pass the message to the next window in chain
       win32api.SendMessage(self.nextWnd, msg, wParam, lParam)
0

精彩评论

暂无评论...
验证码 换一张
取 消